Fix Crafters not inserting into IItemHandlers. Closes #10667

This commit is contained in:
LexManos committed 2025-09-23 13:42:29 -07:00
1 parent 728fd6ff24
commit c1d703e395
6 files changed
+365 -159

No files matched your search

@@ -20,6 +20,7 @@ import net.minecraft.network.chat.MutableComponent;
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;
@@ -28,6 +29,7 @@ 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;
@@ -41,8 +43,11 @@ 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.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.ForgeI18n;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.InheritableEvent;
@@ -254,6 +259,43 @@ public interface IForgeGameTestHelper {
}
}
default void assertContainerContains(BlockPos pos, Item item, int expected) {
BaseContainerBlockEntity basecontainerblockentity = self().getBlockEntity(pos, BaseContainerBlockEntity.class);
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, BlockEntity.class);
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 <T> Flag<T> flag(String name) {
return new Flag<>(name);
}
@@ -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<Pair<IItemHandler, Object>> getItemHandler(Level level, Hopper hopper, Direction hopperFacing)
{
private static Optional<Pair<IItemHandler, Object>> 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<Pair<IItemHandler, Object>> 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<IItemHandler> 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<IItemHandler> 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<IItemHandler> 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<Pair<IItemHandler, Object>> 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<Pair<IItemHandler, Object>> 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.<IItemHandler, Object>of(capability, blockEntity));
}