From e966455143c95bd8665e36149c8b7aa35f44e6f9 Mon Sep 17 00:00:00 2001 From: TropheusJ Date: Sat, 15 Feb 2025 21:46:30 -0500 Subject: [PATCH] rework BlockSpoutingBehaviour and add new behaviors --- src/main/java/com/simibubi/create/Create.java | 4 +- .../api/behaviour/BlockSpoutingBehaviour.java | 46 -------- .../spouting/BlockSpoutingBehaviour.java | 103 ++++++++++++++++++ .../spouting/CauldronSpoutingBehavior.java | 56 ++++++++++ .../spouting/StateChangingBehavior.java | 71 ++++++++++++ .../schematic/nbt/SafeNbtWriterRegistry.java | 2 +- .../SchematicRequirementRegistries.java | 6 +- .../compat/tconstruct/SpoutCasting.java | 24 +--- .../fluids/spout/SpoutBlockEntity.java | 19 ++-- .../behaviour/BlockSpoutingBehaviourImpl.java | 25 ----- 10 files changed, 250 insertions(+), 106 deletions(-) delete mode 100644 src/main/java/com/simibubi/create/api/behaviour/BlockSpoutingBehaviour.java create mode 100644 src/main/java/com/simibubi/create/api/behaviour/spouting/BlockSpoutingBehaviour.java create mode 100644 src/main/java/com/simibubi/create/api/behaviour/spouting/CauldronSpoutingBehavior.java create mode 100644 src/main/java/com/simibubi/create/api/behaviour/spouting/StateChangingBehavior.java delete mode 100644 src/main/java/com/simibubi/create/impl/behaviour/BlockSpoutingBehaviourImpl.java diff --git a/src/main/java/com/simibubi/create/Create.java b/src/main/java/com/simibubi/create/Create.java index 4477ab56af..31119ef0f9 100644 --- a/src/main/java/com/simibubi/create/Create.java +++ b/src/main/java/com/simibubi/create/Create.java @@ -7,7 +7,7 @@ import org.slf4j.Logger; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.mojang.logging.LogUtils; -import com.simibubi.create.api.behaviour.BlockSpoutingBehaviour; +import com.simibubi.create.api.behaviour.spouting.BlockSpoutingBehaviour; import com.simibubi.create.compat.Mods; import com.simibubi.create.compat.computercraft.ComputerCraftProxy; import com.simibubi.create.compat.curios.Curios; @@ -135,7 +135,6 @@ public class Create { AllArmInteractionPointTypes.register(modEventBus); AllFanProcessingTypes.register(modEventBus); AllItemAttributeTypes.register(modEventBus); - BlockSpoutingBehaviour.registerDefaults(); // FIXME: some of these registrations are not thread-safe AllMovementBehaviours.registerDefaults(); @@ -173,6 +172,7 @@ public class Create { BoilerHeaters.registerDefaults(); AllPortalTracks.registerDefaults(); AllDisplayBehaviours.registerDefaults(); + BlockSpoutingBehaviour.registerDefaults(); // -- AllAdvancements.register(); diff --git a/src/main/java/com/simibubi/create/api/behaviour/BlockSpoutingBehaviour.java b/src/main/java/com/simibubi/create/api/behaviour/BlockSpoutingBehaviour.java deleted file mode 100644 index f3590e4b0b..0000000000 --- a/src/main/java/com/simibubi/create/api/behaviour/BlockSpoutingBehaviour.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.simibubi.create.api.behaviour; - -import com.simibubi.create.Create; -import com.simibubi.create.compat.tconstruct.SpoutCasting; -import com.simibubi.create.content.fluids.spout.SpoutBlockEntity; -import com.simibubi.create.impl.behaviour.BlockSpoutingBehaviourImpl; - -import net.minecraft.core.BlockPos; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.Level; -import net.minecraftforge.fluids.FluidStack; - -public abstract class BlockSpoutingBehaviour { - /** - * Register a new custom spout interaction - * - * @param resourceLocation The interaction id - * @param spoutingBehaviour An instance of your behaviour class - */ - public static void addCustomSpoutInteraction(ResourceLocation resourceLocation, BlockSpoutingBehaviour spoutingBehaviour) { - BlockSpoutingBehaviourImpl.addCustomSpoutInteraction(resourceLocation, spoutingBehaviour); - } - - public static void registerDefaults() { - addCustomSpoutInteraction(Create.asResource("ticon_casting"), new SpoutCasting()); - } - - /** - * While idle, Spouts will call this every tick with simulate == true
- * When fillBlock returns > 0, the Spout will start its animation cycle
- *
- * During this animation cycle, fillBlock is called once again with simulate == false but only on the relevant SpoutingBehaviour
- * When fillBlock returns > 0 once again, the Spout will drain its content by the returned amount of units
- * Perform any other side effects in this method
- * This method is called server-side only (except in ponder)
- * - * @param level The current level - * @param pos The position of the affected block - * @param spout The spout block entity that is calling this - * @param availableFluid A copy of the fluidStack that is available, modifying this will do nothing, return the amount to be subtracted instead - * @param simulate Whether the spout is testing or actually performing this behaviour - * @return The amount filled into the block, 0 to idle/cancel - */ - public abstract int fillBlock(Level level, BlockPos pos, SpoutBlockEntity spout, FluidStack availableFluid, boolean simulate); - -} diff --git a/src/main/java/com/simibubi/create/api/behaviour/spouting/BlockSpoutingBehaviour.java b/src/main/java/com/simibubi/create/api/behaviour/spouting/BlockSpoutingBehaviour.java new file mode 100644 index 0000000000..f1d9ebe32f --- /dev/null +++ b/src/main/java/com/simibubi/create/api/behaviour/spouting/BlockSpoutingBehaviour.java @@ -0,0 +1,103 @@ +package com.simibubi.create.api.behaviour.spouting; + +import java.util.List; +import java.util.function.Predicate; + +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.Create; +import com.simibubi.create.api.registry.SimpleRegistry; +import com.simibubi.create.compat.Mods; +import com.simibubi.create.compat.tconstruct.SpoutCasting; +import com.simibubi.create.content.fluids.spout.SpoutBlockEntity; + +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.FarmBlock; +import net.minecraft.world.level.block.LayeredCauldronBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Fluids; + +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.registries.ForgeRegistries; + +/** + * Interface for custom block-filling behavior for spouts. + *

+ * Behaviors are queried by block first, through {@link #BY_BLOCK}. If no behavior was provided, + * they are then queried by block entity type, through {@link #BY_BLOCK_ENTITY}. + * @see StateChangingBehavior + * @see CauldronSpoutingBehavior + */ +@FunctionalInterface +public interface BlockSpoutingBehaviour { + SimpleRegistry BY_BLOCK = SimpleRegistry.create(); + SimpleRegistry, BlockSpoutingBehaviour> BY_BLOCK_ENTITY = SimpleRegistry.create(); + + /** + * Get the behavior that should be used for the block at the given location. + * Queries both the block and the block entity if needed. + */ + @Nullable + static BlockSpoutingBehaviour get(Level level, BlockPos pos) { + BlockState state = level.getBlockState(pos); + BlockSpoutingBehaviour byBlock = BY_BLOCK.get(state.getBlock()); + if (byBlock != null) + return byBlock; + + BlockEntity be = level.getBlockEntity(pos); + if (be == null) + return null; + + return BY_BLOCK_ENTITY.get(be.getType()); + } + + static void registerDefaults() { + Predicate isWater = fluid -> fluid.isSame(Fluids.WATER); + BlockSpoutingBehaviour toMud = StateChangingBehavior.setTo(250, isWater, Blocks.MUD); + + for (Block dirt : List.of(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.ROOTED_DIRT)) { + BY_BLOCK.register(dirt, toMud); + } + + BY_BLOCK.register(Blocks.FARMLAND, StateChangingBehavior.incrementingState(100, isWater, FarmBlock.MOISTURE)); + BY_BLOCK.register(Blocks.WATER_CAULDRON, StateChangingBehavior.incrementingState(250, isWater, LayeredCauldronBlock.LEVEL)); + BY_BLOCK.register(Blocks.CAULDRON, CauldronSpoutingBehavior.INSTANCE); + + if (!Mods.TCONSTRUCT.isLoaded()) + return; + + for (String name : List.of("table", "basin")) { + ResourceLocation id = Mods.TCONSTRUCT.rl(name); + if (ForgeRegistries.BLOCK_ENTITY_TYPES.containsKey(id)) { + BlockEntityType table = ForgeRegistries.BLOCK_ENTITY_TYPES.getValue(id); + BY_BLOCK_ENTITY.register(table, SpoutCasting.INSTANCE); + } else { + Create.LOGGER.warn("Block entity {} wasn't found. Outdated compat?", id); + } + } + } + + /** + * While idle, spouts will query the behavior provided by the block below it. + * If one is present, this method will be called every tick with simulate == true. + *

+ * When a value greater than 0 is returned, the spout will begin processing. It will call this method again + * with simulate == false, which is when any filling behavior should actually occur. + *

+ * This method is only called on the server side, except for in Ponder. + * @param level The current level + * @param pos The position of the affected block + * @param spout The spout block entity that is calling this + * @param availableFluid A copy of the fluidStack that is available, modifying this will do nothing, return the amount to be subtracted instead + * @param simulate Whether the spout is testing or actually performing this behaviour + * @return The amount filled into the block, 0 to idle/cancel + */ + int fillBlock(Level level, BlockPos pos, SpoutBlockEntity spout, FluidStack availableFluid, boolean simulate); +} diff --git a/src/main/java/com/simibubi/create/api/behaviour/spouting/CauldronSpoutingBehavior.java b/src/main/java/com/simibubi/create/api/behaviour/spouting/CauldronSpoutingBehavior.java new file mode 100644 index 0000000000..773f5dc2f6 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/behaviour/spouting/CauldronSpoutingBehavior.java @@ -0,0 +1,56 @@ +package com.simibubi.create.api.behaviour.spouting; + +import com.simibubi.create.api.registry.SimpleRegistry; +import com.simibubi.create.content.fluids.spout.SpoutBlockEntity; + +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Fluids; + +import net.minecraftforge.fluids.FluidStack; + +/** + * {@link BlockSpoutingBehaviour} for empty cauldrons. Mods can register their fluids + * to {@link #CAULDRON_INFO} to allow spouts to fill empty cauldrons with their fluids. + */ +public enum CauldronSpoutingBehavior implements BlockSpoutingBehaviour { + INSTANCE; + + public static final SimpleRegistry CAULDRON_INFO = Util.make(() -> { + SimpleRegistry registry = SimpleRegistry.create(); + registry.register(Fluids.WATER, new CauldronInfo(250, Blocks.WATER_CAULDRON)); + registry.register(Fluids.LAVA, new CauldronInfo(1000, Blocks.LAVA_CAULDRON)); + return registry; + }); + + @Override + public int fillBlock(Level level, BlockPos pos, SpoutBlockEntity spout, FluidStack availableFluid, boolean simulate) { + CauldronInfo info = CAULDRON_INFO.get(availableFluid.getFluid()); + if (info == null) + return 0; + + if (availableFluid.getAmount() < info.amount) + return 0; + + if (!simulate) { + level.setBlockAndUpdate(pos, info.cauldron); + } + + return info.amount; + } + + /** + * @param amount the amount of fluid that must be inserted into an empty cauldron + * @param cauldron the BlockState to set after filling an empty cauldron with the given amount of fluid + */ + public record CauldronInfo(int amount, BlockState cauldron) { + public CauldronInfo(int amount, Block block) { + this(amount, block.defaultBlockState()); + } + } +} diff --git a/src/main/java/com/simibubi/create/api/behaviour/spouting/StateChangingBehavior.java b/src/main/java/com/simibubi/create/api/behaviour/spouting/StateChangingBehavior.java new file mode 100644 index 0000000000..e4064c5f3a --- /dev/null +++ b/src/main/java/com/simibubi/create/api/behaviour/spouting/StateChangingBehavior.java @@ -0,0 +1,71 @@ +package com.simibubi.create.api.behaviour.spouting; + +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +import com.simibubi.create.content.fluids.spout.SpoutBlockEntity; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.material.Fluid; + +import net.minecraftforge.fluids.FluidStack; + +/** + * An implementation of {@link BlockSpoutingBehaviour} that allows for easily modifying a BlockState through spouting. + * @param amount the amount of fluid consumed when filling + * @param fluidTest a predicate for fluids that can be used to fill the target block + * @param canFill a predicate that must match the target BlockState to fill it + * @param fillFunction a function that converts the current state into the filled one + */ +public record StateChangingBehavior(int amount, Predicate fluidTest, Predicate canFill, + UnaryOperator fillFunction) implements BlockSpoutingBehaviour { + @Override + public int fillBlock(Level level, BlockPos pos, SpoutBlockEntity spout, FluidStack availableFluid, boolean simulate) { + if (availableFluid.getAmount() < this.amount || !this.fluidTest.test(availableFluid.getFluid())) + return 0; + + BlockState state = level.getBlockState(pos); + if (!this.canFill.test(state)) + return 0; + + if (!simulate) { + BlockState newState = this.fillFunction.apply(state); + level.setBlockAndUpdate(pos, newState); + } + + return this.amount; + } + + /** + * Shortcut for {@link #setTo(int, Predicate, BlockState)} that uses the Block's default state. + */ + public static BlockSpoutingBehaviour setTo(int amount, Predicate fluidTest, Block block) { + return setTo(amount, fluidTest, block.defaultBlockState()); + } + + /** + * Create a {@link BlockSpoutingBehaviour} that will simply convert the target block to the given state. + * @param newState the state that will be set after filling + */ + public static BlockSpoutingBehaviour setTo(int amount, Predicate fluidTest, BlockState newState) { + return new StateChangingBehavior(amount, fluidTest, state -> true, state -> newState); + } + + /** + * Create a {@link BlockSpoutingBehaviour} that will increment the given {@link IntegerProperty} until it reaches + * its maximum value, consuming {@code amount} each time fluid is filled. + * @param property the property that will be incremented by one on each fill + */ + public static BlockSpoutingBehaviour incrementingState(int amount, Predicate fluidTest, IntegerProperty property) { + int max = property.getPossibleValues().stream().max(Integer::compareTo).orElseThrow(); + + Predicate canFill = state -> state.getValue(property) < max; + UnaryOperator fillFunction = state -> state.setValue(property, state.getValue(property) + 1); + + return new StateChangingBehavior(amount, fluidTest, canFill, fillFunction); + } +} diff --git a/src/main/java/com/simibubi/create/api/schematic/nbt/SafeNbtWriterRegistry.java b/src/main/java/com/simibubi/create/api/schematic/nbt/SafeNbtWriterRegistry.java index c9b7e5b179..4f8f73b6a5 100644 --- a/src/main/java/com/simibubi/create/api/schematic/nbt/SafeNbtWriterRegistry.java +++ b/src/main/java/com/simibubi/create/api/schematic/nbt/SafeNbtWriterRegistry.java @@ -11,7 +11,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType; *

