Proxy ItemStack to CraftItemStack

This commit is contained in:
Jake Potrebic 2024-05-14 11:57:51 -07:00
parent fcc777a0a8
commit 9e5e007003
3 changed files with 77 additions and 135 deletions

View File

@ -281,4 +281,6 @@ public interface UnsafeValues {
// Paper end - lifecycle event API // Paper end - lifecycle event API
@NotNull java.util.List<net.kyori.adventure.text.Component> computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines @NotNull java.util.List<net.kyori.adventure.text.Component> computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines
ItemStack createEmptyStack(); // Paper - proxy ItemStack
} }

View File

@ -28,10 +28,38 @@ import org.jetbrains.annotations.Nullable;
* returns false.</b> * returns false.</b>
*/ */
public class ItemStack implements Cloneable, ConfigurationSerializable, Translatable, net.kyori.adventure.text.event.HoverEventSource<net.kyori.adventure.text.event.HoverEvent.ShowItem>, net.kyori.adventure.translation.Translatable { // Paper public class ItemStack implements Cloneable, ConfigurationSerializable, Translatable, net.kyori.adventure.text.event.HoverEventSource<net.kyori.adventure.text.event.HoverEvent.ShowItem>, net.kyori.adventure.translation.Translatable { // Paper
private Material type = Material.AIR; private ItemStack craftDelegate; // Paper - always delegate to server-backed stack
private int amount = 0;
private MaterialData data = null; private MaterialData data = null;
private ItemMeta meta;
// Paper start - add static factory methods
/**
* Creates an itemstack with the specified item type and a count of 1.
*
* @param type the item type to use
* @return a new itemstack
* @throws IllegalArgumentException if the Material provided is not an item ({@link Material#isItem()})
*/
@org.jetbrains.annotations.Contract(value = "_ -> new", pure = true)
public static @NotNull ItemStack of(final @NotNull Material type) {
return of(type, 1);
}
/**
* Creates an itemstack with the specified item type and count.
*
* @param type the item type to use
* @param amount the count of items in the stack
* @return a new itemstack
* @throws IllegalArgumentException if the Material provided is not an item ({@link Material#isItem()})
* @throws IllegalArgumentException if the amount is less than 1
*/
@org.jetbrains.annotations.Contract(value = "_, _ -> new", pure = true)
public static @NotNull ItemStack of(final @NotNull Material type, final int amount) {
Preconditions.checkArgument(type.asItemType() != null, type + " isn't an item");
Preconditions.checkArgument(amount > 0, "amount must be greater than 0");
return java.util.Objects.requireNonNull(type.asItemType(), type + " is not an item").createItemStack(amount); // Paper - delegate
}
// Paper end
@Utility @Utility
protected ItemStack() {} protected ItemStack() {}
@ -44,7 +72,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* {@link Material#isItem()} returns false.</b> * {@link Material#isItem()} returns false.</b>
* *
* @param type item material * @param type item material
* @apiNote use {@link #of(Material)}
* @see #of(Material)
*/ */
@org.jetbrains.annotations.ApiStatus.Obsolete(since = "1.21") // Paper
public ItemStack(@NotNull final Material type) { public ItemStack(@NotNull final Material type) {
this(type, 1); this(type, 1);
} }
@ -58,7 +89,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* *
* @param type item material * @param type item material
* @param amount stack size * @param amount stack size
* @apiNote Use {@link #of(Material, int)}
* @see #of(Material, int)
*/ */
@org.jetbrains.annotations.ApiStatus.Obsolete(since = "1.21") // Paper
public ItemStack(@NotNull final Material type, final int amount) { public ItemStack(@NotNull final Material type, final int amount) {
this(type, amount, (short) 0); this(type, amount, (short) 0);
} }
@ -93,8 +127,9 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
type = Bukkit.getUnsafe().fromLegacy(new MaterialData(type, data == null ? (byte) damage : data), true); type = Bukkit.getUnsafe().fromLegacy(new MaterialData(type, data == null ? (byte) damage : data), true);
} }
} }
this.type = type; // Paper start - create delegate
this.amount = amount; this.craftDelegate = type == Material.AIR ? ItemStack.empty() : ItemStack.of(type, amount);
// Paper end - create delegate
if (damage != 0) { if (damage != 0) {
setDurability(damage); setDurability(damage);
} }
@ -109,17 +144,16 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* @param stack the stack to copy * @param stack the stack to copy
* @throws IllegalArgumentException if the specified stack is null or * @throws IllegalArgumentException if the specified stack is null or
* returns an item meta not created by the item factory * returns an item meta not created by the item factory
* @apiNote Use {@link #clone()}
* @see #clone()
*/ */
@org.jetbrains.annotations.ApiStatus.Obsolete(since = "1.21") // Paper
public ItemStack(@NotNull final ItemStack stack) throws IllegalArgumentException { public ItemStack(@NotNull final ItemStack stack) throws IllegalArgumentException {
Preconditions.checkArgument(stack != null, "Cannot copy null stack"); Preconditions.checkArgument(stack != null, "Cannot copy null stack");
this.type = stack.getType(); this.craftDelegate = stack.clone(); // Paper - delegate
this.amount = stack.getAmount(); if (stack.getType().isLegacy()) {
if (this.type.isLegacy()) {
this.data = stack.getData(); this.data = stack.getData();
} }
if (stack.hasItemMeta()) {
setItemMeta0(stack.getItemMeta(), type);
}
} }
/** /**
@ -127,10 +161,9 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* *
* @return Type of the items in this stack * @return Type of the items in this stack
*/ */
@Utility
@NotNull @NotNull
public Material getType() { public Material getType() {
return type; return this.craftDelegate.getType(); // Paper - delegate
} }
/** /**
@ -153,19 +186,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* Using this method in ItemStacks passed in events will result in undefined behavior. * Using this method in ItemStacks passed in events will result in undefined behavior.
* @see ItemStack#withType(Material) * @see ItemStack#withType(Material)
*/ */
@Utility
@Deprecated // Paper @Deprecated // Paper
public void setType(@NotNull Material type) { public void setType(@NotNull Material type) {
Preconditions.checkArgument(type != null, "Material cannot be null"); Preconditions.checkArgument(type != null, "Material cannot be null");
this.type = type; this.craftDelegate.setType(type); // Paper - delegate
if (this.meta != null) {
this.meta = Bukkit.getItemFactory().asMetaFor(meta, type);
}
if (type.isLegacy()) {
createData((byte) 0);
} else {
this.data = null;
}
} }
// Paper start // Paper start
/** /**
@ -177,12 +201,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@NotNull @NotNull
@org.jetbrains.annotations.Contract(value = "_ -> new", pure = true) @org.jetbrains.annotations.Contract(value = "_ -> new", pure = true)
public ItemStack withType(@NotNull Material type) { public ItemStack withType(@NotNull Material type) {
ItemStack itemStack = new ItemStack(type, this.amount); return this.craftDelegate.withType(type); // Paper - delegate
if (this.hasItemMeta()) {
itemStack.setItemMeta(this.getItemMeta());
}
return itemStack;
} }
// Paper end // Paper end
@ -192,7 +211,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* @return Amount of items in this stack * @return Amount of items in this stack
*/ */
public int getAmount() { public int getAmount() {
return amount; return this.craftDelegate.getAmount(); // Paper - delegate
} }
/** /**
@ -201,7 +220,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* @param amount New amount of items in this stack * @param amount New amount of items in this stack
*/ */
public void setAmount(int amount) { public void setAmount(int amount) {
this.amount = amount; this.craftDelegate.setAmount(amount); // Paper - delegate
} }
/** /**
@ -254,11 +273,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
*/ */
@Deprecated(since = "1.13") @Deprecated(since = "1.13")
public void setDurability(final short durability) { public void setDurability(final short durability) {
ItemMeta meta = getItemMeta(); this.craftDelegate.setDurability(durability); // Paper - delegate
if (meta != null) {
((Damageable) meta).setDamage(durability);
setItemMeta(meta);
}
} }
/** /**
@ -269,8 +284,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
*/ */
@Deprecated(since = "1.13") @Deprecated(since = "1.13")
public short getDurability() { public short getDurability() {
ItemMeta meta = getItemMeta(); return this.craftDelegate.getDurability(); // Paper - delegate
return (meta == null) ? 0 : (short) ((Damageable) meta).getDamage();
} }
/** /**
@ -282,17 +296,12 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* *
* @return The maximum you can stack this item to. * @return The maximum you can stack this item to.
*/ */
@Utility
public int getMaxStackSize() { public int getMaxStackSize() {
if (meta != null && meta.hasMaxStackSize()) { return this.craftDelegate.getMaxStackSize(); // Paper - delegate
return meta.getMaxStackSize();
}
return getType().getMaxStackSize();
} }
private void createData(final byte data) { private void createData(final byte data) {
this.data = type.getNewData(data); this.data = this.craftDelegate.getType().getNewData(data); // Paper
} }
@Override @Override
@ -306,17 +315,8 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
} }
@Override @Override
@Utility
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) { return this.craftDelegate.equals(obj); // Paper - delegate
return true;
}
if (!(obj instanceof ItemStack)) {
return false;
}
ItemStack stack = (ItemStack) obj;
return getAmount() == stack.getAmount() && isSimilar(stack);
} }
/** /**
@ -326,49 +326,19 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* @param stack the item stack to compare to * @param stack the item stack to compare to
* @return true if the two stacks are equal, ignoring the amount * @return true if the two stacks are equal, ignoring the amount
*/ */
@Utility
public boolean isSimilar(@Nullable ItemStack stack) { public boolean isSimilar(@Nullable ItemStack stack) {
if (stack == null) { return this.craftDelegate.isSimilar(stack); // Paper - delegate
return false;
}
if (stack == this) {
return true;
}
Material comparisonType = (this.type.isLegacy()) ? Bukkit.getUnsafe().fromLegacy(this.getData(), true) : this.type; // This may be called from legacy item stacks, try to get the right material
return comparisonType == stack.getType() && /* getDurability() == stack.getDurability() && */hasItemMeta() == stack.hasItemMeta() && (hasItemMeta() ? Bukkit.getItemFactory().equals(getItemMeta(), stack.getItemMeta()) : true); // Paper - remove redundant item durability check
} }
@NotNull @NotNull
@Override @Override
public ItemStack clone() { public ItemStack clone() {
try { return this.craftDelegate.clone(); // Paper - delegate
ItemStack itemStack = (ItemStack) super.clone();
if (this.meta != null) {
itemStack.meta = this.meta.clone();
}
if (this.data != null) {
itemStack.data = this.data.clone();
}
return itemStack;
} catch (CloneNotSupportedException e) {
throw new Error(e);
}
} }
@Override @Override
@Utility
public int hashCode() { public int hashCode() {
int hash = 1; return this.craftDelegate.hashCode(); // Paper - delegate
hash = hash * 31 + getType().hashCode();
hash = hash * 31 + getAmount();
hash = hash * 31 + (getDurability() & 0xffff);
hash = hash * 31 + (hasItemMeta() ? (meta == null ? getItemMeta().hashCode() : meta.hashCode()) : 0);
return hash;
} }
/** /**
@ -378,7 +348,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* @return True if this has the given enchantment * @return True if this has the given enchantment
*/ */
public boolean containsEnchantment(@NotNull Enchantment ench) { public boolean containsEnchantment(@NotNull Enchantment ench) {
return meta == null ? false : meta.hasEnchant(ench); return this.craftDelegate.containsEnchantment(ench); // Paper - delegate
} }
/** /**
@ -388,7 +358,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* @return Level of the enchantment, or 0 * @return Level of the enchantment, or 0
*/ */
public int getEnchantmentLevel(@NotNull Enchantment ench) { public int getEnchantmentLevel(@NotNull Enchantment ench) {
return meta == null ? 0 : meta.getEnchantLevel(ench); return this.craftDelegate.getEnchantmentLevel(ench); // Paper - delegate
} }
/** /**
@ -398,7 +368,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
*/ */
@NotNull @NotNull
public Map<Enchantment, Integer> getEnchantments() { public Map<Enchantment, Integer> getEnchantments() {
return meta == null ? ImmutableMap.<Enchantment, Integer>of() : meta.getEnchants(); return this.craftDelegate.getEnchantments(); // Paper - delegate
} }
/** /**
@ -474,10 +444,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* @param level Level of the enchantment * @param level Level of the enchantment
*/ */
public void addUnsafeEnchantment(@NotNull Enchantment ench, int level) { public void addUnsafeEnchantment(@NotNull Enchantment ench, int level) {
ItemMeta itemMeta = (meta == null ? meta = Bukkit.getItemFactory().getItemMeta(type) : meta); this.craftDelegate.addUnsafeEnchantment(ench, level); // Paper - delegate
if (itemMeta != null) {
itemMeta.addEnchant(ench, level, true);
}
} }
/** /**
@ -488,23 +455,14 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* @return Previous level, or 0 * @return Previous level, or 0
*/ */
public int removeEnchantment(@NotNull Enchantment ench) { public int removeEnchantment(@NotNull Enchantment ench) {
int level = getEnchantmentLevel(ench); return this.craftDelegate.removeEnchantment(ench); // Paper - delegate
if (level == 0 || meta == null) {
return level;
}
meta.removeEnchant(ench);
return level;
} }
/** /**
* Removes all enchantments on this ItemStack. * Removes all enchantments on this ItemStack.
*/ */
public void removeEnchantments() { public void removeEnchantments() {
if (meta == null) { this.craftDelegate.removeEnchantments(); // Paper - delegate
return;
}
meta.removeEnchantments();
} }
@Override @Override
@ -660,7 +618,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
*/ */
@UndefinedNullability // Paper @UndefinedNullability // Paper
public ItemMeta getItemMeta() { public ItemMeta getItemMeta() {
return this.meta == null ? Bukkit.getItemFactory().getItemMeta(this.type) : this.meta.clone(); return this.craftDelegate.getItemMeta(); // Paper - delegate
} }
/** /**
@ -669,7 +627,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* @return Returns true if some meta data has been set for this item * @return Returns true if some meta data has been set for this item
*/ */
public boolean hasItemMeta() { public boolean hasItemMeta() {
return !Bukkit.getItemFactory().equals(meta, null); return this.craftDelegate.hasItemMeta(); // Paper - delegate
} }
/** /**
@ -682,28 +640,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* the {@link ItemFactory} * the {@link ItemFactory}
*/ */
public boolean setItemMeta(@Nullable ItemMeta itemMeta) { public boolean setItemMeta(@Nullable ItemMeta itemMeta) {
return setItemMeta0(itemMeta, type); return this.craftDelegate.setItemMeta(itemMeta); // Paper - delegate
} }
/* // Paper - delegate
* Cannot be overridden, so it's safe for constructor call
*/
private boolean setItemMeta0(@Nullable ItemMeta itemMeta, @NotNull Material material) {
if (itemMeta == null) {
this.meta = null;
return true;
}
if (!Bukkit.getItemFactory().isApplicable(itemMeta, material)) {
return false;
}
this.meta = Bukkit.getItemFactory().asMetaFor(itemMeta, material);
if (this.meta == itemMeta) {
this.meta = itemMeta.clone();
}
return true;
}
@Override @Override
@NotNull @NotNull
@ -894,11 +834,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
} }
public int getMaxItemUseDuration(@NotNull final org.bukkit.entity.LivingEntity entity) { public int getMaxItemUseDuration(@NotNull final org.bukkit.entity.LivingEntity entity) {
if (type == null || type == Material.AIR || !type.isItem()) { return this.craftDelegate.getMaxItemUseDuration(entity); // Paper - delegate
return 0;
}
// Requires access to NMS
return ensureServerConversions().getMaxItemUseDuration(entity);
} }
/** /**
@ -1148,7 +1084,8 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
*/ */
@NotNull @NotNull
public static ItemStack empty() { public static ItemStack empty() {
return new ItemStack(); //noinspection deprecation
return Bukkit.getUnsafe().createEmptyStack(); // Paper - proxy ItemStack
} }
/** /**
@ -1156,7 +1093,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* it is either air or the stack has a size of 0. * it is either air or the stack has a size of 0.
*/ */
public boolean isEmpty() { public boolean isEmpty() {
return type.isAir() || amount <= 0; return this.craftDelegate.isEmpty(); // Paper - delegate
} }
// Paper end // Paper end
// Paper start - expose itemstack tooltip lines // Paper start - expose itemstack tooltip lines

View File

@ -547,6 +547,7 @@ public abstract class ConfigurationSectionTest {
} }
@Test @Test
@org.junit.jupiter.api.Disabled("ItemStack can't exist without the Server, test moved to server")
public void testGetItemStack_String() { public void testGetItemStack_String() {
ConfigurationSection section = getConfigurationSection(); ConfigurationSection section = getConfigurationSection();
String key = "exists"; String key = "exists";
@ -559,6 +560,7 @@ public abstract class ConfigurationSectionTest {
} }
@Test @Test
@org.junit.jupiter.api.Disabled("ItemStack can't exist without the Server, test moved to server")
public void testGetItemStack_String_ItemStack() { public void testGetItemStack_String_ItemStack() {
ConfigurationSection section = getConfigurationSection(); ConfigurationSection section = getConfigurationSection();
String key = "exists"; String key = "exists";
@ -572,6 +574,7 @@ public abstract class ConfigurationSectionTest {
} }
@Test @Test
@org.junit.jupiter.api.Disabled("ItemStack can't exist without the Server, test moved to server")
public void testIsItemStack() { public void testIsItemStack() {
ConfigurationSection section = getConfigurationSection(); ConfigurationSection section = getConfigurationSection();
String key = "exists"; String key = "exists";