diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 4b9418c5636..1383af15595 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -97,6 +97,7 @@ import org.skriptlang.skript.bukkit.breeding.BreedingModule; import org.skriptlang.skript.bukkit.displays.DisplayModule; import org.skriptlang.skript.bukkit.input.InputModule; +import org.skriptlang.skript.bukkit.equippablecomponents.EquippableComponentModule; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.converter.Converter; @@ -537,6 +538,8 @@ public void onEnable() { BreedingModule.load(); DisplayModule.load(); InputModule.load(); + BreedingModule.load(); + EquippableComponentModule.load(); } catch (final Exception e) { exception(e, "Could not load required .class files: " + e.getLocalizedMessage()); setEnabled(false); diff --git a/src/main/java/ch/njol/skript/bukkitutil/NamespacedUtils.java b/src/main/java/ch/njol/skript/bukkitutil/NamespacedUtils.java new file mode 100644 index 00000000000..5df97725e2a --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/NamespacedUtils.java @@ -0,0 +1,111 @@ +package ch.njol.skript.bukkitutil; + +import ch.njol.skript.Skript; +import ch.njol.util.Pair; +import com.google.common.collect.Sets; +import org.apache.commons.lang.ArrayUtils; +import org.bukkit.NamespacedKey; + +import java.util.Set; + +public class NamespacedUtils { + + private static final Set LEGAL_NAMESPACE_CHARS = Sets.newHashSet(ArrayUtils.toObject("abcdefghijklmnopqrstuvwxyz0123456789._-/".toCharArray())); + + /** + * Gets a namespaced key. This method will try to get existing keys first, but if that fails + * it will create the key in Skript's namespace. + * + * @param key the unparsed key + * @return the resulting NamespacedKey + */ + public static NamespacedKey getNamespacedKey(String key) { + NamespacedKey namespacedKey = NamespacedKey.fromString(key, Skript.getInstance()); + if (namespacedKey != null) + return namespacedKey; + + return createNamespacedKey(key); + } + + /** + * Creates a namespaced key in Skript's namespace encoded to avoid the character limitations of a normal key. + * This key will be created in Skript's namespace. + * + * @param key The key to use + * @return a NamespacedKey with the encoded key in corresponding Namespace + */ + public static NamespacedKey createNamespacedKey(String key) { + StringBuilder encodedKeyBuilder = new StringBuilder(); + // keys must be all lowercase + key = key.toLowerCase().replace(' ', '_'); + int keyLength = key.length(); + for (int i = 0; i < keyLength; i++) { + char currentChar = key.charAt(i); + // if this character is legal to use in a namespace key + if (LEGAL_NAMESPACE_CHARS.contains(currentChar)) { + // if the original string had a ".x" in it, we need to escape it + // so decoding doesn't think it's a hex sequence + if (currentChar == '.' && key.charAt(i + 1) == 'x') { + i += 1; // skip the "x" + encodedKeyBuilder.append(".x"); + encodedKeyBuilder.append(Integer.toHexString('.')); + encodedKeyBuilder.append(".x"); + encodedKeyBuilder.append(Integer.toHexString('x')); + // if we're not at the end and the next char is a legal char, add the trailing dot + // to represent the end of the hex sequence + if (i != (keyLength - 1) && LEGAL_NAMESPACE_CHARS.contains(key.charAt(i + 1))) + encodedKeyBuilder.append('.'); + } else { + // we are dealing with a legal character, so we can just append it + encodedKeyBuilder.append(currentChar); + } + } else { + // add ".x(hex code)" to the encoded key + encodedKeyBuilder.append(".x"); + encodedKeyBuilder.append(Integer.toHexString(currentChar)); + // only add the trailing dot if the next character is a legal character + if (i != (keyLength - 1) && LEGAL_NAMESPACE_CHARS.contains(key.charAt(i + 1))) + encodedKeyBuilder.append('.'); + } + } + return NamespacedKey.fromString(encodedKeyBuilder.toString(), Skript.getInstance()); + } + + /** + * Decodes a NamespacedKey encoded by #getNamespacedKey + * + * @param namespacedKey the namespaced key to decode + * @return a Pair with the first element as the namespace and the second as the decoded key + */ + public static Pair decodeNamespacedKey(NamespacedKey namespacedKey) { + String encodedKey = namespacedKey.getKey(); + StringBuilder decodedKeyBuilder = new StringBuilder(); + int encodedKeyLength = encodedKey.length(); + int lastCharIndex = encodedKeyLength - 1; + for (int i = 0; i < encodedKeyLength; i++) { + char currentChar = encodedKey.charAt(i); + // if we are encountering a ".x" hex sequence + if (i != lastCharIndex && currentChar == '.' && encodedKey.charAt(i + 1) == 'x') { + i += 2; // skip the ".x" so it isn't part of our hex string + StringBuilder hexString = new StringBuilder(); + // The hex sequence continues until a . is encountered or we reach the end of the string + while (i <= lastCharIndex && encodedKey.charAt(i) != '.') { + hexString.append(encodedKey.charAt(i)); + i++; + } + // if the . was the start of another ".x" hex sequence, back up by 1 character + if (i <= lastCharIndex && encodedKey.charAt(i + 1) == 'x') + i--; + // parse the hex sequence to a char + char decodedChar = (char) Long.parseLong(hexString.toString(), 16); + decodedKeyBuilder.append(decodedChar); + } else { + // this is just a normal character, not a hex sequence + // so we can just append it + decodedKeyBuilder.append(currentChar); + } + } + return new Pair<>(namespacedKey.getNamespace(), decodedKeyBuilder.toString()); + } + +} \ No newline at end of file diff --git a/src/main/java/ch/njol/skript/bukkitutil/SoundUtils.java b/src/main/java/ch/njol/skript/bukkitutil/SoundUtils.java index 36ad5136d9a..19f08ca6785 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/SoundUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/SoundUtils.java @@ -2,6 +2,7 @@ import org.bukkit.Keyed; import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.Sound; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,17 +19,15 @@ public final class SoundUtils { /** * Gets the key of a sound, given its enum-name-style name. + * If no sound is found, will create a new namespaced key from the given soundString. * @param soundString The enum name to use to find the sound. * @return The key of the sound. */ public static @Nullable NamespacedKey getKey(String soundString) { soundString = soundString.toUpperCase(Locale.ENGLISH); if (SOUND_IS_INTERFACE) { - try { - //noinspection deprecation - return Sound.valueOf(soundString).getKey(); - } catch (IllegalArgumentException ignore) { - } + //noinspection deprecation + return Sound.valueOf(soundString).getKey(); } else { try { //noinspection unchecked,rawtypes @@ -37,7 +36,7 @@ public final class SoundUtils { } catch (IllegalArgumentException ignore) { } } - return null; + return NamespacedKey.fromString(soundString.toLowerCase(Locale.ENGLISH)); } /** @@ -54,4 +53,16 @@ public final class SoundUtils { } } + /** + * returns the sound of a given string. + * @param soundString The sound string to get the sound + * @return The sound if found + */ + public static Sound getSound(String soundString) { + NamespacedKey key = getKey(soundString); + if (key == null) + return null; + return Registry.SOUNDS.get(key); + } + } diff --git a/src/main/java/ch/njol/skript/effects/EffPlaySound.java b/src/main/java/ch/njol/skript/effects/EffPlaySound.java index c67a86adde3..99ef291d322 100644 --- a/src/main/java/ch/njol/skript/effects/EffPlaySound.java +++ b/src/main/java/ch/njol/skript/effects/EffPlaySound.java @@ -3,11 +3,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.bukkitutil.SoundUtils; import ch.njol.skript.bukkitutil.sounds.SoundReceiver; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; -import ch.njol.skript.doc.Since; +import ch.njol.skript.doc.*; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -23,10 +19,7 @@ import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.OptionalLong; -import java.util.regex.Matcher; -import java.util.regex.Pattern; @Name("Play Sound") @Description({ @@ -73,8 +66,6 @@ public class EffPlaySound extends Effect { private static final boolean ENTITY_EMITTER_SOUND = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class); private static final boolean ENTITY_EMITTER_STRING = Skript.methodExists(Player.class, "playSound", Entity.class, String.class, SoundCategory.class, float.class, float.class); private static final boolean ENTITY_EMITTER = ENTITY_EMITTER_SOUND || ENTITY_EMITTER_STRING; - - public static final Pattern KEY_PATTERN = Pattern.compile("([a-z0-9._-]+:)?([a-z0-9/._-]+)"); static { String seedOption = HAS_SEED ? "[[with] seed %-number%] " : ""; @@ -145,25 +136,6 @@ protected void execute(Event event) { List validSounds = new ArrayList<>(); for (String sound : sounds.getArray(event)) { NamespacedKey key = SoundUtils.getKey(sound); - if (key == null) { - sound = sound.toLowerCase(Locale.ENGLISH); - Matcher keyMatcher = KEY_PATTERN.matcher(sound); - if (!keyMatcher.matches()) - continue; - try { - String namespace = keyMatcher.group(1); - String keyValue = keyMatcher.group(2); - if (namespace == null) { - key = NamespacedKey.minecraft(keyValue); - } else { - namespace = namespace.substring(0, namespace.length() - 1); - key = new NamespacedKey(namespace, keyValue); - } - } catch (IllegalArgumentException argument) { - // The user input invalid characters - } - } - if (key == null) continue; validSounds.add(key); diff --git a/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java b/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java index d20628ce6d2..4e3c65231c4 100644 --- a/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java +++ b/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java @@ -48,6 +48,11 @@ public ItemStack get(final EntityEquipment e) { public void set(final EntityEquipment e, final @Nullable ItemStack item) { e.setItemInMainHand(item); } + + @Override + public org.bukkit.inventory.EquipmentSlot getBukkitEquipSlot() { + return org.bukkit.inventory.EquipmentSlot.HAND; + } }, OFF_HAND(40) { @@ -61,6 +66,11 @@ public ItemStack get(EntityEquipment e) { public void set(EntityEquipment e, @Nullable ItemStack item) { e.setItemInOffHand(item); } + + @Override + public org.bukkit.inventory.EquipmentSlot getBukkitEquipSlot() { + return org.bukkit.inventory.EquipmentSlot.OFF_HAND; + } }, HELMET(39) { @@ -74,6 +84,11 @@ public ItemStack get(final EntityEquipment e) { public void set(final EntityEquipment e, final @Nullable ItemStack item) { e.setHelmet(item); } + + @Override + public org.bukkit.inventory.EquipmentSlot getBukkitEquipSlot() { + return org.bukkit.inventory.EquipmentSlot.HEAD; + } }, CHESTPLATE(38) { @Override @@ -86,6 +101,11 @@ public ItemStack get(final EntityEquipment e) { public void set(final EntityEquipment e, final @Nullable ItemStack item) { e.setChestplate(item); } + + @Override + public org.bukkit.inventory.EquipmentSlot getBukkitEquipSlot() { + return org.bukkit.inventory.EquipmentSlot.CHEST; + } }, LEGGINGS(37) { @Override @@ -98,6 +118,11 @@ public ItemStack get(final EntityEquipment e) { public void set(final EntityEquipment e, final @Nullable ItemStack item) { e.setLeggings(item); } + + @Override + public org.bukkit.inventory.EquipmentSlot getBukkitEquipSlot() { + return org.bukkit.inventory.EquipmentSlot.LEGS; + } }, BOOTS(36) { @Override @@ -110,6 +135,11 @@ public ItemStack get(final EntityEquipment e) { public void set(final EntityEquipment e, final @Nullable ItemStack item) { e.setBoots(item); } + + @Override + public org.bukkit.inventory.EquipmentSlot getBukkitEquipSlot() { + return org.bukkit.inventory.EquipmentSlot.FEET; + } }; public final int slotNumber; @@ -126,6 +156,8 @@ public void set(final EntityEquipment e, final @Nullable ItemStack item) { public abstract ItemStack get(EntityEquipment e); public abstract void set(EntityEquipment e, @Nullable ItemStack item); + + public abstract org.bukkit.inventory.EquipmentSlot getBukkitEquipSlot(); } @@ -204,6 +236,18 @@ public int getIndex() { return slotIndex != -1 ? slotIndex : slot.slotNumber; } + public static EquipSlot convertToSkriptEquipSlot(org.bukkit.inventory.EquipmentSlot bukkitSlot) { + return switch (bukkitSlot) { + case HEAD -> EquipSlot.HELMET; + case CHEST -> EquipSlot.CHESTPLATE; + case LEGS -> EquipSlot.LEGGINGS; + case FEET -> EquipSlot.BOOTS; + case HAND -> EquipSlot.TOOL; + case OFF_HAND -> EquipSlot.OFF_HAND; + default -> null; + }; + } + @Override public String toString(@Nullable Event event, boolean debug) { if (slotToString) // Slot to string diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/EquippableComponentModule.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/EquippableComponentModule.java new file mode 100644 index 00000000000..b2a2ea79aa2 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/EquippableComponentModule.java @@ -0,0 +1,47 @@ +package org.skriptlang.skript.bukkit.equippablecomponents; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.EnumClassInfo; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.slot.EquipmentSlot.EquipSlot; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.skriptlang.skript.lang.comparator.Comparator; +import org.skriptlang.skript.lang.comparator.Comparators; +import org.skriptlang.skript.lang.comparator.Relation; + +import java.io.IOException; + +public class EquippableComponentModule { + + public static void load() throws IOException { + if (!Skript.classExists("org.bukkit.inventory.meta.components.EquippableComponent")) + return; + + Skript.getAddonInstance().loadClasses("org.skriptlang.skript.bukkit.equippablecomponents", "elements"); + + Classes.registerClass(new EnumClassInfo<>(EquipSlot.class, "equipmentslot", "equipment slot") + .user("equipment ?slots?") + .name("Equipment Slot") + .description("Represents an equipment slot") + .since("INSERT VERSION") + ); + + Classes.registerClass(new ClassInfo<>(EquippableComponent.class, "equippablecomponent") + .user("equippable ?components?") + .name("Equippable Components") + .description("Represents an equippable component used for items.") + .requiredPlugins("Minecraft 1.21.2+") + .since("INSERT VERSION") + ); + + Comparators.registerComparator(EquipSlot.class, EquipSlot.class, new Comparator() { + @Override + public Relation compare(EquipSlot slot1, EquipSlot slot2) { + return Relation.get(slot2.equals(slot1)); + } + }); + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/CondEquipCompDamage.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/CondEquipCompDamage.java new file mode 100644 index 00000000000..6d031ab2d0f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/CondEquipCompDamage.java @@ -0,0 +1,72 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component - Is Damageable") +@Description("Checks if the item can be damaged when the wearer gets injured.") +@Examples({ + "if {_item} is damageable:", + "\tadd \"Damageable\" to lore of {_item}", + "", + "set {_component} to the equippable component of {_item}", + "if {_component} is not damageable:", + "\tmake {_component} damageable" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class CondEquipCompDamage extends PropertyCondition { + + static { + Skript.registerCondition(CondEquipCompDamage.class, ConditionType.PROPERTY, + "[the] %itemstacks/itemtypes/slots/equippablecomponents% (is|are) [:un]damageable", + "[the] %itemstacks/itemtypes/slots/equippablecomponents% (isn't|is not|aren't|are not) [:un]damageable" + ); + } + + private Expression objects; + private boolean damageable; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + objects = exprs[0]; + damageable = !parseResult.hasTag("un"); + setNegated(matchedPattern == 1); + setExpr(objects); + return true; + } + + @Override + public boolean check(Object object) { + if (object instanceof EquippableComponent component) { + return component.isDamageOnHurt() == damageable; + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack != null) + return itemStack.getItemMeta().getEquippable().isDamageOnHurt() == damageable; + } + return isNegated(); + } + + @Override + protected String getPropertyName() { + return null; + } + + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the " + objects.toString(event, debug) + (isNegated() ? " are not " : " are ") + + (damageable ? "damageable" : "undamageable"); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/CondEquipCompDispensable.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/CondEquipCompDispensable.java new file mode 100644 index 00000000000..664a6b63a09 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/CondEquipCompDispensable.java @@ -0,0 +1,71 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component - Is Dispensable") +@Description("Checks if the item can be dispensed by a dispenser.") +@Examples({ + "if {_item} is dispensable:", + "\tadd \"Dispensable\" to lore of {_item}", + "", + "set {_component} to the equippable component of {_item}", + "if {_component} is not dispensable:", + "\tmake {_component} dispensable" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class CondEquipCompDispensable extends PropertyCondition { + + static { + Skript.registerCondition(CondEquipCompDispensable.class, ConditionType.PROPERTY, + "[the] %itemstacks/itemtypes/slots/equippablecomponents% (is|are) [:un]dispensable", + "[the] %itemstacks/itemtypes/slots/equippablecomponents% (isn't|is not|aren't|are not) [:un]dispensable" + ); + } + + private Expression objects; + private boolean dispensable; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objects = exprs[0]; + dispensable = !parseResult.hasTag("un"); + setNegated(matchedPattern == 1); + setExpr(objects); + return true; + } + + @Override + public boolean check(Object object) { + if (object instanceof EquippableComponent component) { + return component.isDispensable() == dispensable; + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack != null) + return itemStack.getItemMeta().getEquippable().isDispensable() == dispensable; + } + return isNegated(); + } + + @Override + protected String getPropertyName() { + return null; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the " + objects.toString(event, debug) + (isNegated() ? " are not " : " are ") + + (dispensable ? "dispensable" : "undispensable"); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/CondEquipCompSwappable.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/CondEquipCompSwappable.java new file mode 100644 index 00000000000..01038cd9fcc --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/CondEquipCompSwappable.java @@ -0,0 +1,71 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component - Is Swappable") +@Description("Checks if the item can be swapped by right clicking in it your hand.") +@Examples({ + "if {_item} is swappable:", + "\tadd \"Swappable\" to lore of {_item}", + "", + "set {_component} to the equippable component of {_item}", + "if {_component} is not swappable:", + "\tmake {_component} swappable" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class CondEquipCompSwappable extends PropertyCondition { + + static { + Skript.registerCondition(CondEquipCompSwappable.class, ConditionType.PROPERTY, + "[the] %itemstacks/itemtypes/slots/equippablecomponents% (is|are) [:un]swappable", + "[the] %itemstacks/itemtypes/slots/equippablecomponents% (isn't|is not|aren't|are not) [:un]swappable" + ); + } + + private Expression objects; + private boolean swappable; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objects = exprs[0]; + swappable = !parseResult.hasTag("un"); + setNegated(matchedPattern == 1); + setExpr(objects); + return true; + } + + @Override + public boolean check(Object object) { + if (object instanceof EquippableComponent component) { + return component.isSwappable() == swappable; + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack != null) + return itemStack.getItemMeta().getEquippable().isSwappable() == swappable; + } + return isNegated(); + } + + @Override + protected String getPropertyName() { + return null; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the " + objects.toString(event, debug) + (isNegated() ? " are not " : " are ") + + (swappable ? "swappable" : "unswappable"); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/EffEquipCompDamageable.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/EffEquipCompDamageable.java new file mode 100644 index 00000000000..2fe075ece9a --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/EffEquipCompDamageable.java @@ -0,0 +1,78 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component - Damageable") +@Description("If the item should take damage when the wearer gets injured.") +@Examples({ + "set {_item} to be damageable", + "", + "set {_component} to the equippable component of {_item}", + "make {_component} undamageable", + "set the equippable component of {_item} to {_component}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class EffEquipCompDamageable extends Effect { + + static { + Skript.registerEffect(EffEquipCompDamageable.class, + "(set|make) [the] %itemstacks/itemtypes/slots/equippablecomponents% [to [be]] damageable", + "(set|make) [the] %itemstacks/itemtypes/slots/equippablecomponents% [to [be]] undamageable" + ); + } + + private Expression objects; + private boolean damageable; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objects = exprs[0]; + damageable = matchedPattern == 0; + return true; + } + + @Override + protected void execute(Event event) { + for (Object object : objects.getArray(event)) { + if (object instanceof EquippableComponent component) { + component.setDamageOnHurt(damageable); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + ItemMeta meta = itemStack.getItemMeta(); + EquippableComponent component = meta.getEquippable(); + component.setDamageOnHurt(damageable); + meta.setEquippable(component); + itemStack.setItemMeta(meta); + if (object instanceof Slot slot) { + slot.setItem(itemStack); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(meta); + } else if (object instanceof ItemStack itemStack1) { + itemStack1.setItemMeta(meta); + } + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "set the " + objects.toString(event, debug) + " to be " + (damageable ? "damageable" : "undamageable"); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/EffEquipCompDispensable.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/EffEquipCompDispensable.java new file mode 100644 index 00000000000..1cbeeebf2f5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/EffEquipCompDispensable.java @@ -0,0 +1,78 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component - Dispensable") +@Description("If the item can be dispensed by a dispenser.") +@Examples({ + "set {_item} to be dispensable", + "", + "set {_component} to the equippable component of {_item}", + "make {_component} undispensable", + "set the equippable component of {_item} to {_component}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class EffEquipCompDispensable extends Effect { + + static { + Skript.registerEffect(EffEquipCompDispensable.class, + "(set|make) [the] %itemstacks/itemtypes/slots/equippablecomponents% [to [be]] dispensable", + "(set|make) [the] %itemstacks/itemtypes/slots/equippablecomponents% [to [be]] (un|in|non)dispensable" + ); + } + + private Expression objects; + private boolean dispensable; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objects = exprs[0]; + dispensable = matchedPattern == 0; + return true; + } + + @Override + protected void execute(Event event) { + for (Object object : objects.getArray(event)) { + if (object instanceof EquippableComponent component) { + component.setDispensable(dispensable); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + ItemMeta meta = itemStack.getItemMeta(); + EquippableComponent component = meta.getEquippable(); + component.setDispensable(dispensable); + meta.setEquippable(component); + itemStack.setItemMeta(meta); + if (object instanceof Slot slot) { + slot.setItem(itemStack); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(meta); + } else if (object instanceof ItemStack itemStack1) { + itemStack1.setItemMeta(meta); + } + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "set the " + objects.toString(event, debug) + " to be " + (dispensable ? "dispensable" : "undispensable"); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/EffEquipCompSwappable.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/EffEquipCompSwappable.java new file mode 100644 index 00000000000..739ab12ff26 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/EffEquipCompSwappable.java @@ -0,0 +1,77 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component - Swappable") +@Description("If the item can be swapped by right clicking it in your hand.") +@Examples({ + "set {_item} to be swappable", + "", + "set {_component} to the equippable component of {_item}", + "make {_component} unswappable", + "set the equippable component of {_item} to {_component}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class EffEquipCompSwappable extends Effect { + + static { + Skript.registerEffect(EffEquipCompSwappable.class, + "(set|make) [the] %itemstacks/itemtypes/slots/equippablecomponents% [to [be]] swappable", + "(set|make) [the] %itemstacks/itemtypes/slots/equippablecomponents% [to [be]] unswappable" + ); + } + + private Expression objects; + private boolean swappable; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objects = exprs[0]; + swappable = matchedPattern == 0; + return true; + } + + @Override + protected void execute(Event event) { + for (Object object : objects.getArray(event)) { + if (object instanceof EquippableComponent component) { + component.setSwappable(swappable); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + ItemMeta meta = itemStack.getItemMeta(); + EquippableComponent component = meta.getEquippable(); + component.setSwappable(swappable); + meta.setEquippable(component); + itemStack.setItemMeta(meta); + if (object instanceof Slot slot) { + slot.setItem(itemStack); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(meta); + } else if (object instanceof ItemStack itemStack1) { + itemStack1.setItemMeta(meta); + } + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "set the " + objects.toString(event, debug) + " to be " + (swappable ? "swappable" : "unswappable"); + } +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompCameraOverlay.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompCameraOverlay.java new file mode 100644 index 00000000000..11466308d04 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompCameraOverlay.java @@ -0,0 +1,116 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.bukkitutil.NamespacedUtils; +import ch.njol.skript.classes.Changer; +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component - Camera Overlay") +@Description({ + "The camera overlay for the player when the item is equipped.", + "Example: The jack-o'-lantern view when having a jack-o'-lantern equipped as a helmet." +}) +@Examples({ + "set the camera overlay of {_item} to \"custom_overlay\"", + "", + "set {_component} to the equippable component of {_item}", + "set the camera overlay of {_component} to \"custom_overlay\"", + "set the equippable component of {_item} to {_component}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class ExprEquipCompCameraOverlay extends PropertyExpression { + + static { + Skript.registerExpression(ExprEquipCompCameraOverlay.class, String.class, ExpressionType.PROPERTY, + "[the] camera overlay of %itemstacks/itemtypes/slots/equippablecomponents%" + ); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr(exprs[0]); + return true; + } + + @Override + protected String @Nullable [] get(Event event, Object[] source) { + return get(source, object -> { + if (object instanceof EquippableComponent component) { + return component.getCameraOverlay().toString(); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + return null; + return itemStack.getItemMeta().getEquippable().getCameraOverlay().toString(); + } + }); + } + + @Override + public Class @Nullable [] acceptChange(Changer.ChangeMode mode) { + if (mode == Changer.ChangeMode.SET || mode == Changer.ChangeMode.DELETE) + return CollectionUtils.array(String.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, Changer.ChangeMode mode) { + NamespacedKey key = null; + if (delta[0] != null && delta[0] instanceof String string) + key = NamespacedUtils.getNamespacedKey(string); + + for (Object object : getExpr().getArray(event)) { + if (object instanceof EquippableComponent component) { + component.setCameraOverlay(key); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + ItemMeta meta = itemStack.getItemMeta(); + EquippableComponent component = meta.getEquippable(); + component.setCameraOverlay(key); + meta.setEquippable(component); + itemStack.setItemMeta(meta); + if (object instanceof Slot slot) { + slot.setItem(itemStack); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(meta); + } else if (object instanceof ItemStack itemStack1) { + itemStack1.setItemMeta(meta); + } + } + } + } + + @Override + public boolean isSingle() { + return getExpr().isSingle(); + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the camera overlay of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompEntities.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompEntities.java new file mode 100644 index 00000000000..d2d8f3fa04d --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompEntities.java @@ -0,0 +1,154 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.EntityUtils; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.*; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +@SuppressWarnings("rawtypes") +@Name("Equippable Component - Allowed Entities") +@Description("The entities allowed to wear the item.") +@Examples({ + "set the allowed entities of {_item} to zombie and skeleton", + "", + "set {_component} to the equippable component of {_item}", + "clear the allowed entities of {_component}", + "set the equippable component of {_item} to {_component}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class ExprEquipCompEntities extends PropertyExpression { + + static { + Skript.registerExpression(ExprEquipCompEntities.class, EntityData.class, ExpressionType.PROPERTY, + "[the] allowed entities of %itemstacks/itemtypes/slots/equippablecomponents%" + ); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr(exprs[0]); + return true; + } + + @Override + protected EntityData @Nullable [] get(Event event, Object[] source) { + List types = new ArrayList<>(); + for (Object object : getExpr().getArray(event)) { + EquippableComponent component = null; + if (object instanceof EquippableComponent equippableComponent) { + component = equippableComponent; + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack != null) + component = itemStack.getItemMeta().getEquippable(); + } + if (component != null) { + component.getAllowedEntities().forEach(entityType -> { + Class clazz = entityType.getEntityClass(); + types.add(EntityData.fromClass(clazz)); + }); + } + } + return types.toArray(new EntityData[0]); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, REMOVE, ADD -> CollectionUtils.array(EntityData[].class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + + EntityData[] types = (EntityData[]) delta; + List converted = new ArrayList<>(); + if (types != null && types.length > 0) { + Arrays.stream(types).forEach(entityData -> { + converted.add(EntityUtils.toBukkitEntityType(entityData)); + }); + } + + Consumer changer = switch (mode) { + case SET -> component -> { + component.setAllowedEntities(converted); + }; + case ADD -> component -> { + List current = component.getAllowedEntities().stream().toList(); + current.addAll(converted); + component.setAllowedEntities(current); + }; + case REMOVE -> component -> { + List current = component.getAllowedEntities().stream().toList(); + current.removeAll(converted); + component.setAllowedEntities(current); + }; + case DELETE -> component -> { + component.setAllowedEntities(new ArrayList<>()); + }; + default -> throw new IllegalStateException("Unexpected value: " + mode); + }; + + for (Object object : getExpr().getArray(event)) { + if (object instanceof EquippableComponent component) { + changer.accept(component); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + ItemMeta meta = itemStack.getItemMeta(); + EquippableComponent component = meta.getEquippable(); + changer.accept(component); + meta.setEquippable(component); + itemStack.setItemMeta(meta); + if (object instanceof Slot slot) { + slot.setItem(itemStack); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(meta); + } else if (object instanceof ItemStack itemStack1) { + itemStack1.setItemMeta(meta); + } + } + } + } + + @Override + public Class getReturnType() { + return EntityData.class; + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the allowed entities of " + getExpr().toString(event, debug); + } +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompModel.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompModel.java new file mode 100644 index 00000000000..e654fe86f99 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompModel.java @@ -0,0 +1,113 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.bukkitutil.NamespacedUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component - Model") +@Description("The model of the item when equipped.") +@Examples({ + "set the model key of {_item} to \"custom_model\"", + "", + "set {_component} to the equippable component of {_item}", + "set the model id of {_component} to \"custom_model\"", + "set the equippable component of {_item} to {_component}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class ExprEquipCompModel extends PropertyExpression { + + static { + Skript.registerExpression(ExprEquipCompModel.class, String.class, ExpressionType.PROPERTY, + "[the] model (key|id) of %itemstacks/itemtypes/slots/equippablecomponents%" + ); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr(exprs[0]); + return true; + } + + @Override + protected String @Nullable [] get(Event event, Object[] source) { + return get(source, object -> { + if (object instanceof EquippableComponent component) { + return component.getModel().toString(); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + return null; + return itemStack.getItemMeta().getEquippable().getModel().toString(); + } + }); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) + return CollectionUtils.array(String.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + NamespacedKey key = null; + if (delta[0] != null && delta[0] instanceof String string) + key = NamespacedUtils.getNamespacedKey(string); + + for (Object object : getExpr().getArray(event)) { + if (object instanceof EquippableComponent component) { + component.setModel(key); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + ItemMeta meta = itemStack.getItemMeta(); + EquippableComponent component = meta.getEquippable(); + component.setModel(key); + meta.setEquippable(component); + itemStack.setItemMeta(meta); + if (object instanceof Slot slot) { + slot.setItem(itemStack); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(meta); + } else if (object instanceof ItemStack itemStack1) { + itemStack1.setItemMeta(meta); + } + } + } + } + + @Override + public boolean isSingle() { + return getExpr().isSingle(); + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the model key of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompSlot.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompSlot.java new file mode 100644 index 00000000000..317848c52a2 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompSlot.java @@ -0,0 +1,115 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.EquipmentSlot.EquipSlot; +import ch.njol.skript.util.slot.EquipmentSlot; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippaable Component - Equipment Slot") +@Description("The equipment slot an item can be equipped to.") +@Examples({ + "set the equipment slot of {_item} to chest slot", + "", + "set {_component} to the equippable component of {_item}", + "set the equipment slot of {_component} to boots slot", + "set the equippable component of {_item} to {_component}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class ExprEquipCompSlot extends PropertyExpression { + + static { + Skript.registerExpression(ExprEquipCompSlot.class, EquipSlot.class, ExpressionType.PROPERTY, + "[the] equipment slot of %itemstacks/itemtypes/slots/equippablecomponents%" + ); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr(exprs[0]); + return true; + } + + @Override + protected EquipSlot[] get(Event event, Object[] source) { + return get(source, object -> { + if (object instanceof EquippableComponent component) + return EquipmentSlot.convertToSkriptEquipSlot(component.getSlot()); + + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + return null; + org.bukkit.inventory.EquipmentSlot bukkitSlot = itemStack.getItemMeta().getEquippable().getSlot(); + return EquipmentSlot.convertToSkriptEquipSlot(bukkitSlot); + }); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET) + return CollectionUtils.array(EquipSlot.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (delta == null) + return; + EquipSlot providedSlot = (EquipSlot) delta[0]; + if (providedSlot == null) + return; + org.bukkit.inventory.EquipmentSlot bukkitSlot = providedSlot.getBukkitEquipSlot(); + for (Object object : getExpr().getArray(event)) { + if (object instanceof EquippableComponent component) { + component.setSlot(bukkitSlot); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + ItemMeta meta = itemStack.getItemMeta(); + EquippableComponent component = meta.getEquippable(); + component.setSlot(bukkitSlot); + meta.setEquippable(component); + itemStack.setItemMeta(meta); + if (object instanceof Slot slot) { + slot.setItem(itemStack); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(meta); + } else if (object instanceof ItemStack itemStack1) { + itemStack1.setItemMeta(meta); + } + } + } + } + + @Override + public boolean isSingle() { + return getExpr().isSingle(); + } + + @Override + public Class getReturnType() { + return EquipSlot.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the equipment slot of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompSound.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompSound.java new file mode 100644 index 00000000000..d9d37d77cf7 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquipCompSound.java @@ -0,0 +1,114 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.bukkitutil.SoundUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Sound; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component - Equip Sound") +@Description("The sound to be played when the item is equipped.") +@Examples({ + "set the equip sound of {_item} to \"entity.experience_orb.pickup\"", + "", + "set {_component} to the equippable component of {_item}", + "set the equip sound of {_component} to \"block.note_block.pling\"", + "set the equippable component of {_item} to {_component}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class ExprEquipCompSound extends PropertyExpression { + + static { + Skript.registerExpression(ExprEquipCompSound.class, String.class, ExpressionType.PROPERTY, + "[the] equip sound of %itemstacks/itemtypes/slots/equippablecomponents%" + ); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr(exprs[0]); + return true; + } + + @Override + protected String @Nullable [] get(Event event, Object[] source) { + return get(source, object -> { + if (object instanceof EquippableComponent component) { + return SoundUtils.getKey(component.getEquipSound()).getKey(); + } + + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + return null; + return SoundUtils.getKey(itemStack.getItemMeta().getEquippable().getEquipSound()).getKey(); + }); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) + return CollectionUtils.array(String.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Sound enumSound = null; + if (delta[0] != null) { + String soundString = (String) delta[0]; + enumSound = SoundUtils.getSound(soundString); + } + + for (Object object : getExpr().getArray(event)) { + if (object instanceof EquippableComponent component) { + component.setEquipSound(enumSound); + } else { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + ItemMeta meta = itemStack.getItemMeta(); + EquippableComponent component = meta.getEquippable(); + component.setEquipSound(enumSound); + meta.setEquippable(component); + itemStack.setItemMeta(meta); + if (object instanceof Slot slot) { + slot.setItem(itemStack); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(meta); + } else if (object instanceof ItemStack itemStack1) { + itemStack1.setItemMeta(meta); + } + } + } + } + + @Override + public boolean isSingle() { + return getExpr().isSingle(); + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the equip sound of " + getExpr().toString(event, debug); + } +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquippableComponent.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquippableComponent.java new file mode 100644 index 00000000000..2828f21575e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprEquippableComponent.java @@ -0,0 +1,105 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("Equippable Component") +@Description({ + "The equippable component of an item.", + "NOTE: When setting a variable to a component of an item, it will be a copy.", + "Meaning any changes made to the component will not be present on the item.", + "Set the components of the item directly or change the component of an item to the stored component." +}) +@Examples({ + "set {_component} to the equippable component of {_item}", + "set the equipment slot {_component} to helmet slot", + "set the equippable component of {_item} to {_component}", + "", + "set the equipment slot of {_item} to helmet slot", + "", + "clear the equippable component of {_item}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class ExprEquippableComponent extends PropertyExpression { + + static { + register(ExprEquippableComponent.class, EquippableComponent.class, "equippable component[s]", "itemstacks/itemtypes/slots"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr(exprs[0]); + return true; + } + + @Override + protected EquippableComponent @Nullable [] get(Event event, Object[] source) { + return get(source, object -> { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + return null; + return itemStack.getItemMeta().getEquippable(); + }); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) + return CollectionUtils.array(EquippableComponent.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + + EquippableComponent equipComp = null; + if (delta != null && delta[0] != null) + equipComp = (EquippableComponent) delta[0]; + + for (Object object : getExpr().getArray(event)) { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + ItemMeta meta = itemStack.getItemMeta(); + meta.setEquippable(equipComp); + itemStack.setItemMeta(meta); + if (object instanceof Slot slot) { + slot.setItem(itemStack); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(meta); + } else if (object instanceof ItemStack itemStack1) { + itemStack1.setItemMeta(meta); + } + } + } + + @Override + public boolean isSingle() { + return getExpr().isSingle(); + } + + @Override + public Class getReturnType() { + return EquippableComponent.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the equippable components of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprNewEquipComp.java b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprNewEquipComp.java new file mode 100644 index 00000000000..5e7fd4bbc6c --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/equippablecomponents/elements/ExprNewEquipComp.java @@ -0,0 +1,56 @@ +package org.skriptlang.skript.bukkit.equippablecomponents.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.Material; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.jetbrains.annotations.Nullable; + +@Name("New Equippable Component") +@Description("Gets a blank equippable component.") +@Examples({ + "set {_component} to a new blank equippable component", + "set the equippable component of {_item} to {_component}" +}) +@RequiredPlugins("Minecraft 1.21.2+") +@Since("INSERT VERSION") +public class ExprNewEquipComp extends SimpleExpression { + + static { + Skript.registerExpression(ExprNewEquipComp.class, EquippableComponent.class, ExpressionType.SIMPLE, + "a new [blank|empty] equippable component"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + protected EquippableComponent @Nullable [] get(Event event) { + return new EquippableComponent[]{(new ItemStack(Material.APPLE)).getItemMeta().getEquippable()}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return EquippableComponent.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "a new equippable component"; + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index fd9a449be22..6c29da2a4ae 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2418,6 +2418,15 @@ experience cooldown change reasons: plugin: plugin pickup_orb: orb pickup, pickup orb +# -- Equipment Slots -- +equipment slot: + helmet: helmet slot, head slot + chestplate: chestplate slot, chest slot + leggings: leggings slot, legs slot + boots: feet slot, boots slot + tool: tool slot, hand slot, main hand slot + off_hand: off hand slot + # -- Input Keys -- input keys: forward: forward movement key, forward key @@ -2515,6 +2524,7 @@ types: itemdisplaytransform: item display transform¦s @an experiencecooldownchangereason: experience cooldown change reason¦s @a inputkey: input key¦s @an + equippablecomponent: equippable component¦s @an # Skript weathertype: weather type¦s @a @@ -2534,6 +2544,7 @@ types: experience.pattern: (e?xp|experience( points?)?) classinfo: type¦s @a visualeffect: visual effect¦s @a + equipmentslot: equipment slot¦s @a # Hooks money: money diff --git a/src/test/java/org/skriptlang/skript/test/tests/utils/NamespacedUtilsTest.java b/src/test/java/org/skriptlang/skript/test/tests/utils/NamespacedUtilsTest.java new file mode 100644 index 00000000000..ba67fc64d43 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/utils/NamespacedUtilsTest.java @@ -0,0 +1,32 @@ +package org.skriptlang.skript.test.tests.utils; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.NamespacedUtils; +import ch.njol.util.Pair; +import org.bukkit.NamespacedKey; +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +public class NamespacedUtilsTest { + + @Test + public void testCoder() { + char[] chars = ",<>?;'\"[]{}\\|=+)(*&^%%$#@!".toCharArray(); + for (char c : chars) { + String hex = Integer.toHexString(c); + NamespacedKey encodedKey = NamespacedUtils.createNamespacedKey("test" + c); + assertEquals(encodedKey.toString(), "skript:test.x"+hex); + Pair decodedKey = NamespacedUtils.decodeNamespacedKey(encodedKey); + String combinedDecode = decodedKey.getKey() + ":" + decodedKey.getValue(); + assertEquals(combinedDecode, "skript:test" + c); + } + String dotHex = Integer.toHexString('.'); + String xHex = Integer.toHexString('x'); + NamespacedKey encodedKey = NamespacedUtils.createNamespacedKey("test.x"); + assertEquals(encodedKey.toString(), "skript:test.x" + dotHex + ".x" + xHex); + Pair decodedKey = NamespacedUtils.decodeNamespacedKey(encodedKey); + String combinedDecode = decodedKey.getKey() + ":" + decodedKey.getValue(); + assertEquals(combinedDecode, "skript:test.x"); + } + +} \ No newline at end of file diff --git a/src/test/skript/tests/syntaxes/expressions/ExprNewEquipComp.sk b/src/test/skript/tests/syntaxes/expressions/ExprNewEquipComp.sk new file mode 100644 index 00000000000..41ed4e91378 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprNewEquipComp.sk @@ -0,0 +1,48 @@ +test "new equippable component" when running minecraft "1.21.2": + ### + Combines: + ExprNewEquipComp + ExprEquipCompCameraOverlay + ExprEquipCompEntities + ExprEquipCompModel + ExprEquipCompSlot + ExprEquipCompSound + ExprEquippableComponent + EffEquipCompDamageable + EffEquipCompDispensable + EffEquipCompSwappable + CondEquipCompDamageable + CondEquipCompDispensable + CondEquipCompSwappable + ### + + set {_component} to a new equippable component + assert {_component} is an equippable component with "New equippable component is not an equippable component" + set the camera overlay of {_component} to "test_equippable_component_camera_overlay" + assert the camera overlay of {_component} is "skript:test_equippable_component_camera_overlay" with "Camera overlay of equippable component did not get set" + set the model key of {_component} to "test_equippable_component_model" + assert the model key of {_component} is "skript:test_equippable_component_model" with "Model key of equippable component did not get set" + set the allowed entities of {_component} to a zombie and a skeleton + assert the allowed entities of {_component} is a zombie and a skeleton with "Allowed entities of equippable component did not get set" + set the equipment slot of {_component} to helmet slot + assert the equipment slot of {_component} is helmet slot with "Equipment slot of equippable component did not get set" + set the equip sound of {_component} to "entity.experience_orb.pickup" + assert the equip sound of {_component} is "entity.experience_orb.pickup" with "Equip sound of equippable component did not get set" + set {_component} to be damageable + assert the {_component} is damageable with "Equippable component did not set damageable to true" + set the {_component} to be dispensable + assert the {_component} is dispensable with "Equippable component did not set dispensable to true" + set the {_component} to be swappable + assert the {_component} is swappable with "Equippable component did not set swappable to true" + + set {_item} to diamond helmet + set the equippable component of {_item} to {_component} + assert the equippable component of {_item} is {_component} with "Item's equippable component does not match variable component" + assert the camera overlay of {_item} is "skript:test_equippable_component_camera_overlay" with "Camera overlay of item's equippable component is not ""skript:test_equippable_component_camera_overlay""" + assert the model key of {_item} is "skript:test_equippable_component_model" with "Model key of item's equippable component is not ""skript:test_equippable_component_model""" + assert the allowed entities of {_item} is a zombie and a skeleton with "Allowed entities of item's equippable component is not a zombie and a skeleton" + assert the equipment slot of {_item} is helmet slot with "Equipment slot of item's equippable component is not helmet slot" + assert the equip sound of {_item} is "entity.experience_orb.pickup" with "Equip sound of item's equippable component is not ""entity.experience_orb.pickup""" + assert the {_item} is damageable with "Item's equippable component damageable is not true" + assert the {_item} is dispensable with "Item's equippable component dispensable is not true" + assert the {_item} is swappable with "Item's equippable component swappable is not true"