Fixed subtypes of SKIN

This commit is contained in:
Jules 2023-09-18 21:58:41 +02:00
parent 8f20edde22
commit dd9cdc34bc
4 changed files with 214 additions and 165 deletions

View File

@ -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)

View File

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

View File

@ -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<StoredTagsData> {
private final List<ItemTag> tags = new ArrayList<>();
private final List<ItemTag> tags = new ArrayList<>();
private static final List<String> IGNORED_TAGS = Arrays.asList(
"Unbreakable", "BlockEntityTag", "display", "Enchantments", "HideFlags", "Damage",
"AttributeModifiers", "SkullOwner", "CanDestroy", "PickupDelay", "Age");
private static final List<String> IGNORED_TAGS = Arrays.asList(
"Unbreakable", "BlockEntityTag", "display", "Enchantments", "HideFlags", "Damage",
"AttributeModifiers", "SkullOwner", "CanDestroy", "PickupDelay", "Age");
@Deprecated
private static final List<String> SAVED_MMOITEMS_TAGS = Arrays.asList(
"MMOITEMS_SKIN_ID");
/**
* TODO Make this list publically accessible to other plugins
*/
@Deprecated
private static final List<String> 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<ItemTag> tgs) { tags.addAll(tgs); }
public StoredTagsData(List<ItemTag> 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<ItemTag> 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<ItemTag> 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;
}
}

View File

@ -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) {