diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java b/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java index 9727300c1e..2e79b5249c 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java @@ -41,6 +41,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.level.Level; import net.minecraftforge.common.ToolAction; import net.minecraftforge.common.ToolActions; +import net.minecraftforge.items.wrapper.ShulkerItemStackInvWrapper; import net.minecraftforge.registries.IForgeRegistry; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -655,7 +656,8 @@ public interface IForgeItem @Nullable default net.minecraftforge.common.capabilities.ICapabilityProvider initCapabilities(ItemStack stack, @Nullable CompoundTag nbt) { - return null; + var ret = ShulkerItemStackInvWrapper.createDefaultProvider(stack); + return ret; } /** diff --git a/src/main/java/net/minecraftforge/items/wrapper/ShulkerItemStackInvWrapper.java b/src/main/java/net/minecraftforge/items/wrapper/ShulkerItemStackInvWrapper.java new file mode 100644 index 0000000000..63e938276a --- /dev/null +++ b/src/main/java/net/minecraftforge/items/wrapper/ShulkerItemStackInvWrapper.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.items.wrapper; + +import net.minecraft.core.Direction; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; +import net.minecraftforge.items.ItemHandlerHelper; +import net.minecraftforge.registries.ForgeRegistries; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Set; + +@ApiStatus.Internal +public class ShulkerItemStackInvWrapper implements IItemHandlerModifiable, ICapabilityProvider +{ + @ApiStatus.Internal + @Nullable + public static ICapabilityProvider createDefaultProvider(ItemStack itemStack) + { + var item = itemStack.getItem(); + if (item == Items.SHULKER_BOX || + item == Items.BLACK_SHULKER_BOX || + item == Items.BLUE_SHULKER_BOX || + item == Items.BROWN_SHULKER_BOX || + item == Items.CYAN_SHULKER_BOX || + item == Items.GRAY_SHULKER_BOX || + item == Items.GREEN_SHULKER_BOX || + item == Items.LIGHT_BLUE_SHULKER_BOX || + item == Items.LIGHT_GRAY_SHULKER_BOX || + item == Items.LIME_SHULKER_BOX || + item == Items.MAGENTA_SHULKER_BOX || + item == Items.ORANGE_SHULKER_BOX || + item == Items.PINK_SHULKER_BOX || + item == Items.PURPLE_SHULKER_BOX || + item == Items.RED_SHULKER_BOX || + item == Items.WHITE_SHULKER_BOX || + item == Items.YELLOW_SHULKER_BOX + ) + return new ShulkerItemStackInvWrapper(itemStack); + return null; + } + + private final ItemStack stack; + private final LazyOptional holder = LazyOptional.of(() -> this); + + private CompoundTag cachedTag; + private NonNullList itemStacksCache; + + private ShulkerItemStackInvWrapper(ItemStack stack) + { + this.stack = stack; + } + + @Override + public int getSlots() + { + return 27; + } + + @Override + @NotNull + public ItemStack getStackInSlot(int slot) + { + validateSlotIndex(slot); + return getItemList().get(slot); + } + + @Override + @NotNull + public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) + { + if (stack.isEmpty()) + return ItemStack.EMPTY; + + if (!isItemValid(slot, stack)) + return stack; + + validateSlotIndex(slot); + + NonNullList itemStacks = getItemList(); + + ItemStack existing = itemStacks.get(slot); + + int limit = Math.min(getSlotLimit(slot), stack.getMaxStackSize()); + + if (!existing.isEmpty()) + { + if (!ItemHandlerHelper.canItemStacksStack(stack, existing)) + return stack; + + limit -= existing.getCount(); + } + + if (limit <= 0) + return stack; + + boolean reachedLimit = stack.getCount() > limit; + + if (!simulate) + { + if (existing.isEmpty()) + { + itemStacks.set(slot, reachedLimit ? ItemHandlerHelper.copyStackWithSize(stack, limit) : stack); + } + else + { + existing.grow(reachedLimit ? limit : stack.getCount()); + } + setItemList(itemStacks); + } + + return reachedLimit ? ItemHandlerHelper.copyStackWithSize(stack, stack.getCount()- limit) : ItemStack.EMPTY; + } + + @Override + @NotNull + public ItemStack extractItem(int slot, int amount, boolean simulate) + { + NonNullList itemStacks = getItemList(); + if (amount == 0) + return ItemStack.EMPTY; + + validateSlotIndex(slot); + + ItemStack existing = itemStacks.get(slot); + + if (existing.isEmpty()) + return ItemStack.EMPTY; + + int toExtract = Math.min(amount, existing.getMaxStackSize()); + + if (existing.getCount() <= toExtract) + { + if (!simulate) + { + itemStacks.set(slot, ItemStack.EMPTY); + setItemList(itemStacks); + return existing; + } + else + { + return existing.copy(); + } + } + else + { + if (!simulate) + { + itemStacks.set(slot, ItemHandlerHelper.copyStackWithSize(existing, existing.getCount() - toExtract)); + setItemList(itemStacks); + } + + return ItemHandlerHelper.copyStackWithSize(existing, toExtract); + } + } + + private void validateSlotIndex(int slot) + { + if (slot < 0 || slot >= getSlots()) + throw new RuntimeException("Slot " + slot + " not in valid range - [0," + getSlots() + ")"); + } + + @Override + public int getSlotLimit(int slot) + { + return 64; + } + + @Override + public boolean isItemValid(int slot, @NotNull ItemStack stack) + { + return stack.getItem().canFitInsideContainerItems(); + } + + @Override + public void setStackInSlot(int slot, @NotNull ItemStack stack) + { + validateSlotIndex(slot); + if (!isItemValid(slot, stack)) throw new RuntimeException("Invalid stack " + stack + " for slot " + slot + ")"); + NonNullList itemStacks = getItemList(); + itemStacks.set(slot, stack); + setItemList(itemStacks); + } + + private NonNullList getItemList() + { + CompoundTag rootTag = BlockItem.getBlockEntityData(this.stack); + if (cachedTag == null || !cachedTag.equals(rootTag)) + itemStacksCache = refreshItemList(rootTag); + return itemStacksCache; + } + + private NonNullList refreshItemList(CompoundTag rootTag) + { + NonNullList itemStacks = NonNullList.withSize(getSlots(), ItemStack.EMPTY); + if (rootTag != null && rootTag.contains("Items", CompoundTag.TAG_LIST)) + { + ContainerHelper.loadAllItems(rootTag, itemStacks); + } + cachedTag = rootTag; + return itemStacks; + } + + private void setItemList(NonNullList itemStacks) + { + CompoundTag existing = BlockItem.getBlockEntityData(this.stack); + CompoundTag rootTag = ContainerHelper.saveAllItems(existing == null ? new CompoundTag() : existing, itemStacks); + BlockItem.setBlockEntityData(this.stack, BlockEntityType.SHULKER_BOX, rootTag); + cachedTag = rootTag; + } + + @Override + @NotNull + public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) + { + return CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.orEmpty(cap, this.holder); + } +}