Skip to content

Commit

Permalink
[1.20.6] Add ComponentItemHandler for using IItemHandlerModifiable wi…
Browse files Browse the repository at this point in the history
…th Data Components (#990)
  • Loading branch information
Shadows-of-Fire authored May 29, 2024
1 parent 78b483e commit 8a7c09c
Show file tree
Hide file tree
Showing 10 changed files with 356 additions and 155 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--- a/net/minecraft/world/item/component/ItemContainerContents.java
+++ b/net/minecraft/world/item/component/ItemContainerContents.java
@@ -146,6 +_,38 @@
return this.hashCode;
}

+ // Neo Start
+
+ /**
+ * {@return the number of slots in this container}
+ */
+ public int getSlots() {
+ return this.items.size();
+ }
+
+ /**
+ * Gets a copy of the stack at a particular slot.
+ *
+ * @param slot The slot to check. Must be within [0, {@link #getSlots()}]
+ * @return A copy of the stack in that slot
+ * @throws UnsupportedOperationException if the provided slot index is out-of-bounds.
+ */
+ public ItemStack getStackInSlot(int slot) {
+ validateSlotIndex(slot);
+ return this.items.get(slot).copy();
+ }
+
+ /**
+ * Throws {@link UnsupportedOperationException} if the provided slot index is invalid.
+ */
+ private void validateSlotIndex(int slot) {
+ if (slot < 0 || slot >= getSlots()) {
+ throw new UnsupportedOperationException("Slot " + slot + " not in valid range - [0," + getSlots() + ")");
+ }
+ }
+
+ // Neo End
+
static record Slot(int index, ItemStack item) {
public static final Codec<ItemContainerContents.Slot> CODEC = RecordCodecBuilder.create(
p_331695_ -> p_331695_.group(
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package net.neoforged.neoforge.capabilities;

import java.util.List;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.WorldlyContainerHolder;
Expand All @@ -23,14 +24,14 @@
import net.neoforged.neoforge.event.level.ChunkEvent;
import net.neoforged.neoforge.event.tick.LevelTickEvent;
import net.neoforged.neoforge.fluids.capability.wrappers.FluidBucketWrapper;
import net.neoforged.neoforge.items.ComponentItemHandler;
import net.neoforged.neoforge.items.VanillaHopperItemHandler;
import net.neoforged.neoforge.items.wrapper.CombinedInvWrapper;
import net.neoforged.neoforge.items.wrapper.EntityArmorInvWrapper;
import net.neoforged.neoforge.items.wrapper.EntityHandsInvWrapper;
import net.neoforged.neoforge.items.wrapper.ForwardingItemHandler;
import net.neoforged.neoforge.items.wrapper.InvWrapper;
import net.neoforged.neoforge.items.wrapper.PlayerInvWrapper;
import net.neoforged.neoforge.items.wrapper.ShulkerItemStackInvWrapper;
import net.neoforged.neoforge.items.wrapper.SidedInvWrapper;
import org.jetbrains.annotations.ApiStatus;

Expand Down Expand Up @@ -131,7 +132,7 @@ else if (entity instanceof LivingEntity livingEntity)
if (NeoForgeMod.MILK.isBound()) {
event.registerItem(Capabilities.FluidHandler.ITEM, (stack, ctx) -> new FluidBucketWrapper(stack), Items.MILK_BUCKET);
}
event.registerItem(Capabilities.ItemHandler.ITEM, (stack, ctx) -> new ShulkerItemStackInvWrapper(stack),
event.registerItem(Capabilities.ItemHandler.ITEM, (stack, ctx) -> new ComponentItemHandler(stack, DataComponents.CONTAINER, 27),
Items.SHULKER_BOX,
Items.BLACK_SHULKER_BOX,
Items.BLUE_SHULKER_BOX,
Expand Down
203 changes: 203 additions & 0 deletions src/main/java/net/neoforged/neoforge/items/ComponentItemHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.items;

import com.google.common.base.Preconditions;
import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.ItemContainerContents;
import net.neoforged.neoforge.capabilities.ICapabilityProvider;
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
import net.neoforged.neoforge.common.MutableDataComponentHolder;

/**
* Variant of {@link ItemStackHandler} for use with data components.
* <p>
* The actual data storage is managed by a data component, and all changes will write back to that component.
* <p>
* To use this class, register a new {@link DataComponentType} which holds an {@link ItemContainerContents} for your item.
* Then reference that component from your {@link ICapabilityProvider} passed to {@link RegisterCapabilitiesEvent#registerItem} to create an instance of this class.
*
* @implNote All functions in this class should attempt to minimize component read/writes to avoid unnecessary churn, noting that the component can never be cached.
*/
public class ComponentItemHandler implements IItemHandlerModifiable {
protected final MutableDataComponentHolder parent;
protected final DataComponentType<ItemContainerContents> component;
protected final int size;

/**
* Creates a new {@link ComponentItemHandler} with target size. If the existing component is smaller than the given size, it will be expanded on write.
*
* @param parent The parent component holder, such as an {@link ItemStack}
* @param component The data component referencing the stored inventory of the item stack
* @param size The number of slots. Must be less than 256 due to limitations of {@link ItemContainerContents}
*/
public ComponentItemHandler(MutableDataComponentHolder parent, DataComponentType<ItemContainerContents> component, int size) {
this.parent = parent;
this.component = component;
this.size = size;
Preconditions.checkArgument(size <= 256, "The max size of ItemContainerContents is 256 slots.");
}

@Override
public int getSlots() {
return this.size;
}

@Override
public ItemStack getStackInSlot(int slot) {
ItemContainerContents contents = this.getContents();
return this.getStackFromContents(contents, slot);
}

@Override
public void setStackInSlot(int slot, ItemStack stack) {
this.validateSlotIndex(slot);
if (!this.isItemValid(slot, stack)) {
throw new RuntimeException("Invalid stack " + stack + " for slot " + slot + ")");
}
ItemContainerContents contents = this.getContents();
ItemStack existing = this.getStackFromContents(contents, slot);
if (!ItemStack.matches(stack, existing)) {
this.updateContents(contents, stack, slot);
}
}

@Override
public ItemStack insertItem(int slot, ItemStack toInsert, boolean simulate) {
this.validateSlotIndex(slot);

if (toInsert.isEmpty()) {
return ItemStack.EMPTY;
}

if (!this.isItemValid(slot, toInsert)) {
return toInsert;
}

ItemContainerContents contents = this.getContents();
ItemStack existing = this.getStackFromContents(contents, slot);
// Max amount of the stack that could be inserted
int insertLimit = Math.min(this.getSlotLimit(slot), toInsert.getMaxStackSize());

if (!existing.isEmpty()) {
if (!ItemStack.isSameItemSameComponents(toInsert, existing)) {
return toInsert;
}

insertLimit -= existing.getCount();
}

if (insertLimit <= 0) {
return toInsert;
}

int inserted = Math.min(insertLimit, toInsert.getCount());

if (!simulate) {
this.updateContents(contents, toInsert.copyWithCount(existing.getCount() + inserted), slot);
}

return toInsert.copyWithCount(toInsert.getCount() - inserted);
}

@Override
public ItemStack extractItem(int slot, int amount, boolean simulate) {
this.validateSlotIndex(slot);

if (amount == 0) {
return ItemStack.EMPTY;
}

ItemContainerContents contents = this.getContents();
ItemStack existing = this.getStackFromContents(contents, slot);

if (existing.isEmpty()) {
return ItemStack.EMPTY;
}

int toExtract = Math.min(amount, existing.getMaxStackSize());

if (!simulate) {
this.updateContents(contents, existing.copyWithCount(existing.getCount() - toExtract), slot);
}

return existing.copyWithCount(toExtract);
}

@Override
public int getSlotLimit(int slot) {
return Item.ABSOLUTE_MAX_STACK_SIZE;
}

@Override
public boolean isItemValid(int slot, ItemStack stack) {
return stack.getItem().canFitInsideContainerItems();
}

/**
* Called from {@link #updateContents} after the stack stored in a slot has been updated.
* <p>
* Modifications to the stacks used as parameters here will not write-back to the stored data.
*
* @param slot The slot that changed
* @param oldStack The old stack that was present in the slot
* @param newStack The new stack that is now present in the slot
*/
protected void onContentsChanged(int slot, ItemStack oldStack, ItemStack newStack) {}

/**
* Retrieves the {@link ItemContainerContents} from the parent object's data component map.
*/
protected ItemContainerContents getContents() {
return this.parent.getOrDefault(this.component, ItemContainerContents.EMPTY);
}

/**
* Retrieves a copy of a single stack from the underlying data component, returning {@link ItemStack#EMPTY} if the component does not have a slot present.
* <p>
* Throws an exception if the slot is out-of-bounds for this capability.
*
* @param contents The existing contents from {@link #getContents()}
* @param slot The target slot
* @return A copy of the stack in the target slot
*/
protected ItemStack getStackFromContents(ItemContainerContents contents, int slot) {
this.validateSlotIndex(slot);
return contents.getSlots() <= slot ? ItemStack.EMPTY : contents.getStackInSlot(slot);
}

/**
* Performs a copy and write operation on the underlying data component, changing the stack in the target slot.
* <p>
* If the existing component is larger than {@link #getSlots()}, additional slots will <b>not</b> be truncated.
*
* @param contents The existing contents from {@link #getContents()}
* @param stack The new stack to set to the slot
* @param slot The target slot
*/
protected void updateContents(ItemContainerContents contents, ItemStack stack, int slot) {
this.validateSlotIndex(slot);
// Use the max of the contents slots and the capability slots to avoid truncating
NonNullList<ItemStack> list = NonNullList.withSize(Math.max(contents.getSlots(), this.getSlots()), ItemStack.EMPTY);
contents.copyInto(list);
ItemStack oldStack = list.get(slot);
list.set(slot, stack);
this.parent.set(this.component, ItemContainerContents.fromItems(list));
this.onContentsChanged(slot, oldStack, stack);
}

/**
* Throws {@link UnsupportedOperationException} if the provided slot index is invalid.
*/
protected final void validateSlotIndex(int slot) {
if (slot < 0 || slot >= getSlots()) {
throw new RuntimeException("Slot " + slot + " not in valid range - [0," + getSlots() + ")");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,20 @@ public static ItemStack insertItem(IItemHandler dest, ItemStack stack, boolean s
return stack;
}

/**
* @deprecated Use {@link ItemStack#isSameItemSameComponents(ItemStack, ItemStack)}
*/
@Deprecated(forRemoval = true, since = "1.20.5")
public static boolean canItemStacksStack(ItemStack a, ItemStack b) {
return ItemStack.isSameItemSameComponents(a, b);
}

/**
* @deprecated Use {@link ItemStack#copyWithCount(int)}
*/
@Deprecated(forRemoval = true, since = "1.20.5")
public static ItemStack copyStackWithSize(ItemStack itemStack, int size) {
if (size == 0)
return ItemStack.EMPTY;
ItemStack copy = itemStack.copy();
copy.setCount(size);
return copy;
public static ItemStack copyStackWithSize(ItemStack stack, int count) {
return stack.copyWithCount(count);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.common.util.INBTSerializable;

Expand Down Expand Up @@ -122,7 +123,7 @@ public ItemStack extractItem(int slot, int amount, boolean simulate) {

@Override
public int getSlotLimit(int slot) {
return 64;
return Item.ABSOLUTE_MAX_STACK_SIZE;
}

protected int getStackLimit(int slot, ItemStack stack) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected int getIndexForSlot(int slot) {

protected IItemHandlerModifiable getHandlerFromIndex(int index) {
if (index < 0 || index >= itemHandler.length) {
return (IItemHandlerModifiable) EmptyHandler.INSTANCE;
return (IItemHandlerModifiable) EmptyItemHandler.INSTANCE;
}
return itemHandler[index];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import net.neoforged.neoforge.items.IItemHandler;
import net.neoforged.neoforge.items.IItemHandlerModifiable;

public class EmptyHandler implements IItemHandlerModifiable {
public static final IItemHandler INSTANCE = new EmptyHandler();
public class EmptyItemHandler implements IItemHandlerModifiable {
public static final IItemHandler INSTANCE = new EmptyItemHandler();

@Override
public int getSlots() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.List;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.items.IItemHandler;
import net.neoforged.neoforge.items.IItemHandlerModifiable;
Expand Down Expand Up @@ -124,7 +125,7 @@ public ItemStack extractItem(final int slot, final int amount, final boolean sim
@Override
public int getSlotLimit(final int slot) {
final EquipmentSlot equipmentSlot = validateSlotIndex(slot);
return equipmentSlot.getType() == EquipmentSlot.Type.ARMOR ? 1 : 64;
return equipmentSlot.getType() == EquipmentSlot.Type.ARMOR ? 1 : Item.ABSOLUTE_MAX_STACK_SIZE;
}

protected int getStackLimit(final int slot, final ItemStack stack) {
Expand Down
Loading

0 comments on commit 8a7c09c

Please sign in to comment.