Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.20.6] Add ComponentItemHandler for using IItemHandlerModifiable with Data Components #990

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
pupnewfster marked this conversation as resolved.
Show resolved Hide resolved
+ */
+ 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
185 changes: 185 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,185 @@
/*
* 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.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 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) {
validateSlotIndex(slot);

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

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

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

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

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

return existing.copyWithCount(toExtract);
}

@Override
public int getSlotLimit(int slot) {
return 64;
Shadows-of-Fire marked this conversation as resolved.
Show resolved Hide resolved
}

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

@Override
public void setStackInSlot(int slot, ItemStack stack) {
validateSlotIndex(slot);
if (!isItemValid(slot, stack)) {
throw new RuntimeException("Invalid stack " + stack + " for slot " + slot + ")");
}
updateContents(getContents(), stack, slot);
}

/**
* 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) {
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) {
Shadows-of-Fire marked this conversation as resolved.
Show resolved Hide resolved
validateSlotIndex(slot);
Shadows-of-Fire marked this conversation as resolved.
Show resolved Hide resolved
// 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);
Shadows-of-Fire marked this conversation as resolved.
Show resolved Hide resolved
list.set(slot, stack);
pupnewfster marked this conversation as resolved.
Show resolved Hide resolved
this.parent.set(this.component, ItemContainerContents.fromItems(list));
}

/**
* 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
Loading
Loading