Re-introduce ItemStack Capabilities (#10362)

Co-authored-by: RealMangoRage <64402114+RealMangorage@users.noreply.github.com>
Co-authored-by: LexManos <LexManos@gmail.com>
This commit is contained in:
Jonathing 2025-02-12 20:30:29 -05:00 committed by GitHub
parent 056360b050
commit 979abb2b93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 193 additions and 1 deletions

View file

@ -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<ItemStack> implements DataComponentHolder, net.minecraftforge.common.extensions.IForgeItemStack {
public static final Codec<ItemStack> 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 @@
}

View file

@ -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;
}
}

View file

@ -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<DataComponentType<?>> DATA_COMPONENTS = DeferredRegister.create(Registries.DATA_COMPONENT_TYPE, MOD_ID);
public static final RegistryObject<DataComponentType<Integer>> STORAGE = DATA_COMPONENTS.register("energy_storage", () ->
DataComponentType.<Integer>builder()
.persistent(ExtraCodecs.POSITIVE_INT)
.networkSynchronized(ByteBufCodecs.VAR_INT)
.build()
);
public ItemCapabilityTest(FMLJavaModLoadingContext context) {
super(context);
}
@SubscribeEvent
public static void onEvent(AttachCapabilitiesEvent<ItemStack> 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<IEnergyStorage> 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<IEnergyStorage> 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<EnergyStorage> consumer;
public MyEnergyStorage(int capacity, Consumer<EnergyStorage> consumer) {
super(capacity);
this.consumer = consumer;
}
public MyEnergyStorage(int capacity, int maxTransfer, Consumer<EnergyStorage> consumer) {
super(capacity, maxTransfer);
this.consumer = consumer;
}
public MyEnergyStorage(int capacity, int maxReceive, int maxExtract, Consumer<EnergyStorage> consumer) {
super(capacity, maxReceive, maxExtract);
this.consumer = consumer;
}
public MyEnergyStorage(int capacity, int maxReceive, int maxExtract, int energy, Consumer<EnergyStorage> consumer) {
super(capacity, maxReceive, maxExtract, energy);
this.consumer = consumer;
}
static <T, V> T doAndReturn(T value, V value2, Consumer<V> 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<EnergyStorage> 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 <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
if (cap == ForgeCapabilities.ENERGY) {
return storageLazyOptional.cast();
}
return LazyOptional.empty();
}
}
}