File Details
create-bts-1.4.0.jar
- R
- Mar 5, 2026
- 321.29 KB
- 246
- 1.20.1
- Forge
File Name
create-bts-1.4.0.jar
Supported Versions
- 1.20.1
Curse Maven Snippet
1.4.0
Fix 4 — RecipeEssentials overflow: GETFIELD redirect replaces nextInt redirect
Problem with 1.3.0 approach: The 1.3.0 fix redirected Random.nextInt(int) and returned 0 when bound <= 0. This was based on an incorrect assumption that RE treats nextInt == 0 as a cache-refresh signal that resets useCount. In practice, RE does not reset useCount when nextInt returns 0 — it only refreshes the cached recipe result. useCount continued to increment every tick; the overflow state persisted, and the mod was issuing a full vanilla recipe lookup every tick (heavy work), plus logging a WARN every call (~900+ times/second), saturating log I/O and causing 16-second MSPT spikes. Spark profiler analysis of the affected session showed native libc.so.6 at 49.3% of server CPU and __write at 2.0% — entirely log-write overhead.
New approach: RecipeEssentialsOverflowMixin now redirects the GETFIELD CachedRecipeList.useCount read at bytecode ordinal 1 — the operand of the useCount * 30 multiplication — in each of the three overridden methods. The redirect receives the live CachedRecipeList object directly. When useCount > Integer.MAX_VALUE / 30 (71,582,788), the field is immediately reset to 1 in place and 1 is returned:
// Bytecode layout in each method:
// ordinal 0 — threshold check: useCount > 10
// ordinal 1 — multiplication: useCount * 30 ← intercepted here
// ordinal 2 — increment: useCount + 1
if (list.useCount > MAX_SAFE_USE_COUNT) {
list.useCount = 1;
return 1; // multiplication becomes 1 * 30 = 30, nextInt(30) is safe
}
return list.useCount;
The reset takes effect on the same call in which the overflow would have occurred — no additional ticks, no 12-hour wait. An INFO log is written when the fix triggers (naturally rate-limited to once per ~13-hour cycle). All three RE method overrides are covered (m_44015_, m_220248_, m_44056_).
Feature 6 — Contraption fluid and item storage compat (new)
Create 6 requires storage blocks to declare themselves in the MountedFluidStorageType / MountedItemStorageType registries to carry fluid or items on a contraption. Blocks absent from these registries silently lose their contents when mounted and cannot interact with Mechanical Pumps. This release registers the following blocks:
| Block | Mod | Storage type |
|---|---|---|
multi_fluid_tank |
Create: Fluid Stuff | Fluid |
void_tank |
Create: Utilities | Fluid |
ender_chest |
Ender Storage | Item |
ender_tank |
Ender Storage | Fluid |
item_trash_can |
TrashCans | Item |
liquid_trash_can |
TrashCans | Fluid |
ultimate_trash_can |
TrashCans | Item + Fluid |
Ender Storage implementation: Ender chests and tanks on contraptions act as live proxies into the ender frequency network. Items or fluids inserted while the contraption is moving are delivered to the correct shared frequency in real time. The frequency is serialized with the contraption and reconnects correctly after a world reload. This means a contraption carrying an ender chest will interact with all other ender chests on the same frequency, exactly as if the block were stationary.
TrashCan implementation: All three trash can variants (item, liquid, ultimate) accept and void everything inserted into them while on a contraption. They never accumulate state.
All compat entries are loaded conditionally via ModList.get().isLoaded(). If a supported mod is not installed, its compat block is silently skipped.
Fix 7 — Create: Drill Drain fluid amount tied to liquid type
Problem: Create: Drill Drain's DrillMovementBehaviourReplacement.getFluidFromFluidBlock() computes the collected fluid amount as (level / 8.0) × FluidType.getDensity() × fluidPickupModifier. getDensity() returns 1000 for water and 3000 for lava, so lava yields 3× the mB of water per block drained at the same fluidPickupModifier setting.
Fix (DrillDrainDensityMixin): @Redirect on both FluidType.getDensity() calls inside getFluidFromFluidBlock, always returning 1000 regardless of fluid type. All fluids now yield the same mB per block (water baseline density), making fluidPickupModifier behave consistently across fluid types.
1.3.0
Unreleased due to server-crashing bug
Fix 4 — RecipeEssentials overflow: root-cause fix replaces catch-and-cache
Problem with 1.2.0 approach: The per-instance last-result cache worked for existing burners that had previously succeeded, but newly placed burners (and all burners simultaneously when the overflow first triggers) had no cached result to return. Real-world profiling confirmed the issue: on a server with ~80 blaze burners, the overflow fires after ~13 hours of uptime and affects all burners at once, including any placed afterward.
Spark profiler analysis of the affected session showed native libc.so.6 consuming 47–58% of server CPU — the cost of generating exception stack traces for 1,450–1,800 IllegalArgumentException throws per second.
Root-cause analysis (decompiled from recipeessentials-1.20.1-4.0.jar):
RecipeEssentials' RecipeManager caches recipe lookups in a Long2ObjectOpenHashMap<CachedRecipeList> keyed by calcHash(container, type). All LiquidBlazeBurnerBlockEntity instances produce the same hash — the entity has no item slots and no hashCode() override, so calcHash returns just RecipeType.hashCode() for all of them. Their shared CachedRecipeList.useCount is incremented on every cache hit. At 80 burners × 20 ticks/sec, useCount reaches Integer.MAX_VALUE / 30 ≈ 71.6M in ~13 hours; useCount * 30 then silently wraps to a negative int and nextInt(negative) throws on every subsequent call.
New fix (RecipeEssentialsOverflowMixin): Targets com.recipeessentials.recipecache.RecipeManager directly. Redirects Random.nextInt(int) in m_44015_, m_220248_, and m_44056_. When bound <= 0, returns 0 — which RecipeEssentials treats as a cache-refresh signal, creating a new CachedRecipeList with useCount = 0 and falling through to the vanilla recipe lookup. All burners (including new placements) are healed immediately. CPU overhead from exception generation drops to zero. The fix re-triggers ~13 hours later and self-heals again.
LiquidBlazeBurnerMixin (the 1.2.0 catch-and-cache) remains as secondary defence.

