From 29f1a5c1176c7e8cc3f9665e26bcf0ea169f1159 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 25 Sep 2025 16:36:49 -0400 Subject: [PATCH] [1.21.3] Fix crafter not inserting into iitemhandler (#10667) (#10670) Co-authored-by: LexManos --- .../world/level/block/CrafterBlock.java.patch | 33 ++ .../extensions/IForgeGameTestHelper.java | 61 ++++ .../items/VanillaInventoryCodeHooks.java | 317 +++++++++--------- .../gameplay/block/CrafterBlockTest.java | 118 +++++++ 4 files changed, 370 insertions(+), 159 deletions(-) create mode 100644 patches/minecraft/net/minecraft/world/level/block/CrafterBlock.java.patch create mode 100644 src/test/java/net/minecraftforge/debug/gameplay/block/CrafterBlockTest.java diff --git a/patches/minecraft/net/minecraft/world/level/block/CrafterBlock.java.patch b/patches/minecraft/net/minecraft/world/level/block/CrafterBlock.java.patch new file mode 100644 index 0000000000..310406151f --- /dev/null +++ b/patches/minecraft/net/minecraft/world/level/block/CrafterBlock.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/world/level/block/CrafterBlock.java ++++ b/net/minecraft/world/level/block/CrafterBlock.java +@@ -185,12 +_,15 @@ + ServerLevel p_336186_, BlockPos p_312358_, CrafterBlockEntity p_309887_, ItemStack p_310474_, BlockState p_310667_, RecipeHolder p_329387_ + ) { + Direction direction = p_310667_.getValue(ORIENTATION).front(); ++ var itemhandler = net.minecraftforge.items.VanillaInventoryCodeHooks.getItemHandler(p_336186_, p_312358_.relative(direction), direction.getOpposite()).orElse(null); + Container container = HopperBlockEntity.getContainerAt(p_336186_, p_312358_.relative(direction)); + ItemStack itemstack = p_310474_.copy(); + if (container != null && (container instanceof CrafterBlockEntity || p_310474_.getCount() > container.getMaxStackSize(p_310474_))) { + while (!itemstack.isEmpty()) { + ItemStack itemstack2 = itemstack.copyWithCount(1); + ItemStack itemstack1 = HopperBlockEntity.addItem(p_309887_, container, itemstack2, direction.getOpposite()); ++ if (itemhandler != null && !itemstack1.isEmpty()) // Vanilla insert failed, try IItemHandler ++ itemstack1 = net.minecraftforge.items.ItemHandlerHelper.insertItem(itemhandler, itemstack1, false); + if (!itemstack1.isEmpty()) { + break; + } +@@ -201,10 +_,14 @@ + while (!itemstack.isEmpty()) { + int i = itemstack.getCount(); + itemstack = HopperBlockEntity.addItem(p_309887_, container, itemstack, direction.getOpposite()); ++ if (itemhandler != null && i == itemstack.getCount()) // Vanilla insert failed, try IItemHandler ++ itemstack = net.minecraftforge.items.ItemHandlerHelper.insertItem(itemhandler, itemstack, false); + if (i == itemstack.getCount()) { + break; + } + } ++ } else { ++ itemstack = net.minecraftforge.items.ItemHandlerHelper.insertItem(itemhandler, itemstack, false); + } + + if (!itemstack.isEmpty()) { diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeGameTestHelper.java b/src/main/java/net/minecraftforge/common/extensions/IForgeGameTestHelper.java index 1060b72471..81cc72c197 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeGameTestHelper.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeGameTestHelper.java @@ -16,13 +16,19 @@ import net.minecraft.Util; import net.minecraft.network.chat.MutableComponent; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.Item; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; import org.jetbrains.annotations.Nullable; import com.mojang.authlib.GameProfile; import io.netty.channel.embedded.EmbeddedChannel; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.gametest.framework.GameTestAssertException; import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.network.Connection; @@ -36,8 +42,10 @@ import net.minecraft.server.network.CommonListenerCookie; import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; import net.minecraftforge.common.ForgeI18n; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.eventbus.api.Event; public interface IForgeGameTestHelper { @@ -45,6 +53,10 @@ public interface IForgeGameTestHelper { return (GameTestHelper) this; } + default GameTestAssertException throwing(String message) { + return new GameTestAssertException(Component.literal(message).getString()); + } + default void say(String message) { this.say(message, Style.EMPTY); } @@ -207,6 +219,55 @@ public interface IForgeGameTestHelper { return state; } + default void removeAllItemEntitiesInRange(BlockPos pos, double range) { + BlockPos blockpos = this.self().absolutePos(pos); + for (ItemEntity itemEntity : this.self().getLevel().getEntities(EntityType.ITEM, new AABB(blockpos).inflate(range), Entity::isAlive)) { + itemEntity.remove(Entity.RemovalReason.DISCARDED); + } + } + + default void assertContainerContains(BlockPos pos, Item item, int expected) { + BaseContainerBlockEntity basecontainerblockentity = self().getBlockEntity(pos); + var actual = basecontainerblockentity.countItem(item); + assertTrue(actual == expected, () -> "Failed to find %s x %d in container at %s, found %d".formatted(item, expected, pos, actual)); + } + + default void assertItemHandlerContains(BlockPos pos, Item item) { + assertItemHandlerContains(pos, null, item); + } + + default void assertItemHandlerContains(BlockPos pos, @Nullable Direction side, Item item) { + assertFalse(countItemHandler(self(), pos, side, item) == 0, () -> "Failed to find any %s in IItemHandler at %s".formatted(item, pos)); + } + + default void assertItemHandlerContains(BlockPos pos, Item item, int expected) { + assertItemHandlerContains(pos, null, item, expected); + } + + default void assertItemHandlerContains(BlockPos pos, @Nullable Direction side, Item item, int expected) { + int actual = countItemHandler(self(), pos, side, item); + assertTrue(actual == expected, () -> "Failed to find %s x %d in IItemHandler at %s, found %d".formatted(item, expected, pos, actual)); + } + + private static int countItemHandler(GameTestHelper self, BlockPos pos, @Nullable Direction side, Item item) { + var blockEntity = self.getBlockEntity(pos); + var handler = blockEntity.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElseThrow( + () -> self.throwing("Block at %s has no item handler capability on side %s".formatted(pos, side))); + + int actual = 0; + for (int slot = 0; slot < handler.getSlots(); slot++) { + var stack = handler.getStackInSlot(slot); + if (stack.getItem().equals(item)) + actual += stack.getCount(); + } + return actual; + } + + default void tickBlock(BlockPos pos) { + var blentity = self().getBlockEntity(pos); + blentity.getBlockState().tick(self().getLevel(), self().absolutePos(pos), self().getLevel().random); + } + default Flag flag(String name) { return new Flag<>(name); } diff --git a/src/main/java/net/minecraftforge/items/VanillaInventoryCodeHooks.java b/src/main/java/net/minecraftforge/items/VanillaInventoryCodeHooks.java index 1b75dfb09d..d27881f3bd 100644 --- a/src/main/java/net/minecraftforge/items/VanillaInventoryCodeHooks.java +++ b/src/main/java/net/minecraftforge/items/VanillaInventoryCodeHooks.java @@ -12,10 +12,12 @@ import net.minecraft.world.level.block.entity.Hopper; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.DispenserBlockEntity; import net.minecraft.world.level.block.entity.HopperBlockEntity; +import net.minecraft.world.phys.AABB; import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraft.core.Direction; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -25,169 +27,129 @@ import org.jetbrains.annotations.Nullable; import java.util.Optional; -public class VanillaInventoryCodeHooks -{ +//TODO: [Forge][1.21.9][Cleanup] Mark as @ApiStatus.Internal +public class VanillaInventoryCodeHooks { /** - * Copied from TileEntityHopper#captureDroppedItems and added capability support + * Copied from HopperBlockEntity#suckInItems and added capability support * @return Null if we did nothing {no IItemHandler}, True if we moved an item, False if we moved no items */ @Nullable - public static Boolean extractHook(Level level, Hopper dest) - { - return getItemHandler(level, dest, Direction.UP) - .map(itemHandlerResult -> { - IItemHandler handler = itemHandlerResult.getKey(); + public static Boolean extractHook(Level level, Hopper dest) { + var handler = getItemHandler(level, dest, Direction.UP).map(Pair::getKey).orElse(null); + if (handler == null) + return null; - for (int i = 0; i < handler.getSlots(); i++) - { - ItemStack extractItem = handler.extractItem(i, 1, true); - if (!extractItem.isEmpty()) - { - for (int j = 0; j < dest.getContainerSize(); j++) - { - ItemStack destStack = dest.getItem(j); - if (dest.canPlaceItem(j, extractItem) && (destStack.isEmpty() || destStack.getCount() < destStack.getMaxStackSize() && destStack.getCount() < dest.getMaxStackSize() && ItemHandlerHelper.canItemStacksStack(extractItem, destStack))) - { - extractItem = handler.extractItem(i, 1, false); - if (destStack.isEmpty()) - dest.setItem(j, extractItem); - else - { - destStack.grow(1); - dest.setItem(j, destStack); - } - dest.setChanged(); - return true; - } - } + for (int i = 0; i < handler.getSlots(); i++) { + ItemStack extractItem = handler.extractItem(i, 1, true); + if (!extractItem.isEmpty()) { + for (int j = 0; j < dest.getContainerSize(); j++) { + ItemStack destStack = dest.getItem(j); + if (dest.canPlaceItem(j, extractItem) && (destStack.isEmpty() || destStack.getCount() < destStack.getMaxStackSize() && destStack.getCount() < dest.getMaxStackSize() && ItemHandlerHelper.canItemStacksStack(extractItem, destStack))) { + extractItem = handler.extractItem(i, 1, false); + if (destStack.isEmpty()) + dest.setItem(j, extractItem); + else { + destStack.grow(1); + dest.setItem(j, destStack); } + dest.setChanged(); + return true; } - - return false; - }) - .orElse(null); // TODO bad null - } - - /** - * Copied from BlockDropper#dispense and added capability support - */ - public static boolean dropperInsertHook(Level level, BlockPos pos, DispenserBlockEntity dropper, int slot, @NotNull ItemStack stack) - { - Direction enumfacing = level.getBlockState(pos).getValue(DropperBlock.FACING); - BlockPos blockpos = pos.relative(enumfacing); - return getItemHandler(level, (double) blockpos.getX(), (double) blockpos.getY(), (double) blockpos.getZ(), enumfacing.getOpposite()) - .map(destinationResult -> { - IItemHandler itemHandler = destinationResult.getKey(); - Object destination = destinationResult.getValue(); - ItemStack dispensedStack = stack.copy().split(1); - ItemStack remainder = putStackInInventoryAllSlots(dropper, destination, itemHandler, dispensedStack); - - if (remainder.isEmpty()) - { - remainder = stack.copy(); - remainder.shrink(1); - } - else - { - remainder = stack.copy(); - } - - dropper.setItem(slot, remainder); - return false; - }) - .orElse(true); - } - - /** - * Copied from TileEntityHopper#transferItemsOut and added capability support - */ - public static boolean insertHook(HopperBlockEntity hopper) - { - Direction hopperFacing = hopper.getBlockState().getValue(HopperBlock.FACING); - return getItemHandler(hopper.getLevel(), hopper, hopperFacing) - .map(destinationResult -> { - IItemHandler itemHandler = destinationResult.getKey(); - Object destination = destinationResult.getValue(); - if (isFull(itemHandler)) - { - return false; - } - else - { - for (int i = 0; i < hopper.getContainerSize(); ++i) - { - if (!hopper.getItem(i).isEmpty()) - { - ItemStack originalSlotContents = hopper.getItem(i).copy(); - ItemStack insertStack = hopper.removeItem(i, 1); - ItemStack remainder = putStackInInventoryAllSlots(hopper, destination, itemHandler, insertStack); - - if (remainder.isEmpty()) - { - return true; - } - - hopper.setItem(i, originalSlotContents); - } - } - - return false; - } - }) - .orElse(false); - } - - private static ItemStack putStackInInventoryAllSlots(BlockEntity source, Object destination, IItemHandler destInventory, ItemStack stack) - { - for (int slot = 0; slot < destInventory.getSlots() && !stack.isEmpty(); slot++) - { - stack = insertStack(source, destination, destInventory, stack, slot); + } + } } + + return false; + } + + /** + * Copied from DropperBlock#dispenseFrom and added capability support + */ + public static boolean dropperInsertHook(Level level, BlockPos pos, DispenserBlockEntity dropper, int slot, @NotNull ItemStack stack) { + var direction = level.getBlockState(pos).getValue(DropperBlock.FACING); + var handler = getItemHandlerPair(level, pos.relative(direction), direction.getOpposite()); + if (handler.isEmpty()) + return true; + + var itemHandler = handler.get().getKey(); + var destination = handler.get().getValue(); + + ItemStack dispensedStack = stack.copy().split(1); + ItemStack remainder = putStackInInventoryAllSlots(dropper, destination, itemHandler, dispensedStack); + + if (remainder.isEmpty()) { + remainder = stack.copy(); + remainder.shrink(1); + } else + remainder = stack.copy(); + + dropper.setItem(slot, remainder); + return false; + } + + /** + * Copied from HopperBlockEntity#ejectItems and added capability support + */ + public static boolean insertHook(HopperBlockEntity hopper) { + var handler = getItemHandler(hopper.getLevel(), hopper, hopper.getBlockState().getValue(HopperBlock.FACING)); + if (handler.isEmpty()) + return false; + + var itemHandler = handler.get().getKey(); + var destination = handler.get().getValue(); + + if (isFull(itemHandler)) + return false; + + for (int i = 0; i < hopper.getContainerSize(); ++i) { + if (!hopper.getItem(i).isEmpty()) { + ItemStack originalSlotContents = hopper.getItem(i).copy(); + ItemStack insertStack = hopper.removeItem(i, 1); + ItemStack remainder = putStackInInventoryAllSlots(hopper, destination, itemHandler, insertStack); + + if (remainder.isEmpty()) + return true; + + hopper.setItem(i, originalSlotContents); + } + } + + return false; + } + + private static ItemStack putStackInInventoryAllSlots(BlockEntity source, Object destination, IItemHandler destInventory, ItemStack stack) { + for (int slot = 0; slot < destInventory.getSlots() && !stack.isEmpty(); slot++) + stack = insertStack(source, destination, destInventory, stack, slot); return stack; } /** - * Copied from TileEntityHopper#insertStack and added capability support + * Copied from HopperBlockEntity#tryMoveInItem and added capability support */ - private static ItemStack insertStack(BlockEntity source, Object destination, IItemHandler destInventory, ItemStack stack, int slot) - { + private static ItemStack insertStack(BlockEntity source, Object destination, IItemHandler destInventory, ItemStack stack, int slot) { ItemStack itemstack = destInventory.getStackInSlot(slot); - if (destInventory.insertItem(slot, stack, true).isEmpty()) - { + if (destInventory.insertItem(slot, stack, true).isEmpty()) { boolean insertedItem = false; boolean inventoryWasEmpty = isEmpty(destInventory); - if (itemstack.isEmpty()) - { + if (itemstack.isEmpty()) { destInventory.insertItem(slot, stack, false); stack = ItemStack.EMPTY; insertedItem = true; - } - else if (ItemHandlerHelper.canItemStacksStack(itemstack, stack)) - { + } else if (ItemHandlerHelper.canItemStacksStack(itemstack, stack)) { int originalSize = stack.getCount(); stack = destInventory.insertItem(slot, stack, false); insertedItem = originalSize < stack.getCount(); } - if (insertedItem) - { - if (inventoryWasEmpty && destination instanceof HopperBlockEntity) - { - HopperBlockEntity destinationHopper = (HopperBlockEntity)destination; - - if (!destinationHopper.isOnCustomCooldown()) - { + if (insertedItem) { + if (inventoryWasEmpty && destination instanceof HopperBlockEntity dHopper) { + if (!dHopper.isOnCustomCooldown()) { int k = 0; - if (source instanceof HopperBlockEntity) - { - if (destinationHopper.getLastUpdateTime() >= ((HopperBlockEntity) source).getLastUpdateTime()) - { - k = 1; - } - } - destinationHopper.setCooldown(8 - k); + if (source instanceof HopperBlockEntity sHopper && dHopper.getLastUpdateTime() >= sHopper.getLastUpdateTime()) + k = 1; + dHopper.setCooldown(8 - k); } } } @@ -196,53 +158,90 @@ public class VanillaInventoryCodeHooks return stack; } - private static Optional> getItemHandler(Level level, Hopper hopper, Direction hopperFacing) - { + private static Optional> getItemHandler(Level level, Hopper hopper, Direction hopperFacing) { double x = hopper.getLevelX() + (double) hopperFacing.getStepX(); double y = hopper.getLevelY() + (double) hopperFacing.getStepY(); double z = hopper.getLevelZ() + (double) hopperFacing.getStepZ(); return getItemHandler(level, x, y, z, hopperFacing.getOpposite()); } - private static boolean isFull(IItemHandler itemHandler) - { - for (int slot = 0; slot < itemHandler.getSlots(); slot++) - { + private static boolean isFull(IItemHandler itemHandler) { + for (int slot = 0; slot < itemHandler.getSlots(); slot++) { ItemStack stackInSlot = itemHandler.getStackInSlot(slot); if (stackInSlot.isEmpty() || stackInSlot.getCount() < itemHandler.getSlotLimit(slot)) - { return false; - } } return true; } - private static boolean isEmpty(IItemHandler itemHandler) - { - for (int slot = 0; slot < itemHandler.getSlots(); slot++) - { + private static boolean isEmpty(IItemHandler itemHandler) { + for (int slot = 0; slot < itemHandler.getSlots(); slot++) { ItemStack stackInSlot = itemHandler.getStackInSlot(slot); if (stackInSlot.getCount() > 0) - { return false; - } } return true; } - public static Optional> getItemHandler(Level worldIn, double x, double y, double z, final Direction side) - { + /** + * Gets the IItemHandler at the given position, checking for both BlockEntities + * and Entities. If both are present, the BlockEntity is preferred. + * + * This is equivalent to HopperBlockEntity#getContainerAt + * + * @param level The level to check + * @param pos The position to check + * @param side The side to check from. Can be null. + * @return An IItemHandler and the object it was obtained from, or an empty + * Optional if none was found. + */ + public static Optional getItemHandler(Level level, BlockPos pos, @Nullable Direction side) { + var handler = getItemHandlerBlock(level, pos, side); + if (!handler.isPresent()) + handler = getItemHandlerEntity(level, pos, side); + return handler; + } + + public static Optional getItemHandlerEntity(Level level, BlockPos pos, @Nullable Direction side) { + var entities = level.getEntities((Entity)null, new AABB(pos), e -> e.getCapability(ForgeCapabilities.ITEM_HANDLER, side).isPresent()); + + if (entities.isEmpty()) + return Optional.empty(); + + var rand = level.random.nextInt(entities.size()); + var entity = entities.get(rand); + return entity.getCapability(ForgeCapabilities.ITEM_HANDLER, side).resolve(); + } + + public static Optional getItemHandlerBlock(Level level, BlockPos pos, @Nullable Direction side) { + if (!level.getBlockState(pos).hasBlockEntity()) + return Optional.empty(); + + var entity = level.getBlockEntity(pos); + if (entity == null) + return Optional.empty(); + + return entity.getCapability(ForgeCapabilities.ITEM_HANDLER, side).resolve(); + } + + /** + * I don't think anyone should be using this. If they are they probably want the non-pair version. + * As the only use for knowing the object holding the IItemHandler is for hopper cooldowns + */ + @Deprecated(forRemoval = true, since = "1.21.8") + public static Optional> getItemHandler(Level worldIn, double x, double y, double z, final Direction side) { int i = Mth.floor(x); int j = Mth.floor(y); int k = Mth.floor(z); - BlockPos blockpos = new BlockPos(i, j, k); - net.minecraft.world.level.block.state.BlockState state = worldIn.getBlockState(blockpos); + return getItemHandlerPair(worldIn, new BlockPos(i, j, k), side); + } - if (state.hasBlockEntity()) - { - BlockEntity blockEntity = worldIn.getBlockEntity(blockpos); - if (blockEntity != null) - { + private static Optional> getItemHandlerPair(Level level, BlockPos pos, Direction side) { + var state = level.getBlockState(pos); + + if (state.hasBlockEntity()) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity != null) { return blockEntity.getCapability(ForgeCapabilities.ITEM_HANDLER, side) .map(capability -> ImmutablePair.of(capability, blockEntity)); } diff --git a/src/test/java/net/minecraftforge/debug/gameplay/block/CrafterBlockTest.java b/src/test/java/net/minecraftforge/debug/gameplay/block/CrafterBlockTest.java new file mode 100644 index 0000000000..65c106cd72 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/gameplay/block/CrafterBlockTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.gameplay.block; + +import net.minecraftforge.gametest.GameTestHolder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.mojang.serialization.MapCodec; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.FrontAndTop; +import net.minecraft.core.registries.Registries; +import net.minecraft.gametest.framework.*; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.CrafterBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemStackHandler; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.RegistryObject; +import net.minecraftforge.test.BaseTestMod; + + +@Mod(CrafterBlockTest.MOD_ID) +@GameTestHolder("forge." + CrafterBlockTest.MOD_ID) +public class CrafterBlockTest extends BaseTestMod { + static final String MOD_ID = "crafter_block"; + + public static final DeferredRegister BLOCKS = DeferredRegister.create(Registries.BLOCK, MOD_ID); + private static final RegistryObject IITEM_BLOCK = BLOCKS.register("iitem", () -> new SimpleIItemHandlerBlock(Block.Properties.of().setId(BLOCKS.key("iitem")))); + + public static final DeferredRegister> BLOCK_TYPES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, MOD_ID); + private static final RegistryObject> IITEM_BLOCK_TYPE = BLOCK_TYPES.register("iitem", () -> new BlockEntityType<>(SimpleIItemHandler::new, null) { + @Override + public boolean isValid(BlockState state) { + return state.is(IITEM_BLOCK.get()); + } + }); + + public CrafterBlockTest(FMLJavaModLoadingContext context) { + super(context); + } + + // Make sure they can add items to vanilla containers. + @GameTest(template = "forge:empty3x3x3") + public static void places_in_chest(GameTestHelper helper) { + var chestPos = new BlockPos(0, 1, 0); + helper.setBlock(chestPos, Blocks.CHEST); + helper.setBlock(BlockPos.ZERO, Blocks.CRAFTER.defaultBlockState().setValue(BlockStateProperties.ORIENTATION, FrontAndTop.UP_NORTH)); + var crafter = (CrafterBlockEntity) helper.getBlockEntity(BlockPos.ZERO); + crafter.setItem(0, new ItemStack(Blocks.OAK_LOG)); + helper.tickBlock(BlockPos.ZERO); + helper.assertContainerContains(chestPos, Blocks.OAK_PLANKS.asItem(), 4); + helper.succeed(); + } + + // Make sure they can add items to IItemHandler BlockEntities that are not WorldlyContainers. + @GameTest(template = "forge:empty3x3x3") + public static void places_in_iitemhandler_block(GameTestHelper helper) { + var chestPos = new BlockPos(0, 1, 0); + helper.setBlock(chestPos, IITEM_BLOCK.get()); + helper.setBlock(BlockPos.ZERO, Blocks.CRAFTER.defaultBlockState().setValue(BlockStateProperties.ORIENTATION, FrontAndTop.UP_NORTH)); + var crafter = (CrafterBlockEntity) helper.getBlockEntity(BlockPos.ZERO); + crafter.setItem(0, new ItemStack(Blocks.OAK_LOG)); + helper.tickBlock(BlockPos.ZERO); + helper.assertItemHandlerContains(chestPos, Blocks.OAK_PLANKS.asItem(), 4); + helper.succeed(); + } + + private static class SimpleIItemHandlerBlock extends BaseEntityBlock { + private static final MapCodec CODEC = simpleCodec(SimpleIItemHandlerBlock::new); + + protected SimpleIItemHandlerBlock(Properties props) { + super(props); + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return IITEM_BLOCK_TYPE.get().create(pos, state); + } + + @Override + protected MapCodec codec() { + return CODEC; + } + } + + private static class SimpleIItemHandler extends BlockEntity { + private final LazyOptional itemHandler = LazyOptional.of(() -> new ItemStackHandler(1)); + + public SimpleIItemHandler(BlockPos pos, BlockState state) { + super(IITEM_BLOCK_TYPE.get(), pos, state); + } + + @Override + public @NotNull LazyOptional getCapability(@NotNull final Capability cap, final @Nullable Direction side) { + if (cap == ForgeCapabilities.ITEM_HANDLER) + return itemHandler.cast(); + return super.getCapability(cap, side); + } + } +}