* This is used to exclude specific tags that would result in exploits, ex. signs that execute commands when clicked. *

- * This is provided as an alternative to {@link IPartialSafeNBT}. + * This is provided as an alternative to {@link PartialSafeNBT}. */ public class SafeNbtWriterRegistry { public static final SimpleRegistry, SafeNbtWriter> REGISTRY = SimpleRegistry.create(); diff --git a/src/main/java/com/simibubi/create/api/schematic/requirement/SchematicRequirementRegistries.java b/src/main/java/com/simibubi/create/api/schematic/requirement/SchematicRequirementRegistries.java index ca7244432d..af2a0a87ab 100644 --- a/src/main/java/com/simibubi/create/api/schematic/requirement/SchematicRequirementRegistries.java +++ b/src/main/java/com/simibubi/create/api/schematic/requirement/SchematicRequirementRegistries.java @@ -18,9 +18,9 @@ import net.minecraft.world.level.block.state.BlockState; *

* This is provided as an alternative to the following interfaces: *

*/ public class SchematicRequirementRegistries { diff --git a/src/main/java/com/simibubi/create/compat/tconstruct/SpoutCasting.java b/src/main/java/com/simibubi/create/compat/tconstruct/SpoutCasting.java index 18df35cd14..f777cce56b 100644 --- a/src/main/java/com/simibubi/create/compat/tconstruct/SpoutCasting.java +++ b/src/main/java/com/simibubi/create/compat/tconstruct/SpoutCasting.java @@ -1,31 +1,25 @@ package com.simibubi.create.compat.tconstruct; -import com.simibubi.create.api.behaviour.BlockSpoutingBehaviour; -import com.simibubi.create.compat.Mods; +import com.simibubi.create.api.behaviour.spouting.BlockSpoutingBehaviour; import com.simibubi.create.content.fluids.spout.SpoutBlockEntity; import com.simibubi.create.foundation.fluid.FluidHelper; import com.simibubi.create.infrastructure.config.AllConfigs; -import net.createmod.catnip.platform.CatnipServices; + import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; -public class SpoutCasting extends BlockSpoutingBehaviour { - - private static final boolean TICON_PRESENT = Mods.TCONSTRUCT.isLoaded(); - - ResourceLocation TABLE = new ResourceLocation("tconstruct", "table"); - ResourceLocation BASIN = new ResourceLocation("tconstruct", "basin"); +public enum SpoutCasting implements BlockSpoutingBehaviour { + INSTANCE; @Override - public int fillBlock(Level level, BlockPos pos, SpoutBlockEntity spout, FluidStack availableFluid, - boolean simulate) { + public int fillBlock(Level level, BlockPos pos, SpoutBlockEntity spout, FluidStack availableFluid, boolean simulate) { if (!enabled()) return 0; @@ -40,9 +34,6 @@ public class SpoutCasting extends BlockSpoutingBehaviour { if (handler.getTanks() != 1) return 0; - ResourceLocation registryName = CatnipServices.REGISTRIES.getKeyOrThrow(blockEntity.getType()); - if (!registryName.equals(TABLE) && !registryName.equals(BASIN)) - return 0; if (!handler.isFluidValid(0, availableFluid)) return 0; @@ -61,9 +52,6 @@ public class SpoutCasting extends BlockSpoutingBehaviour { } private boolean enabled() { - if (!TICON_PRESENT) - return false; return AllConfigs.server().recipes.allowCastingBySpout.get(); } - } diff --git a/src/main/java/com/simibubi/create/content/fluids/spout/SpoutBlockEntity.java b/src/main/java/com/simibubi/create/content/fluids/spout/SpoutBlockEntity.java index 76774b1836..03e055c648 100644 --- a/src/main/java/com/simibubi/create/content/fluids/spout/SpoutBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/fluids/spout/SpoutBlockEntity.java @@ -8,7 +8,7 @@ import java.util.List; import com.simibubi.create.AllItems; import com.simibubi.create.AllSoundEvents; -import com.simibubi.create.api.behaviour.BlockSpoutingBehaviour; +import com.simibubi.create.api.behaviour.spouting.BlockSpoutingBehaviour; import com.simibubi.create.api.equipment.goggles.IHaveGoggleInformation; import com.simibubi.create.content.fluids.FluidFX; import com.simibubi.create.content.kinetics.belt.behaviour.BeltProcessingBehaviour; @@ -22,7 +22,6 @@ import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.fluid.SmartFluidTankBehaviour; import com.simibubi.create.foundation.fluid.FluidHelper; -import com.simibubi.create.impl.behaviour.BlockSpoutingBehaviourImpl; import net.createmod.catnip.math.VecHelper; import net.createmod.catnip.nbt.NBTHelper; @@ -199,15 +198,13 @@ public class SpoutBlockEntity extends SmartBlockEntity implements IHaveGoggleInf FluidStack currentFluidInTank = getCurrentFluidInTank(); if (processingTicks == -1 && (isVirtual() || !level.isClientSide()) && !currentFluidInTank.isEmpty()) { - BlockSpoutingBehaviourImpl.forEach(behaviour -> { - if (customProcess != null) - return; - if (behaviour.fillBlock(level, worldPosition.below(2), this, currentFluidInTank.copy(), true) > 0) { - processingTicks = FILLING_TIME; - customProcess = behaviour; - notifyUpdate(); - } - }); + BlockPos filling = this.worldPosition.below(2); + BlockSpoutingBehaviour behavior = BlockSpoutingBehaviour.get(this.level, filling); + if (behavior != null && behavior.fillBlock(this.level, filling, this, currentFluidInTank.copy(), true) > 0) { + processingTicks = FILLING_TIME; + customProcess = behavior; + notifyUpdate(); + } } if (processingTicks >= 0) { diff --git a/src/main/java/com/simibubi/create/impl/behaviour/BlockSpoutingBehaviourImpl.java b/src/main/java/com/simibubi/create/impl/behaviour/BlockSpoutingBehaviourImpl.java deleted file mode 100644 index 952a4d9b1d..0000000000 --- a/src/main/java/com/simibubi/create/impl/behaviour/BlockSpoutingBehaviourImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.simibubi.create.impl.behaviour; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; - -import org.jetbrains.annotations.ApiStatus; - -import com.simibubi.create.api.behaviour.BlockSpoutingBehaviour; - -import net.minecraft.resources.ResourceLocation; - -// TODO - Make this use AttachedRegistry later -@ApiStatus.Internal -public class BlockSpoutingBehaviourImpl { - private static final Map BLOCK_SPOUTING_BEHAVIOURS = new ConcurrentHashMap<>(); - - public static void addCustomSpoutInteraction(ResourceLocation resourceLocation, BlockSpoutingBehaviour spoutingBehaviour) { - BLOCK_SPOUTING_BEHAVIOURS.put(resourceLocation, spoutingBehaviour); - } - - public static void forEach(Consumer accept) { - BLOCK_SPOUTING_BEHAVIOURS.values().forEach(accept); - } -}