mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 01:04:24 +00:00
Fix Crafters not inserting into IItemHandlers. Closes #10667
This commit is contained in:
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));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user