Proxy ItemStack to CraftItemStack

This commit is contained in:
Jake Potrebic 2024-05-14 11:57:43 -07:00
parent c3d1a03428
commit c441981c54
6 changed files with 150 additions and 50 deletions

View File

@ -31,13 +31,55 @@ import org.jetbrains.annotations.ApiStatus;
@DelegateDeserialization(ItemStack.class)
public final class CraftItemStack extends ItemStack {
// Paper start - delegate api-ItemStack to CraftItemStack
private static final java.lang.invoke.VarHandle API_ITEM_STACK_CRAFT_DELEGATE_FIELD;
static {
try {
API_ITEM_STACK_CRAFT_DELEGATE_FIELD = java.lang.invoke.MethodHandles.privateLookupIn(
ItemStack.class,
java.lang.invoke.MethodHandles.lookup()
).findVarHandle(ItemStack.class, "craftDelegate", ItemStack.class);
} catch (final IllegalAccessException | NoSuchFieldException exception) {
throw new RuntimeException(exception);
}
}
private static CraftItemStack getCraftStack(final ItemStack bukkit) {
if (bukkit instanceof final CraftItemStack craftItemStack) {
return craftItemStack;
} else {
return (CraftItemStack) API_ITEM_STACK_CRAFT_DELEGATE_FIELD.get(bukkit);
}
}
@Override
public int hashCode() {
if (this.handle == null || this.handle.isEmpty()) {
return net.minecraft.world.item.ItemStack.EMPTY.hashCode();
} else {
int hash = net.minecraft.world.item.ItemStack.hashItemAndComponents(this.handle);
hash = hash * 31 + this.handle.getCount();
return hash;
}
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof final org.bukkit.inventory.ItemStack bukkit)) return false;
final CraftItemStack craftStack = getCraftStack(bukkit);
if (this.handle == craftStack.handle) return true;
else if (this.handle == null || craftStack.handle == null) return false;
else if (this.handle.isEmpty() && craftStack.handle.isEmpty()) return true;
else return net.minecraft.world.item.ItemStack.matches(this.handle, craftStack.handle);
}
// Paper end
// Paper start - MC Utils
public static net.minecraft.world.item.ItemStack unwrap(ItemStack bukkit) {
if (bukkit instanceof CraftItemStack craftItemStack) {
return craftItemStack.handle != null ? craftItemStack.handle : net.minecraft.world.item.ItemStack.EMPTY;
} else {
return asNMSCopy(bukkit);
}
// Paper start - re-implement after delegating all api ItemStack calls to CraftItemStack
final CraftItemStack craftItemStack = getCraftStack(bukkit);
return craftItemStack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : craftItemStack.handle;
// Paper end - re-implement after delegating all api ItemStack calls to CraftItemStack
}
public static net.minecraft.world.item.ItemStack getOrCloneOnMutation(ItemStack old, ItemStack newInstance) {
@ -53,25 +95,13 @@ public final class CraftItemStack extends ItemStack {
// Paper end - override isEmpty to use vanilla's impl
public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) {
if (original instanceof CraftItemStack) {
CraftItemStack stack = (CraftItemStack) original;
return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy();
}
if (original == null || original.isEmpty()) { // Paper - override isEmpty to use vanilla's impl; use isEmpty
// Paper start - re-implement after delegating all api ItemStack calls to CraftItemStack
if (original == null || original.isEmpty()) {
return net.minecraft.world.item.ItemStack.EMPTY;
}
Item item = CraftItemType.bukkitToMinecraft(original.getType());
if (item == null) {
return net.minecraft.world.item.ItemStack.EMPTY;
}
net.minecraft.world.item.ItemStack stack = new net.minecraft.world.item.ItemStack(item, original.getAmount());
if (original.hasItemMeta()) {
CraftItemStack.setItemMeta(stack, original.getItemMeta());
}
return stack;
final CraftItemStack stack = getCraftStack(original);
return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy();
// Paper end - re-implement after delegating all api ItemStack calls to CraftItemStack
}
// Paper start
@ -94,14 +124,10 @@ public final class CraftItemStack extends ItemStack {
* Copies the NMS stack to return as a strictly-Bukkit stack
*/
public static ItemStack asBukkitCopy(net.minecraft.world.item.ItemStack original) {
if (original.isEmpty()) {
return new ItemStack(Material.AIR);
}
ItemStack stack = new ItemStack(CraftItemType.minecraftToBukkit(original.getItem()), original.getCount());
if (CraftItemStack.hasItemMeta(original)) {
stack.setItemMeta(CraftItemStack.getItemMeta(original));
}
return stack;
// Paper start - no such thing as a "strictly-Bukkit stack" anymore
// we copy the stack since it should be a complete copy not a mirror
return asCraftMirror(original.copy());
// Paper end
}
public static CraftItemStack asCraftMirror(net.minecraft.world.item.ItemStack original) {
@ -329,11 +355,7 @@ public final class CraftItemStack extends ItemStack {
@Override
public CraftItemStack clone() {
CraftItemStack itemStack = (CraftItemStack) super.clone();
if (this.handle != null) {
itemStack.handle = this.handle.copy();
}
return itemStack;
return new org.bukkit.craftbukkit.inventory.CraftItemStack(this.handle != null ? this.handle.copy() : null); // Paper
}
@Override
@ -436,22 +458,14 @@ public final class CraftItemStack extends ItemStack {
if (stack == this) {
return true;
}
if (!(stack instanceof CraftItemStack)) {
return stack.getClass() == ItemStack.class && stack.isSimilar(this);
}
CraftItemStack that = (CraftItemStack) stack;
final CraftItemStack that = getCraftStack(stack); // Paper - re-implement after delegating all api ItemStack calls to CraftItemStack
if (this.handle == that.handle) {
return true;
}
if (this.handle == null || that.handle == null) {
return false;
}
Material comparisonType = CraftLegacy.fromLegacy(that.getType()); // This may be called from legacy item stacks, try to get the right material
if (!(comparisonType == this.getType() && this.getDurability() == that.getDurability())) {
return false;
}
return this.hasItemMeta() ? that.hasItemMeta() && this.handle.getComponents().equals(that.handle.getComponents()) : !that.hasItemMeta();
return net.minecraft.world.item.ItemStack.isSameItemSameComponents(this.handle, that.handle); // Paper - re-implement after delegating all api ItemStack calls to CraftItemStack
}
@Override

View File

@ -100,13 +100,14 @@ public class CraftItemType<M extends ItemMeta> implements ItemType.Typed<M>, Han
@NotNull
@Override
public ItemStack createItemStack(final int amount, @Nullable final Consumer<? super M> metaConfigurator) {
final ItemStack itemStack = new ItemStack(this.asMaterial(), amount);
// Paper start - re-implement to return CraftItemStack
final net.minecraft.world.item.ItemStack stack = new net.minecraft.world.item.ItemStack(this.item, amount);
final CraftItemStack mirror = CraftItemStack.asCraftMirror(stack);
if (metaConfigurator != null) {
final ItemMeta itemMeta = itemStack.getItemMeta();
metaConfigurator.accept((M) itemMeta);
itemStack.setItemMeta(itemMeta);
mirror.editMeta(this.getItemMetaClass(), metaConfigurator);
}
return itemStack;
return mirror;
// Paper start - reimplement to return CraftItemStack
}
@Override

View File

@ -678,4 +678,16 @@ public class MaterialRerouting {
return itemStack.withType(material);
}
// Paper end - register paper API specific material consumers in rerouting
// Paper start - methods added post 1.13, no-op
@RerouteStatic("org/bukkit/inventory/ItemStack")
public static ItemStack of(final Material material) {
return ItemStack.of(material);
}
@RerouteStatic("org/bukkit/inventory/ItemStack")
public static ItemStack of(final Material material, final int amount) {
return ItemStack.of(material, amount);
}
// Paper end
}

View File

@ -669,6 +669,13 @@ public final class CraftMagicNumbers implements UnsafeValues {
}
// Paper end - lifecycle event API
// Paper start - proxy ItemStack
@Override
public org.bukkit.inventory.ItemStack createEmptyStack() {
return CraftItemStack.asCraftMirror(null);
}
// Paper end - proxy ItemStack
/**
* This helper class represents the different NBT Tags.
* <p>

View File

@ -0,0 +1,53 @@
package io.papermc.paper.configuration;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.bukkit.support.environment.VanillaFeature;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public abstract class ConfigurationSectionTest {
public abstract ConfigurationSection getConfigurationSection();
@Test
public void testGetItemStack_String() {
ConfigurationSection section = getConfigurationSection();
String key = "exists";
ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50);
section.set(key, value);
assertEquals(value, section.getItemStack(key));
assertNull(section.getString("doesntExist"));
}
@Test
public void testGetItemStack_String_ItemStack() {
ConfigurationSection section = getConfigurationSection();
String key = "exists";
ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50);
ItemStack def = new ItemStack(Material.STONE, 1);
section.set(key, value);
assertEquals(value, section.getItemStack(key, def));
assertEquals(def, section.getItemStack("doesntExist", def));
}
@Test
public void testIsItemStack() {
ConfigurationSection section = getConfigurationSection();
String key = "exists";
ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50);
section.set(key, value);
assertTrue(section.isItemStack(key));
assertFalse(section.isItemStack("doesntExist"));
}
}

View File

@ -0,0 +1,13 @@
package io.papermc.paper.configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.support.environment.Normal;
@Normal
public class MemorySectionTest extends ConfigurationSectionTest {
@Override
public ConfigurationSection getConfigurationSection() {
return new MemoryConfiguration().createSection("section");
}
}