diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/interaction/ItemSkin.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/interaction/ItemSkin.java index f5ce9df4..41994b0f 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/interaction/ItemSkin.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/interaction/ItemSkin.java @@ -106,7 +106,9 @@ public class ItemSkin extends UseItem { * in the target item so that if deskined, it can be retrieved * and given back to the player. */ - public static final String SKIN_ID_TAG = "MMOITEMS_SKIN_ID"; + public static final String + SKIN_ID_TAG = "MMOITEMS_SKIN_ID", + SKIN_TYPE_TAG = "MMOITEMS_SKIN_TYPE"; /** * Applies the skin information from a skin consumable onto any item. @@ -126,9 +128,10 @@ public class ItemSkin extends UseItem { final NBTItem nbtSkin = volSkin.getNBT(); // Apply skin ID to new item - @Nullable String appliedSkinId = volSkin.getNBT().getString(SKIN_ID_TAG); - appliedSkinId = MMOUtils.isNonEmpty(appliedSkinId) ? appliedSkinId : nbtSkin.getString("MMOITEMS_ITEM_ID"); + final String appliedSkinId = MMOUtils.requireNonEmptyElse(volSkin.getNBT().getString(SKIN_ID_TAG), nbtSkin.getString("MMOITEMS_ITEM_ID")); + final String appliedTypeId = MMOUtils.requireNonEmptyElse(volSkin.getNBT().getString(SKIN_TYPE_TAG), nbtSkin.getString("MMOITEMS_ITEM_TYPE")); target.addTag(new ItemTag(SKIN_ID_TAG, appliedSkinId)); + target.addTag(new ItemTag(SKIN_TYPE_TAG, appliedTypeId)); // Custom model data if (nbtSkin.getInteger("CustomModelData") != 0) diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/CanDeskin.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/CanDeskin.java index 9fd0f681..ae005dc6 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/CanDeskin.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/CanDeskin.java @@ -4,13 +4,13 @@ import com.google.gson.JsonObject; import io.lumine.mythic.lib.api.item.ItemTag; import io.lumine.mythic.lib.api.item.NBTItem; import io.lumine.mythic.lib.api.util.SmartGive; +import io.lumine.mythic.lib.util.annotation.BackwardsCompatibility; import io.lumine.mythic.lib.version.VersionMaterial; import net.Indyuce.mmoitems.ItemStats; import net.Indyuce.mmoitems.MMOItems; -import net.Indyuce.mmoitems.api.interaction.ItemSkin; -import net.Indyuce.mmoitems.util.MMOUtils; import net.Indyuce.mmoitems.api.Type; import net.Indyuce.mmoitems.api.interaction.Consumable; +import net.Indyuce.mmoitems.api.interaction.ItemSkin; import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem; import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate; import net.Indyuce.mmoitems.api.player.PlayerData; @@ -19,6 +19,8 @@ import net.Indyuce.mmoitems.stat.data.ParticleData; import net.Indyuce.mmoitems.stat.data.SkullTextureData; import net.Indyuce.mmoitems.stat.type.BooleanStat; import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction; +import net.Indyuce.mmoitems.util.MMOUtils; +import org.apache.commons.lang3.Validate; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -30,77 +32,94 @@ import org.bukkit.inventory.meta.LeatherArmorMeta; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; +import java.util.Objects; +import java.util.logging.Level; public class CanDeskin extends BooleanStat implements ConsumableItemInteraction { - public CanDeskin() { - super("CAN_DESKIN", Material.LEATHER, "Can Deskin?", - new String[] { "Players can deskin their item", "and get their skin back", "from the item." }, new String[] { "consumable" }); - } + public CanDeskin() { + super("CAN_DESKIN", Material.LEATHER, "Can Deskin?", + new String[]{"Players can deskin their item", "and get their skin back", "from the item."}, new String[]{"consumable"}); + } - @Override - public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) { - final String skinId = target.getString(ItemSkin.SKIN_ID_TAG); - Player player = playerData.getPlayer(); + @Override + public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) { + final String skinId = target.getString(ItemSkin.SKIN_ID_TAG); + Player player = playerData.getPlayer(); - if (consumable.getNBTItem().getBoolean("MMOITEMS_CAN_DESKIN") && !skinId.isEmpty()) { + if (consumable.getNBTItem().getBoolean("MMOITEMS_CAN_DESKIN") && !skinId.isEmpty()) { - // Set target item to default skin - String targetItemId = target.getString("MMOITEMS_ITEM_ID"); - target.removeTag(ItemSkin.SKIN_ID_TAG); + // Set target item to default skin + String targetItemId = target.getString("MMOITEMS_ITEM_ID"); + target.removeTag(ItemSkin.SKIN_ID_TAG); - MMOItemTemplate targetTemplate = MMOItems.plugin.getTemplates().getTemplateOrThrow(targetType, targetItemId); - MMOItem originalMmoitem = targetTemplate.newBuilder(playerData.getRPG()).build(); - ItemStack originalItem = targetTemplate.newBuilder(playerData.getRPG()).build().newBuilder().build(); + MMOItemTemplate targetTemplate = MMOItems.plugin.getTemplates().getTemplateOrThrow(targetType, targetItemId); + MMOItem originalMmoitem = targetTemplate.newBuilder(playerData.getRPG()).build(); + ItemStack originalItem = targetTemplate.newBuilder(playerData.getRPG()).build().newBuilder().build(); - int originalCustomModelData = originalItem.getItemMeta().hasCustomModelData() ? originalItem.getItemMeta().getCustomModelData() : -1; - if (originalCustomModelData != -1) - target.addTag(new ItemTag("CustomModelData", originalCustomModelData)); - else - target.removeTag("CustomModelData"); + int originalCustomModelData = originalItem.getItemMeta().hasCustomModelData() ? originalItem.getItemMeta().getCustomModelData() : -1; + if (originalCustomModelData != -1) + target.addTag(new ItemTag("CustomModelData", originalCustomModelData)); + else + target.removeTag("CustomModelData"); - if (originalMmoitem.hasData(ItemStats.ITEM_PARTICLES)) { - JsonObject itemParticles = ((ParticleData) originalMmoitem.getData(ItemStats.ITEM_PARTICLES)).toJson(); - target.addTag(new ItemTag("MMOITEMS_ITEM_PARTICLES", itemParticles.toString())); - } else - target.removeTag("MMOITEMS_ITEM_PARTICLES"); + if (originalMmoitem.hasData(ItemStats.ITEM_PARTICLES)) { + JsonObject itemParticles = ((ParticleData) originalMmoitem.getData(ItemStats.ITEM_PARTICLES)).toJson(); + target.addTag(new ItemTag("MMOITEMS_ITEM_PARTICLES", itemParticles.toString())); + } else + target.removeTag("MMOITEMS_ITEM_PARTICLES"); - ItemStack targetItem = target.toItem(); - ItemMeta targetItemMeta = targetItem.getItemMeta(); - ItemMeta originalItemMeta = originalItem.getItemMeta(); + ItemStack targetItem = target.toItem(); + ItemMeta targetItemMeta = targetItem.getItemMeta(); + ItemMeta originalItemMeta = originalItem.getItemMeta(); - if (targetItemMeta.isUnbreakable()) { - targetItemMeta.setUnbreakable(originalItemMeta.isUnbreakable()); - if (targetItemMeta instanceof Damageable && originalItemMeta instanceof Damageable) - ((Damageable) targetItemMeta).setDamage(((Damageable) originalItemMeta).getDamage()); - } + if (targetItemMeta.isUnbreakable()) { + targetItemMeta.setUnbreakable(originalItemMeta.isUnbreakable()); + if (targetItemMeta instanceof Damageable && originalItemMeta instanceof Damageable) + ((Damageable) targetItemMeta).setDamage(((Damageable) originalItemMeta).getDamage()); + } - if (targetItemMeta instanceof LeatherArmorMeta && originalItemMeta instanceof LeatherArmorMeta) - ((LeatherArmorMeta) targetItemMeta).setColor(((LeatherArmorMeta) originalItemMeta).getColor()); + if (targetItemMeta instanceof LeatherArmorMeta && originalItemMeta instanceof LeatherArmorMeta) + ((LeatherArmorMeta) targetItemMeta).setColor(((LeatherArmorMeta) originalItemMeta).getColor()); - if (target.hasTag("SkullOwner") && (targetItem.getType() == VersionMaterial.PLAYER_HEAD.toMaterial()) - && (originalItem.getType() == VersionMaterial.PLAYER_HEAD.toMaterial())) { - try { - Field profileField = targetItemMeta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profileField.set(targetItemMeta, ((SkullTextureData) originalMmoitem.getData(ItemStats.SKULL_TEXTURE)).getGameProfile()); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { - MMOItems.plugin.getLogger().warning("Could not read skull texture"); - } - } + if (target.hasTag("SkullOwner") && (targetItem.getType() == VersionMaterial.PLAYER_HEAD.toMaterial()) + && (originalItem.getType() == VersionMaterial.PLAYER_HEAD.toMaterial())) { + try { + Field profileField = targetItemMeta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profileField.set(targetItemMeta, ((SkullTextureData) originalMmoitem.getData(ItemStats.SKULL_TEXTURE)).getGameProfile()); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { + MMOItems.plugin.getLogger().warning("Could not read skull texture"); + } + } - // Update deskined item - final ItemStack updated = target.getItem(); - updated.setItemMeta(targetItemMeta); - updated.setType(originalItem.getType()); + // Update deskined item + final ItemStack updated = target.getItem(); + updated.setItemMeta(targetItemMeta); + updated.setType(originalItem.getType()); - // Give back skin item - MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplateOrThrow(Type.SKIN, skinId); - MMOItem mmoitem = template.newBuilder(playerData.getRPG()).build(); - new SmartGive(player).give(mmoitem.newBuilder().build()); + // Give back the skin item + try { - Message.SKIN_REMOVED.format(ChatColor.YELLOW, "#item#", MMOUtils.getDisplayName(targetItem)).send(player); - return true; - } - return false; - } + // Try to find the skin item. + // This is for backwards compatibility as cases with SKIN subtypes were not handled + // in the past, inducing an unfixable data loss for item skins applied onto items + @BackwardsCompatibility(version = "unknown") final String skinTypeId = target.getString(ItemSkin.SKIN_TYPE_TAG); + final Type type = Objects.requireNonNullElse(Type.get(skinTypeId), Type.SKIN); + Validate.notNull(type, "Could not find item type of applied skin"); + final MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplateOrThrow(Type.SKIN, skinId); + Validate.notNull(template, "Could not find item template of applied skin"); + + // Item found, giving it to the player + final MMOItem mmoitem = template.newBuilder(playerData.getRPG()).build(); + new SmartGive(player).give(mmoitem.newBuilder().build()); + } catch (Exception exception) { + MMOItems.plugin.getLogger().log(Level.INFO, "Could not retrieve item skin with ID '" + skinId + "' for player " + playerData.getUniqueId()); + // No luck :( + } + + Message.SKIN_REMOVED.format(ChatColor.YELLOW, "#item#", MMOUtils.getDisplayName(targetItem)).send(player); + return true; + } + return false; + } } diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/data/StoredTagsData.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/data/StoredTagsData.java index dfac9786..3c319ddb 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/data/StoredTagsData.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/data/StoredTagsData.java @@ -2,6 +2,7 @@ package net.Indyuce.mmoitems.stat.data; import io.lumine.mythic.lib.api.item.ItemTag; import io.lumine.mythic.lib.api.item.NBTItem; +import net.Indyuce.mmoitems.api.interaction.ItemSkin; import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder; import net.Indyuce.mmoitems.stat.data.type.Mergeable; import net.Indyuce.mmoitems.stat.data.type.StatData; @@ -12,123 +13,144 @@ import java.util.Arrays; import java.util.List; public class StoredTagsData implements StatData, Mergeable { - private final List tags = new ArrayList<>(); + private final List tags = new ArrayList<>(); - private static final List IGNORED_TAGS = Arrays.asList( - "Unbreakable", "BlockEntityTag", "display", "Enchantments", "HideFlags", "Damage", - "AttributeModifiers", "SkullOwner", "CanDestroy", "PickupDelay", "Age"); + private static final List IGNORED_TAGS = Arrays.asList( + "Unbreakable", "BlockEntityTag", "display", "Enchantments", "HideFlags", "Damage", + "AttributeModifiers", "SkullOwner", "CanDestroy", "PickupDelay", "Age"); - @Deprecated - private static final List SAVED_MMOITEMS_TAGS = Arrays.asList( - "MMOITEMS_SKIN_ID"); + /** + * TODO Make this list publically accessible to other plugins + */ + @Deprecated + private static final List SAVED_MMOITEMS_TAGS = Arrays.asList( + ItemSkin.SKIN_ID_TAG, + ItemSkin.SKIN_TYPE_TAG); - @Deprecated - public StoredTagsData(ItemStack stack) { - this(NBTItem.get(stack)); - } + @Deprecated + public StoredTagsData(ItemStack stack) { + this(NBTItem.get(stack)); + } - public StoredTagsData(List tgs) { tags.addAll(tgs); } + public StoredTagsData(List tgs) { + tags.addAll(tgs); + } - public StoredTagsData(NBTItem nbt) { - for (String tag : nbt.getTags()) { + public StoredTagsData(NBTItem nbt) { + for (String tag : nbt.getTags()) { - // Usually ignore mmoitems - if (tag.startsWith("MMOITEMS_") && !SAVED_MMOITEMS_TAGS.contains(tag)) + // Usually ignore mmoitems + if (tag.startsWith("MMOITEMS_") && !SAVED_MMOITEMS_TAGS.contains(tag)) - // Must be handled by its respective stat. - continue; + // Must be handled by its respective stat. + continue; - // Any vanilla or MMOItem tag should be ignored as those are - // automatically handled. Same for the History stat ones. - if (IGNORED_TAGS.contains(tag) || tag.startsWith(ItemStackBuilder.history_keyword)) - continue; + // Any vanilla or MMOItem tag should be ignored as those are + // automatically handled. Same for the History stat ones. + if (IGNORED_TAGS.contains(tag) || tag.startsWith(ItemStackBuilder.history_keyword)) + continue; - // As more methods are added we can add more types here - switch (getTagType(nbt.getTypeId(tag))) { - case "double": - tags.add(new ItemTag(tag, nbt.getDouble(tag))); - break; - case "int": - tags.add(new ItemTag(tag, nbt.getInteger(tag))); - break; - case "byte": - tags.add(new ItemTag(tag, nbt.getBoolean(tag))); - break; - case "string": - tags.add(new ItemTag(tag, nbt.getString(tag))); - break; - // default: - // tags.add(new ItemTag(tag, "UNSUPPORTED TAG TYPE!")); - } - } - } - - public void addTag(ItemTag tag) { - tags.add(tag); - } + // As more methods are added we can add more types here + switch (getTagType(nbt.getTypeId(tag))) { + case "double": + tags.add(new ItemTag(tag, nbt.getDouble(tag))); + break; + case "int": + tags.add(new ItemTag(tag, nbt.getInteger(tag))); + break; + case "byte": + tags.add(new ItemTag(tag, nbt.getBoolean(tag))); + break; + case "string": + tags.add(new ItemTag(tag, nbt.getString(tag))); + break; + // default: + // tags.add(new ItemTag(tag, "UNSUPPORTED TAG TYPE!")); + } + } + } - public List getTags() { - return tags; - } + public void addTag(ItemTag tag) { + tags.add(tag); + } - private String getTagType(int id) { - switch (id) { - case 0: - return "end"; - case 1: - return "byte"; - case 2: - return "short"; - case 3: - return "int"; - case 4: - return "long"; - case 5: - return "float"; - case 6: - return "double"; - case 7: - return "bytearray"; - case 8: - return "string"; - case 9: - return "list"; - case 10: - return "compound"; - case 11: - return "intarray"; - default: - return "unknown"; - } - } + public List getTags() { + return tags; + } - @Override - public void merge(StoredTagsData data) { - tags.addAll(data.tags); - } + private String getTagType(int id) { + switch (id) { + case 0: + return "end"; + case 1: + return "byte"; + case 2: + return "short"; + case 3: + return "int"; + case 4: + return "long"; + case 5: + return "float"; + case 6: + return "double"; + case 7: + return "bytearray"; + case 8: + return "string"; + case 9: + return "list"; + case 10: + return "compound"; + case 11: + return "intarray"; + default: + return "unknown"; + } + } - @Override - public StoredTagsData cloneData() { return new StoredTagsData(getTags()); } + @Override + public void merge(StoredTagsData data) { + tags.addAll(data.tags); + } - @Override - public boolean isEmpty() { - return tags.isEmpty(); - } + @Override + public StoredTagsData cloneData() { + return new StoredTagsData(getTags()); + } - @Override - public boolean equals(Object obj) { - if (!(obj instanceof StoredTagsData)) { return false; } + @Override + public boolean isEmpty() { + return tags.isEmpty(); + } - if (((StoredTagsData) obj).getTags().size() != getTags().size()) { return false; } + @Override + public boolean equals(Object obj) { + if (!(obj instanceof StoredTagsData)) { + return false; + } - for (ItemTag tag : ((StoredTagsData) obj).getTags()) { + if (((StoredTagsData) obj).getTags().size() != getTags().size()) { + return false; + } - if (tag == null) { continue; } + for (ItemTag tag : ((StoredTagsData) obj).getTags()) { - boolean unmatched = true; - for (ItemTag tg : getTags()) { - if (tag.equals(tg)) { unmatched = false; break; } } - if (unmatched) { return false; } } - return true; - } + if (tag == null) { + continue; + } + + boolean unmatched = true; + for (ItemTag tg : getTags()) { + if (tag.equals(tg)) { + unmatched = false; + break; + } + } + if (unmatched) { + return false; + } + } + return true; + } } \ No newline at end of file diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/util/MMOUtils.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/util/MMOUtils.java index a216e26f..976cb881 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/util/MMOUtils.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/util/MMOUtils.java @@ -57,6 +57,11 @@ public class MMOUtils { return str != null && !str.isEmpty(); } + @NotNull + public static String requireNonEmptyElse(@Nullable String str, @NotNull String fallback) { + return isNonEmpty(str) ? str : Objects.requireNonNull(fallback); + } + /** * @return The skull texture URL from a given player head */ @@ -87,8 +92,8 @@ public class MMOUtils { * - for item upgrading TODO * - item repairing * - * @param ref1 First reference - * @param ref2 Second reference + * @param ref1 First reference + * @param ref2 Second reference * @return If items can interact */ public static boolean checkReference(@Nullable String ref1, @Nullable String ref2) {