diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 487a5e92e7b..2b8d8c142d1 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -3,54 +3,30 @@ import ch.njol.skript.aliases.Aliases; import ch.njol.skript.bukkitutil.BurgerHelper; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.data.BukkitClasses; -import ch.njol.skript.classes.data.BukkitEventValues; -import ch.njol.skript.classes.data.DefaultComparators; -import ch.njol.skript.classes.data.DefaultConverters; -import ch.njol.skript.classes.data.DefaultFunctions; -import ch.njol.skript.classes.data.DefaultOperations; -import ch.njol.skript.classes.data.JavaClasses; -import ch.njol.skript.classes.data.SkriptClasses; +import ch.njol.skript.classes.data.*; import ch.njol.skript.command.Commands; import ch.njol.skript.doc.Documentation; import ch.njol.skript.events.EvtSkript; import ch.njol.skript.expressions.arithmetic.ExprArithmetic; import ch.njol.skript.hooks.Hook; import ch.njol.skript.lang.*; +import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Condition.ConditionType; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.localization.Language; import ch.njol.skript.localization.Message; import ch.njol.skript.localization.PluralizingArgsMessage; -import ch.njol.skript.log.BukkitLoggerFilter; -import ch.njol.skript.log.CountingLogHandler; -import ch.njol.skript.log.ErrorDescLogHandler; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.log.LogEntry; -import ch.njol.skript.log.LogHandler; -import ch.njol.skript.log.SkriptLogger; -import ch.njol.skript.log.TestingLogHandler; -import ch.njol.skript.log.Verbosity; +import ch.njol.skript.log.*; import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.EventValues; import ch.njol.skript.registrations.Feature; -import ch.njol.skript.test.runner.EffObjectives; -import ch.njol.skript.test.runner.SkriptAsyncJUnitTest; -import ch.njol.skript.test.runner.SkriptJUnitTest; -import ch.njol.skript.test.runner.SkriptTestEvent; -import ch.njol.skript.test.runner.TestMode; -import ch.njol.skript.test.runner.TestTracker; +import ch.njol.skript.test.runner.*; import ch.njol.skript.timings.SkriptTimings; import ch.njol.skript.update.ReleaseManifest; import ch.njol.skript.update.ReleaseStatus; import ch.njol.skript.update.UpdateManifest; import ch.njol.skript.util.Date; -import ch.njol.skript.util.EmptyStacktraceException; -import ch.njol.skript.util.ExceptionUtils; -import ch.njol.skript.util.FileUtils; -import ch.njol.skript.util.Task; -import ch.njol.skript.util.Utils; -import ch.njol.skript.util.Version; +import ch.njol.skript.util.*; import ch.njol.skript.util.chat.BungeeConverter; import ch.njol.skript.util.chat.ChatMessages; import ch.njol.skript.variables.Variables; @@ -63,11 +39,7 @@ import com.google.gson.GsonBuilder; import io.papermc.lib.PaperLib; import org.bstats.bukkit.Metrics; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Server; +import org.bukkit.*; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginCommand; import org.bukkit.entity.Player; @@ -104,12 +76,15 @@ import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys; import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; import org.skriptlang.skript.bukkit.tags.TagModule; +import org.skriptlang.skript.common.CommonModule; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.lang.entry.EntryValidator; import org.skriptlang.skript.lang.experiment.ExperimentRegistry; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.PropertyRegistry; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.lang.structure.StructureInfo; @@ -502,6 +477,9 @@ public void onEnable() { experimentRegistry = new ExperimentRegistry(this); Feature.registerAll(getAddonInstance(), experimentRegistry); + skript.storeRegistry(PropertyRegistry.class, new PropertyRegistry(this)); + Property.registerDefaultProperties(); + // Load classes which are always safe to use new JavaClasses(); // These may be needed in configuration @@ -590,10 +568,11 @@ public void onEnable() { FurnaceModule.load(); LootTableModule.load(); skript.loadModules( - new DamageSourceModule(), - new ItemComponentModule(), - new BrewingModule() - ); + new DamageSourceModule(), + new ItemComponentModule(), + new BrewingModule(), + new CommonModule() + ); } catch (final Exception e) { exception(e, "Could not load required .class files: " + e.getLocalizedMessage()); setEnabled(false); diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index 9c407b5985b..5f4caf5f67d 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -294,6 +294,9 @@ public static String formatDate(final long timestamp) { }) .optional(true); + public static final Option useTypeProperties = new Option<>("use type properties", false) + .optional(true); + public static final Option allowUnsafePlatforms = new Option<>("allow unsafe platforms", false) .optional(true); diff --git a/src/main/java/ch/njol/skript/classes/AnyInfo.java b/src/main/java/ch/njol/skript/classes/AnyInfo.java index 283e0c5189b..15d52ccee2c 100644 --- a/src/main/java/ch/njol/skript/classes/AnyInfo.java +++ b/src/main/java/ch/njol/skript/classes/AnyInfo.java @@ -12,7 +12,9 @@ * These auto-generate their user patterns (e.g. {@code named} -> {@code any named thing}). * * @see AnyProvider + * @deprecated Use {@link org.skriptlang.skript.lang.properties.Property} instead. */ +@Deprecated(since="INSERT VERSION", forRemoval = true) public class AnyInfo extends ClassInfo { /** diff --git a/src/main/java/ch/njol/skript/classes/ClassInfo.java b/src/main/java/ch/njol/skript/classes/ClassInfo.java index 41380de7b41..da8dfd1c50e 100644 --- a/src/main/java/ch/njol/skript/classes/ClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/ClassInfo.java @@ -6,16 +6,20 @@ import ch.njol.skript.lang.DefaultExpression; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.localization.Noun; +import ch.njol.skript.registrations.Classes; import ch.njol.util.coll.iterator.ArrayIterator; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.bukkit.event.Event; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.Property.PropertyInfo; +import org.skriptlang.skript.lang.properties.PropertyHandler; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; +import java.util.*; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -476,4 +480,74 @@ public String toString(final @Nullable Event event, final boolean debug) { return getName().getSingular(); } + private final Map, PropertyInfo> propertyInfos = new HashMap<>(); + private final Map, PropertyDocs> propertyDocumentation = new HashMap<>(); + + @ApiStatus.Experimental + public record PropertyDocs(Property property, String description, SkriptAddon provider) {} + + /** + * Registers this class as having the given property, using the given property handler. + * @param property The property this class should have + * @param description A short description of the property for documentation + * @param handler The handler for this property + * @return This ClassInfo object + * @param The type of the property handler + * @throws IllegalStateException If this property is already registered for this class + */ + @ApiStatus.Experimental + public > ClassInfo property(Property property, String description, SkriptAddon addon, @NotNull Handler handler) { + if (propertyInfos.containsKey(property)) { + throw new IllegalStateException("Property " + property.name() + " is already registered for the " + c.getName() + " type."); + } + propertyInfos.put(property, new PropertyInfo<>(property, handler)); + Classes.hasProperty(property, this); + propertyDocumentation.put(property, new PropertyDocs(property, description, addon)); + return this; + } + + /** + * Checks whether this class already has the given property registered. + * @param property The property to check + * @return True if this class has the property, false otherwise + */ + @ApiStatus.Experimental + public boolean hasProperty(Property property) { + return propertyInfos.containsKey(property); + } + + /** + * @return An unmodifiable collection of all the properties this class has. + */ + @ApiStatus.Experimental + public @Unmodifiable Collection> getAllProperties() { + return Collections.unmodifiableCollection(propertyInfos.keySet()); + } + + /** + * Gets the property info for the given property, or null if this class does not have the property. + * @param property The property to get the info for + * @return The property info, or null if this class does not have the property + * @param The type of the property handler + */ + @ApiStatus.Experimental + public > @Nullable PropertyInfo getPropertyInfo(Property property) { + if (!propertyInfos.containsKey(property)) { + return null; + } + //noinspection unchecked + return (PropertyInfo) propertyInfos.get(property); + } + + /** + * Gets the type-specific documentation for the given property, or null if this type does not have the property. + * Meant to be used for documentation. + * @param property The property to get the documentation for + * @return The documentation, or null if this type does not have the property + */ + @ApiStatus.Experimental + public PropertyDocs getPropertyDocumentation(Property property) { + return propertyDocumentation.get(property); + } + } 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 e4331315ce3..c145c1d82fc 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1,12 +1,8 @@ package ch.njol.skript.classes.data; import ch.njol.skript.Skript; -import ch.njol.skript.SkriptConfig; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.BukkitUtils; import ch.njol.skript.bukkitutil.EntityUtils; -import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.bukkitutil.SkriptTeleportFlag; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.ConfigurationSerializer; @@ -20,7 +16,6 @@ import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.util.SimpleLiteral; -import ch.njol.skript.localization.Language; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.BlockUtils; import ch.njol.skript.util.PotionEffectUtils; @@ -32,7 +27,6 @@ import org.bukkit.World.Environment; import org.bukkit.attribute.Attribute; import org.bukkit.block.Biome; -import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.DoubleChest; import org.bukkit.block.banner.PatternType; @@ -75,6 +69,10 @@ import org.bukkit.util.CachedServerIcon; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.base.types.*; +import org.skriptlang.skript.bukkit.base.types.EntityClassInfo.EntityChanger; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; import java.io.StreamCorruptedException; import java.util.ArrayList; @@ -93,45 +91,7 @@ public class BukkitClasses { public BukkitClasses() {} static { - Classes.registerClass(new ClassInfo<>(Entity.class, "entity") - .user("entit(y|ies)") - .name("Entity") - .description("An entity is something in a world that's not a block, " + - "e.g. a player, a skeleton, or a zombie, but also " + - "projectiles like arrows, fireballs or thrown potions, " + - "or special entities like dropped items, falling blocks or paintings.") - .usage("player, op, wolf, tamed ocelot, powered creeper, zombie, unsaddled pig, fireball, arrow, dropped item, item frame, etc.") - .examples("entity is a zombie or creeper", - "player is an op", - "projectile is an arrow", - "shoot a fireball from the player") - .since("1.0") - .defaultExpression(new EventValueExpression<>(Entity.class)) - .parser(new Parser() { - @Override - public @Nullable Entity parse(final String s, final ParseContext context) { - if (Utils.isValidUUID(s)) - return Bukkit.getEntity(UUID.fromString(s)); - - return null; - } - - @Override - public boolean canParse(final ParseContext context) { - return context == ParseContext.COMMAND || context == ParseContext.PARSE; - } - - @Override - public String toVariableNameString(final Entity e) { - return "entity:" + e.getUniqueId().toString().toLowerCase(Locale.ENGLISH); - } - - @Override - public String toString(final Entity e, final int flags) { - return EntityData.toString(e, flags); - } - }) - .changer(DefaultChangers.entityChanger)); + Classes.registerClass(new EntityClassInfo()); Classes.registerClass(new ClassInfo<>(LivingEntity.class, "livingentity") .user("living ?entit(y|ies)") @@ -143,7 +103,7 @@ public String toString(final Entity e, final int flags) { "shoot a zombie from the creeper") .since("1.0") .defaultExpression(new EventValueExpression<>(LivingEntity.class)) - .changer(DefaultChangers.entityChanger)); + .changer(new EntityChanger())); Classes.registerClass(new ClassInfo<>(Projectile.class, "projectile") .user("projectiles?") @@ -156,98 +116,7 @@ public String toString(final Entity e, final int flags) { .defaultExpression(new EventValueExpression<>(Projectile.class)) .changer(DefaultChangers.nonLivingEntityChanger)); - Classes.registerClass(new ClassInfo<>(Block.class, "block") - .user("blocks?") - .name("Block") - .description("A block in a world. It has a location and a type, " + - "and can also have a direction (mostly a facing), an inventory, or other special properties.") - .usage("") - .examples("") - .since("1.0") - .defaultExpression(new EventValueExpression<>(Block.class)) - .parser(new Parser() { - @Override - @Nullable - public Block parse(final String s, final ParseContext context) { - return null; - } - - @Override - public boolean canParse(final ParseContext context) { - return false; - } - - @Override - public String toString(final Block b, final int flags) { - return BlockUtils.blockToString(b, flags); - } - - @Override - public String toVariableNameString(final Block b) { - return b.getWorld().getName() + ":" + b.getX() + "," + b.getY() + "," + b.getZ(); - } - - @Override - public String getDebugMessage(final Block b) { - return toString(b, 0) + " block (" + b.getWorld().getName() + ":" + b.getX() + "," + b.getY() + "," + b.getZ() + ")"; - } - }) - .changer(DefaultChangers.blockChanger) - .serializer(new Serializer() { - @Override - public Fields serialize(final Block b) { - final Fields f = new Fields(); - f.putObject("world", b.getWorld()); - f.putPrimitive("x", b.getX()); - f.putPrimitive("y", b.getY()); - f.putPrimitive("z", b.getZ()); - return f; - } - - @Override - public void deserialize(final Block o, final Fields f) { - assert false; - } - - @Override - protected Block deserialize(final Fields fields) throws StreamCorruptedException { - final World w = fields.getObject("world", World.class); - final int x = fields.getPrimitive("x", int.class), y = fields.getPrimitive("y", int.class), z = fields.getPrimitive("z", int.class); - if (w == null) - throw new StreamCorruptedException(); - return w.getBlockAt(x, y, z); - } - - @Override - public boolean mustSyncDeserialization() { - return true; - } - - @Override - public boolean canBeInstantiated() { - return false; - } - - // return b.getWorld().getName() + ":" + b.getX() + "," + b.getY() + "," + b.getZ(); - @Override - @Nullable - public Block deserialize(final String s) { - final String[] split = s.split("[:,]"); - if (split.length != 4) - return null; - final World w = Bukkit.getWorld(split[0]); - if (w == null) - return null; - try { - final int[] l = new int[3]; - for (int i = 0; i < 3; i++) - l[i] = Integer.parseInt(split[i + 1]); - return w.getBlockAt(l[0], l[1], l[2]); - } catch (final NumberFormatException e) { - return null; - } - } - })); + Classes.registerClass(new BlockClassInfo()); Classes.registerClass(new ClassInfo<>(BlockData.class, "blockdata") .user("block ?datas?") @@ -558,47 +427,14 @@ public World deserialize(final String s) { public boolean mustSyncDeserialization() { return true; } - })); - - Classes.registerClass(new ClassInfo<>(Inventory.class, "inventory") - .user("inventor(y|ies)") - .name("Inventory") - .description("An inventory of a player or block. " + - "Inventories have many effects and conditions regarding the items contained.", - "An inventory has a fixed amount of slots which represent a specific place in the inventory, " + - "e.g. the helmet slot for players " + - "(Please note that slot support is still very limited but will be improved eventually).") - .usage("") - .examples("") - .since("1.0") - .defaultExpression(new EventValueExpression<>(Inventory.class)) - .parser(new Parser() { - @Override - @Nullable - public Inventory parse(final String s, final ParseContext context) { - return null; - } - - @Override - public boolean canParse(final ParseContext context) { - return false; - } - - @Override - public String toString(final Inventory i, final int flags) { - return "inventory of " + Classes.toString(i.getHolder()); - } - - @Override - public String getDebugMessage(final Inventory i) { - return "inventory of " + Classes.getDebugMessage(i.getHolder()); - } + }) + .property(Property.NAME, + "A world's name, as text. Cannot be changed.", + Skript.instance(), + ExpressionPropertyHandler.of(World::getName, String.class)) + ); - @Override - public String toVariableNameString(final Inventory i) { - return "inventory of " + Classes.toString(i.getHolder(), StringMode.VARIABLE_NAME); - } - }).changer(DefaultChangers.inventoryChanger)); + Classes.registerClass(new InventoryClassInfo()); Classes.registerClass(new EnumClassInfo<>(InventoryAction.class, "inventoryaction", "inventory actions") .user("inventory ?actions?") @@ -622,178 +458,9 @@ public String toVariableNameString(final Inventory i) { .examples("") .since("2.2-dev32")); - Classes.registerClass(new ClassInfo<>(Player.class, "player") - .user("players?") - .name("Player") - .description( - "A player. Depending on whether a player is online or offline several actions can be performed with them, " + - "though you won't get any errors when using effects that only work if the player is online (e.g. changing their inventory) on an offline player.", - "You have two possibilities to use players as command arguments: and . " + - "The first requires that the player is online and also accepts only part of the name, " + - "while the latter doesn't require that the player is online, but the player's name has to be entered exactly." - ).usage( - "Parsing an offline player as a player (online) will return nothing (none), for that case you would need to parse as " + - "offlineplayer which only returns nothing (none) if player doesn't exist in Minecraft databases (name not taken) otherwise it will return the player regardless of their online status." - ).examples( - "set {_p} to \"Notch\" parsed as a player # returns unless Notch is actually online or starts with Notch like Notchan", - "set {_p} to \"N\" parsed as a player # returns Notch if Notch is online because their name starts with 'N' (case insensitive) however, it would return nothing if no player whose name starts with 'N' is online." - ).since("1.0") - .defaultExpression(new EventValueExpression<>(Player.class)) - .after("string", "world") - .parser(new Parser() { - @Override - @Nullable - public Player parse(String string, ParseContext context) { - if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { - if (string.isEmpty()) - return null; - - if (Utils.isValidUUID(string)) - return Bukkit.getPlayer(UUID.fromString(string)); - - String name = string.toLowerCase(Locale.ENGLISH); - int nameLength = name.length(); // caching - List players = new ArrayList<>(); - for (Player player : Bukkit.getOnlinePlayers()) { - if (player.getName().toLowerCase(Locale.ENGLISH).startsWith(name)) { - if (player.getName().length() == nameLength) // a little better in performance than String#equals() - return player; - players.add(player); - } - } - if (players.size() == 1) - return players.get(0); - if (players.size() == 0) - Skript.error(String.format(Language.get("commands.no player starts with"), string)); - else - Skript.error(String.format(Language.get("commands.multiple players start with"), string)); - return null; - } - assert false; - return null; - } - - @Override - public boolean canParse(final ParseContext context) { - return context == ParseContext.COMMAND || context == ParseContext.PARSE; - } - - @Override - public String toString(final Player p, final int flags) { - return "" + p.getName(); - } - - @Override - public String toVariableNameString(final Player p) { - if (SkriptConfig.usePlayerUUIDsInVariableNames.value()) - return "" + p.getUniqueId(); - else - return "" + p.getName(); - } - - @Override - public String getDebugMessage(final Player p) { - return p.getName() + " " + Classes.getDebugMessage(p.getLocation()); - } - }) - .changer(DefaultChangers.playerChanger) - .serializeAs(OfflinePlayer.class)); - - Classes.registerClass(new ClassInfo<>(OfflinePlayer.class, "offlineplayer") - .user("offline ?players?") - .name("Offline Player") - .description( - "A player that is possibly offline. See player for more information. " + - "Please note that while all effects and conditions that require a player can be used with an " + - "offline player as well, they will not work if the player is not actually online." - ).usage( - "Parsing an offline player as a player (online) will return nothing (none), for that case you would need to parse as " + - "offlineplayer which only returns nothing (none) if player doesn't exist in Minecraft databases (name not taken) otherwise it will return the player regardless of their online status." - ).examples("set {_p} to \"Notch\" parsed as an offlineplayer # returns Notch even if they're offline") - .since("2.0 beta 8") - .defaultExpression(new EventValueExpression<>(OfflinePlayer.class)) - .after("string", "world") - .parser(new Parser() { - @Override - public @Nullable OfflinePlayer parse(final String s, final ParseContext context) { - if (Utils.isValidUUID(s)) - return Bukkit.getOfflinePlayer(UUID.fromString(s)); - else if (SkriptConfig.playerNameRegexPattern.value().matcher(s).matches()) - return Bukkit.getOfflinePlayer(s); - return null; - } - - @Override - public boolean canParse(ParseContext context) { - return context == ParseContext.COMMAND || context == ParseContext.PARSE; - } - - @Override - public String toString(OfflinePlayer p, int flags) { - return p.getName() == null ? p.getUniqueId().toString() : p.getName(); - } - - @Override - public String toVariableNameString(OfflinePlayer p) { - if (SkriptConfig.usePlayerUUIDsInVariableNames.value() || p.getName() == null) - return "" + p.getUniqueId(); - else - return "" + p.getName(); - } + Classes.registerClass(new PlayerClassInfo()); - @Override - public String getDebugMessage(OfflinePlayer p) { - if (p.isOnline()) - return Classes.getDebugMessage(p.getPlayer()); - return toString(p, 0); - } - }).serializer(new Serializer() { - @Override - public Fields serialize(final OfflinePlayer p) { - final Fields f = new Fields(); - f.putObject("uuid", p.getUniqueId()); - return f; - } - - @Override - public void deserialize(final OfflinePlayer o, final Fields f) { - assert false; - } - - @Override - public boolean canBeInstantiated() { - return false; - } - - @SuppressWarnings("deprecation") - @Override - protected OfflinePlayer deserialize(final Fields fields) throws StreamCorruptedException { - if (fields.contains("uuid")) { - final UUID uuid = fields.getObject("uuid", UUID.class); - if (uuid == null) - throw new StreamCorruptedException(); - return Bukkit.getOfflinePlayer(uuid); - } else { - final String name = fields.getObject("name", String.class); - if (name == null) - throw new StreamCorruptedException(); - return Bukkit.getOfflinePlayer(name); - } - } - - // return p.getName(); - @SuppressWarnings("deprecation") - @Override - @Nullable - public OfflinePlayer deserialize(final String s) { - return Bukkit.getOfflinePlayer(s); - } - - @Override - public boolean mustSyncDeserialization() { - return true; - } - })); + Classes.registerClass(new OfflinePlayerClassInfo()); Classes.registerClass(new ClassInfo<>(CommandSender.class, "commandsender") .user("((commands?)? ?)?(sender|executor)s?") @@ -814,7 +481,7 @@ public boolean mustSyncDeserialization() { "\t\t\tsend \"Yay!\" to sender and arg-1") .since("1.0") .defaultExpression(new EventValueExpression<>(CommandSender.class)) - .parser(new Parser() { + .parser(new Parser<>() { @Override @Nullable public CommandSender parse(final String s, final ParseContext context) { @@ -828,14 +495,20 @@ public boolean canParse(final ParseContext context) { @Override public String toString(final CommandSender s, final int flags) { - return "" + s.getName(); + return s.getName(); } @Override public String toVariableNameString(final CommandSender s) { - return "" + s.getName(); + return s.getName(); } - })); + }) + .property(Property.NAME, + "A command sender's name, as text. Cannot be changed.", + Skript.instance(), + ExpressionPropertyHandler.of(CommandSender::getName, String.class))); + + Classes.registerClass(new NameableClassInfo()); Classes.registerClass(new ClassInfo<>(InventoryHolder.class, "inventoryholder") .name(ClassInfo.NO_DOC) @@ -875,66 +548,7 @@ public String toVariableNameString(InventoryHolder holder) { "set the player argument's game mode to creative") .since("1.0")); - Classes.registerClass(new ClassInfo<>(ItemStack.class, "itemstack") - .user("items?", "item ?stacks?") - .name("Item") - .description("An item, e.g. a stack of torches, a furnace, or a wooden sword of sharpness 2. " + - "Unlike item type an item can only represent exactly one item (e.g. an upside-down cobblestone stair facing west), " + - "while an item type can represent a whole range of items (e.g. any cobble stone stairs regardless of direction).", - "You don't usually need this type except when you want to make a command that only accepts an exact item.", - "Please note that currently 'material' is exactly the same as 'item', i.e. can have an amount & enchantments.") - .usage("[ [of]] [of ], Where must be an alias that represents exactly one item " + - "(i.e cannot be a general alias like 'sword' or 'plant')") - .examples("set {_item} to type of the targeted block", - "{_item} is a torch") - .since("1.0") - .after("number") - .supplier(() -> Arrays.stream(Material.values()) - .filter(Material::isItem) - .map(ItemStack::new) - .iterator()) - .parser(new Parser() { - @Override - @Nullable - public ItemStack parse(final String s, final ParseContext context) { - ItemType t = Aliases.parseItemType(s); - if (t == null) - return null; - t = t.getItem(); - if (t.numTypes() != 1) { - Skript.error("'" + s + "' represents multiple materials"); - return null; - } - - final ItemStack i = t.getRandom(); - if (i == null) { - Skript.error("'" + s + "' cannot represent an item"); - return null; - } - return i; - } - - @Override - public String toString(final ItemStack i, final int flags) { - return ItemType.toString(i, flags); - } - - @Override - public String toVariableNameString(final ItemStack i) { - final StringBuilder b = new StringBuilder("item:"); - b.append(i.getType().name()); - b.append(":" + ItemUtils.getDamage(i)); - b.append("*" + i.getAmount()); - - for (Entry entry : i.getEnchantments().entrySet()) - b.append("#" + entry.getKey().getKey()) - .append(":" + entry.getValue()); - - return b.toString(); - } - }) - .cloner(ItemStack::clone) - .serializer(new ConfigurationSerializer<>())); + Classes.registerClass(new ItemStackClassInfo()); Classes.registerClass(new ClassInfo<>(Item.class, "itementity") .name(ClassInfo.NO_DOC) @@ -1289,9 +903,10 @@ public String toVariableNameString(FireworkEffect effect) { .description("The health regain reason in a heal event.") .since("2.5")); + //noinspection rawtypes PatternedParser gameRuleParser = new PatternedParser<>() { - private String[] patterns = Arrays.stream(GameRule.values()).map(GameRule::getName).toArray(String[]::new); + private final String[] patterns = Arrays.stream(GameRule.values()).map(GameRule::getName).toArray(String[]::new); @Override public @Nullable GameRule parse(String string, ParseContext context) { @@ -1313,6 +928,7 @@ public String[] getPatterns() { return patterns; } }; + Classes.registerClass(new ClassInfo<>(GameRule.class, "gamerule") .user("gamerules?") .name("Gamerule") @@ -1322,6 +938,10 @@ public String[] getPatterns() { .requiredPlugins("Minecraft 1.13 or newer") .supplier(GameRule.values()) .parser(gameRuleParser) + .property(Property.NAME, + "A gamerule's name, as text. Cannot be changed.", + Skript.instance(), + ExpressionPropertyHandler.of(GameRule::getName, String.class)) ); Classes.registerClass(new ClassInfo<>(EnchantmentOffer.class, "enchantmentoffer") @@ -1530,7 +1150,7 @@ public String toVariableNameString(WorldBorder border) { .name("Vehicle") .description("Represents a vehicle.") .since("2.10.2") - .changer(DefaultChangers.entityChanger) + .changer(new EntityChanger()) ); Classes.registerClass(new EnumClassInfo<>(EquipmentSlot.class, "equipmentslot", "equipment slots") diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java b/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java index 8a8ce77dc55..6338b442808 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java @@ -1,26 +1,18 @@ package ch.njol.skript.classes.data; -import org.bukkit.Material; +import ch.njol.skript.classes.Changer; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.Item; -import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.bukkitutil.PlayerUtils; -import ch.njol.skript.classes.Changer; -import ch.njol.skript.util.Experience; -import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.bukkit.base.types.BlockClassInfo; +import org.skriptlang.skript.bukkit.base.types.EntityClassInfo; +import org.skriptlang.skript.bukkit.base.types.InventoryClassInfo; +import org.skriptlang.skript.bukkit.base.types.PlayerClassInfo; /** * @author Peter Güttinger @@ -28,95 +20,11 @@ public class DefaultChangers { public DefaultChangers() {} - - public final static Changer entityChanger = new Changer() { - @Override - @Nullable - public Class[] acceptChange(final ChangeMode mode) { - switch (mode) { - case ADD: - return CollectionUtils.array(ItemType[].class, Inventory.class, Experience[].class); - case DELETE: - return CollectionUtils.array(); - case REMOVE: - return CollectionUtils.array(PotionEffectType[].class, ItemType[].class, Inventory.class); - case REMOVE_ALL: - return CollectionUtils.array(PotionEffectType[].class, ItemType[].class); - case SET: - case RESET: // REMIND reset entity? (unshear, remove held item, reset weapon/armour, ...) - return null; - } - assert false; - return null; - } - - @Override - public void change(final Entity[] entities, final @Nullable Object[] delta, final ChangeMode mode) { - if (delta == null) { - for (final Entity e : entities) { - if (!(e instanceof Player)) - e.remove(); - } - return; - } - boolean hasItem = false; - for (final Entity e : entities) { - for (final Object d : delta) { - if (d instanceof PotionEffectType) { - assert mode == ChangeMode.REMOVE || mode == ChangeMode.REMOVE_ALL; - if (!(e instanceof LivingEntity)) - continue; - ((LivingEntity) e).removePotionEffect((PotionEffectType) d); - } else { - if (e instanceof Player) { - final Player p = (Player) e; - if (d instanceof Experience) { - p.giveExp(((Experience) d).getXP()); - } else if (d instanceof Inventory) { - PlayerInventory inventory = p.getInventory(); - for (ItemStack itemStack : (Inventory) d) { - if (itemStack == null) - continue; - if (mode == ChangeMode.ADD) { - inventory.addItem(itemStack); - } else { - inventory.remove(itemStack); - } - } - } else if (d instanceof ItemType) { - hasItem = true; - final PlayerInventory invi = p.getInventory(); - if (mode == ChangeMode.ADD) - ((ItemType) d).addTo(invi); - else if (mode == ChangeMode.REMOVE) - ((ItemType) d).removeFrom(invi); - else - ((ItemType) d).removeAll(invi); - } - } - } - } - if (e instanceof Player && hasItem) - PlayerUtils.updateInventory((Player) e); - } - } - }; - - public final static Changer playerChanger = new Changer() { - @Override - @Nullable - public Class[] acceptChange(final ChangeMode mode) { - if (mode == ChangeMode.DELETE) - return null; - return entityChanger.acceptChange(mode); - } - - @Override - public void change(final Player[] players, final @Nullable Object[] delta, final ChangeMode mode) { - entityChanger.change(players, delta, mode); - } - }; - + + public final static Changer entityChanger = new EntityClassInfo.EntityChanger(); + + public final static Changer playerChanger = new PlayerClassInfo.PlayerChanger(); + public final static Changer nonLivingEntityChanger = new Changer() { @Override @Nullable @@ -125,7 +33,7 @@ public Class[] acceptChange(final ChangeMode mode) { return CollectionUtils.array(); return null; } - + @Override public void change(final Entity[] entities, final @Nullable Object[] delta, final ChangeMode mode) { assert mode == ChangeMode.DELETE; @@ -157,182 +65,9 @@ public void change(final Item[] what, final @Nullable Object[] delta, final Chan } } }; - - public final static Changer inventoryChanger = new Changer() { - - private Material[] cachedMaterials = Material.values(); - - @Override - @Nullable - public Class[] acceptChange(final ChangeMode mode) { - if (mode == ChangeMode.RESET) - return null; - if (mode == ChangeMode.REMOVE_ALL) - return CollectionUtils.array(ItemType[].class); - if (mode == ChangeMode.SET) - return CollectionUtils.array(ItemType[].class, Inventory.class); - return CollectionUtils.array(ItemType[].class, Inventory[].class); - } - - @Override - public void change(final Inventory[] invis, final @Nullable Object[] delta, final ChangeMode mode) { - for (final Inventory invi : invis) { - assert invi != null; - switch (mode) { - case DELETE: - invi.clear(); - break; - case SET: - invi.clear(); - //$FALL-THROUGH$ - case ADD: - assert delta != null; - - if(delta instanceof ItemStack[]) { // Old behavior - legacy code (is it used? no idea) - ItemStack[] items = (ItemStack[]) delta; - if(items.length > 36) { - return; - } - for (final Object d : delta) { - if (d instanceof Inventory) { - for (final ItemStack i : (Inventory) d) { - if (i != null) - invi.addItem(i); - } - } else { - ((ItemType) d).addTo(invi); - } - } - } else { - for (final Object d : delta) { - if (d instanceof ItemStack) { - new ItemType((ItemStack) d).addTo(invi); // Can't imagine why would be ItemStack, but just in case... - } else if (d instanceof ItemType) { - ((ItemType) d).addTo(invi); - } else if (d instanceof Block) { - new ItemType((Block) d).addTo(invi); - } else { - Skript.error("Can't " + d.toString() + " to an inventory!"); - } - } - } - - break; - case REMOVE: - case REMOVE_ALL: - assert delta != null; - if (delta.length == cachedMaterials.length) { - // Potential fast path: remove all items -> clear inventory - boolean equal = true; - for (int i = 0; i < delta.length; i++) { - if (!(delta[i] instanceof ItemType)) { - equal = false; - break; // Not an item, take slow path - } - if (((ItemType) delta[i]).getMaterial() != cachedMaterials[i]) { - equal = false; - break; - } - } - if (equal) { // Take fast path, break out before slow one - invi.clear(); - break; - } - } - - // Slow path - for (final Object d : delta) { - if (d instanceof Inventory) { - assert mode == ChangeMode.REMOVE; - for (ItemStack itemStack : (Inventory) d) { - if (itemStack != null) - invi.removeItem(itemStack); - } - } else { - if (mode == ChangeMode.REMOVE) - ((ItemType) d).removeFrom(invi); - else - ((ItemType) d).removeAll(invi); - } - } - break; - case RESET: - assert false; - } - InventoryHolder holder = invi.getHolder(); - if (holder instanceof Player) { - ((Player) holder).updateInventory(); - } - } - } - }; - - public final static Changer blockChanger = new Changer() { - @Override - @Nullable - public Class[] acceptChange(final ChangeMode mode) { - if (mode == ChangeMode.RESET) - return null; // REMIND regenerate? - if (mode == ChangeMode.SET) - return CollectionUtils.array(ItemType.class, BlockData.class); - return CollectionUtils.array(ItemType[].class, Inventory[].class); - } - - @Override - public void change(final Block[] blocks, final @Nullable Object[] delta, final ChangeMode mode) { - for (Block block : blocks) { - assert block != null; - switch (mode) { - case SET: - assert delta != null; - Object object = delta[0]; - if (object instanceof ItemType) { - ((ItemType) object).getBlock().setBlock(block, true); - } else if (object instanceof BlockData) { - block.setBlockData(((BlockData) object)); - } - break; - case DELETE: - block.setType(Material.AIR, true); - break; - case ADD: - case REMOVE: - case REMOVE_ALL: - assert delta != null; - BlockState state = block.getState(); - if (!(state instanceof InventoryHolder)) - break; - Inventory invi = ((InventoryHolder) state).getInventory(); - if (mode == ChangeMode.ADD) { - for (Object obj : delta) { - if (obj instanceof Inventory) { - for (ItemStack i : (Inventory) obj) { - if (i != null) - invi.addItem(i); - } - } else { - ((ItemType) obj).addTo(invi); - } - } - } else { - for (Object obj : delta) { - if (obj instanceof Inventory) { - invi.removeItem(((Inventory) obj).getContents()); - } else { - if (mode == ChangeMode.REMOVE) - ((ItemType) obj).removeFrom(invi); - else - ((ItemType) obj).removeAll(invi); - } - } - } - state.update(); - break; - case RESET: - assert false; - } - } - } - }; + + public final static Changer inventoryChanger = new InventoryClassInfo.InventoryChanger(); + + public final static Changer blockChanger = new BlockClassInfo.BlockChanger(); } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java index f6af95098bb..6eb48386e13 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -1,6 +1,7 @@ package ch.njol.skript.classes.data; import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.EntityUtils; import ch.njol.skript.command.Commands; @@ -41,6 +42,7 @@ import java.util.UUID; +@SuppressWarnings("removal") // temporary due to usage of AnyX classes. public class DefaultConverters { public DefaultConverters() {} @@ -159,91 +161,94 @@ public DefaultConverters() {} return null; }, Converter.NO_CHAINING); - // Anything with a name -> AnyNamed - Converters.registerConverter(OfflinePlayer.class, AnyNamed.class, player -> player::getName, Converter.NO_RIGHT_CHAINING); - if (Skript.classExists("org.bukkit.generator.WorldInfo")) - Converters.registerConverter(World.class, AnyNamed.class, world -> world::getName, Converter.NO_RIGHT_CHAINING); - else //noinspection RedundantCast getName method is on World itself in older versions - Converters.registerConverter(World.class, AnyNamed.class, world -> () -> ((World) world).getName(), Converter.NO_RIGHT_CHAINING); - Converters.registerConverter(GameRule.class, AnyNamed.class, rule -> rule::getName, Converter.NO_RIGHT_CHAINING); - Converters.registerConverter(Server.class, AnyNamed.class, server -> server::getName, Converter.NO_RIGHT_CHAINING); - Converters.registerConverter(Plugin.class, AnyNamed.class, plugin -> plugin::getName, Converter.NO_RIGHT_CHAINING); - Converters.registerConverter(WorldType.class, AnyNamed.class, type -> type::getName, Converter.NO_RIGHT_CHAINING); - Converters.registerConverter(Team.class, AnyNamed.class, team -> team::getName, Converter.NO_RIGHT_CHAINING); - Converters.registerConverter(Objective.class, AnyNamed.class, objective -> objective::getName, Converter.NO_RIGHT_CHAINING); - Converters.registerConverter(Nameable.class, AnyNamed.class, // - nameable -> new AnyNamed() { - @Override - public @UnknownNullability String name() { - //noinspection deprecation - return nameable.getCustomName(); - } - - @Override - public boolean supportsNameChange() { - return true; - } - @Override - public void setName(String name) { - //noinspection deprecation - nameable.setCustomName(name); - } - }, - // - Converter.NO_RIGHT_CHAINING); - Converters.registerConverter(Block.class, AnyNamed.class, // - block -> new AnyNamed() { - @Override - public @UnknownNullability String name() { - BlockState state = block.getState(); - if (state instanceof Nameable nameable) + if (!SkriptConfig.useTypeProperties.value()) { + // Anything with a name -> AnyNamed + Converters.registerConverter(OfflinePlayer.class, AnyNamed.class, player -> player::getName, Converter.NO_RIGHT_CHAINING); + if (Skript.classExists("org.bukkit.generator.WorldInfo")) + Converters.registerConverter(World.class, AnyNamed.class, world -> world::getName, Converter.NO_RIGHT_CHAINING); + else //noinspection RedundantCast getName method is on World itself in older versions + Converters.registerConverter(World.class, AnyNamed.class, world -> () -> ((World) world).getName(), Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(GameRule.class, AnyNamed.class, rule -> rule::getName, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(Server.class, AnyNamed.class, server -> server::getName, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(Plugin.class, AnyNamed.class, plugin -> plugin::getName, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(WorldType.class, AnyNamed.class, type -> type::getName, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(Team.class, AnyNamed.class, team -> team::getName, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(Objective.class, AnyNamed.class, objective -> objective::getName, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(Nameable.class, AnyNamed.class, // + nameable -> new AnyNamed() { + @Override + public @UnknownNullability String name() { //noinspection deprecation return nameable.getCustomName(); - return null; - } + } - @Override - public boolean supportsNameChange() { - return true; - } + @Override + public boolean supportsNameChange() { + return true; + } - @Override - public void setName(String name) { - BlockState state = block.getState(); - if (state instanceof Nameable nameable) { + @Override + public void setName(String name) { //noinspection deprecation nameable.setCustomName(name); - state.update(true, false); } - } - }, - // - Converter.NO_RIGHT_CHAINING); - Converters.registerConverter(CommandSender.class, AnyNamed.class, thing -> thing::getName, Converter.NO_RIGHT_CHAINING); - // Command senders should be done last because there might be a better alternative above - - // Anything with an amount -> AnyAmount - Converters.registerConverter(ItemStack.class, AnyAmount.class, // - item -> new AnyAmount() { - - @Override - public @NotNull Number amount() { - return item.getAmount(); - } + }, + // + Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(Block.class, AnyNamed.class, // + block -> new AnyNamed() { + @Override + public @UnknownNullability String name() { + BlockState state = block.getState(); + if (state instanceof Nameable nameable) + //noinspection deprecation + return nameable.getCustomName(); + return null; + } - @Override - public boolean supportsAmountChange() { - return true; - } + @Override + public boolean supportsNameChange() { + return true; + } - @Override - public void setAmount(Number amount) { - item.setAmount(amount != null ? amount.intValue() : 0); - } - }, - // - Converter.NO_RIGHT_CHAINING); + @Override + public void setName(String name) { + BlockState state = block.getState(); + if (state instanceof Nameable nameable) { + //noinspection deprecation + nameable.setCustomName(name); + state.update(true, false); + } + } + }, + // + Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(CommandSender.class, AnyNamed.class, thing -> thing::getName, Converter.NO_RIGHT_CHAINING); + // Command senders should be done last because there might be a better alternative above + + // Anything with an amount -> AnyAmount + Converters.registerConverter(ItemStack.class, AnyAmount.class, // + item -> new AnyAmount() { + + @Override + public @NotNull Number amount() { + return item.getAmount(); + } + + @Override + public boolean supportsAmountChange() { + return true; + } + + @Override + public void setAmount(Number amount) { + item.setAmount(amount != null ? amount.intValue() : 0); + } + }, + // + Converter.NO_RIGHT_CHAINING); + } // InventoryHolder - Location // since the individual ones can't be trusted to chain. diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index d2011548fd9..63f3e5accf6 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -18,6 +18,9 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; +import org.skriptlang.skript.lang.properties.PropertyHandler.ContainsHandler; import java.io.StreamCorruptedException; import java.util.UUID; @@ -301,7 +304,26 @@ public String deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - })); + }) + .property(Property.CONTAINS, + "Strings can contain other strings.", + Skript.instance(), + new ContainsHandler() { + @Override + public boolean contains(String container, String element) { + return StringUtils.contains(container, element, SkriptConfig.caseSensitive.value()); + } + + @Override + public Class[] elementTypes() { + //noinspection unchecked + return new Class[]{String.class}; + } + }) + .property(Property.IS_EMPTY, + "Whether the string is empty, i.e. has no characters.", + Skript.instance(), + ConditionPropertyHandler.of(String::isEmpty))); // joml type - for display entities if (Skript.classExists("org.joml.Quaternionf")) diff --git a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java index 22539ac0187..87df08749ad 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -1,13 +1,10 @@ package ch.njol.skript.classes.data; -import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemData; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.classes.*; +import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.config.Config; +import ch.njol.skript.config.EntryNode; import ch.njol.skript.config.Node; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.ParseContext; @@ -21,25 +18,25 @@ import ch.njol.skript.localization.RegexMessage; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.*; -import ch.njol.skript.util.slot.Slot; import ch.njol.skript.util.visual.VisualEffect; import ch.njol.skript.util.visual.VisualEffects; import ch.njol.yggdrasil.Fields; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.util.SkriptQueue; +import org.skriptlang.skript.bukkit.base.types.ItemTypeClassInfo; +import org.skriptlang.skript.bukkit.base.types.SlotClassInfo; +import org.skriptlang.skript.common.types.QueueClassInfo; +import org.skriptlang.skript.common.types.ScriptClassInfo; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; +import org.skriptlang.skript.lang.properties.PropertyHandler.ContainsHandler; +import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.PropertyHandler.TypedValuePropertyHandler; import org.skriptlang.skript.util.Executable; import java.io.File; -import java.io.NotSerializableException; import java.io.StreamCorruptedException; -import java.nio.file.Path; -import java.util.Arrays; import java.util.Iterator; -import java.util.List; import java.util.Locale; import java.util.regex.Pattern; @@ -100,7 +97,7 @@ public boolean canBeInstantiated() { } @Override - public void deserialize(final ClassInfo o, final Fields f) throws StreamCorruptedException { + public void deserialize(final ClassInfo o, final Fields f) { assert false; } @@ -115,13 +112,6 @@ protected ClassInfo deserialize(final Fields fields) throws StreamCorruptedExcep return ci; } -// return c.getCodeName(); - @Override - @Nullable - public ClassInfo deserialize(final String s) { - return Classes.getClassInfoNoError(s); - } - @Override public boolean mustSyncDeserialization() { return false; @@ -158,66 +148,7 @@ public String toVariableNameString(final WeatherType o) { }) .serializer(new EnumSerializer<>(WeatherType.class))); - Classes.registerClass(new ClassInfo<>(ItemType.class, "itemtype") - .user("item ?types?", "materials?") - .name("Item Type") - .description("An item type is an alias that can result in different items when added to an inventory, " + - "and unlike items they are well suited for checking whether an inventory contains a certain item or whether a certain item is of a certain type.", - "An item type can also have one or more enchantments with or without a specific level defined, " + - "and can optionally start with 'all' or 'every' to make this item type represent all types that the alias represents, including data ranges.") - .usage("[ [of]] [all/every] [of [] [,/and ]]") - .examples("give 4 torches to the player", - "add oak slab to the inventory of the block", - "player's tool is a diamond sword of sharpness", - "block is dirt or farmland") - .since("1.0") - .before("itemstack", "entitydata", "entitytype") - .after("number", "integer", "long", "time") - .supplier(() -> Arrays.stream(Material.values()) - .map(ItemType::new) - .iterator()) - .parser(new Parser() { - @Override - @Nullable - public ItemType parse(final String s, final ParseContext context) { - return Aliases.parseItemType(s); - } - - @Override - public String toString(final ItemType t, final int flags) { - return t.toString(flags); - } - - @Override - public String getDebugMessage(final ItemType t) { - return t.getDebugMessage(); - } - - @Override - public String toVariableNameString(final ItemType t) { - final StringBuilder b = new StringBuilder("itemtype:"); - b.append(t.getInternalAmount()); - b.append("," + t.isAll()); - // TODO this is missing information - for (final ItemData d : t.getTypes()) { - b.append("," + d.getType()); - } - final EnchantmentType[] enchs = t.getEnchantmentTypes(); - if (enchs != null) { - b.append("|"); - for (final EnchantmentType ench : enchs) { - Enchantment e = ench.getType(); - if (e == null) - continue; - b.append("#" + e.getKey().toString()); - b.append(":" + ench.getLevel()); - } - } - return "" + b.toString(); - } - }) - .cloner(ItemType::clone) - .serializer(new YggdrasilSerializer<>())); + Classes.registerClass(new ItemTypeClassInfo()); Classes.registerClass(new ClassInfo<>(Time.class, "time") .user("times?") @@ -420,122 +351,7 @@ public String toVariableNameString(final Direction o) { }) .serializer(new YggdrasilSerializer<>())); - Classes.registerClass(new ClassInfo<>(Slot.class, "slot") - .user("(inventory )?slots?") - .name("Slot") - .description("Represents a single slot of an inventory. " + - "Notable slots are the armour slots and furnace slots. ", - "The most important property that distinguishes a slot from an item is its ability to be changed, e.g. it can be set, deleted, enchanted, etc. " + - "(Some item expressions can be changed as well, e.g. items stored in variables. " + - "For that matter: slots are never saved to variables, only the items they represent at the time when the variable is set).", - "Please note that tool can be regarded a slot, but it can actually change it's position, i.e. doesn't represent always the same slot.") - .usage("") - .examples("set tool of player to dirt", - "delete helmet of the victim", - "set the color of the player's tool to green", - "enchant the player's chestplate with projectile protection 5") - .since("") - .defaultExpression(new EventValueExpression<>(Slot.class)) - .changer(new Changer() { - @SuppressWarnings("unchecked") - @Override - @Nullable - public Class[] acceptChange(final ChangeMode mode) { - if (mode == ChangeMode.RESET) - return null; - if (mode == ChangeMode.SET) - return new Class[] {ItemType[].class, ItemStack[].class}; - return new Class[] {ItemType.class, ItemStack.class}; - } - - @Override - public void change(final Slot[] slots, final @Nullable Object[] deltas, final ChangeMode mode) { - if (mode == ChangeMode.SET) { - if (deltas != null) { - if (deltas.length == 1) { - final Object delta = deltas[0]; - for (final Slot slot : slots) { - slot.setItem(delta instanceof ItemStack ? (ItemStack) delta : ((ItemType) delta).getItem().getRandom()); - } - } else if (deltas.length == slots.length) { - for (int i = 0; i < slots.length; i++) { - final Object delta = deltas[i]; - slots[i].setItem(delta instanceof ItemStack ? (ItemStack) delta : ((ItemType) delta).getItem().getRandom()); - } - } - } - return; - } - final Object delta = deltas == null ? null : deltas[0]; - for (final Slot slot : slots) { - switch (mode) { - case ADD: - assert delta != null; - if (delta instanceof ItemStack) { - final ItemStack i = slot.getItem(); - if (i == null || i.getType() == Material.AIR || ItemUtils.itemStacksEqual(i, (ItemStack) delta)) { - if (i != null && i.getType() != Material.AIR) { - i.setAmount(Math.min(i.getAmount() + ((ItemStack) delta).getAmount(), i.getMaxStackSize())); - slot.setItem(i); - } else { - slot.setItem((ItemStack) delta); - } - } - } else { - slot.setItem(((ItemType) delta).getItem().addTo(slot.getItem())); - } - break; - case REMOVE: - case REMOVE_ALL: - assert delta != null; - if (delta instanceof ItemStack) { - final ItemStack i = slot.getItem(); - if (i != null && ItemUtils.itemStacksEqual(i, (ItemStack) delta)) { - final int a = mode == ChangeMode.REMOVE_ALL ? 0 : i.getAmount() - ((ItemStack) delta).getAmount(); - if (a <= 0) { - slot.setItem(null); - } else { - i.setAmount(a); - slot.setItem(i); - } - } - } else { - if (mode == ChangeMode.REMOVE) - slot.setItem(((ItemType) delta).removeFrom(slot.getItem())); - else - // REMOVE_ALL - slot.setItem(((ItemType) delta).removeAll(slot.getItem())); - } - break; - case DELETE: - slot.setItem(null); - break; - case RESET: - assert false; - } - } - } - }) - .parser(new Parser() { - @Override - public boolean canParse(final ParseContext context) { - return false; - } - - @Override - public String toString(Slot o, int flags) { - ItemStack i = o.getItem(); - if (i == null) - return new ItemType(Material.AIR).toString(flags); - return ItemType.toString(i, flags); - } - - @Override - public String toVariableNameString(Slot o) { - return "slot:" + o.toString(); - } - }) - .serializeAs(ItemStack.class)); + Classes.registerClass(new SlotClassInfo()); Classes.registerClass(new ClassInfo<>(Color.class, "color") .user("colou?rs?") @@ -698,90 +514,7 @@ public String toVariableNameString(VisualEffect e) { .serializer(new YggdrasilSerializer()) ); - Classes.registerClass(new ClassInfo<>(SkriptQueue.class, "queue") - .user("queues?") - .name("Queue") - .description("A queued list of values. Entries are removed from a queue when they are queried.") - .examples( - "set {queue} to a new queue", - "add \"hello\" to {queue}", - "broadcast the 1st element of {queue}" - ) - .since("2.10") - .changer(new Changer<>() { - @Override - public Class @Nullable [] acceptChange(ChangeMode mode) { - return switch (mode) { - case ADD, REMOVE, DELETE -> new Class[] {Object.class}; - case RESET -> new Class[0]; - default -> null; - }; - } - - @Override - public void change(SkriptQueue[] what, Object @Nullable [] delta, ChangeMode mode) { - for (SkriptQueue queue : what) { - switch (mode) { - case RESET, DELETE -> queue.clear(); - case ADD -> { - assert delta != null; - queue.addAll(Arrays.asList(delta)); - } - case REMOVE -> { - assert delta != null; - queue.removeAll(Arrays.asList(delta)); - } - } - } - } - }) - .parser(new Parser() { - - @Override - public boolean canParse(ParseContext context) { - return false; - } - - @Override - public String toString(SkriptQueue queue, int flags) { - return Classes.toString(queue.toArray(), flags, true); - } - - @Override - public String toVariableNameString(SkriptQueue queue) { - return this.toString(queue, 0); - } - - }) - .serializer(new Serializer() { - @Override - public Fields serialize(SkriptQueue queue) throws NotSerializableException { - Fields fields = new Fields(); - fields.putObject("contents", queue.toArray()); - return fields; - } - - @Override - public void deserialize(SkriptQueue queue, Fields fields) - throws StreamCorruptedException, NotSerializableException { - Object[] contents = fields.getObject("contents", Object[].class); - queue.clear(); - if (contents != null) - queue.addAll(List.of(contents)); - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - - @Override - protected boolean canBeInstantiated() { - return true; - } - }) - ); - + Classes.registerClass(new QueueClassInfo()); Classes.registerClass(new ClassInfo<>(Config.class, "config") .user("configs?") @@ -811,7 +544,11 @@ public String toString(Config config, int flags) { public String toVariableNameString(Config config) { return this.toString(config, 0); } - })); + }) + .property(Property.NAME, + "The filename of the Config, as text.", + Skript.instance(), + ExpressionPropertyHandler.of(Config::name, String.class))); Classes.registerClass(new ClassInfo<>(Node.class, "node") .user("nodes?") @@ -838,54 +575,31 @@ public String toVariableNameString(Node node) { return this.toString(node, 0); } - })); - - Classes.registerClass(new ClassInfo<>(Script.class, "script") - .user("scripts?") - .name("Script") - .description("A script loaded by Skript.", - "Disabled scripts will report as being empty since their content has not been loaded.") - .usage("") - .examples("the current script") - .since("2.10") - .parser(new Parser