From 9d951ab19861c2dcc1839e9673a780b5c4af9fb9 Mon Sep 17 00:00:00 2001 From: KnightMiner Date: Sat, 24 Apr 2021 21:43:39 -0400 Subject: [PATCH] Improve behavior of tanks and drains Fixes issues with stacked containers (#4342) and hopefully speed (#4361) --- .../library/fluid/FluidTransferUtil.java | 150 ++++++++++++++++++ .../smeltery/block/MelterBlock.java | 3 +- .../block/component/SearedDrainBlock.java | 30 +--- .../block/component/SearedTankBlock.java | 3 +- .../smeltery/tileentity/ITankTileEntity.java | 80 ---------- 5 files changed, 157 insertions(+), 109 deletions(-) create mode 100644 src/main/java/slimeknights/tconstruct/library/fluid/FluidTransferUtil.java diff --git a/src/main/java/slimeknights/tconstruct/library/fluid/FluidTransferUtil.java b/src/main/java/slimeknights/tconstruct/library/fluid/FluidTransferUtil.java new file mode 100644 index 00000000000..1dce839193b --- /dev/null +++ b/src/main/java/slimeknights/tconstruct/library/fluid/FluidTransferUtil.java @@ -0,0 +1,150 @@ +package slimeknights.tconstruct.library.fluid; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.fluid.Fluid; +import net.minecraft.fluid.Fluids; +import net.minecraft.item.BucketItem; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.DrinkHelper; +import net.minecraft.util.Hand; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidAttributes; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; +import net.minecraftforge.fluids.capability.templates.EmptyFluidHandler; +import net.minecraftforge.items.ItemHandlerHelper; +import slimeknights.tconstruct.TConstruct; + +/** + * Alternative to {@link net.minecraftforge.fluids.FluidUtil} since no one has time to make the forge util not a buggy mess + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class FluidTransferUtil { + public static boolean tryTransfer(IFluidHandler input, IFluidHandler output, int maxFill) { + // first, figure out how much we can drain + FluidStack simulated = input.drain(maxFill, FluidAction.SIMULATE); + if (!simulated.isEmpty()) { + // next, find out how much we can fill + int simulatedFill = output.fill(simulated, FluidAction.SIMULATE); + if (simulatedFill > 0) { + // actually drain + FluidStack drainedFluid = input.drain(simulatedFill, FluidAction.EXECUTE); + if (!drainedFluid.isEmpty()) { + // acutally fill + int actualFill = output.fill(drainedFluid, FluidAction.EXECUTE); + if (actualFill != drainedFluid.getAmount()) { + TConstruct.log.error("Lost {} fluid during transfer", drainedFluid.getAmount() - actualFill); + } + } + return true; + } + } + return false; + } + + /** + * Attempts to interact with a flilled bucket on a fluid tank. This is unique as it handles fish buckets, which don't expose fluid capabilities + * @param world World instance + * @param pos Block position + * @param player Player + * @param hand Hand + * @param hit Hit side + * @param offset Direction to place fish + * @return True if using a bucket + */ + public static boolean interactWithBucket(World world, BlockPos pos, PlayerEntity player, Hand hand, Direction hit, Direction offset) { + ItemStack held = player.getHeldItem(hand); + if (held.getItem() instanceof BucketItem) { + BucketItem bucket = (BucketItem) held.getItem(); + Fluid fluid = bucket.getFluid(); + if (fluid != Fluids.EMPTY) { + if (!world.isRemote) { + TileEntity te = world.getTileEntity(pos); + if (te != null) { + te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, hit) + .ifPresent(handler -> { + FluidStack fluidStack = new FluidStack(bucket.getFluid(), FluidAttributes.BUCKET_VOLUME); + // must empty the whole bucket + if (handler.fill(fluidStack, FluidAction.SIMULATE) == FluidAttributes.BUCKET_VOLUME) { + handler.fill(fluidStack, FluidAction.EXECUTE); + bucket.onLiquidPlaced(world, held, pos.offset(offset)); + world.playSound(null, pos, fluid.getAttributes().getEmptySound(), SoundCategory.BLOCKS, 1.0F, 1.0F); + if (!player.isCreative()) { + player.setHeldItem(hand, held.getContainerItem()); + } + } + }); + } + } + return true; + } + } + return false; + } + + /** + * Base logic to interact with a tank + * @param world World instance + * @param pos Tank position + * @param player Player instance + * @param hand Hand used + * @param hit Hit position + * @return True if further interactions should be blocked, false otherwise + */ + public static boolean interactWithFluidItem(World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { + // success if the item is a fluid handler, regardless of if fluid moved + ItemStack stack = player.getHeldItem(hand); + Direction face = hit.getFace(); + // fetch capability before copying, bit more work when its a fluid handler, but saves copying time when its not + if (stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY).isPresent()) { + TileEntity te = world.getTileEntity(pos); + if (te != null) { + LazyOptional teCapability = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face); + if (teCapability.isPresent()) { + IFluidHandler teHandler = teCapability.orElse(EmptyFluidHandler.INSTANCE); + ItemStack copy = ItemHandlerHelper.copyStackWithSize(stack, 1); + copy.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY).ifPresent(itemHandler -> { + // first, try filling the TE from the item + boolean isSuccess = false; + if (tryTransfer(teHandler, itemHandler, Integer.MAX_VALUE)) { + isSuccess = true; + // if that failed, try filling the item handler from the TE + } else if (tryTransfer(itemHandler, teHandler, Integer.MAX_VALUE)) { + isSuccess = true; + } + // if either worked, update the player's inventory + if (isSuccess) { + player.setHeldItem(hand, DrinkHelper.fill(stack, player, itemHandler.getContainer())); + } + }); + } + } + return true; + } + return false; + } + + /** + * Utility to try fluid item then bucket + * @param world World instance + * @param pos Tank position + * @param player Player instance + * @param hand Hand used + * @param hit Hit position + * @return True if interacted + */ + public static boolean interactWithTank(World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { + return interactWithFluidItem(world, pos, player, hand, hit) + || interactWithBucket(world, pos, player, hand, hit.getFace(), hit.getFace()); + } +} diff --git a/src/main/java/slimeknights/tconstruct/smeltery/block/MelterBlock.java b/src/main/java/slimeknights/tconstruct/smeltery/block/MelterBlock.java index a5876679f5a..b50501252f9 100644 --- a/src/main/java/slimeknights/tconstruct/smeltery/block/MelterBlock.java +++ b/src/main/java/slimeknights/tconstruct/smeltery/block/MelterBlock.java @@ -15,6 +15,7 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import slimeknights.tconstruct.common.TinkerTags; +import slimeknights.tconstruct.library.fluid.FluidTransferUtil; import slimeknights.tconstruct.smeltery.tileentity.ITankTileEntity; import slimeknights.tconstruct.smeltery.tileentity.MelterTileEntity; @@ -101,7 +102,7 @@ public TileEntity createTileEntity(BlockState blockState, IBlockReader iBlockRea @Deprecated @Override public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { - if (ITankTileEntity.interactWithTank(world, pos, player, hand, hit)) { + if (FluidTransferUtil.interactWithTank(world, pos, player, hand, hit)) { return ActionResultType.SUCCESS; } return super.onBlockActivated(state, world, pos, player, hand, hit); diff --git a/src/main/java/slimeknights/tconstruct/smeltery/block/component/SearedDrainBlock.java b/src/main/java/slimeknights/tconstruct/smeltery/block/component/SearedDrainBlock.java index c305962abe6..3e14de79228 100644 --- a/src/main/java/slimeknights/tconstruct/smeltery/block/component/SearedDrainBlock.java +++ b/src/main/java/slimeknights/tconstruct/smeltery/block/component/SearedDrainBlock.java @@ -2,20 +2,13 @@ import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ActionResultType; -import net.minecraft.util.Direction; import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; -import net.minecraftforge.fluids.FluidActionResult; -import net.minecraftforge.fluids.FluidUtil; -import net.minecraftforge.fluids.capability.CapabilityFluidHandler; -import net.minecraftforge.items.CapabilityItemHandler; +import slimeknights.tconstruct.library.fluid.FluidTransferUtil; import slimeknights.tconstruct.smeltery.tileentity.DrainTileEntity; -import slimeknights.tconstruct.smeltery.tileentity.ITankTileEntity; /** Extenson to include interaction behavior */ public class SearedDrainBlock extends OrientableSmelteryBlock { @@ -27,26 +20,9 @@ public SearedDrainBlock(Properties properties) { @Deprecated @Override public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { - // success if the item is a fluid handler, regardless of if fluid moved - ItemStack held = player.getHeldItem(hand); - Direction face = hit.getFace(); - if (FluidUtil.getFluidHandler(held).isPresent()) { - if (!world.isRemote()) { - // find the player inventory and the tank fluid handler and interact - TileEntity te = world.getTileEntity(pos); - if (te != null) { - te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face) - .ifPresent(handler -> player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) - .ifPresent(inv -> { - FluidActionResult result = FluidUtil.tryEmptyContainerAndStow(held, handler, inv, Integer.MAX_VALUE, player, true); - if (result.isSuccess()) { - player.setHeldItem(hand, result.getResult()); - } - })); - } - } + if (FluidTransferUtil.interactWithFluidItem(world, pos, player, hand, hit)) { return ActionResultType.SUCCESS; - } else if (ITankTileEntity.interactWithBucket(world, pos, player, hand, face, state.get(FACING).getOpposite())) { + } else if (FluidTransferUtil.interactWithBucket(world, pos, player, hand, hit.getFace(), state.get(FACING).getOpposite())) { return ActionResultType.SUCCESS; } return ActionResultType.PASS; diff --git a/src/main/java/slimeknights/tconstruct/smeltery/block/component/SearedTankBlock.java b/src/main/java/slimeknights/tconstruct/smeltery/block/component/SearedTankBlock.java index b056483cbdf..67b9f3e8ce1 100644 --- a/src/main/java/slimeknights/tconstruct/smeltery/block/component/SearedTankBlock.java +++ b/src/main/java/slimeknights/tconstruct/smeltery/block/component/SearedTankBlock.java @@ -19,6 +19,7 @@ import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.fluids.FluidStack; import slimeknights.mantle.util.TileEntityHelper; +import slimeknights.tconstruct.library.fluid.FluidTransferUtil; import slimeknights.tconstruct.library.materials.MaterialValues; import slimeknights.tconstruct.library.utils.Tags; import slimeknights.tconstruct.smeltery.tileentity.ITankTileEntity; @@ -55,7 +56,7 @@ public TileEntity createTileEntity(BlockState state, IBlockReader worldIn) { @Deprecated @Override public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { - if (ITankTileEntity.interactWithTank(world, pos, player, hand, hit)) { + if (FluidTransferUtil.interactWithTank(world, pos, player, hand, hit)) { return ActionResultType.SUCCESS; } return super.onBlockActivated(state, world, pos, player, hand, hit); diff --git a/src/main/java/slimeknights/tconstruct/smeltery/tileentity/ITankTileEntity.java b/src/main/java/slimeknights/tconstruct/smeltery/tileentity/ITankTileEntity.java index 30dea625866..ab27ad53691 100644 --- a/src/main/java/slimeknights/tconstruct/smeltery/tileentity/ITankTileEntity.java +++ b/src/main/java/slimeknights/tconstruct/smeltery/tileentity/ITankTileEntity.java @@ -2,25 +2,12 @@ import net.minecraft.block.BlockState; import net.minecraft.client.Minecraft; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.fluid.Fluid; -import net.minecraft.fluid.Fluids; -import net.minecraft.item.BucketItem; -import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.Direction; -import net.minecraft.util.Hand; -import net.minecraft.util.SoundCategory; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.IWorld; import net.minecraft.world.World; import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fluids.FluidAttributes; import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.fluids.FluidUtil; -import net.minecraftforge.fluids.capability.CapabilityFluidHandler; -import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; import net.minecraftforge.fml.DistExecutor; import slimeknights.mantle.client.model.util.ModelHelper; import slimeknights.tconstruct.common.config.Config; @@ -117,73 +104,6 @@ default TileEntity getTE() { * Helpers */ - /** - * Attempts to interact with a flilled bucket on a fluid tank. This is unique as it handles fish buckets, which don't expose fluid capabilities - * @param world World instance - * @param pos Block position - * @param player Player - * @param hand Hand - * @param hit Hit side - * @param offset Direction to place fish - * @return True if using a bucket - */ - static boolean interactWithBucket(World world, BlockPos pos, PlayerEntity player, Hand hand, Direction hit, Direction offset) { - ItemStack held = player.getHeldItem(hand); - if (held.getItem() instanceof BucketItem) { - BucketItem bucket = (BucketItem) held.getItem(); - Fluid fluid = bucket.getFluid(); - if (fluid != Fluids.EMPTY) { - if (!world.isRemote) { - TileEntity te = world.getTileEntity(pos); - if (te != null) { - te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, hit) - .ifPresent(handler -> { - FluidStack fluidStack = new FluidStack(bucket.getFluid(), FluidAttributes.BUCKET_VOLUME); - // must empty the whole bucket - if (handler.fill(fluidStack, FluidAction.SIMULATE) == FluidAttributes.BUCKET_VOLUME) { - handler.fill(fluidStack, FluidAction.EXECUTE); - bucket.onLiquidPlaced(world, held, pos.offset(offset)); - world.playSound(null, pos, fluid.getAttributes().getEmptySound(), SoundCategory.BLOCKS, 1.0F, 1.0F); - if (!player.isCreative()) { - player.setHeldItem(hand, held.getContainerItem()); - } - } - }); - } - } - return true; - } - } - return false; - } - - /** - * Base logic to interact with a tank - * @param world World instance - * @param pos Tank position - * @param player Player instance - * @param hand Hand used - * @param hit Hit position - * @return True if further interactions should be blocked, false otherwise - */ - static boolean interactWithTank(World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { - // success if the item is a fluid handler, regardless of if fluid moved - ItemStack stack = player.getHeldItem(hand); - Direction face = hit.getFace(); - if (FluidUtil.getFluidHandler(stack).isPresent()) { - if (!world.isRemote()) { - TileEntity te = world.getTileEntity(pos); - if (te != null) { - te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face) - .ifPresent(handler -> FluidUtil.interactWithFluidHandler(player, hand, handler)); - } - } - return true; - } - // fall back to buckets for fish buckets - return interactWithBucket(world, pos, player, hand, face, face); - } - /** * Implements logic for {@link net.minecraft.block.Block#getComparatorInputOverride(BlockState, World, BlockPos)} * @param world World instance