diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 3b183b34e4e..3063734afc5 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -1,6 +1,10 @@ name: Java 17 CI (MC 1.17+) -on: [push, pull_request] +on: + push: + branches: + - master + - 'dev/**' jobs: build: diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index 6f54fcc450a..3ccf5e21a8d 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -1,6 +1,10 @@ name: Java 8 CI (MC 1.13-1.16) -on: [push, pull_request] +on: + push: + branches: + - master + - 'dev/**' jobs: build: diff --git a/.github/workflows/legacy-tests.yml b/.github/workflows/legacy-tests.yml index 83c4c14d15a..c1d1f37f1ea 100644 --- a/.github/workflows/legacy-tests.yml +++ b/.github/workflows/legacy-tests.yml @@ -1,6 +1,10 @@ name: Legacy Java 8 (MC 1.9.4-1.12.2) -on: [push, pull_request] +on: + push: + branches: + - master + - 'dev/**' jobs: build: diff --git a/gradle.properties b/gradle.properties index 903f8e9f0b9..492078c53f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.6.3 +version=2.6.4 jarName=Skript.jar testEnv=java17/paper-1.19 testEnvJavaVersion=17 diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index 29a226d843f..e9fee63b716 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -79,39 +79,18 @@ public static class OldItemData { static final ItemFactory itemFactory = Bukkit.getServer().getItemFactory(); - static final MaterialRegistry materialRegistry; - private static final boolean SPAWN_EGG_META_EXISTS = Skript.classExists("org.bukkit.inventory.meta.SpawnEggMeta"); private static final boolean HAS_NEW_SKULL_META_METHODS = Skript.methodExists(SkullMeta.class, "getOwningPlayer"); // Load or create material registry static { - Gson gson = new GsonBuilder().registerTypeAdapterFactory(EnumTypeAdapter.factory).serializeNulls().create(); Path materialsFile = Paths.get(Skript.getInstance().getDataFolder().getAbsolutePath(), "materials.json"); if (Files.exists(materialsFile)) { - String content = null; try { - content = new String(Files.readAllBytes(materialsFile), StandardCharsets.UTF_8); + Files.delete(materialsFile); } catch (IOException e) { - Skript.exception(e, "Loading material registry failed!"); - } - if (content != null) { - String[] names = gson.fromJson(content, String[].class); - assert names != null; - materialRegistry = MaterialRegistry.load(names); - } else { - materialRegistry = new MaterialRegistry(); + Skript.exception(e, "Failed to remove legacy material registry file!"); } - } else { - materialRegistry = new MaterialRegistry(); - } - - // Always rewrite material registry, in case some updates got applied to it - String content = gson.toJson(materialRegistry.getMaterials()); - try { - Files.write(materialsFile, content.getBytes(StandardCharsets.UTF_8)); - } catch (IOException e) { - Skript.exception(e, "Saving material registry failed!"); } } @@ -142,7 +121,7 @@ public static class OldItemData { * allow comparing it against other blocks. */ @Nullable - transient BlockValues blockValues; + BlockValues blockValues; /** * Whether this represents an item (that definitely cannot have @@ -613,20 +592,24 @@ public boolean matchPlain(ItemData other) { @Override public Fields serialize() throws NotSerializableException { Fields fields = new Fields(this); // ItemStack is transient, will be ignored - fields.putPrimitive("id", materialRegistry.getId(type)); + fields.putPrimitive("id", type.ordinal()); fields.putObject("meta", stack.getItemMeta()); return fields; } + private static final Material[] materials = Material.values(); + @Override public void deserialize(Fields fields) throws StreamCorruptedException, NotSerializableException { - this.type = materialRegistry.getMaterial(fields.getAndRemovePrimitive("id", int.class)); + this.type = materials[fields.getAndRemovePrimitive("id", int.class)]; ItemMeta meta = fields.getAndRemoveObject("meta", ItemMeta.class); fields.setFields(this); // Everything but ItemStack and Material // Initialize ItemStack this.stack = new ItemStack(type); stack.setItemMeta(meta); // Just set meta to it + + fields.setFields(this); // Everything but ItemStack and Material } /** diff --git a/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java b/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java index 345d16b36b8..cddd0405ec4 100644 --- a/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java +++ b/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java @@ -29,6 +29,7 @@ * Manages Skript's own number -> Material mappings. They are used to save * items as variables. */ +@Deprecated public class MaterialRegistry { static final boolean newMaterials = Skript.isRunningMinecraft(1, 13); diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index 68168525984..ff92653c6b9 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -138,5 +138,20 @@ public static boolean itemStacksEqual(final @Nullable ItemStack is1, final @Null return is1.getType() == is2.getType() && ItemUtils.getDamage(is1) == ItemUtils.getDamage(is2) && is1.getItemMeta().equals(is2.getItemMeta()); } + + // Only 1.15 and versions after have Material#isAir method + private static final boolean IS_AIR_EXISTS = Skript.methodExists(Material.class, "isAir"); + // Version 1.14 have multiple air types but no Material#isAir method + private static final boolean OTHER_AIR_EXISTS = Skript.isRunningMinecraft(1, 14); + + public static boolean isAir(Material type) { + if (IS_AIR_EXISTS) { + return type.isAir(); + } else if (OTHER_AIR_EXISTS) { + return type == Material.AIR || type == Material.CAVE_AIR || type == Material.VOID_AIR; + } + // All versions prior to 1.14 only have 1 air type + return type == Material.AIR; + } } diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/BlockValues.java b/src/main/java/ch/njol/skript/bukkitutil/block/BlockValues.java index d14cd483e3b..c8f1c4539bb 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/BlockValues.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/BlockValues.java @@ -18,16 +18,14 @@ */ package ch.njol.skript.bukkitutil.block; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.aliases.MatchQuality; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import org.eclipse.jdt.annotation.Nullable; /** * Contains all data block has that is needed for comparisions. */ -public abstract class BlockValues { +public abstract class BlockValues implements YggdrasilExtendedSerializable { public abstract boolean isDefault(); diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/MagicBlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/MagicBlockCompat.java index 1677263557f..181662218a5 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/MagicBlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/MagicBlockCompat.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.bukkitutil.block; +import java.io.StreamCorruptedException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -30,12 +31,15 @@ import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemFlags; import ch.njol.skript.aliases.MatchQuality; import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.variables.Variables; +import ch.njol.yggdrasil.Fields; /** * Block compatibility implemented with magic numbers. No other choice until @@ -68,12 +72,21 @@ public class MagicBlockCompat implements BlockCompat { } @SuppressWarnings({"deprecation"}) - private class MagicBlockValues extends BlockValues { + private static class MagicBlockValues extends BlockValues { + + static { + Variables.yggdrasil.registerSingleClass(MagicBlockValues.class, "MagicBlockValues"); + } private Material id; short data; private int itemFlags; + /** + * Used for serialization + */ + private MagicBlockValues() {} + public MagicBlockValues(BlockState block) { this.id = ItemUtils.asItem(block.getType()); this.data = block.getRawData(); // Some black magic here, please look away... @@ -131,6 +144,24 @@ public MatchQuality match(BlockValues other) { return MatchQuality.DIFFERENT; } } + + @Override + public Fields serialize() { + Fields fields = new Fields(); + fields.putPrimitive("material", id.ordinal()); + fields.putPrimitive("data", data); + fields.putPrimitive("itemFlags", itemFlags); + return fields; + } + + private static final Material[] materials = Material.values(); + + @Override + public void deserialize(@NonNull Fields fields) throws StreamCorruptedException { + this.id = materials[fields.getAndRemovePrimitive("id", int.class)]; + this.data = fields.getAndRemovePrimitive("data", Short.class); + this.itemFlags = fields.getAndRemovePrimitive("itemFlags", Integer.class); + } } private static class MagicBlockSetter implements BlockSetter { diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java index bef3b585528..0610022d1c9 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java @@ -18,8 +18,12 @@ */ package ch.njol.skript.bukkitutil.block; -import java.util.Map; - +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.aliases.MatchQuality; +import ch.njol.skript.variables.Variables; +import ch.njol.yggdrasil.Fields; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -34,12 +38,11 @@ import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.aliases.MatchQuality; +import java.io.StreamCorruptedException; +import java.util.Map; /** * 1.13+ block compat. @@ -48,15 +51,26 @@ public class NewBlockCompat implements BlockCompat { private static class NewBlockValues extends BlockValues { + static { + Variables.yggdrasil.registerSingleClass(NewBlockValues.class, "NewBlockValues"); + } + Material type; BlockData data; boolean isDefault; public NewBlockValues(Material type, BlockData data, boolean isDefault) { + if (type != data.getMaterial()) + throw new IllegalArgumentException("'type' does not match material of 'data'"); this.type = type; this.data = data; this.isDefault = isDefault; } + + /** + * For Serialization - INTERNAL USAGE ONLY!! + */ + private NewBlockValues() { } @Override public boolean isDefault() { @@ -104,7 +118,26 @@ public MatchQuality match(BlockValues other) { return MatchQuality.DIFFERENT; } } - + + @Override + public Fields serialize() { + Fields fields = new Fields(); + fields.putObject("data", data.getAsString()); + fields.putPrimitive("isDefault", isDefault); + return fields; + } + + @Override + public void deserialize(@NonNull Fields fields) throws StreamCorruptedException { + String data = fields.getObject("data", String.class); + boolean isDefault = fields.getPrimitive("isDefault", Boolean.class); + if (data == null) + throw new StreamCorruptedException("'data' is missing."); + + this.data = Bukkit.createBlockData(data); + this.type = this.data.getMaterial(); + this.isDefault = isDefault; + } } private static class NewBlockSetter implements BlockSetter { diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index c3efa0b54c3..89775724418 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1655,6 +1655,7 @@ public String toVariableNameString(SoundCategory category) { }) .serializer(new EnumSerializer<>(SoundCategory.class))); } + if (Skript.classExists("org.bukkit.entity.Panda$Gene")) { EnumUtils genes = new EnumUtils<>(Gene.class, "genes"); Classes.registerClass(new ClassInfo<>(Gene.class, "gene") @@ -1684,6 +1685,7 @@ public String toVariableNameString(Gene gene) { }) .serializer(new EnumSerializer<>(Gene.class))); } + EnumUtils regainReasons = new EnumUtils<>(RegainReason.class, "heal reasons"); Classes.registerClass(new ClassInfo<>(RegainReason.class, "healreason") .user("(regen|heal) (reason|cause)") @@ -1710,6 +1712,7 @@ public String toVariableNameString(RegainReason o) { } }) .serializer(new EnumSerializer<>(RegainReason.class))); + if (Skript.classExists("org.bukkit.entity.Cat$Type")) { EnumUtils races = new EnumUtils<>(Cat.Type.class, "cat types"); Classes.registerClass(new ClassInfo<>(Cat.Type.class, "cattype") @@ -1753,12 +1756,12 @@ public String toVariableNameString(Cat.Type race) { public GameRule parse(final String input, final ParseContext context) { return GameRule.getByName(input); } - + @Override public String toString(GameRule o, int flags) { return o.getName(); } - + @Override public String toVariableNameString(GameRule o) { return o.getName(); @@ -1766,25 +1769,6 @@ public String toVariableNameString(GameRule o) { }) ); } - -// Temporarily disabled until bugs are fixed -// if (Skript.classExists("org.bukkit.persistence.PersistentDataHolder")) { -// Classes.registerClass(new ClassInfo<>(PersistentDataHolder.class, "persistentdataholder") -// .user("persistent data ?holders?") -// .name("Persistent Data Holder") -// .description( -// "Represents something that can have persistent data. " -// + "The following can all hold persistent data: " -// + "entities, projectiles, items, banners, barrels, beds, beehives (1.15), bells, blast furnaces, " -// + "brewing stands, campfires, chests, command blocks, comparators, conduits, mob spawners, " -// + "daylight detectors, dispensers, droppers, enchanting tables, ender chests, end gateways, furnaces, " -// + "hoppers, jigsaw blocks, jukeboxes, lecterns, shulker boxes, signs, skulls, smokers, and structure blocks. " -// + "For the source list, see this page." -// ) -// .examples("set persistent data value \"epic\" of player to true") -// .requiredPlugins("1.14 or newer") -// .since("2.5")); -// } if (Skript.classExists("org.bukkit.enchantments.EnchantmentOffer")) { Classes.registerClass(new ClassInfo<>(EnchantmentOffer.class, "enchantmentoffer") @@ -1812,6 +1796,7 @@ public String toVariableNameString(EnchantmentOffer eo) { } })); } + EnumUtils attributes = new EnumUtils<>(Attribute.class, "attribute types"); Classes.registerClass(new ClassInfo<>(Attribute.class, "attributetype") .user("attribute ?types?") @@ -1840,4 +1825,5 @@ public String toVariableNameString(Attribute a) { }) .serializer(new EnumSerializer<>(Attribute.class))); } + } diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java index 1a9fc9c5363..54d1a69d818 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -350,11 +350,11 @@ public Block get(final BlockIgniteEvent e) { } }, 0); // BlockDispenseEvent - EventValues.registerEventValue(BlockDispenseEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(BlockDispenseEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(final BlockDispenseEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(final BlockDispenseEvent e) { + return e.getItem(); } }, 0); // BlockCanBuildEvent @@ -552,11 +552,11 @@ public PotionEffectType get(AreaEffectCloudApplyEvent e) { }, 0); } // ItemSpawnEvent - EventValues.registerEventValue(ItemSpawnEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(ItemSpawnEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(final ItemSpawnEvent e) { - return new ItemType(e.getEntity().getItemStack()); + public ItemStack get(final ItemSpawnEvent e) { + return e.getEntity().getItemStack(); } }, 0); // LightningStrikeEvent @@ -648,11 +648,11 @@ public Item get(final PlayerDropItemEvent e) { return e.getItemDrop(); } }, 0); - EventValues.registerEventValue(PlayerDropItemEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerDropItemEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(final PlayerDropItemEvent e) { - return new ItemType(e.getItemDrop().getItemStack()); + public ItemStack get(final PlayerDropItemEvent e) { + return e.getItemDrop().getItemStack(); } }, 0); // PlayerPickupItemEvent @@ -670,11 +670,11 @@ public Item get(final PlayerPickupItemEvent e) { return e.getItem(); } }, 0); - EventValues.registerEventValue(PlayerPickupItemEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerPickupItemEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(final PlayerPickupItemEvent e) { - return new ItemType(e.getItem().getItemStack()); + public ItemStack get(final PlayerPickupItemEvent e) { + return e.getItem().getItemStack(); } }, 0); // EntityPickupItemEvent @@ -702,21 +702,21 @@ public ItemType get(final EntityPickupItemEvent e) { } // PlayerItemConsumeEvent if (Skript.supports("org.bukkit.event.player.PlayerItemConsumeEvent")) { - EventValues.registerEventValue(PlayerItemConsumeEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerItemConsumeEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(final PlayerItemConsumeEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(final PlayerItemConsumeEvent e) { + return e.getItem(); } }, 0); } // PlayerItemBreakEvent if (Skript.supports("org.bukkit.event.player.PlayerItemBreakEvent")) { - EventValues.registerEventValue(PlayerItemBreakEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerItemBreakEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(final PlayerItemBreakEvent e) { - return new ItemType(e.getBrokenItem()); + public ItemStack get(final PlayerItemBreakEvent e) { + return e.getBrokenItem(); } }, 0); } @@ -728,30 +728,29 @@ public Entity get(final PlayerInteractEntityEvent e) { return e.getRightClicked(); } }, 0); - EventValues.registerEventValue(PlayerInteractEntityEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerInteractEntityEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(final PlayerInteractEntityEvent e) { + public ItemStack get(final PlayerInteractEntityEvent e) { if (offHandSupport) { EquipmentSlot hand = e.getHand(); if (hand == EquipmentSlot.HAND) - return new ItemType(e.getPlayer().getInventory().getItemInMainHand()); + return e.getPlayer().getInventory().getItemInMainHand(); else if (hand == EquipmentSlot.OFF_HAND) - return new ItemType(e.getPlayer().getInventory().getItemInOffHand()); + return e.getPlayer().getInventory().getItemInOffHand(); else return null; } else { - return new ItemType(e.getPlayer().getItemInHand()); + return e.getPlayer().getItemInHand(); } } }, 0); // PlayerInteractEvent - EventValues.registerEventValue(PlayerInteractEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerInteractEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(final PlayerInteractEvent e) { - ItemStack item = e.getItem(); - return item == null ? null : new ItemType(item); + public ItemStack get(final PlayerInteractEvent e) { + return e.getItem(); } }, 0); EventValues.registerEventValue(PlayerInteractEvent.class, Block.class, new Getter() { @@ -785,10 +784,10 @@ public Block get(final PlayerMoveEvent e) { } }, 0); // PlayerItemDamageEvent - EventValues.registerEventValue(PlayerItemDamageEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerItemDamageEvent.class, ItemStack.class, new Getter() { @Override - public ItemType get(PlayerItemDamageEvent event) { - return new ItemType(event.getItem()); + public ItemStack get(PlayerItemDamageEvent event) { + return event.getItem(); } }, 0); //PlayerItemMendEvent @@ -800,11 +799,11 @@ public Player get(PlayerItemMendEvent e) { return e.getPlayer(); } }, 0); - EventValues.registerEventValue(PlayerItemMendEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerItemMendEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(PlayerItemMendEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(PlayerItemMendEvent e) { + return e.getItem(); } }, 0); EventValues.registerEventValue(PlayerItemMendEvent.class, Entity.class, new Getter() { @@ -982,14 +981,13 @@ public World get(final InventoryClickEvent e) { return e.getWhoClicked().getWorld(); } }, 0); - EventValues.registerEventValue(InventoryClickEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(InventoryClickEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(final InventoryClickEvent e) { + public ItemStack get(final InventoryClickEvent e) { if (e instanceof CraftItemEvent) - return new ItemType(((CraftItemEvent) e).getRecipe().getResult()); - ItemStack item = e.getCurrentItem(); - return item == null ? null : new ItemType(item); + return ((CraftItemEvent) e).getRecipe().getResult(); + return e.getCurrentItem(); } }, 0); EventValues.registerEventValue(InventoryClickEvent.class, Slot.class, new Getter() { @@ -1148,11 +1146,11 @@ public Item get(InventoryPickupItemEvent event) { return event.getItem(); } }, 0); - EventValues.registerEventValue(InventoryPickupItemEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(InventoryPickupItemEvent.class, ItemStack.class, new Getter() { @Nullable @Override - public ItemType get(InventoryPickupItemEvent event) { - return new ItemType(event.getItem().getItemStack()); + public ItemStack get(InventoryPickupItemEvent event) { + return event.getItem().getItemStack(); } }, 0); //PortalCreateEvent @@ -1173,12 +1171,12 @@ public Entity get(final PortalCreateEvent e) { }, 0); } //PlayerEditBookEvent - EventValues.registerEventValue(PlayerEditBookEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerEditBookEvent.class, ItemStack.class, new Getter() { @Override - public ItemType get(PlayerEditBookEvent e) { + public ItemStack get(PlayerEditBookEvent e) { ItemStack book = new ItemStack(e.getPlayer().getItemInHand().getType()); book.setItemMeta(e.getNewBookMeta()); - return new ItemType(book); //TODO: Find better way to derive this event value + return book; // TODO: Find better way to derive this event value } }, 0); //ItemDespawnEvent @@ -1189,11 +1187,11 @@ public Item get(ItemDespawnEvent e) { return e.getEntity(); } }, 0); - EventValues.registerEventValue(ItemDespawnEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(ItemDespawnEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(ItemDespawnEvent e) { - return new ItemType(e.getEntity().getItemStack()); + public ItemStack get(ItemDespawnEvent e) { + return e.getEntity().getItemStack(); } }, 0); //ItemMergeEvent @@ -1211,11 +1209,11 @@ public Item get(ItemMergeEvent e) { return e.getTarget(); } }, 1); - EventValues.registerEventValue(ItemMergeEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(ItemMergeEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(ItemMergeEvent e) { - return new ItemType(e.getEntity().getItemStack()); + public ItemStack get(ItemMergeEvent e) { + return e.getEntity().getItemStack(); } }, 0); //PlayerTeleportEvent @@ -1271,21 +1269,20 @@ public FireworkEffect get(FireworkExplodeEvent e) { } //PlayerRiptideEvent if (Skript.classExists("org.bukkit.event.player.PlayerRiptideEvent")) { - EventValues.registerEventValue(PlayerRiptideEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerRiptideEvent.class, ItemStack.class, new Getter() { @Override - public ItemType get(PlayerRiptideEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(PlayerRiptideEvent e) { + return e.getItem(); } }, 0); } //PlayerArmorChangeEvent if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerArmorChangeEvent")) { - EventValues.registerEventValue(PlayerArmorChangeEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PlayerArmorChangeEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(PlayerArmorChangeEvent e) { - ItemStack stack = e.getNewItem(); - return stack == null ? null : new ItemType(stack); + public ItemStack get(PlayerArmorChangeEvent e) { + return e.getNewItem(); } }, 0); } @@ -1297,11 +1294,11 @@ public Player get(PrepareItemEnchantEvent e) { return e.getEnchanter(); } }, 0); - EventValues.registerEventValue(PrepareItemEnchantEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(PrepareItemEnchantEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(PrepareItemEnchantEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(PrepareItemEnchantEvent e) { + return e.getItem(); } }, 0); EventValues.registerEventValue(PrepareItemEnchantEvent.class, Block.class, new Getter() { @@ -1319,11 +1316,11 @@ public Player get(EnchantItemEvent e) { return e.getEnchanter(); } }, 0); - EventValues.registerEventValue(EnchantItemEvent.class, ItemType.class, new Getter() { + EventValues.registerEventValue(EnchantItemEvent.class, ItemStack.class, new Getter() { @Override @Nullable - public ItemType get(EnchantItemEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(EnchantItemEvent e) { + return e.getItem(); } }, 0); EventValues.registerEventValue(EnchantItemEvent.class, Block.class, new Getter() { diff --git a/src/main/java/ch/njol/skript/conditions/CondHasRelationalVariable.java b/src/main/java/ch/njol/skript/conditions/CondHasRelationalVariable.java deleted file mode 100644 index f022db5dde9..00000000000 --- a/src/main/java/ch/njol/skript/conditions/CondHasRelationalVariable.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.conditions; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.conditions.base.PropertyCondition; -import ch.njol.skript.conditions.base.PropertyCondition.PropertyType; -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.lang.Condition; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionList; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Variable; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.util.PersistentDataUtils; -import ch.njol.util.Kleenean; - -@Name("Has Relational Variable") -@Description({"Checks whether the given relation variables are present on the given holders.", - "See persistent data holder for a list of all holders." -}) -@Examples({"player holds relational variable {isAdmin}", - "player holds relational variable {oldNames::*}"}) -@RequiredPlugins("1.14 or newer") -@Since("2.5") -public class CondHasRelationalVariable extends Condition { - - static { - // Temporarily disabled until bugs are fixed - if (false && Skript.isRunningMinecraft(1, 14)) { - Skript.registerCondition(CondHasRelationalVariable.class, - "%persistentdataholders/itemtypes/blocks% (has|have|holds) [(relational|relation( |-)based) variable[s]] %objects%", - "%persistentdataholders/itemtypes/blocks% (doesn't|does not|do not|don't) (have|hold) [(relational|relation( |-)based) variable[s]] %objects%" - ); - } - } - - @SuppressWarnings("null") - private Expression holders; - @SuppressWarnings("null") - private ExpressionList> variables; - - @Override - @SuppressWarnings({"unchecked", "null"}) - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - ExpressionList exprList = exprs[1] instanceof ExpressionList ? (ExpressionList) exprs[1] : new ExpressionList<>(new Expression[]{exprs[1]}, Object.class, false); - for (Expression expr : exprList.getExpressions()) { - if (!(expr instanceof Variable)) { // Input not a variable - return false; - } else if (((Variable) expr).isLocal()) { // Input is a variable, but it's local - Skript.error("Setting a relational variable using a local variable is not supported." - + " If you are trying to set a value temporarily, consider using metadata", ErrorQuality.SEMANTIC_ERROR - ); - return false; - } - } - variables = (ExpressionList>) exprList; - holders = (Expression) exprs[0]; - setNegated(matchedPattern == 1); - return true; - } - - @Override - public boolean check(Event e) { - for (Expression expr : variables.getExpressions()) { - if (!(holders.check(e, holder -> PersistentDataUtils.has(((Variable) expr).getName().toString(e), holder), isNegated()))) - return false; - } - return true; - - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return PropertyCondition.toString(this, PropertyType.HAVE, e, debug, holders, - "relational variable(s) " + variables.toString(e, debug)); - } - -} diff --git a/src/main/java/ch/njol/skript/effects/EffConnect.java b/src/main/java/ch/njol/skript/effects/EffConnect.java index 4a7444fc89a..1a82a6d5b73 100644 --- a/src/main/java/ch/njol/skript/effects/EffConnect.java +++ b/src/main/java/ch/njol/skript/effects/EffConnect.java @@ -59,12 +59,14 @@ public class EffConnect extends Effect { @Override protected void execute(Event e) { String server = this.server.getSingle(e); - Player[] players = this.players.getArray(e); + Player[] players = this.players.stream(e) + .filter(Player::isOnline) + .toArray(Player[]::new); if (server == null || players.length == 0) return; // the message channel is case sensitive so let's fix that - Utils.sendPluginMessage(BUNGEE_CHANNEL, r -> GET_SERVERS_CHANNEL.equals(r.readUTF()), GET_SERVERS_CHANNEL) + Utils.sendPluginMessage(players[0], BUNGEE_CHANNEL, r -> GET_SERVERS_CHANNEL.equals(r.readUTF()), GET_SERVERS_CHANNEL) .thenAccept(response -> { // for loop isn't as pretty a stream, but will be faster with tons of servers for (String validServer : response.readUTF().split(", ")) { diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index d3c814a909d..74a36d2c72d 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -27,12 +27,16 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.TriggerSection; +import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.sections.SecLoop; +import ch.njol.skript.sections.SecWhile; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.util.List; +import java.util.stream.Collectors; @Name("Continue") @Description("Skips the value currently being looped, moving on to the next value if it exists.") @@ -40,7 +44,7 @@ "\tif loop-value does not have permission \"moderator\":", "\t\tcontinue # filter out non moderators", "\tbroadcast \"%loop-player% is a moderator!\" # Only moderators get broadcast"}) -@Since("2.2-dev37") +@Since("2.2-dev37, INSERT VERSION (while loops)") public class EffContinue extends Effect { static { @@ -48,16 +52,20 @@ public class EffContinue extends Effect { } @SuppressWarnings("NotNullFieldNotInitialized") - private SecLoop loop; + private TriggerSection section; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - List loops = getParser().getCurrentSections(SecLoop.class); - if (loops.isEmpty()) { - Skript.error("Continue may only be used in loops"); + List currentSections = ParserInstance.get().getCurrentSections().stream() + .filter(s -> s instanceof SecLoop || s instanceof SecWhile) + .collect(Collectors.toList()); + + if (currentSections.isEmpty()) { + Skript.error("Continue may only be used in while or loops"); return false; } - loop = loops.get(loops.size() - 1); + + section = currentSections.get(currentSections.size() - 1); return true; } @@ -69,7 +77,7 @@ protected void execute(Event e) { @Nullable @Override protected TriggerItem walk(Event e) { - return loop; + return section; } @Override diff --git a/src/main/java/ch/njol/skript/effects/EffDrop.java b/src/main/java/ch/njol/skript/effects/EffDrop.java index ee0e99f645f..7efdd225162 100644 --- a/src/main/java/ch/njol/skript/effects/EffDrop.java +++ b/src/main/java/ch/njol/skript/effects/EffDrop.java @@ -20,6 +20,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -85,7 +86,7 @@ public void execute(Event e) { if (o instanceof ItemStack) o = new ItemType((ItemStack) o); for (ItemStack is : ((ItemType) o).getItem().getAll()) { - if (!isAir(is.getType()) && is.getAmount() > 0) { + if (!ItemUtils.isAir(is.getType()) && is.getAmount() > 0) { if (useVelocity) { lastSpawned = l.getWorld().dropItemNaturally(itemDropLoc, is); } else { @@ -101,21 +102,6 @@ public void execute(Event e) { } } - // Only 1.15 and versions after have Material#isAir method - private static final boolean IS_AIR_EXISTS = Skript.methodExists(Material.class, "isAir"); - // Version 1.14 have multiple air types but no Material#isAir method - private static final boolean OTHER_AIR_EXISTS = Skript.isRunningMinecraft(1, 14); - - private static boolean isAir(Material type) { - if (IS_AIR_EXISTS) { - return type.isAir(); - } else if (OTHER_AIR_EXISTS) { - return type == Material.AIR || type == Material.CAVE_AIR || type == Material.VOID_AIR; - } - // All versions prior to 1.14 only have 1 air type - return type == Material.AIR; - } - @Override public String toString(@Nullable Event e, boolean debug) { return "drop " + drops.toString(e, debug) + " " + locations.toString(e, debug); diff --git a/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java b/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java index 2d8e82eb8af..d7252b793bd 100644 --- a/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java +++ b/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java @@ -18,8 +18,11 @@ */ package ch.njol.skript.effects; +import java.util.Optional; + import org.bukkit.FireworkEffect; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.entity.Firework; import org.bukkit.event.Event; import org.bukkit.inventory.meta.FireworkMeta; @@ -40,19 +43,16 @@ @Examples("launch ball large coloured red, purple and white fading to light green and black at player's location with duration 1") @Since("2.4") public class EffFireworkLaunch extends Effect { - + static { Skript.registerEffect(EffFireworkLaunch.class, "(launch|deploy) [[a] firework [with effect[s]]] %fireworkeffects% at %locations% [([with] (duration|power)|timed) %number%]"); } - @SuppressWarnings("null") private Expression effects; - @SuppressWarnings("null") private Expression locations; - @SuppressWarnings("null") private Expression lifetime; - - @SuppressWarnings({"unchecked", "null"}) + + @SuppressWarnings("unchecked") @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { effects = (Expression) exprs[0]; @@ -62,24 +62,27 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected void execute(Event e) { - Number power = lifetime.getSingle(e); - if (power == null) - power = 1; - for (Location location : locations.getArray(e)) { - Firework firework = location.getWorld().spawn(location, Firework.class); + protected void execute(Event event) { + FireworkEffect[] effects = this.effects.getArray(event); + int power = Optional.ofNullable(lifetime.getSingle(event)).orElse(1).intValue(); + power = Math.min(127, Math.max(0, power)); + for (Location location : locations.getArray(event)) { + World world = location.getWorld(); + if (world == null) + continue; + Firework firework = world.spawn(location, Firework.class); FireworkMeta meta = firework.getFireworkMeta(); - meta.addEffects(effects.getArray(e)); - meta.setPower(power.intValue()); + meta.addEffects(effects); + meta.setPower(power); firework.setFireworkMeta(meta); } } - + @Override - public String toString(@Nullable Event e, boolean debug) { - return "Launch firework(s) " + effects.toString(e, debug) + - " at location(s) " + locations.toString(e, debug) + - " timed " + lifetime.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "Launch firework(s) " + effects.toString(event, debug) + + " at location(s) " + locations.toString(event, debug) + + " timed " + lifetime.toString(event, debug); } -} \ No newline at end of file +} diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 255e0581468..09c7af5912a 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -42,25 +42,25 @@ @Name("Potion Effects") @Description("Apply or remove potion effects to/from entities.") @Examples({"apply swiftness 2 to the player", - "remove haste from the victim", - "on join:", - "\tapply potion of strength of tier {strength.%player%} to the player for 999 days", - "apply potion effects of player's tool to player"}) + "remove haste from the victim", + "on join:", + "\tapply potion of strength of tier {strength.%player%} to the player for 999 days", + "apply potion effects of player's tool to player"}) @Since("2.0, 2.2-dev27 (ambient and particle-less potion effects), 2.5 (replacing existing effect), 2.5.2 (potion effects)") public class EffPotion extends Effect { static { Skript.registerEffect(EffPotion.class, - "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply ambient [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] without [any] particles to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply %potioneffects% to %livingentities%" - //, "apply %itemtypes% to %livingentities%" - /*,"remove %potioneffecttypes% from %livingentities%"*/); + "apply %potioneffects% to %livingentities%", + "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", + "apply ambient [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", + "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] without [any] particles to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]" + //, "apply %itemtypes% to %livingentities%" + /*,"remove %potioneffecttypes% from %livingentities%"*/); } - + private final static int DEFAULT_DURATION = 15 * 20; // 15 seconds, same as EffPoison private boolean replaceExisting; - + @SuppressWarnings("null") private Expression potions; @Nullable @@ -75,12 +75,12 @@ public class EffPotion extends Effect { private boolean ambient; // Ambient means less particles private boolean particles; // Particles or no particles? private boolean potionEffect; // PotionEffects rather than PotionEffectTypes - + @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - apply = matchedPattern < 3; - potionEffect = matchedPattern == 3; + apply = matchedPattern > 0; + potionEffect = matchedPattern == 0; replaceExisting = parseResult.mark == 1; if (potionEffect) { potionEffects = (Expression) exprs[0]; @@ -94,26 +94,26 @@ public boolean init(final Expression[] exprs, final int matchedPattern, final potions = (Expression) exprs[0]; entities = (Expression) exprs[1]; } - + // Ambience and particles switch (matchedPattern) { - case 0: + case 1: ambient = false; particles = true; break; - case 1: + case 2: ambient = true; particles = true; break; - case 2: + case 3: ambient = false; particles = false; break; } - + return true; } - + @Override protected void execute(final Event e) { if (potionEffect) { @@ -164,7 +164,7 @@ protected void execute(final Event e) { } } } - + @Override public String toString(final @Nullable Event e, final boolean debug) { if (potionEffect) @@ -174,5 +174,5 @@ else if (apply) else return "remove " + potions.toString(e, debug) + " from " + entities.toString(e, debug); } - + } diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index 66e2c9378f6..f911133d415 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -52,7 +52,7 @@ public class FallingBlockData extends EntityData { private final static Message m_not_a_block_error = new Message("entities.falling block.not a block error"); private final static Adjective m_adjective = new Adjective("entities.falling block.adjective"); - + @Nullable private ItemType[] types = null; @@ -88,6 +88,8 @@ public ItemType convert(ItemType t) { Skript.error(m_not_a_block_error.toString()); return false; } + } else { + types = new ItemType[] {new ItemType(Material.STONE)}; } return true; } diff --git a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java index 6ddcc7af27c..65d8192efb2 100644 --- a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java +++ b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java @@ -18,8 +18,12 @@ */ package ch.njol.skript.events; -import java.util.Locale; - +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Checker; import org.bukkit.Material; import org.bukkit.entity.Enderman; import org.bukkit.entity.FallingBlock; @@ -29,17 +33,8 @@ import org.bukkit.event.entity.EntityChangeBlockEvent; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.util.Checker; +import java.util.Locale; -/** - * @author Peter Güttinger - */ public class EvtEntityBlockChange extends SkriptEvent { static { @@ -55,83 +50,54 @@ public class EvtEntityBlockChange extends SkriptEvent { .since("unknown, 2.5.2 (falling block)"); } - static final ItemType monsterEgg = Aliases.javaItemType("any spawn egg"); - - private static enum ChangeEvent { - ENDERMAN_PLACE("enderman place", new Checker() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Enderman && e.getTo() != Material.AIR; - } - }), - ENDERMAN_PICKUP("enderman pickup", new Checker() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Enderman && e.getTo() == Material.AIR; - } - }), - SHEEP_EAT("sheep eat", new Checker() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Sheep; - } - }), - SILVERFISH_ENTER("silverfish enter", new Checker() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Silverfish && e.getTo() != monsterEgg.getMaterial(); - } - }), - SILVERFISH_EXIT("silverfish exit", new Checker() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Silverfish && e.getTo() != monsterEgg.getMaterial(); - } - }), - FALLING_BLOCK_LANDING("falling block land[ing]", new Checker() { - @Override - public boolean check(EntityChangeBlockEvent e) { - return e.getEntity() instanceof FallingBlock; - } - }); - + private enum ChangeEvent { + + ENDERMAN_PLACE("enderman place", e -> e.getEntity() instanceof Enderman && e.getTo() != Material.AIR), + ENDERMAN_PICKUP("enderman pickup", e -> e.getEntity() instanceof Enderman && e.getTo() == Material.AIR), + + SHEEP_EAT("sheep eat", e -> e.getEntity() instanceof Sheep), + + SILVERFISH_ENTER("silverfish enter", e -> e.getEntity() instanceof Silverfish && !ItemUtils.isAir(e.getTo())), + SILVERFISH_EXIT("silverfish exit", e -> e.getEntity() instanceof Silverfish && ItemUtils.isAir(e.getTo())), + + FALLING_BLOCK_LANDING("falling block land[ing]", e -> e.getEntity() instanceof FallingBlock); + private final String pattern; - final Checker checker; - - private ChangeEvent(final String pattern, final Checker c) { + private final Checker checker; + + ChangeEvent(String pattern, Checker c) { this.pattern = pattern; checker = c; } - - static String[] patterns; + + private static final String[] patterns; + static { patterns = new String[ChangeEvent.values().length]; - for (int i = 0; i < patterns.length; i++) { + for (int i = 0; i < patterns.length; i++) patterns[i] = values()[i].pattern; - } } } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private ChangeEvent event; - - @SuppressWarnings("null") + @Override - public boolean init(final Literal[] args, final int matchedPattern, final ParseResult parser) { + public boolean init(Literal[] args, int matchedPattern, ParseResult parser) { event = ChangeEvent.values()[matchedPattern]; return true; } @Override - public boolean check(final Event e) { + public boolean check(Event e) { if (!(e instanceof EntityChangeBlockEvent)) return false; return event.checker.check((EntityChangeBlockEvent) e); } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "" + event.name().toLowerCase(Locale.ENGLISH).replace('_', ' '); + public String toString(@Nullable Event e, boolean debug) { + return event.name().toLowerCase(Locale.ENGLISH).replace('_', ' '); } } diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index 5e82cfb297c..6524af3f2c7 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -32,6 +32,7 @@ import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -151,7 +152,13 @@ public boolean check(final Event e) { } else if (e instanceof CraftItemEvent) { is = ((CraftItemEvent) e).getRecipe().getResult(); } else if (hasPrepareCraftEvent && e instanceof PrepareItemCraftEvent) { - is = ((PrepareItemCraftEvent) e).getRecipe().getResult(); + PrepareItemCraftEvent event = (PrepareItemCraftEvent) e; + Recipe recipe = event.getRecipe(); + if (recipe != null) { + is = recipe.getResult(); + } else { + return false; + } } else if (e instanceof EntityPickupItemEvent) { is = ((EntityPickupItemEvent) e).getItem().getItemStack(); } else if (e instanceof PlayerPickupItemEvent) { @@ -170,6 +177,10 @@ public boolean check(final Event e) { assert false; return false; } + + if (is == null) + return false; + return types.check(e, new Checker() { @Override public boolean check(final ItemType t) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntities.java b/src/main/java/ch/njol/skript/expressions/ExprEntities.java index e65d6f257a2..5752b8ed98c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEntities.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEntities.java @@ -18,22 +18,6 @@ */ package ch.njol.skript.expressions; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -50,7 +34,20 @@ import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.CheckedIterator; -import ch.njol.util.coll.iterator.NonNullIterator; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; @Name("Entities") @Description("All entities in all worlds, in a specific world, in a chunk or in a radius around a certain location, " + @@ -138,7 +135,7 @@ protected Entity[] get(Event e) { if (isUsingRadius) { Iterator iter = iterator(e); if (iter == null || !iter.hasNext()) - return new Entity[0]; + return null; List l = new ArrayList<>(); while (iter.hasNext()) diff --git a/src/main/java/ch/njol/skript/expressions/ExprParse.java b/src/main/java/ch/njol/skript/expressions/ExprParse.java index 14df7ceaf02..72516bcf0e2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprParse.java +++ b/src/main/java/ch/njol/skript/expressions/ExprParse.java @@ -40,16 +40,12 @@ import ch.njol.skript.lang.VariableString; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.localization.Language; -import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.log.LogEntry; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.util.Kleenean; import ch.njol.util.NonNullPair; -/** - * @author Peter Güttinger - */ @Name("Parse") @Description({"Parses text as a given type, or as a given pattern.", "This expression can be used in two different ways: One which parses the entire text as a single instance of a type, e.g. as a number, " + @@ -61,38 +57,41 @@ "- You have to save the expression's value in a list variable, e.g. set {parsed::*} to message parsed as \"...\".", "- The list variable will contain the parsed values from all %types% in the pattern in order. If a type was plural, e.g. %items%, the variable's value at the respective index will be a list variable," + " e.g. the values will be stored in {parsed::1::*}, not {parsed::1}."}) -@Examples({"set {var} to line 1 parsed as number", - "on chat:", - " set {var::*} to message parsed as \"buying %items% for %money%\"", - " if parse error is set:", - " message \"%parse error%\"", - " else if {var::*} is set:", - " cancel event", - " remove {var::2} from the player's balance", - " give {var::1::*} to the player"}) +@Examples({ + "set {var} to line 1 parsed as number", + "on chat:", + "\tset {var::*} to message parsed as \"buying %items% for %money%\"", + "\tif parse error is set:", + "\t\tmessage \"%parse error%\"", + "\telse if {var::*} is set:", + "\t\tcancel event", + "\t\tremove {var::2} from the player's balance", + "\t\tgive {var::1::*} to the player" +}) @Since("2.0") public class ExprParse extends SimpleExpression { + static { Skript.registerExpression(ExprParse.class, Object.class, ExpressionType.COMBINED, "%string% parsed as (%-*classinfo%|\"<.*>\")"); } - + @Nullable static String lastError = null; - + @SuppressWarnings("NotNullFieldNotInitialized") private Expression text; - + @Nullable private String pattern; @Nullable private boolean[] plurals; - + @Nullable - private ClassInfo c; - - @SuppressWarnings({"unchecked", "null"}) + private ClassInfo classInfo; + @Override + @SuppressWarnings({"unchecked", "null"}) public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { text = (Expression) exprs[0]; if (exprs[1] == null) { @@ -101,13 +100,13 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye Skript.error("Invalid amount and/or placement of double quotes in '" + pattern + "'"); return false; } - + NonNullPair p = SkriptParser.validatePattern(pattern); if (p == null) return false; pattern = p.getFirst(); - - // escape '¦' + + // Escape '¦' and ':' (used for parser tags/marks) StringBuilder b = new StringBuilder(pattern.length()); for (int i = 0; i < pattern.length(); i++) { char c = pattern.charAt(i); @@ -115,48 +114,49 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye b.append(c); b.append(pattern.charAt(i + 1)); i++; - } else if (c == '¦') { - b.append("\\¦"); + } else if (c == '¦' || c == ':') { + b.append("\\"); + b.append(c); } else { b.append(c); } } - pattern = "" + b.toString(); - + pattern = b.toString(); + this.pattern = pattern; plurals = p.getSecond(); } else { - c = ((Literal>) exprs[1]).getSingle(); - if (c.getC() == String.class) { + classInfo = ((Literal>) exprs[1]).getSingle(); + if (classInfo.getC() == String.class) { Skript.error("Parsing as text is useless as only things that are already text may be parsed"); return false; } - Parser p = c.getParser(); + Parser p = classInfo.getParser(); if (p == null || !p.canParse(ParseContext.COMMAND)) { // TODO special parse context? - Skript.error("Text cannot be parsed as " + c.getName().withIndefiniteArticle()); + Skript.error("Text cannot be parsed as " + classInfo.getName().withIndefiniteArticle()); return false; } } return true; } - - @SuppressWarnings("null") + @Override @Nullable - protected Object[] get(Event e) { - String t = text.getSingle(e); + @SuppressWarnings("null") + protected Object[] get(Event event) { + String t = text.getSingle(event); if (t == null) return null; ParseLogHandler h = SkriptLogger.startParseLogHandler(); try { lastError = null; - - if (c != null) { - Parser p = c.getParser(); - assert p != null; // checked in init() - Object o = p.parse(t, ParseContext.COMMAND); + + if (classInfo != null) { + Parser parser = classInfo.getParser(); + assert parser != null; // checked in init() + Object o = parser.parse(t, ParseContext.COMMAND); if (o != null) { - Object[] one = (Object[]) Array.newInstance(c.getC(), 1); + Object[] one = (Object[]) Array.newInstance(classInfo.getC(), 1); one[0] = o; return one; } @@ -164,19 +164,19 @@ protected Object[] get(Event e) { assert pattern != null && plurals != null; ParseResult r = SkriptParser.parse(t, pattern); if (r != null) { - assert plurals.length == r.exprs.length; + assert plurals.length == r.exprs.length; int resultCount = 0; for (int i = 0; i < r.exprs.length; i++) { if (r.exprs[i] != null) // Ignore missing optional parts resultCount++; } - + Object[] os = new Object[resultCount]; for (int i = 0, slot = 0; i < r.exprs.length; i++) { if (r.exprs[i] != null) os[slot++] = plurals[i] ? r.exprs[i].getArray(null) : r.exprs[i].getSingle(null); } - + return os; } } @@ -184,8 +184,8 @@ protected Object[] get(Event e) { if (err != null) { lastError = err.toString(); } else { - if (c != null) { - lastError = t + " could not be parsed as " + c.getName().withIndefiniteArticle(); + if (classInfo != null) { + lastError = t + " could not be parsed as " + classInfo.getName().withIndefiniteArticle(); } else { lastError = t + " could not be parsed as \"" + pattern + "\""; } @@ -196,20 +196,20 @@ protected Object[] get(Event e) { h.printLog(); } } - + @Override public boolean isSingle() { return pattern == null; } - + @Override public Class getReturnType() { - return c != null ? c.getC() : Object[].class; + return classInfo != null ? classInfo.getC() : Object[].class; } - + @Override public String toString(@Nullable Event e, boolean debug) { - return text.toString(e, debug) + " parsed as " + (c != null ? c.toString(Language.F_INDEFINITE_ARTICLE) : pattern); + return text.toString(e, debug) + " parsed as " + (classInfo != null ? classInfo.toString(Language.F_INDEFINITE_ARTICLE) : pattern); } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprRelationalVariable.java b/src/main/java/ch/njol/skript/expressions/ExprRelationalVariable.java deleted file mode 100644 index 001c8d42084..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprRelationalVariable.java +++ /dev/null @@ -1,273 +0,0 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Comparator.Relation; -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.lang.Expression; -import ch.njol.skript.lang.ExpressionList; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Variable; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.registrations.Comparators; -import ch.njol.skript.registrations.Converters; -import ch.njol.skript.util.PersistentDataUtils; -import ch.njol.skript.util.Utils; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; - -@Name("Relational Variable") -@Description({"A relational variable is a variable stored on an entity, projectile, item, or certain blocks, and it can only be accessed using that entity.", - " See persistent data holder for a list of all holders.", - " Relational Variables will persist through a server restart, however, just like normal variables,", - " not all values can be stored permanently (e.g. entities). If the value can't be stored permanently,", - " it will be stored until the server is restarted." -}) -@Examples({"set {isAdmin} of player to true", - "set {oldNames::*} of player to \"Noob_Sl4yer\" and \"Skr1pt_M4st3r\""}) -@RequiredPlugins("1.14 or newer") -@Since("2.5") -@SuppressWarnings({"null", "unchecked"}) -public class ExprRelationalVariable extends SimpleExpression { - - static { - // Temporarily disabled until bugs are fixed - if (false && Skript.isRunningMinecraft(1, 14)) { - Skript.registerExpression(ExprRelationalVariable.class, Object.class, ExpressionType.PROPERTY, - "[(relational|relation( |-)based) variable[s]] %objects% of %persistentdataholders/itemtypes/blocks%" - ); - } - } - - private ExpressionList> variables; - private Expression holders; - - private ExprRelationalVariable source; - private Class[] types; - private Class superType; - - public ExprRelationalVariable() { - this(null, (Class) Object.class); - } - - private ExprRelationalVariable(ExprRelationalVariable source, Class... types) { - this.source = source; - if (source != null) { - this.variables = source.variables; - this.holders = source.holders; - } - this.types = types; - this.superType = (Class) Utils.getSuperType(types); - } - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - ExpressionList exprList = exprs[0] instanceof ExpressionList ? (ExpressionList) exprs[0] : new ExpressionList<>(new Expression[]{exprs[0]}, Object.class, false); - for (Expression expr : exprList.getExpressions()) { - if (!(expr instanceof Variable)) { // Input isn't a variable - return false; - } else if (((Variable) expr).isLocal()) { // Input is a variable, but it's local - Skript.error("Setting a relational variable using a local variable is not supported." - + " If you are trying to set a value temporarily, consider using metadata", ErrorQuality.SEMANTIC_ERROR - ); - return false; - } - } - variables = (ExpressionList>) exprList; - holders = (Expression) exprs[1]; - return true; - } - - @Override - @Nullable - public T[] get(Event e) { - List values = new ArrayList<>(); - Object[] holders = this.holders.getArray(e); - for (Expression expr : variables.getExpressions()) { - String varName = ((Variable) expr).getName().toString(e); - if (varName.contains(Variable.SEPARATOR)) { // It's a list - Collections.addAll(values, PersistentDataUtils.getList(varName, holders)); - } else { // It's a single variable - Collections.addAll(values, PersistentDataUtils.getSingle(varName, holders)); - } - } - try { - return Converters.convertArray(values.toArray(), types, superType); - } catch (ClassCastException ex) { - return (T[]) Array.newInstance(superType, 0); - } - } - - @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.RESET) - return null; - for (Expression expr : variables.getExpressions()) { - if (!((Variable) expr).isList()) { // It's a single variable - if (mode == ChangeMode.REMOVE_ALL) - return null; - return CollectionUtils.array(Object.class); - } - } - return CollectionUtils.array(Object[].class); - } - - @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (delta == null && mode != ChangeMode.DELETE) - return; - Object[] holders = this.holders.getArray(e); - switch (mode) { - case SET: - for (Expression expr : variables.getExpressions()) { - Variable var = (Variable) expr; - String varName = var.getName().toString(e); - if (var.isList()) { - varName = varName.replace("*", ""); - for (int i = 1; i <= delta.length; i++) // varName + i = var::i (e.g. exampleList::1, exampleList::2, etc.) - PersistentDataUtils.setList(varName + i, delta[i - 1], holders); - } else if (varName.contains(Variable.SEPARATOR)) { // Specific index of a list - PersistentDataUtils.setList(varName, delta[0], holders); - } else { // It's a single variable - PersistentDataUtils.setSingle(varName, delta[0], holders); - } - } - break; - case DELETE: - for (Expression expr : variables.getExpressions()) { - String varName = ((Variable) expr).getName().toString(e); - if (varName.contains(Variable.SEPARATOR)) { // It's a list - PersistentDataUtils.removeList(varName, holders); - } else { // It's a single variable - PersistentDataUtils.removeSingle(varName, holders); - } - } - break; - case ADD: - for (Expression expr : variables.getExpressions()) { - Variable var = (Variable) expr; - String varName = var.getName().toString(e); - if (var.isList()) { - varName = varName.replace("*", ""); - for (Object holder : holders) { - Set varIndexes = PersistentDataUtils.getListIndexes(varName + "*", holder); - if (varIndexes == null) { - // The list is empty, so we don't need to check for the next available index. - for (int i = 1; i <= delta.length; i++) { - // varName + i = var::i (e.g. exampleList::1, exampleList::2, etc.) - PersistentDataUtils.setList(varName + i, delta[i - 1], holder); - } - } else { - int start = 1 + varIndexes.size(); - for (Object value : delta) { - while (varIndexes.contains(String.valueOf(start))) - start++; - PersistentDataUtils.setList(varName + start, value, holder); - start++; - } - } - } - } else if (delta[0] instanceof Number) { - for (Object holder : holders) { - Object[] n = PersistentDataUtils.getSingle(varName, holder); - double oldValue = 0; - if (n.length != 0 && n[0] instanceof Number) - oldValue = ((Number) n[0]).doubleValue(); - PersistentDataUtils.setSingle(varName, oldValue + ((Number) delta[0]).doubleValue(), holder); - } - } - } - break; - case REMOVE: - case REMOVE_ALL: - for (Expression expr : variables.getExpressions()) { - Variable var = (Variable) expr; - String varName = var.getName().toString(e); - if (var.isList() || mode == ChangeMode.REMOVE_ALL) { - for (Object holder : holders) { - Map varMap = PersistentDataUtils.getListMap(varName, holder); - int sizeBefore = varMap.size(); - if (varMap != null) { - for (Object value : delta) - varMap.entrySet().removeIf(entry -> Relation.EQUAL.is(Comparators.compare(entry.getValue(), value))); - if (sizeBefore != varMap.size()) // It changed so we should set it - PersistentDataUtils.setListMap(varName, varMap, holder); - } - } - } else if (delta[0] instanceof Number) { - for (Object holder : holders) { - Object[] n = PersistentDataUtils.getSingle(varName, holder); - double oldValue = 0; - if (n.length != 0 && n[0] instanceof Number) - oldValue = ((Number) n[0]).doubleValue(); - PersistentDataUtils.setSingle(varName, oldValue - ((Number) delta[0]).doubleValue(), holder); - } - } - } - break; - case RESET: - assert false; - } - } - - @Override - public boolean isSingle() { - return variables.isSingle() && holders.isSingle(); - } - - @Override - public Class getReturnType() { - return superType; - } - - @Override - public Expression getConvertedExpression(Class... to) { - return new ExprRelationalVariable<>(this, to); - } - - @Override - public Expression getSource() { - return source == null ? this : source; - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return variables.toString(e, debug) + " of " + holders.toString(e, debug); - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprReversedList.java b/src/main/java/ch/njol/skript/expressions/ExprReversedList.java index 11b8d6b0034..3af5f3d93df 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprReversedList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprReversedList.java @@ -29,13 +29,11 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; @Name("Reversed List") @Description("Reverses given list.") @@ -50,6 +48,14 @@ public class ExprReversedList extends SimpleExpression { @SuppressWarnings("NotNullFieldNotInitialized") private Expression list; + @SuppressWarnings("unused") + public ExprReversedList() { + } + + public ExprReversedList(Expression list) { + this.list = list; + } + @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { list = LiteralUtils.defendExpression(exprs[0]); @@ -66,6 +72,20 @@ protected Object[] get(Event e) { return array; } + @Override + @Nullable + @SuppressWarnings("unchecked") + public Expression getConvertedExpression(Class... to) { + if (CollectionUtils.containsSuperclass(to, getReturnType())) + return (Expression) this; + + Expression convertedList = list.getConvertedExpression(to); + if (convertedList != null) + return (Expression) new ExprReversedList(convertedList); + + return null; + } + private void reverse(Object[] array) { for (int i = 0; i < array.length / 2; i++) { Object temp = array[i]; diff --git a/src/main/java/ch/njol/skript/expressions/ExprShuffledList.java b/src/main/java/ch/njol/skript/expressions/ExprShuffledList.java index 1db4729942f..edeee89739a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprShuffledList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprShuffledList.java @@ -18,15 +18,6 @@ */ package ch.njol.skript.expressions; -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import ch.njol.skript.util.LiteralUtils; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -36,7 +27,16 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; @Name("Shuffled List") @Description("Shuffles given list randomly. This is done by replacing indices by random numbers in resulting list.") @@ -51,6 +51,14 @@ public class ExprShuffledList extends SimpleExpression { @SuppressWarnings("NotNullFieldNotInitialized") private Expression list; + @SuppressWarnings("unused") + public ExprShuffledList() { + } + + public ExprShuffledList(Expression list) { + this.list = list; + } + @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { list = LiteralUtils.defendExpression(exprs[0]); @@ -69,7 +77,21 @@ protected Object[] get(Event e) { } @Override - public Class getReturnType() { + @Nullable + @SuppressWarnings("unchecked") + public Expression getConvertedExpression(Class... to) { + if (CollectionUtils.containsSuperclass(to, getReturnType())) + return (Expression) this; + + Expression convertedList = list.getConvertedExpression(to); + if (convertedList != null) + return (Expression) new ExprShuffledList(convertedList); + + return null; + } + + @Override + public Class getReturnType() { return list.getReturnType(); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java index 2e8f3506a18..a19779c0774 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java @@ -18,13 +18,6 @@ */ package ch.njol.skript.expressions; -import java.lang.reflect.Array; -import java.util.Arrays; - -import ch.njol.skript.util.LiteralUtils; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -34,7 +27,14 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.reflect.Array; +import java.util.Arrays; @Name("Sorted List") @Description({"Sorts given list in natural order. All objects in list must be comparable;", @@ -51,6 +51,14 @@ public class ExprSortedList extends SimpleExpression { @SuppressWarnings("NotNullFieldNotInitialized") private Expression list; + @SuppressWarnings("unused") + public ExprSortedList() { + } + + public ExprSortedList(Expression list) { + this.list = list; + } + @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { list = LiteralUtils.defendExpression(exprs[0]); @@ -67,7 +75,7 @@ protected Object[] get(Event e) { Object value = unsorted[i]; if (value instanceof Long) { // Hope it fits to the double... - sorted[i] = Double.valueOf(((Long) value).longValue()); + sorted[i] = (double) (Long) value; } else { // No conversion needed sorted[i] = value; @@ -82,6 +90,20 @@ protected Object[] get(Event e) { return sorted; } + @Override + @Nullable + @SuppressWarnings("unchecked") + public Expression getConvertedExpression(Class... to) { + if (CollectionUtils.containsSuperclass(to, getReturnType())) + return (Expression) this; + + Expression convertedList = list.getConvertedExpression(to); + if (convertedList != null) + return (Expression) new ExprSortedList(convertedList); + + return null; + } + @Override public boolean isSingle() { return false; diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java index 9a98fe96464..7e15ce2a5c3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java @@ -18,36 +18,39 @@ */ package ch.njol.skript.expressions; -import ch.njol.skript.classes.Changer.ChangeMode; -import org.bukkit.OfflinePlayer; -import org.bukkit.Statistic; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer; +import ch.njol.skript.classes.Changer.ChangeMode; 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.expressions.base.SimplePropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Timespan; -import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.OfflinePlayer; +import org.bukkit.Statistic; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; @Name("Time Played") -@Description("The amount of time a player has played for on the server. This info is stored in the player's statistics in " + - "the main world's data folder. Changing this will also change the player's stats which can be views in the client's statistics menu.") -@Examples({"set {_t} to time played of player", +@Description({ + "The amount of time a player has played for on the server. This info is stored in the player's statistics in " + + "the main world's data folder. Changing this will also change the player's stats which can be views in the client's statistics menu.", + "Using this expression on offline players on Minecraft 1.14 and below will return nothing <none>." +}) +@Examples({ + "set {_t} to time played of player", "if player's time played is greater than 10 minutes:", "\tgive player a diamond sword", - "set player's time played to 0 seconds"}) -@Since("2.5") + "", + "set player's time played to 0 seconds" +}) +@RequiredPlugins("MC 1.15+ (offline players)") +@Since("2.5, INSERT VERSION (offline players)") public class ExprTimePlayed extends SimplePropertyExpression { + private static final boolean IS_OFFLINE_SUPPORTED = Skript.methodExists(OfflinePlayer.class, "getStatistic", Statistic.class); private static final Statistic TIME_PLAYED; static { @@ -58,35 +61,36 @@ public class ExprTimePlayed extends SimplePropertyExpression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - setExpr((Expression) exprs[0]); - return true; - } - + @Nullable @Override public Timespan convert(OfflinePlayer offlinePlayer) { - return Timespan.fromTicks_i(offlinePlayer.getStatistic(TIME_PLAYED)); + return getTimePlayed(offlinePlayer); } - + @Nullable @Override public Class[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.ADD || mode == ChangeMode.REMOVE) { + if (mode == ChangeMode.SET || mode == ChangeMode.ADD || mode == ChangeMode.REMOVE) return CollectionUtils.array(Timespan.class); - } else { - return null; - } + return null; } - + @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (delta == null) + return; + long ticks = ((Timespan) delta[0]).getTicks_i(); - for (OfflinePlayer offlinePlayer : getExpr().getArray(e)) { - long playerTicks = offlinePlayer.getStatistic(TIME_PLAYED); + for (OfflinePlayer offlinePlayer : getExpr().getArray(event)) { + if (!IS_OFFLINE_SUPPORTED && !offlinePlayer.isOnline()) + continue; + + Timespan playerTimespan = getTimePlayed(offlinePlayer); + if (playerTimespan == null) + continue; + + long playerTicks = playerTimespan.getTicks_i(); switch (mode) { case ADD: ticks = playerTicks + ticks; @@ -95,18 +99,32 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { ticks = playerTicks - ticks; break; } - offlinePlayer.setStatistic(TIME_PLAYED, (int) ticks); + if (IS_OFFLINE_SUPPORTED) { + offlinePlayer.setStatistic(TIME_PLAYED, (int) ticks); + } else if (offlinePlayer.isOnline()) { + offlinePlayer.getPlayer().setStatistic(TIME_PLAYED, (int) ticks); // No NPE due to isOnline check + } } } - + @Override public Class getReturnType() { return Timespan.class; } - + @Override protected String getPropertyName() { return "time played"; } - + + @Nullable + private Timespan getTimePlayed(OfflinePlayer offlinePlayer) { + if (IS_OFFLINE_SUPPORTED) { + return Timespan.fromTicks_i(offlinePlayer.getStatistic(TIME_PLAYED)); + } else if (offlinePlayer.isOnline()) { + return Timespan.fromTicks_i(offlinePlayer.getPlayer().getStatistic(TIME_PLAYED)); + } + return null; + } + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprTool.java b/src/main/java/ch/njol/skript/expressions/ExprTool.java index 4f32c010960..150cebc97af 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTool.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTool.java @@ -82,7 +82,7 @@ protected Slot[] get(final Event e, final LivingEntity[] source) { @Nullable public Slot get(final LivingEntity ent) { if (!delayed) { - if (e instanceof PlayerItemHeldEvent && ((PlayerItemHeldEvent) e).getPlayer() == ent) { + if (!offHand && e instanceof PlayerItemHeldEvent && ((PlayerItemHeldEvent) e).getPlayer() == ent) { final PlayerInventory i = ((PlayerItemHeldEvent) e).getPlayer().getInventory(); return new InventorySlot(i, getTime() >= 0 ? ((PlayerItemHeldEvent) e).getNewSlot() : ((PlayerItemHeldEvent) e).getPreviousSlot()); } else if (e instanceof PlayerBucketEvent && ((PlayerBucketEvent) e).getPlayer() == ent) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVelocity.java b/src/main/java/ch/njol/skript/expressions/ExprVelocity.java index d776264bcd7..1745acabbe3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVelocity.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVelocity.java @@ -51,6 +51,7 @@ public Vector convert(Entity e) { } @Override + @Nullable @SuppressWarnings("null") public Class[] acceptChange(ChangeMode mode) { if ((mode == ChangeMode.ADD || mode == ChangeMode.REMOVE || mode == ChangeMode.SET || mode == ChangeMode.DELETE || mode == ChangeMode.RESET)) @@ -61,7 +62,7 @@ public Class[] acceptChange(ChangeMode mode) { @Override @SuppressWarnings("null") public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - assert delta != null; + assert mode == ChangeMode.DELETE || mode == ChangeMode.RESET || delta != null; for (final Entity entity : getExpr().getArray(e)) { if (entity == null) return; diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index 30615c4f955..c95a7c3c389 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -140,7 +140,7 @@ public static VariableString newInstance(String s) { * @return Whether the string is quoted correctly */ public static boolean isQuotedCorrectly(String s, boolean withQuotes) { - if (withQuotes && (!s.startsWith("\"") || !s.endsWith("\""))) + if (withQuotes && (!s.startsWith("\"") || !s.endsWith("\"") || s.length() < 2)) return false; boolean quote = false; boolean percentage = false; diff --git a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java index 2b686cde2ba..e585c3521e3 100644 --- a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java +++ b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java @@ -18,54 +18,82 @@ */ package ch.njol.skript.lang.function; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.registrations.Converters; +import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ public class ExprFunctionCall extends SimpleExpression { - private final FunctionReference function; - - public ExprFunctionCall(final FunctionReference function) { + private final FunctionReference function; + private final Class[] returnTypes; + private final Class returnType; + + public ExprFunctionCall(FunctionReference function) { + this(function, function.returnTypes); + } + + @SuppressWarnings("unchecked") + public ExprFunctionCall(FunctionReference function, Class[] expectedReturnTypes) { this.function = function; + Class functionReturnType = function.getReturnType(); + assert functionReturnType != null; + if (CollectionUtils.containsSuperclass(expectedReturnTypes, functionReturnType)) { + // Function returns expected type already + this.returnTypes = new Class[] {functionReturnType}; + this.returnType = (Class) functionReturnType; + } else { + // Return value needs to be converted + this.returnTypes = expectedReturnTypes; + this.returnType = (Class) Utils.getSuperType(expectedReturnTypes); + } } - + @Override @Nullable - protected T[] get(final Event e) { - T[] returnValue = function.execute(e); + protected T[] get(Event e) { + Object[] returnValue = function.execute(e); function.resetReturnValue(); - return returnValue; + return Converters.convertArray(returnValue, returnTypes, returnType); + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + public Expression getConvertedExpression(Class... to) { + if (CollectionUtils.containsSuperclass(to, getReturnType())) + return (Expression) this; + assert function.getReturnType() != null; + if (Converters.converterExists(function.getReturnType(), to)) { + return new ExprFunctionCall<>(function, to); + } + return null; } - + @Override public boolean isSingle() { return function.isSingle(); } - + @Override public Class getReturnType() { - Class type = function.getReturnType(); - assert type != null : "validateFunction() let invalid reference pass"; - return type; + return returnType; } - + @Override - public String toString(@Nullable final Event e, final boolean debug) { + public String toString(@Nullable Event e, boolean debug) { return function.toString(e, debug); } - + @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { assert false; return false; } - + } diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index 8027d848316..d6d0d10f80d 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -29,6 +29,7 @@ import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; +import ch.njol.skript.registrations.Converters; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; @@ -84,7 +85,7 @@ public class FunctionReference { * of the function signature. */ @Nullable - private final Class[] returnTypes; + final Class[] returnTypes; /** * Node for {@link #validateFunction(boolean)} to use for logging. @@ -147,7 +148,7 @@ public boolean validateFunction(boolean first) { } return false; } - if (!CollectionUtils.containsAnySuperclass(returnTypes, rt.getC())) { + if (!Converters.converterExists(rt.getC(), returnTypes)) { if (first) { Skript.error("The returned value of the function '" + functionName + "', " + sign.returnType + ", is " + SkriptParser.notOfType(returnTypes) + "."); } else { @@ -224,6 +225,16 @@ public boolean validateFunction(boolean first) { function = previousFunction; } return false; + } else if (p.single && !e.isSingle()) { + if (first) { + Skript.error("The " + StringUtils.fancyOrderNumber(i + 1) + " argument given to the function '" + functionName + "' is plural, " + + "but a single argument was expected"); + } else { + Skript.error("The function '" + functionName + "' was redefined with different, incompatible arguments, but is still used in other script(s)." + + " These will continue to use the old version of the function until Skript restarts."); + function = previousFunction; + } + return false; } parameters[i] = e; } finally { diff --git a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java index bdeb7f5dc09..418ff05cdf7 100644 --- a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java @@ -165,9 +165,9 @@ public String toString() { if (isNullable) stringBuilder.append("-"); if (flagMask != ~0) { - if ((flagMask & SkriptParser.PARSE_LITERALS) != 0) + if ((flagMask & SkriptParser.PARSE_LITERALS) == 0) stringBuilder.append("~"); - else if ((flagMask & SkriptParser.PARSE_EXPRESSIONS) != 0) + else if ((flagMask & SkriptParser.PARSE_EXPRESSIONS) == 0) stringBuilder.append("*"); } for (int i = 0; i < classes.length; i++) { diff --git a/src/main/java/ch/njol/skript/registrations/EventValues.java b/src/main/java/ch/njol/skript/registrations/EventValues.java index 7201dc86081..82089400a71 100644 --- a/src/main/java/ch/njol/skript/registrations/EventValues.java +++ b/src/main/java/ch/njol/skript/registrations/EventValues.java @@ -203,7 +203,7 @@ public static T getEventValue(E e, Class c, int time) { *

* Can print an error if the event value is blocked for the given event. * - * @param e the event class the getter will be getting from + * @param event the event class the getter will be getting from * @param c type of getter * @param time the event-value's time * @return A getter to get values for a given type of events @@ -211,118 +211,139 @@ public static T getEventValue(E e, Class c, int time) { * @see EventValueExpression#EventValueExpression(Class) */ @Nullable - public static Getter getEventValueGetter(Class e, Class c, int time) { - return getEventValueGetter(e, c, time, true); + public static Getter getEventValueGetter(Class event, Class c, int time) { + return getEventValueGetter(event, c, time, true); } @SuppressWarnings("unchecked") @Nullable - private static Getter getEventValueGetter(Class e, Class c, int time, boolean allowDefault) { + private static Getter getEventValueGetter(Class event, Class c, int time, boolean allowDefault) { List> eventValues = getEventValuesList(time); // First check for exact classes matching the parameters. - for (EventValueInfo ev : eventValues) { - if (!c.equals(ev.c)) + for (EventValueInfo eventValueInfo : eventValues) { + if (!c.equals(eventValueInfo.c)) continue; - if (!checkExcludes(ev, e)) + if (!checkExcludes(eventValueInfo, event)) return null; - if (ev.event.isAssignableFrom(e)) - return (Getter) ev.getter; + if (eventValueInfo.event.isAssignableFrom(event)) + return (Getter) eventValueInfo.getter; } // Second check for assignable subclasses. - for (EventValueInfo ev : eventValues) { - if (!c.isAssignableFrom(ev.c)) + for (EventValueInfo eventValueInfo : eventValues) { + if (!c.isAssignableFrom(eventValueInfo.c)) continue; - if (!checkExcludes(ev, e)) + if (!checkExcludes(eventValueInfo, event)) return null; - if (ev.event.isAssignableFrom(e)) - return (Getter) ev.getter; - if (!e.isAssignableFrom(ev.event)) + if (eventValueInfo.event.isAssignableFrom(event)) + return (Getter) eventValueInfo.getter; + if (!event.isAssignableFrom(eventValueInfo.event)) continue; return new Getter() { @Override @Nullable public T get(E event) { - if (!ev.event.isInstance(event)) + if (!eventValueInfo.event.isInstance(event)) return null; - return ((Getter) ev.getter).get(event); + return ((Getter) eventValueInfo.getter).get(event); } }; } // Most checks have returned before this below is called, but Skript will attempt to convert or find an alternative. // Third check is if the returned object matches the class. - for (EventValueInfo ev : eventValues) { - if (!ev.c.isAssignableFrom(c)) + for (EventValueInfo eventValueInfo : eventValues) { + if (!eventValueInfo.c.isAssignableFrom(c)) continue; - boolean checkInstanceOf = !ev.event.isAssignableFrom(e); - if (checkInstanceOf && !e.isAssignableFrom(ev.event)) + boolean checkInstanceOf = !eventValueInfo.event.isAssignableFrom(event); + if (checkInstanceOf && !event.isAssignableFrom(eventValueInfo.event)) continue; - if (!checkExcludes(ev, e)) + if (!checkExcludes(eventValueInfo, event)) return null; return new Getter() { @Override @Nullable public T get(E event) { - if (checkInstanceOf && !ev.event.isInstance(event)) + if (checkInstanceOf && !eventValueInfo.event.isInstance(event)) return null; - Object object = ((Getter) ev.getter).get(event); + Object object = ((Getter) eventValueInfo.getter).get(event); if (c.isInstance(object)) return (T) object; return null; } }; } - // Fourth check will attempt to convert the event value to the type. - for (EventValueInfo ev : eventValues) { - boolean checkInstanceOf = !ev.event.isAssignableFrom(e); - if (checkInstanceOf && !e.isAssignableFrom(ev.event)) + // Fourth check will attempt to convert the event value to the requesting type. + // This first for loop will check that the events are exact. See issue #5016 + for (EventValueInfo eventValueInfo : eventValues) { + if (!event.equals(eventValueInfo.event)) continue; - Getter getter = (Getter) getConvertedGetter(ev, c, checkInstanceOf); + Getter getter = (Getter) getConvertedGetter(eventValueInfo, c, false); if (getter == null) continue; - if (!checkExcludes(ev, e)) + if (!checkExcludes(eventValueInfo, event)) + return null; + return getter; + } + // This loop will attempt to look for converters assignable to the class of the provided event. + for (EventValueInfo eventValueInfo : eventValues) { + // The requesting event must be assignable to the event value's event. Otherwise it'll throw an error. + if (!event.isAssignableFrom(eventValueInfo.event)) + continue; + + Getter getter = (Getter) getConvertedGetter(eventValueInfo, c, true); + if (getter == null) + continue; + + if (!checkExcludes(eventValueInfo, event)) return null; return getter; } // If the check should try again matching event values with a 0 time (most event values). if (allowDefault && time != 0) - return getEventValueGetter(e, c, 0, false); + return getEventValueGetter(event, c, 0, false); return null; } /** * Check if the event value states to exclude events. * - * @param ev - * @param e + * @param info The event value info that will be used to grab the value from + * @param event The event class to check the excludes against. * @return boolean if true the event value passes for the events. */ - @SuppressWarnings("unchecked") - private static boolean checkExcludes(EventValueInfo ev, Class e) { - if (ev.excludes == null) + private static boolean checkExcludes(EventValueInfo info, Class event) { + if (info.excludes == null) return true; - for (Class ex : (Class[]) ev.excludes) { - if (ex.isAssignableFrom(e)) { - Skript.error(ev.excludeErrorMessage); + for (Class ex : (Class[]) info.excludes) { + if (ex.isAssignableFrom(event)) { + Skript.error(info.excludeErrorMessage); return false; } } return true; } - + + /** + * Return a converter wrapped in a getter that will grab the requested value by converting from the given event value info. + * + * @param info The event value info that will be used to grab the value from + * @param to The class that the converter will look for to convert the type from the event value to + * @param checkInstanceOf If the event must be an exact instance of the event value info's event or not. + * @return The found Converter wrapped in a Getter object, or null if no Converter was found. + */ @Nullable - private static Getter getConvertedGetter(EventValueInfo i, Class to, boolean checkInstanceOf) { - Converter converter = Converters.getConverter(i.c, to); + private static Getter getConvertedGetter(EventValueInfo info, Class to, boolean checkInstanceOf) { + Converter converter = Converters.getConverter(info.c, to); if (converter == null) return null; return new Getter() { @Override @Nullable public T get(E e) { - if (checkInstanceOf && !i.event.isInstance(e)) + if (checkInstanceOf && !info.event.isInstance(e)) return null; - F f = i.getter.get(e); + F f = info.getter.get(e); if (f == null) return null; return converter.convert(f); diff --git a/src/main/java/ch/njol/skript/util/ListVariablePersistentDataType.java b/src/main/java/ch/njol/skript/util/ListVariablePersistentDataType.java deleted file mode 100644 index 5d2185ded6f..00000000000 --- a/src/main/java/ch/njol/skript/util/ListVariablePersistentDataType.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import org.bukkit.persistence.PersistentDataAdapterContext; -import org.bukkit.persistence.PersistentDataType; - -import ch.njol.skript.variables.SerializedVariable.Value; - -/** - * This {@link PersistentDataType} is used for list variables. - * In this case, a list variable is any variable containing "::" (the separator) - * The map's key is the variable's index and the map's value is the index's value. - * With this {@link PersistentDataType}, the NamespacedKey's key is the rest of the list variable. - * e.g. {one::two::three} where "one//two" would be the {@link org.bukkit.NamespacedKey}'s key and "three" the key for the map. - * @see PersistentDataUtils#getNamespacedKey(String) - * @see PersistentDataUtils - * @author APickledWalrus - */ -public final class ListVariablePersistentDataType implements PersistentDataType> { - - // This is how many bytes an int is. - private final int INT_LENGTH = 4; - - // Charset used for converting bytes and Strings - @SuppressWarnings("null") - private final Charset SERIALIZED_CHARSET = StandardCharsets.UTF_8; - - @Override - public Class getPrimitiveType() { - return byte[].class; - } - - @SuppressWarnings("unchecked") - @Override - public Class> getComplexType() { - return (Class>) (Class) Map.class; - } - - @SuppressWarnings("null") - @Override - public byte[] toPrimitive(Map complex, PersistentDataAdapterContext context) { - int bufferLength = 0; - - for (Entry entry : complex.entrySet()) { - // Store it: index -> type -> data - bufferLength += INT_LENGTH + entry.getKey().getBytes(SERIALIZED_CHARSET).length - + INT_LENGTH + entry.getValue().type.getBytes(SERIALIZED_CHARSET).length - + INT_LENGTH + entry.getValue().data.length; - } - - ByteBuffer bb = ByteBuffer.allocate(bufferLength); - - for (Entry entry : complex.entrySet()) { - byte[] indexBytes = entry.getKey().getBytes(SERIALIZED_CHARSET); - byte[] typeBytes = entry.getValue().type.getBytes(SERIALIZED_CHARSET); - - bb.putInt(indexBytes.length); - bb.put(indexBytes); - - bb.putInt(typeBytes.length); - bb.put(typeBytes); - - bb.putInt(entry.getValue().data.length); - bb.put(entry.getValue().data); - } - - return bb.array(); - } - - @Override - public Map fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) { - ByteBuffer bb = ByteBuffer.wrap(primitive); - - HashMap values = new HashMap<>(); - - while (bb.hasRemaining()) { - int indexLength = bb.getInt(); - byte[] indexBytes = new byte[indexLength]; - bb.get(indexBytes, 0, indexLength); - String index = new String(indexBytes, SERIALIZED_CHARSET); - - int typeLength = bb.getInt(); - byte[] typeBytes = new byte[typeLength]; - bb.get(typeBytes, 0, typeLength); - String type = new String(typeBytes, SERIALIZED_CHARSET); - - int dataLength = bb.getInt(); - byte[] dataBytes = new byte[dataLength]; - bb.get(dataBytes, 0, dataLength); - - values.put(index, new Value(type, dataBytes)); - } - - return values; - } - -} diff --git a/src/main/java/ch/njol/skript/util/LiteralUtils.java b/src/main/java/ch/njol/skript/util/LiteralUtils.java index 3f0ce4c31e6..c004157b48e 100644 --- a/src/main/java/ch/njol/skript/util/LiteralUtils.java +++ b/src/main/java/ch/njol/skript/util/LiteralUtils.java @@ -43,9 +43,16 @@ public class LiteralUtils { @SuppressWarnings("unchecked") public static Expression defendExpression(Expression expr) { if (expr instanceof ExpressionList) { - Expression[] expressions = ((ExpressionList) expr).getExpressions(); - for (int i = 0; i < expressions.length; i++) - expressions[i] = LiteralUtils.defendExpression(expressions[i]); + Expression[] oldExpressions = ((ExpressionList) expr).getExpressions(); + + Expression[] newExpressions = new Expression[oldExpressions.length]; + Class[] returnTypes = new Class[oldExpressions.length]; + + for (int i = 0; i < oldExpressions.length; i++) { + newExpressions[i] = LiteralUtils.defendExpression(oldExpressions[i]); + returnTypes[i] = newExpressions[i].getReturnType(); + } + return new ExpressionList<>(newExpressions, (Class) Utils.getSuperType(returnTypes), expr.getAnd()); } else if (expr instanceof UnparsedLiteral) { Literal parsedLiteral = ((UnparsedLiteral) expr).getConvertedExpression(Object.class); return (Expression) (parsedLiteral == null ? expr : parsedLiteral); diff --git a/src/main/java/ch/njol/skript/util/PersistentDataUtils.java b/src/main/java/ch/njol/skript/util/PersistentDataUtils.java deleted file mode 100644 index 7feeb02398e..00000000000 --- a/src/main/java/ch/njol/skript/util/PersistentDataUtils.java +++ /dev/null @@ -1,615 +0,0 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Base64; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.block.TileState; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.metadata.MetadataValue; -import org.bukkit.metadata.Metadatable; -import org.bukkit.persistence.PersistentDataHolder; -import org.bukkit.persistence.PersistentDataType; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.conditions.CondHasRelationalVariable; -import ch.njol.skript.expressions.ExprRelationalVariable; -import ch.njol.skript.lang.Variable; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.variables.SerializedVariable.Value; - -/** - * This class allows Persistent Data to work properly with Skript. - * In Skript, Persistent Data is formatted like variables. - * This looks like: set persistent data {isAdmin} of player to true - * @author APickledWalrus - * @see SingleVariablePersistentDataType - * @see ListVariablePersistentDataType - * @see ExprRelationalVariable - * @see CondHasRelationalVariable - */ -public class PersistentDataUtils { - - private final static PersistentDataType SINGLE_VARIABLE_TYPE = new SingleVariablePersistentDataType(); - private final static PersistentDataType> LIST_VARIABLE_TYPE = new ListVariablePersistentDataType(); - - /* - * General Utility Methods - */ - - /** - * For a {@link Block} or an {@link ItemType}, only parts/some of them are actually a {@link PersistentDataHolder}. - * This gets the part that is a {@link PersistentDataHolder} from those types (e.g. ItemMeta or TileState). - * @param holders A {@link PersistentDataHolder}, a {@link Block}, or an {@link ItemType}. - * @return A map keyed by the unconverted holder with the converted holder as its value. - */ - private static Map getConvertedHolders(Object[] holders) { - Map actualHolders = new HashMap<>(); - for (Object holder : holders) { - if (holder instanceof PersistentDataHolder) { - actualHolders.put(holder, (PersistentDataHolder) holder); - } else if (holder instanceof ItemType) { - actualHolders.put(holder, ((ItemType) holder).getItemMeta()); - } else if (holder instanceof Block && ((Block) holder).getState() instanceof TileState) { - actualHolders.put(holder, ((TileState) ((Block) holder).getState())); - } - } - return actualHolders; - } - - /** - * This returns a {@link NamespacedKey} from the provided name with Skript as the namespace being used. - * The name will be encoded in Base64 to make sure the key name is valid. - * @param name The name to convert - * @return The created {@link NamespacedKey} - */ - @SuppressWarnings("null") - public static NamespacedKey getNamespacedKey(String name) { - // Encode the name in Base64 to make sure the key name is valid - name = Base64.getEncoder().encodeToString(name.getBytes(StandardCharsets.UTF_8)).replace('=', '_').replace('+', '.'); - return new NamespacedKey(Skript.getInstance(), name); - } - - /* - * Single Variable Modification Methods - */ - - /** - * Gets the Persistent Data Tag's value of the given single variable name from the given holder. - * If the value set was not serializable, it was set under Metadata and is retrieved from Metadata here. - * @param name The name of the single variable (e.g. "myVariable" from {myVariable}) - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @return The Persistent Data Tag's value from each holder, or an empty list if no values could be retrieved. - * @see PersistentDataUtils#setSingle(String, Object, Object...) - * @see PersistentDataUtils#removeSingle(String, Object...) - */ - public static Object[] getSingle(String name, Object... holders) { - if (name.contains(Variable.SEPARATOR)) // This is a list variable.. - return new Object[0]; - - Map actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return new Object[0]; - - name = "!!SINGLE!!" + name; - NamespacedKey key = getNamespacedKey(name); - - List returnValues = new ArrayList<>(); - for (Entry entry : actualHolders.entrySet()) { - Object holder = entry.getKey(); - PersistentDataHolder actualHolder = entry.getValue(); - if (actualHolder.getPersistentDataContainer().has(key, SINGLE_VARIABLE_TYPE)) { - Value value = actualHolder.getPersistentDataContainer().get(key, SINGLE_VARIABLE_TYPE); - if (value != null) - returnValues.add(Classes.deserialize(value.type, value.data)); - } - // Try to get as Metadata instead - if (holder instanceof Metadatable) { - List values = ((Metadatable) holder).getMetadata(name); - for (MetadataValue mv : values) { - if (mv.getOwningPlugin() == Skript.getInstance()) // Get the latest value set by Skript - returnValues.add(mv.value()); - } - } - } - - return returnValues.toArray(); - } - - /** - * Sets the Persistent Data Tag from the given name and value for the given holder. - * @param name The name of the single variable (e.g. "myVariable" from {myVariable}) - * @param value The value for the Persistent Data Tag to be set to. - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * If this value is not serializable (see {@link Classes#serialize(Object)}), this value will be set under Metadata. - * @see PersistentDataUtils#getSingle(String, Object...) - * @see PersistentDataUtils#removeSingle(String, Object...) - */ - public static void setSingle(String name, Object value, Object... holders) { - if (name.contains(Variable.SEPARATOR)) // This is a list variable.. - return; - - Map actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return; - - name = "!!SINGLE!!" + name; - Value serialized = Classes.serialize(value); - - if (serialized != null) { // Can be serialized, set as Persistent Data - NamespacedKey key = getNamespacedKey(name); - for (Entry entry : actualHolders.entrySet()) { - PersistentDataHolder actualHolder = entry.getValue(); - - actualHolder.getPersistentDataContainer().set(key, SINGLE_VARIABLE_TYPE, serialized); - - // This is to store the data on the ItemType or TileState - Object holder = entry.getKey(); - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } - } else { // Set as Metadata instead - for (Object holder : actualHolders.keySet()) { - if (holder instanceof Metadatable) - ((Metadatable) holder).setMetadata(name, new FixedMetadataValue(Skript.getInstance(), value)); - } - } - } - - /** - * Removes the Persistent Data Tag's value for the given holder(s) from the given name and value. - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The name of the single variable (e.g. "myVariable" from {myVariable}) - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @see PersistentDataUtils#getSingle(String, Object...) - * @see PersistentDataUtils#setSingle(String, Object, Object...) - */ - public static void removeSingle(String name, Object... holders) { - if (name.contains(Variable.SEPARATOR)) // This is a list variable.. - return; - - Map actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return; - - name = "!!SINGLE!!" + name; - NamespacedKey key = getNamespacedKey(name); - - for (Entry entry : actualHolders.entrySet()) { - Object holder = entry.getKey(); - PersistentDataHolder actualHolder = entry.getValue(); - - if (actualHolder.getPersistentDataContainer().has(key, SINGLE_VARIABLE_TYPE)) { // Can be serialized, try to remove Persistent Data - actualHolder.getPersistentDataContainer().remove(key); - - // This is to store the data on the ItemType or TileState - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } else if (holder instanceof Metadatable) { // Try to remove Metadata instead - ((Metadatable) holder).removeMetadata(name, Skript.getInstance()); - } - } - } - - /* - * List Variable Modification Methods - */ - - /** - * Gets the Persistent Data Tag's value of the given list variable name from the given holder(s). - * This method may return a single value, or multiple, depending on the given name. - * If the value set was not serializable, it was set under Metadata and is retrieved from Metadata here. - * @param name The name of the list variable (e.g. "myList::*" from {myList::*}) - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @return The Persistent Data Tag's value(s) from the holder, or an empty array if: - * the holder was invalid, the name was invalid, the key was invalid, or if no value(s) could be found. - * @see PersistentDataUtils#setList(String, Object, Object...) - * @see PersistentDataUtils#removeList(String, Object...) - * @see PersistentDataUtils#getListMap(String, Object) - */ - @SuppressWarnings("null") - public static Object[] getList(String name, Object... holders) { - if (!name.contains(Variable.SEPARATOR)) // This is a single variable.. - return new Object[0]; - - // Format the variable for getListMap (e.g. {varName::*}) - // We don't need to worry about the name being invalid, as getListMap will handle that - String listName = name; - if (!name.endsWith("*")) - listName = name.substring(0, name.lastIndexOf(Variable.SEPARATOR) + 2) + "*"; - - List returnValues = new ArrayList<>(); - for (Object holder : holders) { - Map listVar = getListMap(listName, holder); - if (listVar == null) // One of our values was invalid - continue; - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - if (index.equals("*")) { // Return all values - returnValues.addAll(listVar.values()); - } else if (listVar.containsKey(index)){ // Return the value under the given index (if it exists) - returnValues.add(listVar.get(index)); - } - } - return returnValues.toArray(); - } - - /** - * Sets the Persistent Data Tag's value for the given holder(s) from the given list variable name and value. - * @param name The name of the list variable (e.g. "myList::*" from {myList::*}) - * If the index of the name is "*", then the index set in the list will be "1". - * To set a different index, format the list variable like normal (e.g. "myList::index" from {myList::index}) - * @param value The value for the Persistent Data Tag to be set to. - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * If this value is not serializable (see {@link Classes#serialize(Object)}), this value will be set under Metadata. - * @see PersistentDataUtils#getList(String, Object...) - * @see PersistentDataUtils#removeSingle(String, Object...) - * @see PersistentDataUtils#setListMap(String, Map, Object) - */ - @SuppressWarnings("unchecked") - public static void setList(String name, Object value, Object... holders) { - if (!name.contains(Variable.SEPARATOR)) // This is a single variable.. - return; - - Map actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return; - - Value serialized = Classes.serialize(value); - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - - if (serialized != null) { // Can be serialized, set as Persistent Data - NamespacedKey key = getNamespacedKey(keyName); - for (Entry entry : actualHolders.entrySet()) { - PersistentDataHolder actualHolder = entry.getValue(); - - Map values = actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE); - if (values == null) - values = new HashMap<>(); - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - if (index.equals("*")) { // Clear map and set value - values.clear(); - values.put("1", serialized); - } else { - values.put(index, serialized); - } - - actualHolder.getPersistentDataContainer().set(key, LIST_VARIABLE_TYPE, values); - - // This is to store the data on the ItemType or TileState - Object holder = entry.getKey(); - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } - } else { // Try to set as Metadata instead - for (Object holder : actualHolders.keySet()) { - if (holder instanceof Metadatable) { - Metadatable mHolder = (Metadatable) holder; - Map mMap = null; - for (MetadataValue mv : mHolder.getMetadata(keyName)) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance()) { - mMap = (Map) mv.value(); - break; - } - } - if (mMap == null) - mMap = new HashMap<>(); - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - if (index.equals("*")) { // Clear map and set value - mMap.clear(); - mMap.put("1", value); - } else { - mMap.put(index, value); - } - - mHolder.setMetadata(keyName, new FixedMetadataValue(Skript.getInstance(), mMap)); - } - } - } - } - - /** - * Removes the value of the Persistent Data Tag of the given name for the given holder(s). - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The name of the list variable (e.g. "myList::*" from {myList::*}) - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * If the index of the name is "*", then the entire list will be cleared. - * To remove a specific index, format the list variable like normal (e.g. "myList::index" from {myList::index}) - * @see PersistentDataUtils#getList(String, Object...) - * @see PersistentDataUtils#setList(String, Object, Object...) - */ - @SuppressWarnings({"unchecked"}) - public static void removeList(String name, Object... holders) { - if (!name.contains(Variable.SEPARATOR)) // This is a single variable.. - return; - - Map actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return; - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - NamespacedKey key = getNamespacedKey(keyName); - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - - for (Entry entry : actualHolders.entrySet()) { - Object holder = entry.getKey(); - PersistentDataHolder actualHolder = entry.getValue(); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { - if (index.equals("*")) { // Remove the whole thing - actualHolder.getPersistentDataContainer().remove(key); - } else { // Remove just some - Map values = actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE); - if (values != null) { - values.remove(index); - if (values.isEmpty()) { // No point in storing an empty map. The last value was removed. - actualHolder.getPersistentDataContainer().remove(key); - } else { - actualHolder.getPersistentDataContainer().set(key, LIST_VARIABLE_TYPE, values); - } - } - } - - // This is to store the data on the ItemType or TileState - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } else if (holder instanceof Metadatable) { // Try metadata - Metadatable mHolder = (Metadatable) holder; - - if (index.equals("*")) { // Remove ALL values - mHolder.removeMetadata(keyName, Skript.getInstance()); - } else { // Remove just one - List mValues = mHolder.getMetadata(keyName); - - if (!mValues.isEmpty()) { - Map mMap = null; - for (MetadataValue mv : mValues) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance()) { - mMap = (Map) mv.value(); - break; - } - } - - if (mMap != null) { - mMap.remove(index); - if (mMap.isEmpty()) { // No point in storing an empty map. The last value was removed. - mHolder.removeMetadata(keyName, Skript.getInstance()); - } else { - mHolder.setMetadata(keyName, new FixedMetadataValue(Skript.getInstance(), mMap)); - } - } - } - } - } - } - } - - /** - * Returns the map of a list variable. Keyed by variable index. - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The full list variable (e.g. "myList::*" from {myList::*}) - * @param holder The holder of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * If it is not provided in this format, a null value will be returned. - * @return The map of a list variable, or null if: - * If name was provided in an incorrect format, the holder is invalid, or if no value is set under that name for the holder. - * @see PersistentDataUtils#getList(String, Object...) - * @see PersistentDataUtils#setListMap(String, Map, Object) - */ - @Nullable - @SuppressWarnings({"null", "unchecked"}) - public static Map getListMap(String name, Object holder) { - if (!name.endsWith("*")) // Make sure we're getting a whole list - return null; - - PersistentDataHolder actualHolder = getConvertedHolders(new Object[]{holder}).get(holder); - if (actualHolder == null) - return null; - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - NamespacedKey key = getNamespacedKey(keyName); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { // It exists under Persistent Data - Map returnMap = new HashMap<>(); - for (Entry entry : actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE).entrySet()) { - returnMap.put(entry.getKey(), Classes.deserialize(entry.getValue().type, entry.getValue().data)); - } - return returnMap; - } else if (holder instanceof Metadatable) { // Check Metadata - Map mMap = null; - for (MetadataValue mv : ((Metadatable) holder).getMetadata(keyName)) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance()) { - mMap = (Map) mv.value(); - break; - } - } - return mMap; - } - - return null; - } - - /** - * Sets the list map of the given holder. - * This map should be gotten from {@link PersistentDataUtils#getListMap(String, Object)} - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The full list variable (e.g. "myList::*" from {myList::*}) - * If it is not provided in this format, nothing will be set. - * @param varMap The new map for Persistent Data Tag of the given holder. - * If a variable map doesn't already exist in the holder's {@link org.bukkit.persistence.PersistentDataContainer}, - * this map will be set in their Metadata. - * @param holder The holder of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @see PersistentDataUtils#setList(String, Object, Object...) - * @see PersistentDataUtils#getListMap(String, Object) - */ - public static void setListMap(String name, Map varMap, Object holder) { - if (!name.endsWith("*")) // Make sure we're setting a whole list - return; - - if (varMap.isEmpty()) { // If the map is empty, remove the whole value instead. - removeList(name, holder); - return; - } - - PersistentDataHolder actualHolder = getConvertedHolders(new Object[]{holder}).get(holder); - if (actualHolder == null) - return; - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - NamespacedKey key = getNamespacedKey(keyName); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { // It exists under Persistent Data - Map serializedMap = new HashMap<>(); - for (Entry entry : varMap.entrySet()) - serializedMap.put(entry.getKey(), Classes.serialize(entry.getValue())); - actualHolder.getPersistentDataContainer().set(key, LIST_VARIABLE_TYPE, serializedMap); - } else if (holder instanceof Metadatable) { // Check Metadata - ((Metadatable) holder).setMetadata(keyName, new FixedMetadataValue(Skript.getInstance(), varMap)); - } - - // We need to update the data on an ItemType or TileState - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } - - /** - * This returns the indexes of a stored list. - * Mainly used for the ADD changer in {@link ExprRelationalVariable} - * @param name The full list variable (e.g. "myList::*" from {myList::*}) - * If it is not provided in this format, nothing will be set. - * @param holder The holder of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @return The set of indexes, or an empty String set. - */ - @Nullable - @SuppressWarnings({"null", "unchecked"}) - public static Set getListIndexes(String name, Object holder) { - if (!name.endsWith("*")) // Make sure we're getting a whole list - return null; - - PersistentDataHolder actualHolder = getConvertedHolders(new Object[]{holder}).get(holder); - if (actualHolder == null) - return null; - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - NamespacedKey key = getNamespacedKey(keyName); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { // It exists under Persistent Data - return actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE).keySet(); - } else if (holder instanceof Metadatable) { // Check Metadata - for (MetadataValue mv : ((Metadatable) holder).getMetadata(keyName)) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance()) - return ((Map) mv.value()).keySet(); - } - } - - return null; - } - - /* - * Other Utility Methods - */ - - /** - * Whether the given holders have a value under the given name. - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The name of the variable - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * (e.g. "myVariable" from {myVariable} OR "myList::index" from {myList::index} OR "myList::*" from {myList::*}) - * @return True if the user has something under the Persistent Data Tag from the given name. - * This method will return false if: the holder is invalid, the name is invalid, or if no value could be found. - */ - @SuppressWarnings({"null", "unchecked"}) - public static boolean has(String name, Object... holders) { - Map actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return false; - - boolean isList = name.contains(Variable.SEPARATOR); - String keyName = isList ? "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)) : "!!SINGLE!!" + name; - NamespacedKey key = getNamespacedKey(keyName); - - if (isList) { - for (Entry entry: actualHolders.entrySet()) { - Object holder = entry.getKey(); - PersistentDataHolder actualHolder = entry.getValue(); - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { - if (index.equals("*")) // There will NEVER be an empty map stored. - continue; - if (actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE).containsKey(index)) - continue; - return false; - } - if (holder instanceof Metadatable) { - Metadatable mHolder = (Metadatable) holder; - if (!mHolder.hasMetadata(keyName)) { - return false; - } else { // They have something under that key name, check the values. - for (MetadataValue mv : mHolder.getMetadata(keyName)) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance() && !((Map) mv.value()).containsKey(index)) - return false; - } - } - } - } - } else { - for (Entry entry: actualHolders.entrySet()) { - if (entry.getValue().getPersistentDataContainer().has(key, SINGLE_VARIABLE_TYPE)) - continue; - if (((Metadatable) entry.getKey()).hasMetadata(keyName)) - continue; - return false; - } - } - return true; - } - -} diff --git a/src/main/java/ch/njol/skript/util/SingleVariablePersistentDataType.java b/src/main/java/ch/njol/skript/util/SingleVariablePersistentDataType.java deleted file mode 100644 index eeb60b21e11..00000000000 --- a/src/main/java/ch/njol/skript/util/SingleVariablePersistentDataType.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -import org.bukkit.persistence.PersistentDataAdapterContext; -import org.bukkit.persistence.PersistentDataType; - -import ch.njol.skript.variables.SerializedVariable.Value; - -/** - * This {@link PersistentDataType} is used for single variables. - * The {@link org.bukkit.NamespacedKey}'s key should be the variable's name. - * {hello} -> "hello" and the {@link Value} is the variable's serialized value. - * @see PersistentDataUtils#getNamespacedKey(String) - * @see PersistentDataUtils - * @author APickledWalrus - */ -public final class SingleVariablePersistentDataType implements PersistentDataType { - - // This is how many bytes an int is. - private final int INT_LENGTH = 4; - - // Charset used for converting bytes and Strings - @SuppressWarnings("null") - private final Charset SERIALIZED_CHARSET = StandardCharsets.UTF_8; - - @Override - public Class getPrimitiveType() { - return byte[].class; - } - - @Override - public Class getComplexType() { - return Value.class; - } - - @SuppressWarnings("null") - @Override - public byte[] toPrimitive(Value complex, PersistentDataAdapterContext context) { - byte[] type = complex.type.getBytes(SERIALIZED_CHARSET); - - ByteBuffer bb = ByteBuffer.allocate(INT_LENGTH + type.length + complex.data.length); - bb.putInt(type.length); - bb.put(type); - bb.put(complex.data); - - return bb.array(); - } - - @Override - public Value fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) { - ByteBuffer bb = ByteBuffer.wrap(primitive); - - int typeLength = bb.getInt(); - byte[] typeBytes = new byte[typeLength]; - bb.get(typeBytes, 0, typeLength); - String type = new String(typeBytes, SERIALIZED_CHARSET); - - byte[] data = new byte[bb.remaining()]; - bb.get(data); - - return new Value(type, data); - } - -} diff --git a/src/main/java/ch/njol/skript/variables/VariablesMap.java b/src/main/java/ch/njol/skript/variables/VariablesMap.java index 0abad152d00..1b0ca621358 100644 --- a/src/main/java/ch/njol/skript/variables/VariablesMap.java +++ b/src/main/java/ch/njol/skript/variables/VariablesMap.java @@ -18,18 +18,16 @@ */ package ch.njol.skript.variables; +import ch.njol.skript.lang.Variable; +import ch.njol.util.StringUtils; +import org.eclipse.jdt.annotation.Nullable; + import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.lang.Variable; -import ch.njol.skript.util.Utils; -import ch.njol.util.StringUtils; - final class VariablesMap { final static Comparator variableNameComparator = new Comparator() { @@ -45,49 +43,86 @@ public int compare(@Nullable String s1, @Nullable String s2) { int j = 0; boolean lastNumberNegative = false; + boolean afterDecimalPoint = false; while (i < s1.length() && j < s2.length()) { char c1 = s1.charAt(i); char c2 = s2.charAt(j); if ('0' <= c1 && c1 <= '9' && '0' <= c2 && c2 <= '9') { // Numbers/digits are treated differently from other characters. + + // The index after the last digit int i2 = StringUtils.findLastDigit(s1, i); int j2 = StringUtils.findLastDigit(s2, j); - long n1 = Utils.parseLong("" + s1.substring(i, i2)); - long n2 = Utils.parseLong("" + s2.substring(j, j2)); + // Amount of leading zeroes + int z1 = 0; + int z2 = 0; + + // Skip leading zeroes (except for the last if all 0's) + if (!afterDecimalPoint) { + if (c1 == '0') { + while (i < i2 - 1 && s1.charAt(i) == '0') { + i++; + z1++; + } + } + if (c2 == '0') { + while (j < j2 - 1 && s2.charAt(j) == '0') { + j++; + z2++; + } + } + } + // Keep in mind that c1 and c2 may not have the right value (e.g. s1.charAt(i)) for the rest of this block // If the number is prefixed by a '-', it should be treated as negative, thus inverting the order. // If the previous number was negative, and the only thing separating them was a '.', // then this number should also be in inverted order. boolean previousNegative = lastNumberNegative; - lastNumberNegative = i > 0 && s1.charAt(i - 1) == '-'; + // i - z1 contains the first digit, so i - z1 - 1 may contain a `-` indicating this number is negative + lastNumberNegative = i - z1 > 0 && s1.charAt(i - z1 - 1) == '-'; int isPositive = (lastNumberNegative | previousNegative) ? -1 : 1; - if (n1 > n2) - return isPositive; + // Different length numbers (99 > 9) + if (!afterDecimalPoint && i2 - i != j2 - j) + return ((i2 - i) - (j2 - j)) * isPositive; + + // Iterate over the digits + while (i < i2 && j < j2) { + char d1 = s1.charAt(i); + char d2 = s2.charAt(j); + + // If the digits differ, return a value dependent on the sign + if (d1 != d2) + return (d1 - d2) * isPositive; + + i++; + j++; + } - if (n1 < n2) - return -1 * isPositive; + // Different length numbers (1.99 > 1.9) + if (afterDecimalPoint && i2 - i != j2 - j) + return ((i2 - i) - (j2 - j)) * isPositive; - // Represent same number, but different length, indicating leading zeros - if (i2 - i > j2 - j) - return -1; - if (i2 - i < j2 - j) - return 1; + // If the numbers are equal, but either has leading zeroes, + // more leading zeroes is a lesser number (01 < 1) + if (z1 != 0 || z2 != 0) + return (z1 - z2) * isPositive; - i = i2; - j = j2; + afterDecimalPoint = true; } else { // Normal characters - if (c1 > c2) - return 1; - if (c1 < c2) - return -1; - // Reset the last number flag if we're exiting a number. - if (c1 != '.') + if (c1 != c2) + return c1 - c2; + + // Reset the last number flags if we're exiting a number. + if (c1 != '.') { lastNumberNegative = false; + afterDecimalPoint = false; + } + i++; j++; } @@ -99,7 +134,7 @@ public int compare(@Nullable String s1, @Nullable String s2) { return 0; } }; - + final HashMap hashMap = new HashMap<>(); final TreeMap treeMap = new TreeMap<>(); diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 2ef13897172..6ce6ae6ea88 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1907,7 +1907,6 @@ types: vector: vector¦s @a inventorytype: inventory type¦s @an metadataholder: metadata holder¦s @a - persistentdataholder: persistent data holder¦s @ spawnreason: spawn reason¦s @a cachedservericon: server icon¦s @a difficulty: difficult¦y¦ies @a diff --git a/src/test/skript/README.md b/src/test/skript/README.md index dabda3a5c85..3955abe012a 100644 --- a/src/test/skript/README.md +++ b/src/test/skript/README.md @@ -75,12 +75,12 @@ some syntaxes for test development are available. Use Gradle to launch a test development server: ``` -TERM=dumb ./gradlew skriptTestDev +gradlew clean skriptTestDev --console=plain ``` The server launched will be running at localhost:25565. You can use console as normal, though there is some lag due to Gradle. If you're having trouble, -try without TERM=dumb. +try without --console=plain. To run individual test files, use /sk test \. To run last used file again, just use /sk test. diff --git a/src/test/skript/environments/java8/paper-1.14.4.json b/src/test/skript/environments/java8/paper-1.14.4.json index 1e2972dd84c..b619f3d4328 100644 --- a/src/test/skript/environments/java8/paper-1.14.4.json +++ b/src/test/skript/environments/java8/paper-1.14.4.json @@ -14,4 +14,4 @@ "-Dcom.mojang.eula.agree=true", "-jar", "paperclip.jar" ] -} +} \ No newline at end of file diff --git a/src/test/skript/tests/misc/item data serialization.sk b/src/test/skript/tests/misc/item data serialization.sk new file mode 100644 index 00000000000..d79e1713e62 --- /dev/null +++ b/src/test/skript/tests/misc/item data serialization.sk @@ -0,0 +1,6 @@ +test "item data serialization": + + # No assertion needed, it will be done internally + set {a::1} to a dirt block named "DIRT" with lore "LORE1" and "LORE2" + + delete {a::1} diff --git a/src/test/skript/tests/regressions/4524-function call conversion.sk b/src/test/skript/tests/regressions/4524-function call conversion.sk new file mode 100644 index 00000000000..95e1f8649cb --- /dev/null +++ b/src/test/skript/tests/regressions/4524-function call conversion.sk @@ -0,0 +1,7 @@ +function functionCallConversion() :: item: + return stone named "abc" + +test "function call conversion": + assert name of functionCallConversion() is "abc" with "Name of item from function call failed" + + set {_p}'s velocity to vector({_x}, {_y}, {_z}) diff --git a/src/test/skript/tests/regressions/4729-large numbers in list indices.sk b/src/test/skript/tests/regressions/4729-large numbers in list indices.sk new file mode 100644 index 00000000000..ed179aefd4a --- /dev/null +++ b/src/test/skript/tests/regressions/4729-large numbers in list indices.sk @@ -0,0 +1,20 @@ +test "large numbers in list indices": + set {_l::999999999999999999999999999999999999999999999} to 3 + assert {_l::888888888888888888888888888888888888888888} isn't set with "Two large numbers in variable indices were treated as equal" + +test "order of numerical list indices": + set {_l::-0.99} to 1 + set {_l::-0.51} to 2 + set {_l::-0.5} to 3 + set {_l::-0.49} to 4 + set {_l::-0.4} to 5 + set {_l::0.0} to 6 + set {_l::0.01} to 7 + set {_l::0.1} to 8 + set {_l::0.12} to 9 + set {_l::0.69} to 10 + + set {_i} to 1 + loop {_l::*}: + loop-value is {_i} + add 1 to {_i} diff --git a/src/test/skript/tests/regressions/4859-function error with plural argument for single parameter.sk b/src/test/skript/tests/regressions/4859-function error with plural argument for single parameter.sk new file mode 100644 index 00000000000..57b3a83d5bf --- /dev/null +++ b/src/test/skript/tests/regressions/4859-function error with plural argument for single parameter.sk @@ -0,0 +1,5 @@ +function pluralArgSingleParam(a: string, b: number) :: number: + return 1 + +test "function error with plural argument for single parameter": + assert pluralArgSingleParam(("abc", "def"), 1) is 1 to fail with "function call didn't error with plural argument when single expected" \ No newline at end of file diff --git a/src/test/skript/tests/regressions/4938-time played.sk b/src/test/skript/tests/regressions/4938-time played.sk new file mode 100644 index 00000000000..dd1a9de21d5 --- /dev/null +++ b/src/test/skript/tests/regressions/4938-time played.sk @@ -0,0 +1,6 @@ +test "time played": + set {_time} to time played of "Notch" parsed as offlineplayer + if running minecraft "1.15": + assert {_time} is equal to 0 seconds with "Notch hacked your server and built a dirt house on your server (time: %{_time}%)" + else: + assert {_time} is not set with "Played time of offline players are not supported but seems like Notch is a hacker! (time: %{_time}%)" diff --git a/src/test/skript/tests/syntaxes/effects/EffContinue.sk b/src/test/skript/tests/syntaxes/effects/EffContinue.sk new file mode 100644 index 00000000000..2a4685a8877 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffContinue.sk @@ -0,0 +1,11 @@ +test "continue effect": + loop 10 times: + if loop-value is equal to 5: + continue + assert loop-value is not 5 with "continue in loop failed" + set {_i} to 0 + while {_i} is smaller than 10: + increase {_i} by 1 + if {_i} is equal to 5: + continue + assert {_i} is not 5 with "continue in while failed" diff --git a/src/test/skript/tests/syntaxes/effects/EffHealth.sk b/src/test/skript/tests/syntaxes/effects/EffHealth.sk index 12d9e63463d..41ecf33904b 100644 --- a/src/test/skript/tests/syntaxes/effects/EffHealth.sk +++ b/src/test/skript/tests/syntaxes/effects/EffHealth.sk @@ -1,20 +1,23 @@ test "health effect": + clear all entities set {_i} to diamond sword assert {_i}'s damage value is 0 with "new item durability failed" damage {_i} by 50 assert {_i}'s damage value is 50 with "damage item failed" repair {_i} by 49 assert {_i}'s damage value is 1 with "repair item failed" - spawn zombie at location(0, 64, 0, world "world") - set {_m} to last spawned zombie - assert health of {_m} is 10 with "default zombie health failed" + + spawn cow at location(0, 64, 0, world "world") + set {_m} to last spawned cow + assert health of {_m} is 5 with "default cow health failed" damage {_m} by 0.5 - assert health of {_m} is 9.5 with "damage zombie ##1 failed" + assert health of {_m} is 4.5 with "damage cow ##1 failed" damage {_m} by 99 - assert health of {_m} is 0 with "damage zombie ##2 failed" + assert health of {_m} is 0 with "damage cow ##2 failed" heal {_m} by 1 - assert health of {_m} is 1 with "heal zombie ##1 failed" + assert health of {_m} is 1 with "heal cow ##1 failed" heal {_m} by 0.5 - assert health of {_m} is 1.5 with "heal zombie ##2 failed" + assert health of {_m} is 1.5 with "heal cow ##2 failed" heal {_m} by 99 - assert health of {_m} is 10 with "heal zombie ##3 failed" + assert health of {_m} is 5 with "heal cow ##3 failed" + clear all entities diff --git a/src/test/skript/tests/syntaxes/effects/EffPotion.sk b/src/test/skript/tests/syntaxes/effects/EffPotion.sk new file mode 100644 index 00000000000..0db81c9d2d7 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffPotion.sk @@ -0,0 +1,15 @@ +test "potion effect": + spawn a pig at spawn of world "world" + set {_pig} to last spawned pig + set {_potion} to potion effect of slowness of tier 5 for 10 seconds + apply {_potion} to {_pig} + assert {_pig} has potion effect slowness with "pig failed to apply slowness" + apply potion effect of speed of tier 5 for 10 seconds to {_pig} + assert {_pig} has potion effect speed with "pig failed to apply speed" + apply haste of tier 5 to {_pig} for 10 seconds + assert {_pig} has potion effect haste with "pig failed to apply haste" + apply ambient strength of tier 5 to {_pig} for 10 seconds + assert {_pig} has potion effect strength with "pig failed to apply strength" + apply jump boost of tier 5 without particles to {_pig} for 10 seconds + assert {_pig} has potion effect slowness with "pig failed to apply jump boost" + delete last spawned pig \ No newline at end of file diff --git a/src/test/skript/tests/syntaxes/expressions/ExprRelationalVariable.sk b/src/test/skript/tests/syntaxes/expressions/ExprRelationalVariable.sk deleted file mode 100644 index e1b6c354473..00000000000 --- a/src/test/skript/tests/syntaxes/expressions/ExprRelationalVariable.sk +++ /dev/null @@ -1,149 +0,0 @@ -test "relational variables expression/condition" when running minecraft "1.99": #temporarily disabling this test - - # Test entities holding relational variables - - # The variable names are odd to test encoding them in Base64 (this is to allow the use of more characters in NamespacedKey names) - - spawn a chicken at spawn of world "world" - set {_chicken} to the last spawned chicken - - assert {_chicken} doesn't have relational variable {cool+io man} with "(Condition) Entities should not have a relational variable before it is set" - set relational variable {cool+io man} of {_chicken} to true - assert relational variable {cool+io man} of {_chicken} = true with "(Expression) The relational variable was set to be true, but it is not" - assert {_chicken} has relational variable {cool+io man} with "(Condition) Entities should have a relational variable if it is set" - clear relational variable {cool+io man} of {_chicken} - assert {_chicken} doesn't have relational variable {cool+io man} with "(Condition) Entities should not have a relational variable after it is cleared" - - delete {_chicken} - - # Test blocks holding a serializable value - - set block at spawn of world "world" to a campfire - set {_block} to block at spawn of world "world" - - assert {_block} doesn't have relational variable {enchantment} with "(Condition) Blocks should not have a relational variable before it is set" - set relational variable {enchantment} of {_block} to sharpness 10 - assert relational variable {enchantment} of {_block} = sharpness 10 with "(Expression) The relational variable was set to be sharpness 10, but it is not" - assert {_block} has relational variable {enchantment} with "(Condition) Blocks should have a relational variable if it is set" - clear relational variable {enchantment} of {_block} - assert {_block} doesn't have relational variable {enchantment} with "(Condition) Blocks should not have a relational variable after it is cleared" - - set block at spawn of world "world" to air - - # Test entities holding a non-serializable value - - spawn a pig at spawn of world "world" - assert last spawned pig doesn't have relational variable {me} with "(Condition) Entities should not have a relational variable before it is set (Non-serializable Test)" - set relational variable {me} of last spawned pig to last spawned pig - assert relational variable {me} of last spawned pig = last spawned pig with "(Expression) The relational variable was set, but it is not (Non-serializable Test)" - assert last spawned pig has relational variable {me} with "(Condition) Entities should have a relational variable if it is set (Non-serializable Test)" - clear relational variable {me} of last spawned pig - assert last spawned pig doesn't have relational variable {me} with "(Condition) Entities should not have a relational variable after it is cleared (Non-serializable Test)" - delete last spawned pig - - # Test using list variables (Serializable) - - set block at spawn of world "world" to a campfire - set {_block} to block at spawn of world "world" - assert {_block} doesn't have relational variable {enchantment::sharpness} with "(Condition) Blocks should not have a relational variable before it is set (List Test)" - set relational variable {enchantment::sharpness} of {_block} to sharpness 10 - assert relational variable {enchantment::sharpness} of {_block} = sharpness 10 with "(Expression) The relational variable was set to be sharpness 10, but it is not (List Test)" - assert relational variable {enchantment::*} of {_block} = sharpness 10 with "(Expression) The full list should be just sharpness 10, but it is not (List Test)" - assert {_block} doesn't have relational variable {enchantment::smite} with "(Condition) Blocks should not have a relational variable if the index was not set (List Test)" - assert {_block} has relational variable {enchantment::sharpness} with "(Condition) Blocks should have a relational variable if it is set (List Test)" - clear relational variable {enchantment::sharpness} of {_block} - assert {_block} doesn't have relational variable {enchantment::sharpness} with "(Condition) Blocks should not have a relational variable after it is cleared (List Test)" - set block at spawn of world "world" to air - - # Test using list variables (Non-serializable) - - spawn a pig at spawn of world "world" - set {_pig} to the last spawned pig - - assert {_pig} doesn't have relational variable {me::1} and {me::2} with "(Condition) Entities should not have a relational variable before it is set (Non-serializable List Test)" - set relational variable {me::1} of {_pig} to {_pig} - assert relational variable {me::1} of {_pig} = {_pig} with "(Expression) The relational variable was set, but it is not (Non-serializable List Test)" - assert relational variable {me::*} of {_pig} = {_pig} with "(Expression) The full list should be just the last spawned pig, but it is not (Non-serializable List Test)" - assert {_pig} doesn't have relational variable {me::2} with "(Condition) Entities should not have a relational variable if the index was not set (Non-serializable List Test)" - assert {_pig} has relational variable {me::1} with "(Condition) Entities should have a relational variable if it is set (Non-serializable List Test)" - clear relational variable {me::1} of {_pig} - assert {_pig} doesn't have relational variable {me::1} and {me::2} with "(Condition) Entities should not have a relational variable after it is cleared (Non-serializable List Test)" - - delete {_pig} - - # Test add, remove, remove all (Serializable) - - set block at spawn of world "world" to a campfire - set {_block} to block at spawn of world "world" - - set relational variable {change::*} of {_block} to {_block} and sharpness 5 - assert relational variable {change::*} of {_block} contains ({_block} and sharpness 5) with "(Expression) The list was set, but it does not contain the set values (Changer Test)" - remove sharpness 5 from relational variable {change::*} of {_block} - assert relational variable {change::*} of {_block} = {_block} with "(Expression) The enchantment was removed from the list, but it is still in it (Changer Test)" - add sharpness 5 and protection 4 to relational variable {change::*} of {_block} - assert relational variable {change::*} of {_block} contains ({_block}, sharpness 5, and protection 4) with "(Expression) Enchanments were added to the list, but they are not present (% relational variable {change::*} of {_block}%) (Changer Test)" - remove all enchantment types from relational variable {change::*} of {_block} - assert relational variable {change::*} of {_block} = {_block} with "(Expression) The enchantments were removed from the list, but it still contains them (% relational variable {change::*} of {_block}%) (Changer Test)" - - set block at spawn of world "world" to air - - # Test add, remove, remove all (Non-serializable) - - spawn a pig at spawn of world "world" - set {_pig} to the last spawned pig - spawn a cow at spawn of world "world" - set {_cow1} to the last spawned cow - spawn a cow at spawn of world "world" - set {_cow2} to the last spawned cow - - set relational variable {change::*} of {_pig} to {_pig} and {_cow1} - assert relational variable {change::*} of {_pig} contains ({_pig} and {_cow1}) with "(Expression) The list was set, but it does not contain the set values (Non-serializable Changer Test)" - remove {_cow1} from relational variable {change::*} of {_pig} - assert relational variable {change::*} of {_pig} = {_pig} with "(Expression) The cow was removed from the list, but it is still in it (Non-serializable Changer Test)" - add {_cow1} and {_cow2} to relational variable {change::*} of {_pig} - assert relational variable {change::*} of {_pig} contains ({_pig}, {_cow1}, and {_cow2}) with "(Expression) Cows were added to the list, but they are not present (Non-serializable Changer Test)" - remove all cows from relational variable {change::*} of {_pig} - assert relational variable {change::*} of {_pig} = {_pig} with "(Expression) The cows were removed from the list, but it still contains them (Non-serializable Changer Test)" - clear relational variable {change::*} of {_pig} - - delete {_pig}, {_cow1}, and {_cow2} - - # Test adding and removing numbers - - set block at spawn of world "world" to a campfire - set {_block} to block at spawn of world "world" - - set relational variable {number} of {_block} to 10 - assert relational variable {number} of {_block} = 10 with "(Expression) The relational variable was set to 10, but it is not (Numbers Test)" - add 10.5 to relational variable {number} of {_block} - assert relational variable {number} of {_block} = 20.5 with "(Expression) 10 was added to the relational variable, but it is not 20.5 (Numbers Test)" - remove 10 from relational variable {number} of {_block} - assert relational variable {number} of {_block} = 10.5 with "(Expression) 10 was removed from the amount, but it is not 10.5 (Numbers Test)" - clear relational variable {number} of {_block} - assert {_block} doesn't have relational variable {number} with "(Condition) The relational variable was removed, but the block still has it (Numbers Test)" - - set block at spawn of world "world" to air - - # Adding to an empty list Test - - spawn a cow at spawn of world "world" - set {_cow} to the last spawned cow - - add {_cow} to relational variable {cows::*} of {_cow} - assert relational variable {cows::*} of {_cow} = {_cow} with "Adding to an empty list should work, but it didn't (Adding to an empty list Test)" - - delete {_cow} - - # Single vs. List Test - # If the player has the relational variable {myTag::1}, then checking if they have {myTag} should return false - - spawn a pig at spawn of world "world" - set {_pig} to the last spawned pig - - set relational variable {myTag::1} of {_pig} to true - assert {_pig} doesn't have relational variable {myTag} with "A holder shouldn't have {myTag} if they only have {myTag::1} (Serializable Single vs. List Test)" - - set relational variable {myTag::1} of {_pig} to {_pig} - assert {_pig} doesn't have relational variable {myTag} with "A holder shouldn't have {myTag} if they only have {myTag::1} (Non-serializable Single vs. List Test)" - - delete {_pig}