diff --git a/patches/minecraft/net/minecraft/world/item/ItemStack.java.patch b/patches/minecraft/net/minecraft/world/item/ItemStack.java.patch index 8be465c2e3..732381519b 100644 --- a/patches/minecraft/net/minecraft/world/item/ItemStack.java.patch +++ b/patches/minecraft/net/minecraft/world/item/ItemStack.java.patch @@ -5,10 +5,27 @@ import org.slf4j.Logger; -public final class ItemStack implements DataComponentHolder { -+public final class ItemStack implements DataComponentHolder, net.minecraftforge.common.extensions.IForgeItemStack { ++public final class ItemStack extends net.minecraftforge.common.capabilities.CapabilityProvider implements DataComponentHolder, net.minecraftforge.common.extensions.IForgeItemStack { public static final Codec CODEC = Codec.lazyInitialized( () -> RecordCodecBuilder.create( p_359412_ -> p_359412_.group( +@@ -252,13 +_,16 @@ + } + + private ItemStack(ItemLike p_331826_, int p_332766_, PatchedDataComponentMap p_333722_) { ++ super(ItemStack.class, true); + this.item = p_331826_.asItem(); + this.count = p_332766_; + this.components = p_333722_; + this.getItem().verifyComponentsAfterLoad(this); ++ gatherCapabilities(() -> this.item.getCapabilityProvider(this)); + } + + private ItemStack(@Nullable Void p_282703_) { ++ super(ItemStack.class, false); + this.item = null; + this.components = new PatchedDataComponentMap(DataComponentMap.EMPTY); + } @@ -348,13 +_,22 @@ } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java b/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java index 6feb89fcdf..c22988241b 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java @@ -33,6 +33,7 @@ import net.minecraft.world.level.Level; import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.common.ToolAction; import net.minecraftforge.common.ToolActions; +import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.registries.IForgeRegistry; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -611,4 +612,12 @@ public interface IForgeItem { default boolean canGrindstoneRepair(ItemStack stack) { return false; } + + /** + * @return The Default Capability Provider for this item, if any, accounting for the given itemstack. + */ + @Nullable + default ICapabilityProvider getCapabilityProvider(ItemStack stack) { + return null; + } } diff --git a/src/test/java/net/minecraftforge/debug/gameplay/item/ItemCapabilityTest.java b/src/test/java/net/minecraftforge/debug/gameplay/item/ItemCapabilityTest.java new file mode 100644 index 0000000000..b83bbbcffc --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/gameplay/item/ItemCapabilityTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.gameplay.item; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.minecraft.core.Direction; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.registries.Registries; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.energy.EnergyStorage; +import net.minecraftforge.energy.IEnergyStorage; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.gametest.GameTestHolder; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.RegistryObject; +import net.minecraftforge.test.BaseTestMod; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +@GameTestHolder("forge." + ItemCapabilityTest.MOD_ID) +@Mod(ItemCapabilityTest.MOD_ID) +public class ItemCapabilityTest extends BaseTestMod { + public static final String MOD_ID = "item_caps"; + + public static final DeferredRegister> DATA_COMPONENTS = DeferredRegister.create(Registries.DATA_COMPONENT_TYPE, MOD_ID); + public static final RegistryObject> STORAGE = DATA_COMPONENTS.register("energy_storage", () -> + DataComponentType.builder() + .persistent(ExtraCodecs.POSITIVE_INT) + .networkSynchronized(ByteBufCodecs.VAR_INT) + .build() + ); + + public ItemCapabilityTest(FMLJavaModLoadingContext context) { + super(context); + } + + @SubscribeEvent + public static void onEvent(AttachCapabilitiesEvent event) { + if (event.getObject().getItem() == Items.COPPER_INGOT) { + event.addCapability( + ResourceLocation.fromNamespaceAndPath("forge", "test"), + new MyProvider(event.getObject()) + ); + } + } + + @GameTest(template = "forge:empty3x3x3") + public static void testItemCap(GameTestHelper helper) { + + helper.registerEventListener(ItemCapabilityTest.class); + + ItemStack stack = new ItemStack(Items.COPPER_INGOT); + AtomicReference storageAtomicReference = new AtomicReference<>(); + + stack.getCapability(ForgeCapabilities.ENERGY).ifPresent(storage -> { + storage.receiveEnergy(1, false); + storageAtomicReference.set(storage); + }); + + helper.assertTrue( + storageAtomicReference.get() != null, + "Unable to find ForgeCapabilities.ENERGY Capability" + ); + + var registry = RegistryFriendlyByteBuf.decorator(helper.getLevel().registryAccess()).apply(new FriendlyByteBuf(Unpooled.buffer())); + + ItemStack.STREAM_CODEC.encode(registry, stack); + var stackOverWire = ItemStack.STREAM_CODEC.decode(registry); + + AtomicReference storage = new AtomicReference<>(); + stackOverWire.getCapability(ForgeCapabilities.ENERGY).ifPresent(storage::set); + + helper.assertValueEqual( + storage.get() == null ? -1 : storage.get().getEnergyStored(), + 11, + "Value did not Sync to client" + ); + + helper.succeed(); + } + + public static final class MyEnergyStorage extends EnergyStorage { + + private final Consumer consumer; + + public MyEnergyStorage(int capacity, Consumer consumer) { + super(capacity); + this.consumer = consumer; + } + + public MyEnergyStorage(int capacity, int maxTransfer, Consumer consumer) { + super(capacity, maxTransfer); + this.consumer = consumer; + } + + public MyEnergyStorage(int capacity, int maxReceive, int maxExtract, Consumer consumer) { + super(capacity, maxReceive, maxExtract); + this.consumer = consumer; + } + + public MyEnergyStorage(int capacity, int maxReceive, int maxExtract, int energy, Consumer consumer) { + super(capacity, maxReceive, maxExtract, energy); + this.consumer = consumer; + } + + static T doAndReturn(T value, V value2, Consumer consumer) { + consumer.accept(value2); + return value; + } + + @Override + public int receiveEnergy(int maxReceive, boolean simulate) { + return doAndReturn(super.receiveEnergy(maxReceive, simulate), this, consumer); + } + + @Override + public int extractEnergy(int maxExtract, boolean simulate) { + return doAndReturn(super.extractEnergy(maxExtract, simulate), this, consumer); + } + } + + public static final class MyProvider implements ICapabilityProvider { + private final ItemStack stack; + private final EnergyStorage storage; + private final LazyOptional storageLazyOptional; + + public MyProvider(ItemStack stack) { + this.stack = stack; + this.storage = new MyEnergyStorage(1000, 10, 10, stack.getOrDefault(STORAGE.get(), 10), storage -> { + stack.set(STORAGE.get(), storage.getEnergyStored()); + }); + this.storageLazyOptional = LazyOptional.of(() -> storage); + } + + @Override + public @NotNull LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + if (cap == ForgeCapabilities.ENERGY) { + return storageLazyOptional.cast(); + } + return LazyOptional.empty(); + } + } +}