Skip to content
This repository was archived by the owner on Aug 31, 2026. It is now read-only.

Commit 342ca91

Browse files
committed
feat: an emergency dump that throws heat overboard inside a lump of iron
- Only runs once the ship is already losing, never as cooling - Charges whatever you load it with, at what that substance can take - A full slug goes out of the port; a blocked port holds it instead - The heat rides the matter, so what leaves the ship is really gone
1 parent 65f5da8 commit 342ca91

13 files changed

Lines changed: 580 additions & 1 deletion

File tree

‎src/main/java/zmaster587/advancedRocketry/AdvancedRocketry.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ public void preInit(FMLPreInitializationEvent event) {
428428
GameRegistry.registerTileEntity(zmaster587.advancedRocketry.tile.heat.TileHeatAccumulator.class, "ARheatAccumulator");
429429
GameRegistry.registerTileEntity(zmaster587.advancedRocketry.tile.heat.TileHeatRadiator.class, "ARheatRadiator");
430430
GameRegistry.registerTileEntity(zmaster587.advancedRocketry.tile.heat.TileHeatChiller.class, "ARheatChiller");
431+
GameRegistry.registerTileEntity(zmaster587.advancedRocketry.tile.heat.TileHeatDump.class, "ARheatDump");
431432
GameRegistry.registerTileEntity(zmaster587.advancedRocketry.tile.heat.TileHeatIntakeDuct.class, "ARheatIntakeDuct");
432433
GameRegistry.registerTileEntity(TileWarpController.class, "ARStationMonitor");
433434
GameRegistry.registerTileEntity(TileAtmosphereDetector.class, "AROxygenDetector");
@@ -795,6 +796,7 @@ public void registerBlocks(RegistryEvent.Register<Block> evt) {
795796
AdvancedRocketryBlocks.blockHeatAccumulator = new zmaster587.advancedRocketry.block.BlockHeatAccumulator().setCreativeTab(tabAdvRocketry).setUnlocalizedName("heatAccumulator").setHardness(3f);
796797
AdvancedRocketryBlocks.blockHeatRadiator = new zmaster587.advancedRocketry.block.BlockHeatRadiator().setCreativeTab(tabAdvRocketry).setUnlocalizedName("heatRadiator").setHardness(1f);
797798
AdvancedRocketryBlocks.blockHeatChiller = new BlockTile(zmaster587.advancedRocketry.tile.heat.TileHeatChiller.class, GuiHandler.guiId.MODULAR.ordinal()).setCreativeTab(tabAdvRocketry).setUnlocalizedName("heatChiller").setHardness(3f);
799+
AdvancedRocketryBlocks.blockHeatDump = new BlockTile(zmaster587.advancedRocketry.tile.heat.TileHeatDump.class, GuiHandler.guiId.MODULAR.ordinal()).setCreativeTab(tabAdvRocketry).setUnlocalizedName("heatDump").setHardness(3f);
798800
AdvancedRocketryBlocks.blockHeatIntakeDuct = new zmaster587.advancedRocketry.block.BlockHeatIntakeDuct().setCreativeTab(tabAdvRocketry).setUnlocalizedName("heatIntakeDuct").setHardness(1f);
799801
AdvancedRocketryBlocks.blockOxygenVent = new BlockTile(TileOxygenVent.class, GuiHandler.guiId.MODULAR.ordinal()).setUnlocalizedName("oxygenVent").setCreativeTab(tabAdvRocketry).setHardness(3f);
800802
AdvancedRocketryBlocks.blockOxygenCharger = new BlockHalfTile(TileGasChargePad.class, GuiHandler.guiId.MODULAR.ordinal()).setUnlocalizedName("oxygenCharger").setCreativeTab(tabAdvRocketry).setHardness(3f);
@@ -996,6 +998,7 @@ public void registerBlocks(RegistryEvent.Register<Block> evt) {
996998
LibVulpesBlocks.registerBlock(AdvancedRocketryBlocks.blockHeatAccumulator.setRegistryName("heatAccumulator"));
997999
LibVulpesBlocks.registerBlock(AdvancedRocketryBlocks.blockHeatRadiator.setRegistryName("heatRadiator"));
9981000
LibVulpesBlocks.registerBlock(AdvancedRocketryBlocks.blockHeatChiller.setRegistryName("heatChiller"));
1001+
LibVulpesBlocks.registerBlock(AdvancedRocketryBlocks.blockHeatDump.setRegistryName("heatDump"));
9991002
LibVulpesBlocks.registerBlock(AdvancedRocketryBlocks.blockHeatIntakeDuct.setRegistryName("heatIntakeDuct"));
10001003
LibVulpesBlocks.registerBlock(AdvancedRocketryBlocks.blockOxygenVent.setRegistryName("oxygenVent"));
10011004
LibVulpesBlocks.registerBlock(AdvancedRocketryBlocks.blockOxygenCharger.setRegistryName("oxygenCharger"));
@@ -1179,6 +1182,7 @@ public void postInit(FMLPostInitializationEvent event) {
11791182
zmaster587.advancedRocketry.api.capability.CapabilityWear.register();
11801183
zmaster587.advancedRocketry.api.capability.CapabilityHeatEmitter.register();
11811184
zmaster587.advancedRocketry.api.capability.CapabilityHeatPump.register();
1185+
zmaster587.advancedRocketry.api.capability.CapabilityHeatSink.register();
11821186
//Need to raise the Max Entity Radius to allow player interaction with rockets
11831187
World.MAX_ENTITY_RADIUS = 20;
11841188

‎src/main/java/zmaster587/advancedRocketry/api/ARConfiguration.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,10 @@ public class ARConfiguration {
337337
@ConfigProperty
338338
public int shipHeatMeltCheckTicks;
339339
@ConfigProperty
340+
public int shipHeatDumpThroughput;
341+
@ConfigProperty
342+
public int shipHeatDumpTriggerKelvin;
343+
@ConfigProperty
340344
public int solarGeneratorMult;
341345
@ConfigProperty
342346
public boolean gravityAffectsFuel;
@@ -642,6 +646,8 @@ public static void loadPreInit() {
642646
arConfig.shipHeatSlugMarginKelvin = config.get(HEAT, "shipHeatSlugMarginKelvin", 100, "How far below its own melting point a heat slug is charged, in kelvin. This is the margin that keeps a slug a solid object you can eject and pick up again instead of a puddle in the machine, so it is subtracted from every material's usable span. Raise it for a safer, weaker slug.", 0, 5000).getInt();
643647
arConfig.shipHeatSlugJoulesPerUnit = config.get(HEAT, "shipHeatSlugJoulesPerUnit", 1000, "How many real joules one heat unit stands for when a slug's capacity is worked out from its material. The materials table is ordinary physics in SI, and this is the single place those joules become the currency the rest of the thermal system deals in - so it scales every slug at once and changes no material's standing relative to another. At the default a litre of iron is worth about five thousand heat units.", 1, Integer.MAX_VALUE).getInt();
644648
arConfig.shipHeatMeltCheckTicks = config.get(HEAT, "shipHeatMeltCheckTicks", 20, "How often a coolant loop looks at what it is cooking, in ticks. This is the melting rung: past a material's own limit the block stops being damaged and is gone. Checking is the expensive half - it walks the loop and its neighbours - so this is the freshness of that answer rather than a balance number. Raising it does not make a ship safer, only slower to lose its hull.", 1, 1200).getInt();
649+
arConfig.shipHeatDumpThroughput = config.get(HEAT, "shipHeatDumpThroughput", 40000, "How much heat an emergency dump pushes into the slug it is charging each second. Deliberately well under what a radiator array sheds: the dump buys seconds while something else is fixed, and a value large enough to keep a ship cool would turn an emergency into a cooling system that eats iron.", 0, Integer.MAX_VALUE).getInt();
650+
arConfig.shipHeatDumpTriggerKelvin = config.get(HEAT, "shipHeatDumpTriggerKelvin", 700, "How hot the loop must be before an emergency dump will run at all, in kelvin. Below this it does nothing and costs nothing - a ship that is coping does not throw its cargo overboard. 700 K is far past anything a working ship sits at and short of the temperature at which the drive refuses to fire.", 1, 5000).getInt();
645651
arConfig.solarGeneratorMult = config.get(ENERGY, "solarGeneratorMultiplier", 1, "Power produced per tick by the solar generator.").getInt();
646652
arConfig.microwaveRecieverMulitplier = (float) config.get(ENERGY, "MicrowaveRecieverMultiplier", 1f, "Multiplier for microwave receiver power output.").getDouble();
647653
arConfig.defaultItemTimeBlackHole = config.get(ENERGY, "defaultBurnTime", 500, "Burn time in ticks for items not listed in blackHoleTimings.").getInt();

‎src/main/java/zmaster587/advancedRocketry/api/AdvancedRocketryBlocks.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public class AdvancedRocketryBlocks {
9696
public static Block blockHeatAccumulator;
9797
public static Block blockHeatRadiator;
9898
public static Block blockHeatChiller;
99+
public static Block blockHeatDump;
99100
public static Block blockHeatIntakeDuct;
100101
public static Block blockCircleLight;
101102
public static Block blockSolarGenerator;
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package zmaster587.advancedRocketry.api.capability;
2+
3+
import net.minecraft.nbt.NBTBase;
4+
import net.minecraft.tileentity.TileEntity;
5+
import net.minecraft.util.EnumFacing;
6+
import net.minecraftforge.common.capabilities.Capability;
7+
import net.minecraftforge.common.capabilities.CapabilityInject;
8+
import net.minecraftforge.common.capabilities.CapabilityManager;
9+
10+
import javax.annotation.Nullable;
11+
12+
/**
13+
* The capability through which a coolant loop finds a machine that will TAKE its heat, registered
14+
* exactly like {@link CapabilityHeatEmitter}. The host keeps its own state in its own NBT, so the
15+
* {@code IStorage} is a no-op.
16+
* <p>
17+
* One read path and no {@code instanceof}: a loop does not know what a heat dump is, only that
18+
* something beside it answers this capability.
19+
*/
20+
public class CapabilityHeatSink {
21+
22+
@CapabilityInject(IHeatSink.class)
23+
public static Capability<IHeatSink> HEAT_SINK = null;
24+
25+
public CapabilityHeatSink() {
26+
}
27+
28+
/** Convenience: the sink capability on a tile entity, or null if it takes no heat. */
29+
@Nullable
30+
public static IHeatSink get(@Nullable TileEntity te) {
31+
if (te == null || te.isInvalid() || HEAT_SINK == null) {
32+
return null;
33+
}
34+
return te.getCapability(HEAT_SINK, null);
35+
}
36+
37+
public static void register() {
38+
CapabilityManager.INSTANCE.register(IHeatSink.class, new Capability.IStorage<IHeatSink>() {
39+
@Override
40+
public void readNBT(Capability<IHeatSink> capability, IHeatSink instance, EnumFacing side, NBTBase nbt) {
41+
}
42+
43+
@Override
44+
public NBTBase writeNBT(Capability<IHeatSink> capability, IHeatSink instance, EnumFacing side) {
45+
return null;
46+
}
47+
}, () -> new DefaultHeatSink(0L));
48+
}
49+
50+
/** A backing store for a foreign host that wants one, with the drain contract implemented. */
51+
/**
52+
* The plain implementation for a machine that simply holds what it is given up to a limit - the
53+
* dump keeps its own, so this exists for anything given the capability from outside.
54+
*/
55+
public static class DefaultHeatSink implements IHeatSink {
56+
private final long capacity;
57+
private long held;
58+
59+
public DefaultHeatSink(long capacity) {
60+
this.capacity = Math.max(0L, capacity);
61+
}
62+
63+
public long held() {
64+
return held;
65+
}
66+
67+
@Override
68+
public long getSinkRequestPerTick(double loopKelvin) {
69+
return Math.max(0L, capacity - held);
70+
}
71+
72+
@Override
73+
public long acceptHeat(long amount) {
74+
long taken = Math.max(0L, Math.min(amount, capacity - held));
75+
held += taken;
76+
return taken;
77+
}
78+
}
79+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package zmaster587.advancedRocketry.api.capability;
2+
3+
/**
4+
* A machine that TAKES heat off a coolant loop and puts it somewhere that is not the loop.
5+
*
6+
* <p>The mirror of {@link IHeatEmitter}, and deliberately the same shape read backwards: the loop
7+
* asks how much this machine wants and then hands over what it can, so a sink standing between two
8+
* loops is fed by both instead of being counted twice. The loop decides how much moves, never the
9+
* machine - the same asymmetry a radiator lives under, and for the same reason.</p>
10+
*
11+
* <p>A capability rather than a plain interface for the reason the emitter is one: the machines this
12+
* system exists to serve mostly belong to other mods, and a mod that wants to soak a ship's heat
13+
* into something of its own can be given this without either side importing the other.</p>
14+
*/
15+
public interface IHeatSink {
16+
17+
/**
18+
* How much heat this machine would take this tick, given how hot the loop offering it is.
19+
*
20+
* <p>The temperature is an ARGUMENT because a sink is allowed to be conditional - the emergency
21+
* dump only runs when the ship is already losing, which is what keeps it an emergency rather than
22+
* a cooling system. A sink that wants nothing answers zero and the loop moves nothing.</p>
23+
*/
24+
long getSinkRequestPerTick(double loopKelvin);
25+
26+
/** Take up to {@code amount}; returns what was actually absorbed, which is what the loop loses. */
27+
long acceptHeat(long amount);
28+
}

‎src/main/java/zmaster587/advancedRocketry/command/test/TestProbeCommand.java‎

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10905,7 +10905,12 @@ private void handleMachineTickUntil(MinecraftServer server, ICommandSender sende
1090510905
// and world time does not advance inside one probe call - so the interval is
1090610906
// set to 1 to make the sweep run on the tick the test asks for rather than on
1090710907
// whichever tick the phase happens to land on.
10908-
"shipHeatMeltCheckTicks"));
10908+
"shipHeatMeltCheckTicks",
10909+
// The dump's trigger and rate. A test of "it runs only when the ship is
10910+
// already losing" has to arrange BOTH sides of that threshold, and naming the
10911+
// temperature in the test instead would restate the tuned number.
10912+
"shipHeatDumpTriggerKelvin",
10913+
"shipHeatDumpThroughput"));
1090910914

1091010915
private void handleConfig(ICommandSender sender, String[] args) {
1091110916
if (args.length == 0) {
@@ -16775,6 +16780,46 @@ private void handleHeat(MinecraftServer server, ICommandSender sender, String[]
1677516780
handleHeatCycle(server, sender, args);
1677616781
return;
1677716782
}
16783+
if (args.length >= 5 && "dump".equalsIgnoreCase(args[0])) {
16784+
// The emergency dump's own state: what the slug it holds has taken, what it can still
16785+
// take, and whether the port is clear. Reported separately because "charge went to zero"
16786+
// is BOTH "nothing happened" and "it fired", and a test must be able to tell those apart.
16787+
int dim = parseIntOr(args[1], Integer.MIN_VALUE);
16788+
net.minecraft.world.WorldServer world = server.getWorld(dim);
16789+
if (world == null) {
16790+
send(sender, "{\"error\":\"world not loaded\",\"dim\":" + dim + "}");
16791+
return;
16792+
}
16793+
BlockPos pos = new BlockPos(parseIntOr(args[2], 0), parseIntOr(args[3], 0),
16794+
parseIntOr(args[4], 0));
16795+
net.minecraft.tileentity.TileEntity tile = world.getTileEntity(pos);
16796+
if (!(tile instanceof zmaster587.advancedRocketry.tile.heat.TileHeatDump)) {
16797+
send(sender, "{\"ok\":true,\"isDump\":false,\"charge\":0,\"headroom\":0,"
16798+
+ "\"hasStack\":false,\"obstruction\":0}");
16799+
return;
16800+
}
16801+
zmaster587.advancedRocketry.tile.heat.TileHeatDump dump =
16802+
(zmaster587.advancedRocketry.tile.heat.TileHeatDump) tile;
16803+
net.minecraft.item.ItemStack held = dump.getStackInSlot(0);
16804+
if (args.length >= 7 && "load".equalsIgnoreCase(args[5])) {
16805+
net.minecraft.item.Item item = net.minecraft.item.Item.getByNameOrId(args[6]);
16806+
if (item == null) {
16807+
send(sender, "{\"error\":\"unknown item id\",\"id\":\"" + escapeJson(args[6]) + "\"}");
16808+
return;
16809+
}
16810+
dump.setInventorySlotContents(0, new net.minecraft.item.ItemStack(item, 1));
16811+
held = dump.getStackInSlot(0);
16812+
}
16813+
send(sender, "{\"ok\":true,\"isDump\":true,\"charge\":"
16814+
+ zmaster587.advancedRocketry.tile.heat.TileHeatDump.chargeOf(held)
16815+
+ ",\"headroom\":" + dump.headroom()
16816+
+ ",\"hasStack\":" + (!held.isEmpty())
16817+
+ ",\"obstruction\":" + dump.getObstruction()
16818+
+ ",\"chargedThisTick\":" + dump.getChargedThisTick()
16819+
+ ",\"powered\":" + dump.isPowered()
16820+
+ ",\"request\":" + dump.getSinkRequestPerTick(9999.0D) + "}");
16821+
return;
16822+
}
1677816823
if (args.length >= 2 && "item".equalsIgnoreCase(args[0])) {
1677916824
// The same question asked of an ITEM rather than a position: an item has no collision
1678016825
// boxes, so this is the path that falls back to the shape of the block it would place.
@@ -16997,6 +17042,7 @@ private void handleHeatCycle(MinecraftServer server, ICommandSender sender, Stri
1699717042
+ ",\"rejected\":" + after.getRejectedThisTick()
1699817043
+ ",\"heatStored\":" + after.getStoredHeat()
1699917044
+ ",\"heatCapacity\":" + after.getHeatCapacity()
17045+
+ ",\"sunk\":" + after.getSunkThisTick()
1700017046
+ ",\"temperatureMilliK\":" + Math.round(after.getTemperatureKelvin() * 1000.0D)
1700117047
+ ",\"exchangers\":" + after.getExchangerCount()
1700217048
+ ",\"radiatingCells\":" + after.getRadiatingCells()

0 commit comments

Comments
 (0)