diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemHelper.java b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemHelper.java index 257393af8..d0fc4db05 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemHelper.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemHelper.java @@ -82,7 +82,7 @@ public static ItemStack newStack(final CustomItem customItem) { */ public static ItemStack newStack(final CustomItem customItem, final int amount) { final var itemStack = new ItemStack(customItem.baseMaterial(), amount); - itemStack.editMeta(meta -> meta.displayName(customItem.displayName())); + itemStack.editMeta(meta -> meta.itemName(customItem.displayName())); return CustomItemHelper.updateItemStack(customItem, itemStack); } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/DurabilityManager.java b/vane-core/src/main/java/org/oddlama/vane/core/item/DurabilityManager.java index 33fd7c7c7..7018c0348 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/DurabilityManager.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/DurabilityManager.java @@ -1,15 +1,12 @@ package org.oddlama.vane.core.item; -import java.util.ArrayList; - import org.bukkit.NamespacedKey; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.PrepareAnvilEvent; import org.bukkit.event.player.PlayerItemDamageEvent; -import org.bukkit.event.player.PlayerItemMendEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; import org.oddlama.vane.core.Core; import org.oddlama.vane.core.Listener; @@ -20,6 +17,9 @@ import net.kyori.adventure.text.Component; + +// TODO: what about inventory based item repair? + public class DurabilityManager extends Listener { public static final NamespacedKey ITEM_DURABILITY_MAX = StorageUtil.namespaced_key("vane", "durability.max"); public static final NamespacedKey ITEM_DURABILITY_DAMAGE = StorageUtil.namespaced_key("vane", "durability.damage"); @@ -52,68 +52,6 @@ private static void remove_lore(final ItemStack item_stack) { } } - /** - * Returns the remaining uses for a given item, if it has custom durability. - * Returns -1 otherwise. - */ - private static int remaining_uses(final ItemStack item_stack) { - if (item_stack == null || !item_stack.hasItemMeta()) { - return -1; - } - final var data = item_stack.getItemMeta().getPersistentDataContainer(); - final var max = data.getOrDefault(ITEM_DURABILITY_MAX, PersistentDataType.INTEGER, -1); - if (max == -1) { - return -1; - } - final var damage = data.getOrDefault(ITEM_DURABILITY_DAMAGE, PersistentDataType.INTEGER, 0); - return Math.max(0, Math.min(max - damage, max)); - } - - /** - * Updates lore on an item. Both persistent data field should exist, - * otherwise result is unspecified. - */ - private static void update_lore(final CustomItem custom_item, final ItemStack item_stack) { - var lore = item_stack.lore(); - if (lore == null) { - lore = new ArrayList(); - } - - // Remove old component - lore.removeIf(DurabilityManager::is_durability_lore); - - // Add new component only if requested - final var lore_component = custom_item.durabilityLore(); - if (lore_component != null) { - final var max = item_stack.getItemMeta().getPersistentDataContainer().getOrDefault(ITEM_DURABILITY_MAX, PersistentDataType.INTEGER, custom_item.durability()); - final var remaining_uses = remaining_uses(item_stack); - lore.add(ItemUtil.add_sentinel(lore_component.args(Component.text(remaining_uses), Component.text(max)), SENTINEL)); - } - - item_stack.lore(lore); - } - - /** - * Calculates visual damage value given the visual max durability, - * the actual damage and actual maximum damage. The actual damage value - * will be automatically clamped to reasonable values. - */ - private static int visual_damage(final int actual_damage, final int actual_max, final short visual_max) { - if (actual_damage <= 0) { - return 0; - } else if (actual_damage == actual_max - 1) { - // This forces item's that have 1 durability left to also show 1 durability left - // in their visual damage, which allows client mods to swap items if necessary. - return visual_max - 1; - } else if (actual_damage >= actual_max) { - return visual_max; - } else { - final var damage_percentage = (double)actual_damage / actual_max; - // Never allow the calculation to show the item as 0 visual durability, - return Math.min(visual_max - 1, (int)(damage_percentage * visual_max)); - } - } - /** * Sets the item's damage regarding our custom durability. The durability will get * clamped to plausible values. Damage values >= max will result in item breakage. @@ -125,32 +63,11 @@ private static void set_damage_and_update_item(final CustomItem custom_item, fin if (ro_meta.isUnbreakable()) { damage = 0; } - - // Clamp values below or above defined damage values. - final var actual_max = ro_meta.getPersistentDataContainer().getOrDefault(ITEM_DURABILITY_MAX, PersistentDataType.INTEGER, custom_item.durability()); - final var actual_damage = Math.min(actual_max, Math.max(0, damage)); - - // Store new damage values - item_stack.editMeta(meta -> { - final var data = meta.getPersistentDataContainer(); - data.set(ITEM_DURABILITY_DAMAGE, PersistentDataType.INTEGER, actual_damage); - if (!data.has(ITEM_DURABILITY_MAX, PersistentDataType.INTEGER)) { - data.set(ITEM_DURABILITY_MAX, PersistentDataType.INTEGER, actual_max); - } - }); - - // Update lore - update_lore(custom_item, item_stack); - - // Update visual damage if base item is damageable. - item_stack.editMeta(Damageable.class, damage_meta -> { - final var visual_max = item_stack.getType().getMaxDurability(); - damage_meta.setDamage(visual_damage(actual_damage, actual_max, visual_max)); - }); + set_damage_and_max_damage(custom_item, item_stack, damage); } /** - * Initialized damage tags on the item, or removes them if custom durability + * Initializes damage on the item, or removes them if custom durability * is disabled for the given custom item. */ public static boolean initialize_or_update_max(final CustomItem custom_item, final ItemStack item_stack) { @@ -188,91 +105,71 @@ public static boolean initialize_or_update_max(final CustomItem custom_item, fin } set_damage_and_update_item(custom_item, item_stack, actual_damage); + return true; } - /** - * Damages item by given amount regarding our custom durability. - * Negative amounts repair the item. - */ - private static void damage_and_update_item(final CustomItem custom_item, final ItemStack item_stack, final int amount) { - if (!item_stack.getItemMeta().getPersistentDataContainer().has(ITEM_DURABILITY_DAMAGE, PersistentDataType.INTEGER)) { - if (!initialize_or_update_max(custom_item, item_stack)) { - return; - } - } - - final var damage = item_stack.getItemMeta().getPersistentDataContainer().getOrDefault(ITEM_DURABILITY_DAMAGE, PersistentDataType.INTEGER, 0); - set_damage_and_update_item(custom_item, item_stack, damage + amount); - } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void on_item_damage(final PlayerItemDamageEvent event) { final var item = event.getItem(); final var custom_item = get_module().item_registry().get(item); - // Ignore normal items or custom items with standard durability. - if (custom_item == null || custom_item.durability() <= 0) { + // Ignore normal items + if (custom_item == null) { return; } - damage_and_update_item(custom_item, item, event.getDamage()); - - // Wow this is hacky but the only workaround to prevent recusivly - // calling this event. We always increase the visual durability by 1 - // and let the server implementation decrease it again to - // allow the item to break. - item.editMeta(Damageable.class, damage_meta -> damage_meta.setDamage(damage_meta.getDamage() - 1)); - event.setDamage(1); + update_damage(custom_item, item); } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_item_mend(final PlayerItemMendEvent event) { - // TODO: Durability Overridden items can not yet support mending effectively. - // see: https://github.com/PaperMC/Paper/issues/7313 - final var item = event.getItem(); - final var custom_item = get_module().item_registry().get(item); - - // Ignore normal items or custom items with standard durability. - if (custom_item == null || custom_item.durability() <= 0) { - return; + /** + * Update existing max damage to match the configuration + */ + static public void update_damage(CustomItem custom_item, ItemStack item_stack) { + if (!(item_stack.getItemMeta() instanceof Damageable meta)) + return; // everything should be damageable now + + boolean updated = false; + PersistentDataContainer data = meta.getPersistentDataContainer(); + + final int new_max_damage = custom_item.durability() == 0 ? item_stack.getType().getMaxDurability() : custom_item.durability(); + + int old_damage; + int old_max_damage; + // if the item has damage in their data, get the value and remove it from PDC + if(data.has(ITEM_DURABILITY_DAMAGE) && data.has(ITEM_DURABILITY_MAX)) { + old_damage = data.get(ITEM_DURABILITY_DAMAGE, PersistentDataType.INTEGER); + old_max_damage = data.get(ITEM_DURABILITY_MAX, PersistentDataType.INTEGER); + data.remove(ITEM_DURABILITY_DAMAGE); + data.remove(ITEM_DURABILITY_MAX); + updated = true; + } else { + old_damage = meta.hasDamage() ? meta.getDamage() : 0; + old_max_damage = meta.hasMaxDamage() ? meta.getMaxDamage() : item_stack.getType().getMaxDurability(); } + + remove_lore(item_stack); - final var mend_amount = 2 * event.getExperienceOrb().getExperience(); - damage_and_update_item(custom_item, item, -mend_amount); - - // Never let the server do any repairing, our durability bar is our percentage indicator. - event.setCancelled(true); + if(!updated) updated = old_max_damage != new_max_damage; // only update if there was old data or a different max durability + if(!updated) return; // and do nothing if nothing changed + final int new_damage = scale_damage(old_damage, old_max_damage, new_max_damage); + set_damage_and_max_damage(custom_item, item_stack, new_damage); } - // TODO: what about inventory based item repair? - // Update durability on result items. - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_prepare_anvil(final PrepareAnvilEvent event) { - if (event.getResult() == null) { - return; - } - - final var r = event.getResult(); - final var custom_item_r = get_module().item_registry().get(r); - if (custom_item_r == null) { - return; - } - - if (custom_item_r.durability() <= 0) { - // Remove leftover components - damage_and_update_item(custom_item_r, r, 0); - } else { - final var uses_a = remaining_uses(event.getInventory().getFirstItem()); - final var uses_b = remaining_uses(event.getInventory().getSecondItem()); + static public int scale_damage(int old_damage, int old_max_damage, int new_max_damage) { + return old_max_damage == new_max_damage ? old_damage : (int)(new_max_damage * ((float) old_damage / (float) old_max_damage)); + } - final var max = r.getItemMeta().getPersistentDataContainer().getOrDefault(ITEM_DURABILITY_MAX, PersistentDataType.INTEGER, custom_item_r.durability()); - if (uses_a >= 0 && uses_b >= 0) { - set_damage_and_update_item(custom_item_r, r, max - (uses_a + uses_b)); + static public boolean set_damage_and_max_damage(CustomItem custom_item, ItemStack item, int damage) { + return item.editMeta(Damageable.class, meta -> { + if(custom_item.durability() != 0) { + meta.setMaxDamage(custom_item.durability()); + } else { + meta.setMaxDamage((int) item.getType().getMaxDurability()); } - } - - event.setResult(r); + + meta.setDamage(damage); + }); } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/ExistingItemConverter.java b/vane-core/src/main/java/org/oddlama/vane/core/item/ExistingItemConverter.java index 3e3aeddbf..51be425d5 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/ExistingItemConverter.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/ExistingItemConverter.java @@ -7,7 +7,7 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import org.bukkit.persistence.PersistentDataType; +import org.bukkit.inventory.meta.Damageable; import org.jetbrains.annotations.NotNull; import org.oddlama.vane.core.Core; import org.oddlama.vane.core.Listener; @@ -80,7 +80,7 @@ private void process_inventory(@NotNull Inventory inventory) { } contents[i] = convert_to_custom_item.convertExistingStack(is); - contents[i].editMeta(meta -> meta.displayName(convert_to_custom_item.displayName())); + contents[i].editMeta(meta -> meta.itemName(convert_to_custom_item.displayName())); get_module().enchantment_manager.update_enchanted_item(contents[i]); get_module().log.info("Converted legacy item to " + convert_to_custom_item.key()); ++changed; @@ -110,10 +110,14 @@ private void process_inventory(@NotNull Inventory inventory) { } // Update maximum durability on existing items if changed. - if (meta.getPersistentDataContainer().getOrDefault(DurabilityManager.ITEM_DURABILITY_MAX, PersistentDataType.INTEGER, 0) != custom_item.durability()) { + Damageable damageableMeta = (Damageable) contents[i].getItemMeta(); + int max_damage = damageableMeta.hasMaxDamage() ? damageableMeta.getMaxDamage() : contents[i].getType().getMaxDurability(); + int correct_max_damage = custom_item.durability() == 0 ? contents[i].getType().getMaxDurability() : custom_item.durability(); + if (max_damage != correct_max_damage || meta.getPersistentDataContainer().has(DurabilityManager.ITEM_DURABILITY_DAMAGE)) { get_module().log.info("Updated item durability " + custom_item.key()); - DurabilityManager.initialize_or_update_max(custom_item, contents[i]); + DurabilityManager.update_damage(custom_item, contents[i]); ++changed; + continue; } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/ItemUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/ItemUtil.java index 1283504ea..b85ae6364 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/ItemUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/ItemUtil.java @@ -11,19 +11,16 @@ import java.util.UUID; import java.util.stream.Collectors; -import com.destroystokyo.paper.profile.ProfileProperty; -import com.mojang.brigadier.StringReader; -import com.mojang.brigadier.exceptions.CommandSyntaxException; - import org.apache.commons.lang3.tuple.Pair; import org.bukkit.Bukkit; +import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.OfflinePlayer; -import org.bukkit.craftbukkit.enchantments.CraftEnchantment; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.bukkit.inventory.meta.SkullMeta; @@ -32,6 +29,10 @@ import org.oddlama.vane.core.Core; import org.oddlama.vane.core.material.ExtendedMaterial; +import com.destroystokyo.paper.profile.ProfileProperty; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.HoverEvent; @@ -40,8 +41,6 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.item.ItemParser; -import net.minecraft.core.HolderLookup; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.server.MinecraftServer; import net.minecraft.util.RandomSource; import net.minecraft.world.item.Item; @@ -55,9 +54,11 @@ public class ItemUtil { public static final UUID MODIFIER_UUID_GENERIC_ATTACK_SPEED = UUID.fromString( "FA233E1C-4180-4865-B01B-BCCE9785ACA3"); - private static RandomSource randomSource = RandomSource.create(); - public static void damage_item(final Player player, final ItemStack item_stack, final int amount) { + if (player.getGameMode() == GameMode.CREATIVE) { // don't damage the tool if the player is in creative + return; + } + if (amount <= 0) { return; } @@ -66,8 +67,9 @@ public static void damage_item(final Player player, final ItemStack item_stack, if (handle == null) { return; } - - handle.hurtAndBreak(amount, randomSource, player_handle(player), () -> {}); + RandomSource random = Nms.world_handle(player.getWorld()).getRandom(); + + handle.hurtAndBreak(amount, random, player_handle(player), () -> { player.broadcastSlotBreak(EquipmentSlot.HAND); item_stack.subtract(); }); } public static String name_of(final ItemStack item) {