Add fluid block autoplace with a pluggable fluid cost and source API

Water, lava and every Forge fluid block can now be placed by survival autoplace, paid for with fluid instead of with an item.

* Fluid costs are registered per block state, water and lava are registered by default, and a registration can ask for fluid even when the block also has an item form.
* A fluid is only placed once the rest of the structure is in place, so it cannot flow out of a half built multiblock, and only the fluid missing from a position is charged, so topping up a leak costs the difference instead of a whole bucket.
* The fluid comes from the player: their fluid containers, plus whatever mods contribute through the new fluid container extractor and fluid source provider registries.
* Existing structure definitions need no change, as ofBlock and the other block elements become fluid elements when the block has no item form.
This commit is contained in:
ABKQPO committed 2026-09-10 18:35:16 +08:00
1 parent d829ae466b
commit 98101c00aa
27 files changed
+2395 -83

No files matched your search

@@ -13,6 +13,9 @@ import net.minecraft.launchwrapper.Launch;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -20,6 +23,9 @@ import org.apache.logging.log4j.Logger;
import com.gtnewhorizon.structurelib.block.BlockHint;
import com.gtnewhorizon.structurelib.command.CommandConfigureChannels;
import com.gtnewhorizon.structurelib.command.CommandRegistryDebug;
import com.gtnewhorizon.structurelib.fluid.FluidContainerExtractors;
import com.gtnewhorizon.structurelib.fluid.FluidPlacementRegistry;
import com.gtnewhorizon.structurelib.fluid.FluidSourceProviders;
import com.gtnewhorizon.structurelib.item.ItemBlockHint;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import com.gtnewhorizon.structurelib.item.ItemFrontRotationTool;
@@ -109,6 +115,10 @@ public class StructureLib {
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent e) {
InventoryUtility.init();
FluidContainerExtractors.init();
FluidSourceProviders.init();
FluidPlacementRegistry.init();
ConfigurationHandler.INSTANCE.init(e.getSuggestedConfigurationFile());
GameRegistry.registerBlock(blockHint = new BlockHint(), ItemBlockHint.class, "blockhint");
itemBlockHint = ItemBlock.getItemFromBlock(StructureLibAPI.getBlockHint());
@@ -121,8 +131,6 @@ public class StructureLib {
proxy.preInit(e);
NetworkRegistry.INSTANCE.registerGuiHandler(instance(), new GuiHandler());
InventoryUtility.init();
ChannelDescription.set(CHANNEL_SHOW_ERROR, MOD_ID, "channels.structurelib.show_errors");
DevelopHelper.onPreInit();
@@ -149,10 +157,52 @@ public class StructureLib {
case "register_channel_item":
processRegisterChannelItem(message);
break;
case "register_fluid_block_cost":
processRegisterFluidBlockCost(message);
break;
}
}
}
/**
* Teach StructureLib what it costs to place a fluid block, without having to link against StructureLib.
* <p>
* The message must hold a compound with {@code Block} (registry name), {@code Fluid} (fluid name), optionally
* {@code Meta} (default 0), {@code Amount} (default one bucket) and {@code ForceFluid} (default false).
*/
private void processRegisterFluidBlockCost(FMLInterModComms.IMCMessage message) {
if (!message.isNBTMessage()) {
LOGGER.warn("{} sent a register_fluid_block_cost message without any data", message.getSender());
return;
}
NBTTagCompound tag = message.getNBTValue();
String blockName = tag.getString("Block");
String fluidName = tag.getString("Fluid");
Object block = Block.blockRegistry.getObject(blockName);
Fluid fluid = FluidRegistry.getFluid(fluidName);
if (!(block instanceof Block)) {
LOGGER.warn("{} tried to register a fluid block cost for unknown block {}", message.getSender(), blockName);
return;
}
if (fluid == null) {
LOGGER.warn("{} tried to register a fluid block cost for unknown fluid {}", message.getSender(), fluidName);
return;
}
int meta = tag.getInteger("Meta");
int amount = tag.hasKey("Amount") ? tag.getInteger("Amount") : FluidPlacementRegistry.DEFAULT_FLUID_AMOUNT;
boolean forceFluid = tag.getBoolean("ForceFluid");
try {
FluidPlacementRegistry.register((Block) block, meta, new FluidStack(fluid, amount), forceFluid);
} catch (IllegalArgumentException e) {
// never let a badly formed message from another mod break the game
LOGGER.warn(
"{} tried to register a fluid block cost for {}:{} twice",
message.getSender(),
blockName,
meta);
}
}
private void processRegisterChannel(FMLInterModComms.IMCMessage message) {
NBTTagList tags = message.getNBTValue().getTagList("Channels", Constants.NBT.TAG_COMPOUND);
for (int i = 0; i < tags.tagCount(); i++) {