From 3de035b3a79b2769c5bd702b286459d76389f70f Mon Sep 17 00:00:00 2001 From: Bruno Ploumhans <13494793+Technici4n@users.noreply.github.com> Date: Sat, 4 Jan 2025 12:35:53 +0100 Subject: [PATCH] Let vanilla try to handle Container interactions before checking for item handlers (#1787) --- .../world/level/block/CrafterBlock.java.patch | 32 ++- .../world/level/block/DropperBlock.java.patch | 23 +- .../block/entity/HopperBlockEntity.java.patch | 84 +++++- .../neoforge/items/ContainerOrHandler.java | 25 ++ .../items/VanillaHopperItemHandler.java | 4 +- .../items/VanillaInventoryCodeHooks.java | 255 +++++------------- 6 files changed, 207 insertions(+), 216 deletions(-) create mode 100644 src/main/java/net/neoforged/neoforge/items/ContainerOrHandler.java diff --git a/patches/net/minecraft/world/level/block/CrafterBlock.java.patch b/patches/net/minecraft/world/level/block/CrafterBlock.java.patch index a80e470efc2..1d80e9cd8f3 100644 --- a/patches/net/minecraft/world/level/block/CrafterBlock.java.patch +++ b/patches/net/minecraft/world/level/block/CrafterBlock.java.patch @@ -1,11 +1,29 @@ --- a/net/minecraft/world/level/block/CrafterBlock.java +++ b/net/minecraft/world/level/block/CrafterBlock.java -@@ -214,6 +_,8 @@ +@@ -194,7 +_,8 @@ + ServerLevel p_335887_, BlockPos p_307620_, CrafterBlockEntity p_307387_, ItemStack p_307296_, BlockState p_307501_, RecipeHolder p_335494_ + ) { + Direction direction = p_307501_.getValue(ORIENTATION).front(); +- Container container = HopperBlockEntity.getContainerAt(p_335887_, p_307620_.relative(direction)); ++ var containerOrHandler = HopperBlockEntity.getContainerOrHandlerAt(p_335887_, p_307620_.relative(direction), direction.getOpposite()); ++ Container container = containerOrHandler.container(); + ItemStack itemstack = p_307296_.copy(); + if (container != null && (container instanceof CrafterBlockEntity || p_307296_.getCount() > container.getMaxStackSize(p_307296_))) { + while (!itemstack.isEmpty()) { +@@ -206,10 +_,14 @@ + + itemstack.shrink(1); + } +- } else if (container != null) { ++ } else if (!containerOrHandler.isEmpty()) { + while (!itemstack.isEmpty()) { + int i = itemstack.getCount(); +- itemstack = HopperBlockEntity.addItem(p_307387_, container, itemstack, direction.getOpposite()); ++ if (container != null) { ++ itemstack = HopperBlockEntity.addItem(p_307387_, container, itemstack, direction.getOpposite()); ++ } else { ++ itemstack = net.neoforged.neoforge.items.ItemHandlerHelper.insertItem(containerOrHandler.itemHandler(), itemstack, false); ++ } + if (i == itemstack.getCount()) { break; } - } -+ } else { -+ itemstack = net.neoforged.neoforge.items.VanillaInventoryCodeHooks.insertCrafterOutput(p_335887_, p_307620_, p_307387_, itemstack); - } - - if (!itemstack.isEmpty()) { diff --git a/patches/net/minecraft/world/level/block/DropperBlock.java.patch b/patches/net/minecraft/world/level/block/DropperBlock.java.patch index d130ce886b2..48785a2c10d 100644 --- a/patches/net/minecraft/world/level/block/DropperBlock.java.patch +++ b/patches/net/minecraft/world/level/block/DropperBlock.java.patch @@ -1,11 +1,22 @@ --- a/net/minecraft/world/level/block/DropperBlock.java +++ b/net/minecraft/world/level/block/DropperBlock.java -@@ -56,7 +_,7 @@ - p_52944_.levelEvent(1001, p_52945_, 0); - } else { +@@ -58,12 +_,16 @@ ItemStack itemstack = dispenserblockentity.getItem(i); -- if (!itemstack.isEmpty()) { -+ if (!itemstack.isEmpty() && net.neoforged.neoforge.items.VanillaInventoryCodeHooks.dropperInsertHook(p_52944_, p_52945_, dispenserblockentity, i, itemstack)) { + if (!itemstack.isEmpty()) { Direction direction = p_52944_.getBlockState(p_52945_).getValue(FACING); - Container container = HopperBlockEntity.getContainerAt(p_52944_, p_52945_.relative(direction)); +- Container container = HopperBlockEntity.getContainerAt(p_52944_, p_52945_.relative(direction)); ++ var containerOrHandler = HopperBlockEntity.getContainerOrHandlerAt(p_52944_, p_52945_.relative(direction), direction.getOpposite()); ItemStack itemstack1; +- if (container == null) { ++ if (containerOrHandler.isEmpty()) { + itemstack1 = DISPENSE_BEHAVIOUR.dispense(blocksource, itemstack); + } else { +- itemstack1 = HopperBlockEntity.addItem(dispenserblockentity, container, itemstack.copyWithCount(1), direction.getOpposite()); ++ if (containerOrHandler.container() != null) { ++ itemstack1 = HopperBlockEntity.addItem(dispenserblockentity, containerOrHandler.container(), itemstack.copyWithCount(1), direction.getOpposite()); ++ } else { ++ itemstack1 = net.neoforged.neoforge.items.ItemHandlerHelper.insertItem(containerOrHandler.itemHandler(), itemstack.copyWithCount(1), false); ++ } + if (itemstack1.isEmpty()) { + itemstack1 = itemstack.copy(); + itemstack1.shrink(1); diff --git a/patches/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch b/patches/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch index f6b7d8b619d..5dc02264ec9 100644 --- a/patches/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch +++ b/patches/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch @@ -1,22 +1,88 @@ --- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java +++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java -@@ -137,6 +_,7 @@ +@@ -137,10 +_,11 @@ } private static boolean ejectItems(Level p_155563_, BlockPos p_155564_, HopperBlockEntity p_326256_) { -+ if (net.neoforged.neoforge.items.VanillaInventoryCodeHooks.insertHook(p_326256_)) return true; - Container container = getAttachedContainer(p_155563_, p_155564_, p_326256_); - if (container == null) { +- Container container = getAttachedContainer(p_155563_, p_155564_, p_326256_); +- if (container == null) { ++ var containerOrHandler = getContainerOrHandlerAt(p_155563_, p_155564_.relative(p_326256_.facing), p_326256_.facing.getOpposite()); ++ if (containerOrHandler.isEmpty()) { return false; -@@ -214,6 +_,8 @@ +- } else { ++ } else if (containerOrHandler.container() != null) { ++ Container container = containerOrHandler.container(); + Direction direction = p_326256_.facing.getOpposite(); + if (isFullContainer(container, direction)) { + return false; +@@ -164,6 +_,8 @@ + + return false; + } ++ } else { ++ return net.neoforged.neoforge.items.VanillaInventoryCodeHooks.insertHook(p_326256_, containerOrHandler.itemHandler()); + } + } + +@@ -214,8 +_,9 @@ public static boolean suckInItems(Level p_155553_, Hopper p_155554_) { BlockPos blockpos = BlockPos.containing(p_155554_.getLevelX(), p_155554_.getLevelY() + 1.0, p_155554_.getLevelZ()); BlockState blockstate = p_155553_.getBlockState(blockpos); -+ Boolean ret = net.neoforged.neoforge.items.VanillaInventoryCodeHooks.extractHook(p_155553_, p_155554_); -+ if (ret != null) return ret; - Container container = getSourceContainer(p_155553_, p_155554_, blockpos, blockstate); - if (container != null) { +- Container container = getSourceContainer(p_155553_, p_155554_, blockpos, blockstate); +- if (container != null) { ++ var containerOrHandler = getSourceContainerOrHandler(p_155553_, p_155554_, blockpos, blockstate); ++ if (containerOrHandler.container() != null) { ++ Container container = containerOrHandler.container(); Direction direction = Direction.DOWN; + + for (int i : getSlots(container, direction)) { +@@ -225,6 +_,8 @@ + } + + return false; ++ } else if (containerOrHandler.itemHandler() != null) { ++ return net.neoforged.neoforge.items.VanillaInventoryCodeHooks.extractHook(p_155554_, containerOrHandler.itemHandler()); + } else { + boolean flag = p_155554_.isGridAligned() + && blockstate.isCollisionShapeFullBlock(p_155553_, blockpos) +@@ -368,6 +_,8 @@ + return p_155590_.getEntitiesOfClass(ItemEntity.class, aabb, EntitySelector.ENTITY_STILL_ALIVE); + } + ++ /** @deprecated Use IItemHandler capability instead. To preserve Container-specific interactions, use {@link #getContainerOrHandlerAt} and handle both cases. */ ++ @Deprecated + @Nullable + public static Container getContainerAt(Level p_59391_, BlockPos p_59392_) { + return getContainerAt( +@@ -411,6 +_,28 @@ + return !list.isEmpty() ? (Container)list.get(p_326325_.random.nextInt(list.size())) : null; + } + ++ private static net.neoforged.neoforge.items.ContainerOrHandler getSourceContainerOrHandler(Level p_155597_, Hopper p_155598_, BlockPos p_326315_, BlockState p_326093_) { ++ return getContainerOrHandlerAt(p_155597_, p_326315_, p_326093_, p_155598_.getLevelX(), p_155598_.getLevelY() + 1.0, p_155598_.getLevelZ(), Direction.DOWN); ++ } ++ ++ public static net.neoforged.neoforge.items.ContainerOrHandler getContainerOrHandlerAt(Level level, BlockPos pos, @Nullable Direction side) { ++ return getContainerOrHandlerAt( ++ level, pos, level.getBlockState(pos), (double)pos.getX() + 0.5, (double)pos.getY() + 0.5, (double)pos.getZ() + 0.5, side ++ ); ++ } ++ ++ private static net.neoforged.neoforge.items.ContainerOrHandler getContainerOrHandlerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z, @Nullable Direction side) { ++ Container container = getBlockContainer(level, pos, state); ++ if (container != null) { ++ return new net.neoforged.neoforge.items.ContainerOrHandler(container, null); ++ } ++ var blockItemHandler = level.getCapability(net.neoforged.neoforge.capabilities.Capabilities.ItemHandler.BLOCK, pos, state, null, side); ++ if (blockItemHandler != null) { ++ return new net.neoforged.neoforge.items.ContainerOrHandler(null, blockItemHandler); ++ } ++ return net.neoforged.neoforge.items.VanillaInventoryCodeHooks.getEntityContainerOrHandler(level, x, y, z, side); ++ } ++ + private static boolean canMergeItems(ItemStack p_59345_, ItemStack p_59346_) { + return p_59345_.getCount() <= p_59345_.getMaxStackSize() && ItemStack.isSameItemSameComponents(p_59345_, p_59346_); + } @@ -470,5 +_,9 @@ @Override protected AbstractContainerMenu createMenu(int p_59312_, Inventory p_59313_) { diff --git a/src/main/java/net/neoforged/neoforge/items/ContainerOrHandler.java b/src/main/java/net/neoforged/neoforge/items/ContainerOrHandler.java new file mode 100644 index 00000000000..27d3a943845 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/items/ContainerOrHandler.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.items; + +import net.minecraft.world.Container; +import org.jetbrains.annotations.Nullable; + +public record ContainerOrHandler( + @Nullable Container container, + @Nullable IItemHandler itemHandler) { + public ContainerOrHandler { + if (container != null && itemHandler != null) { + throw new IllegalArgumentException("Cannot have both a container and an item handler."); + } + } + + public static final ContainerOrHandler EMPTY = new ContainerOrHandler(null, null); + + public boolean isEmpty() { + return container == null && itemHandler == null; + } +} diff --git a/src/main/java/net/neoforged/neoforge/items/VanillaHopperItemHandler.java b/src/main/java/net/neoforged/neoforge/items/VanillaHopperItemHandler.java index 9893c6ae14a..6c850576d93 100644 --- a/src/main/java/net/neoforged/neoforge/items/VanillaHopperItemHandler.java +++ b/src/main/java/net/neoforged/neoforge/items/VanillaHopperItemHandler.java @@ -32,8 +32,8 @@ public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { // This cooldown is always set to 8 in vanilla with one exception: // Hopper -> Hopper transfer sets this cooldown to 7 when this hopper // has not been updated as recently as the one pushing items into it. - // This vanilla behavior is preserved by VanillaInventoryCodeHooks#insertStack, - // the cooldown is set properly by the hopper that is pushing items into this one. + // This vanilla behavior is preserved because we let vanilla handle + // hopper - hopper interactions. hopper.setCooldown(8); } } diff --git a/src/main/java/net/neoforged/neoforge/items/VanillaInventoryCodeHooks.java b/src/main/java/net/neoforged/neoforge/items/VanillaInventoryCodeHooks.java index 60853975546..cc6d981e8db 100644 --- a/src/main/java/net/neoforged/neoforge/items/VanillaInventoryCodeHooks.java +++ b/src/main/java/net/neoforged/neoforge/items/VanillaInventoryCodeHooks.java @@ -5,185 +5,75 @@ package net.neoforged.neoforge.items; -import java.util.Collections; import java.util.List; -import java.util.Optional; -import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.core.FrontAndTop; +import net.minecraft.world.Container; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntitySelector; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.DropperBlock; -import net.minecraft.world.level.block.HopperBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.CrafterBlockEntity; -import net.minecraft.world.level.block.entity.DispenserBlockEntity; import net.minecraft.world.level.block.entity.Hopper; import net.minecraft.world.level.block.entity.HopperBlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.phys.AABB; import net.neoforged.neoforge.capabilities.Capabilities; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +@ApiStatus.Internal public class VanillaInventoryCodeHooks { /** - * Copied from TileEntityHopper#captureDroppedItems and added capability support - * - * @return Null if we did nothing {no IItemHandler}, True if we moved an item, False if we moved no items + * Tries to extract items from an item handler and insert them in the hopper. + * + * @param handler target item handler + * @return {@code true} if we moved an item, {@code false} if we moved no items */ - @Nullable - public static Boolean extractHook(Level level, Hopper dest) { - return getSourceItemHandler(level, dest) - .map(itemHandlerResult -> { - IItemHandler handler = itemHandlerResult.getKey(); - - 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() && ItemStack.isSameItemSameComponents(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, ItemStack stack) { - Direction facing = level.getBlockState(pos).getValue(DropperBlock.FACING); - return getAttachedItemHandler(level, pos, facing) - .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 getAttachedItemHandler(hopper.getLevel(), hopper.getBlockPos(), 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); - } + public static boolean extractHook(Hopper dest, IItemHandler handler) { + 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() && ItemStack.isSameItemSameComponents(extractItem, destStack))) { + extractItem = handler.extractItem(i, 1, false); + if (destStack.isEmpty()) + dest.setItem(j, extractItem); + else { + destStack.grow(1); + dest.setItem(j, destStack); } - - return false; + dest.setChanged(); + return true; } - }) - .orElse(false); - } - - /** - * Added capability support for the Crafter dispensing the result - */ - public static ItemStack insertCrafterOutput(Level level, BlockPos pos, CrafterBlockEntity crafterBlockEntity, ItemStack stack) { - FrontAndTop frontAndTop = level.getBlockState(pos).getValue(BlockStateProperties.ORIENTATION); - return getAttachedItemHandler(level, pos, frontAndTop.front()) - .map(destinationResult -> { - IItemHandler itemHandler = destinationResult.getKey(); - Object destination = destinationResult.getValue(); - ItemStack remainder = putStackInInventoryAllSlots(crafterBlockEntity, destination, itemHandler, stack); - return remainder; - }) - .orElse(stack); - } - - 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; + return false; } /** - * Copied from TileEntityHopper#insertStack and added capability support + * Tries to insert a hopper's items into an item handler. + * + * @param itemHandler target item handler + * @return {@code true} if we moved an item, {@code false} if we moved no items */ - 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()) { - boolean insertedItem = false; - boolean inventoryWasEmpty = isEmpty(destInventory); - - if (itemstack.isEmpty()) { - destInventory.insertItem(slot, stack, false); - stack = ItemStack.EMPTY; - insertedItem = true; - } else if (ItemStack.isSameItemSameComponents(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()) { - int k = 0; - if (source instanceof HopperBlockEntity) { - if (destinationHopper.getLastUpdateTime() >= ((HopperBlockEntity) source).getLastUpdateTime()) { - k = 1; - } - } - destinationHopper.setCooldown(8 - k); - } + public static boolean insertHook(HopperBlockEntity hopper, IItemHandler itemHandler) { + 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 = ItemHandlerHelper.insertItem(itemHandler, insertStack, false); + + if (remainder.isEmpty()) { + return true; } + + hopper.setItem(i, originalSlotContents); } } - return stack; + return false; } private static boolean isFull(IItemHandler itemHandler) { @@ -196,46 +86,27 @@ private static boolean isFull(IItemHandler itemHandler) { return true; } - 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; - } - - private static Optional> getAttachedItemHandler(Level level, BlockPos pos, Direction direction) { - return getItemHandlerAt(level, pos.getX() + direction.getStepX() + 0.5, pos.getY() + direction.getStepY() + 0.5, pos.getZ() + direction.getStepZ() + 0.5, direction.getOpposite()); - } - - private static Optional> getSourceItemHandler(Level level, Hopper hopper) { - return getItemHandlerAt(level, hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ(), Direction.DOWN); - } - - private static Optional> getItemHandlerAt(Level worldIn, double x, double y, double z, final Direction side) { - BlockPos blockpos = BlockPos.containing(x, y, z); - BlockState state = worldIn.getBlockState(blockpos); - BlockEntity blockEntity = state.hasBlockEntity() ? worldIn.getBlockEntity(blockpos) : null; - - // Look for block capability first - var blockCap = worldIn.getCapability(Capabilities.ItemHandler.BLOCK, blockpos, state, blockEntity, side); - if (blockCap != null) - return Optional.of(ImmutablePair.of(blockCap, blockEntity)); - - // Otherwise fallback to automation entity capability - // Note: the isAlive check matches what vanilla does for hoppers in EntitySelector.CONTAINER_ENTITY_SELECTOR - List list = worldIn.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.ENTITY_STILL_ALIVE); + public static ContainerOrHandler getEntityContainerOrHandler(Level level, double x, double y, double z, @Nullable Direction side) { + List list = level.getEntities( + (Entity) null, + new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), + entity -> { + // Note: the isAlive check matches what vanilla does for hoppers in EntitySelector.CONTAINER_ENTITY_SELECTOR + if (!entity.isAlive()) { + return false; + } + return entity instanceof Container || entity.getCapability(Capabilities.ItemHandler.ENTITY_AUTOMATION, side) != null; + }); if (!list.isEmpty()) { - Collections.shuffle(list); - for (Entity entity : list) { - IItemHandler entityCap = entity.getCapability(Capabilities.ItemHandler.ENTITY_AUTOMATION, side); - if (entityCap != null) - return Optional.of(ImmutablePair.of(entityCap, entity)); + var entity = list.get(level.random.nextInt(list.size())); + if (entity instanceof Container container) { + return new ContainerOrHandler(container, null); + } + IItemHandler entityCap = entity.getCapability(Capabilities.ItemHandler.ENTITY_AUTOMATION, side); + if (entityCap != null) { // Could be null even if it wasn't in the entity predicate above. + return new ContainerOrHandler(null, entityCap); } } - - return Optional.empty(); + return ContainerOrHandler.EMPTY; } }