diff --git a/build.gradle b/build.gradle index 5920cb95ac8..0041b17fc59 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,9 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' - shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' + shadow group: 'net.kyori', name: 'adventure-text-minimessage', version: '4.14.0' + shadow group: 'net.kyori', name: 'adventure-platform-bukkit', version: '4.3.0' + shadow group: 'net.kyori', name: 'adventure-text-serializer-plain', version: '4.14.0' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.1-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' @@ -85,14 +87,9 @@ tasks.withType(ShadowJar) { configurations = [ project.configurations.shadow ] - dependencies { - include(dependency('io.papermc:paperlib')) - include(dependency('org.bstats:bstats-bukkit')) - include(dependency('org.bstats:bstats-base')) - include(dependency('net.kyori:adventure-text-serializer-bungeecord')) - } relocate 'io.papermc.lib', 'ch.njol.skript.paperlib' relocate 'org.bstats', 'ch.njol.skript.bstats' + relocate 'net.kyori', 'org.skriptlang.kyori' manifest { attributes( 'Name': 'ch/njol/skript', diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index dab73c2b5a6..8e3114a86fe 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -53,6 +53,12 @@ import java.util.zip.ZipException; import java.util.zip.ZipFile; +import ch.njol.skript.lang.Section; +import ch.njol.skript.localization.LanguageChangeListener; +import org.skriptlang.skript.bukkit.chat.ChatModule; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bstats.bukkit.Metrics; import org.bstats.charts.SimplePie; import org.bukkit.Bukkit; @@ -104,7 +110,6 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionInfo; import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.Section; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptEventInfo; import ch.njol.skript.lang.Statement; @@ -542,6 +547,7 @@ public void onEnable() { try { getAddonInstance().loadClasses("ch.njol.skript", "conditions", "effects", "events", "expressions", "entity", "sections", "structures"); + new ChatModule().register(getAddonInstance()); // TODO remove eventually } catch (final Exception e) { exception(e, "Could not load required .class files: " + e.getLocalizedMessage()); setEnabled(false); @@ -1843,43 +1849,56 @@ static void logEx(final String... lines) { SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX + line); } - private static final Message SKRIPT_PREFIX_MESSAGE = new Message("skript.prefix"); + private static Component SKRIPT_PREFIX_COMPONENT; + private static String SKRIPT_PREFIX; + + static { + SKRIPT_PREFIX = "[Skript] "; + SKRIPT_PREFIX_COMPONENT = ComponentHandler.parse(SKRIPT_PREFIX); + Language.addListener(() -> { + String prefix = Language.get_("skript.prefix"); + if (prefix != null) { + SKRIPT_PREFIX = prefix; + SKRIPT_PREFIX_COMPONENT = ComponentHandler.parse(SKRIPT_PREFIX); + } + }); + } public static String getSkriptPrefix() { - return SKRIPT_PREFIX_MESSAGE.getValueOrDefault("[Skript] "); + return SKRIPT_PREFIX; } - public static void info(final CommandSender sender, final String info) { - sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + info)); + public static void info(CommandSender sender, String info) { + ComponentHandler.audienceFrom(sender).sendMessage(SKRIPT_PREFIX_COMPONENT.append(ComponentHandler.parse(info))); } - + /** * @param message * @param permission * @see #adminBroadcast(String) */ - public static void broadcast(final String message, final String permission) { - Bukkit.broadcast(Utils.replaceEnglishChatStyles(getSkriptPrefix() + message), permission); + public static void broadcast(String message, String permission) { + Bukkit.broadcast(getSkriptPrefix() + ComponentHandler.toLegacyString(message, false), permission); } - - public static void adminBroadcast(final String message) { - broadcast(message, "skript.admin"); + + public static void adminBroadcast(String message) { + Bukkit.broadcast(getSkriptPrefix() + ComponentHandler.toLegacyString(message, false), "skript.admin"); } - + /** * Similar to {@link #info(CommandSender, String)} but no [Skript] prefix is added. * * @param sender * @param info */ - public static void message(final CommandSender sender, final String info) { - sender.sendMessage(Utils.replaceEnglishChatStyles(info)); + public static void message(CommandSender sender, String info) { + ComponentHandler.audienceFrom(sender).sendMessage(ComponentHandler.parse(info)); } - - public static void error(final CommandSender sender, final String error) { - sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + ChatColor.DARK_RED + error)); + + public static void error(CommandSender sender, String error) { + ComponentHandler.audienceFrom(sender).sendMessage(SKRIPT_PREFIX_COMPONENT.append(ComponentHandler.parse(error).color(NamedTextColor.DARK_RED))); } - + /** * Gets the updater instance currently used by Skript. * @return SkriptUpdater instance. @@ -1888,5 +1907,5 @@ public static void error(final CommandSender sender, final String error) { public SkriptUpdater getUpdater() { return updater; } - + } 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 1f463e87361..848fcd86c8c 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -29,9 +29,9 @@ import ch.njol.skript.localization.Message; import ch.njol.skript.localization.RegexMessage; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.Utils; import ch.njol.util.StringUtils; import ch.njol.yggdrasil.Fields; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; import org.eclipse.jdt.annotation.Nullable; import java.util.regex.Pattern; @@ -573,7 +573,7 @@ public String parse(String s, ParseContext context) { case SCRIPT: case EVENT: if (VariableString.isQuotedCorrectly(s, true)) - return Utils.replaceChatStyles("" + s.substring(1, s.length() - 1).replace("\"\"", "\"")); + return ComponentHandler.toLegacyString("" + s.substring(1, s.length() - 1).replace("\"\"", "\""), false); return null; case COMMAND: return s; diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 93e8cad25b0..778ae5029fa 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -32,6 +32,8 @@ import java.util.Set; import java.util.UUID; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; +import net.kyori.adventure.text.Component; import ch.njol.skript.ScriptLoader; import ch.njol.skript.config.SectionNode; import org.skriptlang.skript.lang.script.Script; @@ -71,9 +73,6 @@ import ch.njol.skript.util.Date; import ch.njol.skript.util.EmptyStacktraceException; import ch.njol.skript.util.Timespan; -import ch.njol.skript.util.Utils; -import ch.njol.skript.util.chat.BungeeConverter; -import ch.njol.skript.util.chat.MessageComponent; import ch.njol.skript.variables.Variables; import ch.njol.util.StringUtils; import ch.njol.util.Validate; @@ -93,7 +92,7 @@ public class ScriptCommand implements TabExecutor { private List activeAliases; private String permission; private final VariableString permissionMessage; - private final String description; + private final Component description; private final String prefix; @Nullable private final Timespan cooldown; @@ -101,7 +100,7 @@ public class ScriptCommand implements TabExecutor { private final String cooldownBypass; @Nullable private final Expression cooldownStorage; - final String usage; + private final Component usage; private final Trigger trigger; @@ -179,8 +178,8 @@ public ScriptCommand( this.aliases = aliases; activeAliases = new ArrayList<>(aliases); - this.description = Utils.replaceEnglishChatStyles(description); - this.usage = Utils.replaceEnglishChatStyles(usage); + this.description = ComponentHandler.parse(description); + this.usage = ComponentHandler.parse(usage); this.executableBy = executableBy; @@ -190,24 +189,20 @@ public ScriptCommand( trigger = new Trigger(script, "command /" + name, new SimpleEvent(), ScriptLoader.loadItems(node)); trigger.setLineNumber(node.getLine()); - bukkitCommand = setupBukkitCommand(); - } - - private PluginCommand setupBukkitCommand() { try { - final Constructor c = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); + Constructor c = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); c.setAccessible(true); - final PluginCommand bukkitCommand = c.newInstance(name, Skript.getInstance()); - bukkitCommand.setAliases(aliases); + PluginCommand bukkitCommand = c.newInstance(name, Skript.getInstance()); + bukkitCommand.setAliases(this.aliases); bukkitCommand.setDescription(description); - bukkitCommand.setLabel(label); - bukkitCommand.setPermission(permission); + bukkitCommand.setLabel(this.label); + bukkitCommand.setPermission(this.permission); // We can only set the message if it's simple (doesn't contains expressions) - if (permissionMessage.isSimple()) - bukkitCommand.setPermissionMessage(permissionMessage.toString(null)); + if (this.permissionMessage.isSimple()) + bukkitCommand.setPermissionMessage(this.permissionMessage.toString(null)); bukkitCommand.setUsage(usage); bukkitCommand.setExecutor(this); - return bukkitCommand; + this.bukkitCommand = bukkitCommand; } catch (final Exception e) { Skript.outdatedError(e); throw new EmptyStacktraceException(); @@ -238,13 +233,7 @@ public boolean execute(final CommandSender sender, final String commandLabel, fi final ScriptCommandEvent event = new ScriptCommandEvent(ScriptCommand.this, sender, commandLabel, rest); if (!permission.isEmpty() && !sender.hasPermission(permission)) { - if (sender instanceof Player) { - List components = - permissionMessage.getMessageComponents(event); - ((Player) sender).spigot().sendMessage(BungeeConverter.convert(components)); - } else { - sender.sendMessage(permissionMessage.getSingle(event)); - } + ComponentHandler.audienceFrom(sender).sendMessage(ComponentHandler.parseFromSingleExpression(event, permissionMessage)); return false; } @@ -318,8 +307,8 @@ boolean execute2(final ScriptCommandEvent event, final CommandSender sender, fin } public void sendHelp(final CommandSender sender) { - if (!description.isEmpty()) - sender.sendMessage(description); + if (!Component.IS_NOT_EMPTY.test(description)) + ComponentHandler.audienceFrom(sender).sendMessage(description); sender.sendMessage(ChatColor.GOLD + "Usage" + ChatColor.RESET + ": " + usage); } diff --git a/src/main/java/ch/njol/skript/effects/EffActionBar.java b/src/main/java/ch/njol/skript/effects/EffActionBar.java deleted file mode 100644 index 09d62e9044c..00000000000 --- a/src/main/java/ch/njol/skript/effects/EffActionBar.java +++ /dev/null @@ -1,78 +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.effects; - -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; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.util.chat.BungeeConverter; -import ch.njol.skript.util.chat.ChatMessages; -import ch.njol.util.Kleenean; -import net.md_5.bungee.api.ChatMessageType; -import net.md_5.bungee.api.chat.BaseComponent; - -@Name("Action Bar") -@Description("Sends an action bar message to the given player(s).") -@Examples("send action bar \"Hello player!\" to player") -@Since("2.3") -public class EffActionBar extends Effect { - - static { - Skript.registerEffect(EffActionBar.class, "send [the] action[ ]bar [with text] %string% to %players%"); - } - - @SuppressWarnings("null") - private Expression message; - - @SuppressWarnings("null") - private Expression recipients; - - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final SkriptParser.ParseResult parser) { - message = (Expression) exprs[0]; - recipients = (Expression) exprs[1]; - return true; - } - - @SuppressWarnings("deprecation") - @Override - protected void execute(final Event e) { - String msg = message.getSingle(e); - assert msg != null; - BaseComponent[] components = BungeeConverter.convert(ChatMessages.parseToArray(msg)); - for (Player player : recipients.getArray(e)) - player.spigot().sendMessage(ChatMessageType.ACTION_BAR, components); - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "send action bar " + message.toString(e, debug) + " to " + recipients.toString(e, debug); - } - -} diff --git a/src/main/java/ch/njol/skript/effects/EffMessage.java b/src/main/java/ch/njol/skript/effects/EffMessage.java deleted file mode 100644 index 1f1b7a0dba2..00000000000 --- a/src/main/java/ch/njol/skript/effects/EffMessage.java +++ /dev/null @@ -1,174 +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.effects; - -import java.util.List; -import java.util.UUID; - -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.LiteralUtils; -import ch.njol.skript.util.chat.MessageComponent; -import ch.njol.util.coll.CollectionUtils; -import org.bukkit.command.CommandSender; -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; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.ExprColoured; -import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionList; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.VariableString; -import ch.njol.skript.util.chat.BungeeConverter; -import ch.njol.skript.util.chat.ChatMessages; -import ch.njol.util.Kleenean; -import net.md_5.bungee.api.chat.BaseComponent; - -@Name("Message") -@Description({"Sends a message to the given player. Only styles written", - "in given string or in formatted expressions will be parsed.", - "Adding an optional sender allows the messages to be sent as if a specific player sent them.", - "This is useful with Minecraft 1.16.4's new chat ignore system, in which players can choose to ignore other players,", - "but for this to work, the message needs to be sent from a player."}) -@Examples({"message \"A wild %player% appeared!\"", - "message \"This message is a distraction. Mwahaha!\"", - "send \"Your kill streak is %{kill streak::%uuid of player%}%.\" to player", - "if the targeted entity exists:", - "\tmessage \"You're currently looking at a %type of the targeted entity%!\"", - "on chat:", - "\tcancel event", - "\tsend \"[%player%] >> %message%\" to all players from player"}) -@RequiredPlugins("Minecraft 1.16.4+ for optional sender") -@Since("1.0, 2.2-dev26 (advanced features), 2.5.2 (optional sender), 2.6 (sending objects)") -public class EffMessage extends Effect { - - private static final boolean SUPPORTS_SENDER = Skript.classExists("org.bukkit.command.CommandSender$Spigot") && - Skript.methodExists(CommandSender.Spigot.class, "sendMessage", UUID.class, BaseComponent.class); - - static { - if (SUPPORTS_SENDER) - Skript.registerEffect(EffMessage.class, "(message|send [message[s]]) %objects% [to %commandsenders%] [from %-player%]"); - else - Skript.registerEffect(EffMessage.class, "(message|send [message[s]]) %objects% [to %commandsenders%]"); - } - - @SuppressWarnings("NotNullFieldNotInitialized") - private Expression[] messages; - - /** - * Used for {@link EffMessage#toString(Event, boolean)} - */ - @SuppressWarnings("NotNullFieldNotInitialized") - private Expression messageExpr; - - @SuppressWarnings("NotNullFieldNotInitialized") - private Expression recipients; - - @Nullable - private Expression sender; - - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { - messageExpr = LiteralUtils.defendExpression(exprs[0]); - - messages = messageExpr instanceof ExpressionList ? - ((ExpressionList) messageExpr).getExpressions() : new Expression[] {messageExpr}; - recipients = (Expression) exprs[1]; - if (SUPPORTS_SENDER) - sender = (Expression) exprs[2]; - return LiteralUtils.canInitSafely(messageExpr); - } - - @Override - protected void execute(Event e) { - Player sender = this.sender != null ? this.sender.getSingle(e) : null; - - CommandSender[] commandSenders = recipients.getArray(e); - - for (Expression message : getMessages()) { - - Object[] messageArray = null; - List messageComponents = null; - - for (CommandSender receiver : commandSenders) { - if (receiver instanceof Player && message instanceof VariableString) { - if (messageComponents == null) - messageComponents = ((VariableString) message).getMessageComponents(e); - } else { - if (messageArray == null) - messageArray = message.getArray(e); - } - - if (receiver instanceof Player) { // Can use JSON formatting - if (message instanceof VariableString) { // Process formatting that is safe - sendMessage((Player) receiver, sender, - BungeeConverter.convert(messageComponents) - ); - } else if (message instanceof ExprColoured && ((ExprColoured) message).isUnsafeFormat()) { // Manually marked as trusted - for (Object object : messageArray) { - sendMessage((Player) receiver, sender, BungeeConverter.convert(ChatMessages.parse((String) object))); - } - } else { // It is just a string, no idea if it comes from a trusted source -> don't parse anything - for (Object object : messageArray) { - List components = ChatMessages.fromParsedString(toString(object)); - sendMessage((Player) receiver, sender, BungeeConverter.convert(components)); - } - } - } else { // Not a player, send plain text with legacy formatting - for (Object object : messageArray) { - receiver.sendMessage(toString(object)); - } - } - } - } - } - - private void sendMessage(Player receiver, @Nullable Player sender, BaseComponent... components) { - if (SUPPORTS_SENDER && sender != null) - receiver.spigot().sendMessage(sender.getUniqueId(), components); - else - receiver.spigot().sendMessage(components); - } - - private Expression[] getMessages() { - if (messageExpr instanceof ExpressionList && !messageExpr.getAnd()) { - return new Expression[] {CollectionUtils.getRandom(messages)}; - } - return messages; - } - - private String toString(Object object) { - return object instanceof String ? (String) object : Classes.toString(object); - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return "send " + messageExpr.toString(e, debug) + " to " + recipients.toString(e, debug) + - (sender != null ? " from " + sender.toString(e, debug) : ""); - } - -} diff --git a/src/main/java/ch/njol/skript/effects/EffSendTitle.java b/src/main/java/ch/njol/skript/effects/EffSendTitle.java deleted file mode 100644 index d6746ad36db..00000000000 --- a/src/main/java/ch/njol/skript/effects/EffSendTitle.java +++ /dev/null @@ -1,139 +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.effects; - -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; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.util.Timespan; -import ch.njol.util.Kleenean; - -@Name("Title - Send") -@Description({ - "Sends a title/subtitle to the given player(s) with optional fadein/stay/fadeout times for Minecraft versions 1.11 and above. ", - "", - "If you're sending only the subtitle, it will be shown only if there's a title displayed at the moment, otherwise it will " + - "be sent with the next title. To show only the subtitle, use: send title \" \" with subtitle \"yourtexthere\" to player.", - "", - "Note: if no input is given for the times, it will keep the ones from the last title sent, " + - "use the reset title effect to restore the default values." -}) -@Examples({ - "send title \"Competition Started\" with subtitle \"Have fun, Stay safe!\" to player for 5 seconds", - "send title \"Hi %player%\" to player", - "send title \"Loot Drop\" with subtitle \"starts in 3 minutes\" to all players", - "send title \"Hello %player%!\" with subtitle \"Welcome to our server\" to player for 5 seconds with fadein 1 second and fade out 1 second", - "send subtitle \"Party!\" to all players" -}) -@Since("2.3") -public class EffSendTitle extends Effect { - - private final static boolean TIME_SUPPORTED = Skript.methodExists(Player.class,"sendTitle", String.class, String.class, int.class, int.class, int.class); - - static { - if (TIME_SUPPORTED) - Skript.registerEffect(EffSendTitle.class, - "send title %string% [with subtitle %-string%] [to %players%] [for %-timespan%] [with fade[(-| )]in %-timespan%] [(and|with) fade[(-| )]out %-timespan%]", - "send subtitle %string% [to %players%] [for %-timespan%] [with fade[(-| )]in %-timespan%] [(and|with) fade[(-| )]out %-timespan%]"); - else - Skript.registerEffect(EffSendTitle.class, - "send title %string% [with subtitle %-string%] [to %players%]", - "send subtitle %string% [to %players%]"); - } - - @Nullable - private Expression title; - @Nullable - private Expression subtitle; - @SuppressWarnings("null") - private Expression recipients; - @Nullable - private Expression fadeIn, stay, fadeOut; - - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - title = matchedPattern == 0 ? (Expression) exprs[0] : null; - subtitle = (Expression) exprs[1 - matchedPattern]; - recipients = (Expression) exprs[2 - matchedPattern]; - if (TIME_SUPPORTED) { - stay = (Expression) exprs[3 - matchedPattern]; - fadeIn = (Expression) exprs[4 - matchedPattern]; - fadeOut = (Expression) exprs[5 - matchedPattern]; - } - return true; - } - - @SuppressWarnings("null") - @Override - protected void execute(final Event e) { - String title = this.title != null ? this.title.getSingle(e) : null; - String subtitle = this.subtitle != null ? this.subtitle.getSingle(e) : null; - - if (TIME_SUPPORTED) { - int fadeIn, stay, fadeOut; - fadeIn = stay = fadeOut = -1; - - if (this.fadeIn != null) { - Timespan t = this.fadeIn.getSingle(e); - fadeIn = t != null ? (int) t.getTicks_i() : -1; - } - - if (this.stay != null) { - Timespan t = this.stay.getSingle(e); - stay = t != null ? (int) t.getTicks_i() : -1; - } - - if (this.fadeOut != null) { - Timespan t = this.fadeOut.getSingle(e); - fadeOut = t != null ? (int) t.getTicks_i() : -1; - } - - for (Player p : recipients.getArray(e)) - p.sendTitle(title, subtitle, fadeIn, stay, fadeOut); - } else { - for (Player p : recipients.getArray(e)) - p.sendTitle(title, subtitle); - } - } - - // TODO: util method to simplify this - @Override - public String toString(final @Nullable Event e, final boolean debug) { - String title = this.title != null ? this.title.toString(e, debug) : "", - sub = subtitle != null ? subtitle.toString(e, debug) : "", - in = fadeIn != null ? fadeIn.toString(e, debug) : "", - stay = this.stay != null ? this.stay.toString(e, debug) : "", - out = fadeOut != null ? this.fadeOut.toString(e, debug) : ""; - return ("send title " + title + - sub == "" ? "" : " with subtitle " + sub) + " to " + - recipients.toString(e, debug) + (TIME_SUPPORTED ? - " for " + stay + " with fade in " + in + " and fade out" + out : ""); - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprColoured.java b/src/main/java/ch/njol/skript/expressions/ExprColoured.java deleted file mode 100644 index efff23ec78a..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprColoured.java +++ /dev/null @@ -1,101 +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 ch.njol.skript.Skript; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.PropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.util.Utils; -import ch.njol.skript.util.chat.ChatMessages; -import ch.njol.util.Kleenean; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -@Name("Colored / Uncolored") -@Description({"Parses <color>s and, optionally, chat styles in a message or removes", - "any colors and chat styles from the message. Parsing all", - "chat styles requires this expression to be used in same line with", - "the send effect."}) -@Examples({"on chat:", - " set message to colored message # Safe; only colors get parsed", - "command /fade <player>:", - " trigger:", - " set display name of the player-argument to uncolored display name of the player-argument", - "command /format <text>:", - " trigger:", - " message formatted text-argument # Safe, because we're sending to whoever used this command"}) -@Since("2.0") -public class ExprColoured extends PropertyExpression { - static { - Skript.registerExpression(ExprColoured.class, String.class, ExpressionType.COMBINED, - "(colo[u]r-|colo[u]red )%strings%", - "(format-|formatted )%strings%", - "(un|non)[-](colo[u]r-|colo[u]red |format-|formatted )%strings%"); - } - - /** - * If colors should be parsed. - */ - boolean color; - - /** - * If all styles should be parsed whenever possible. - */ - boolean format; - - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - setExpr((Expression) exprs[0]); - color = matchedPattern <= 1; // colored and formatted - format = matchedPattern == 1; - return true; - } - - @Override - protected String[] get(final Event e, final String[] source) { - return get(source, s -> color ? Utils.replaceChatStyles(s) : "" + ChatMessages.stripStyles(s)); - } - - @Override - public Class getReturnType() { - return String.class; - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return (color ? "" : "un") + "colored " + getExpr().toString(e, debug); - } - - /** - * If parent of this expression should try to parse all styles instead of - * just colors. This is unsafe to do with untrusted user input. - * @return If unsafe formatting was requested in script. - */ - public boolean isUnsafeFormat() { - return format; - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprPlayerlistHeaderFooter.java b/src/main/java/ch/njol/skript/expressions/ExprPlayerListHeaderFooter.java similarity index 53% rename from src/main/java/ch/njol/skript/expressions/ExprPlayerlistHeaderFooter.java rename to src/main/java/ch/njol/skript/expressions/ExprPlayerListHeaderFooter.java index abbc1327694..0a4d4d4e8cd 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPlayerlistHeaderFooter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPlayerListHeaderFooter.java @@ -18,90 +18,91 @@ */ package ch.njol.skript.expressions; -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.PropertyExpression; import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; @Name("Player List Header and Footer") @Description("The message above and below the player list in the tab menu.") -@Examples({"set all players' tab list header to \"Welcome to the Server!\"", - "send \"%the player's tab list header%\" to player", - "reset all players' tab list header"}) +@Examples({ + "set all players' tab list header to \"Welcome to the Server!\"", + "send \"%the player's tab list header%\" to player", + "reset all players' tab list header" +}) @Since("2.4") -@RequiredPlugins("Minecraft 1.13 or newer") -public class ExprPlayerlistHeaderFooter extends SimplePropertyExpression { - +public class ExprPlayerListHeaderFooter extends SimplePropertyExpression { + static { - if (Skript.methodExists(Player.class, "setPlayerListHeaderFooter", String.class, String.class)) //This method is only present if the header and footer methods we use are - PropertyExpression.register(ExprPlayerlistHeaderFooter.class, String.class, "(player|tab)[ ]list (header|1¦footer) [(text|message)]", "players"); + PropertyExpression.register(ExprPlayerListHeaderFooter.class, String.class, "(player|tab)[ ]list (header|1¦footer) [(text|message)]", "players"); } - - private static final int HEADER = 0, FOOTER = 1; - - private int mark; - + + private boolean isHeader; + @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { - mark = parseResult.mark; + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isHeader = parseResult.mark == 0; return super.init(exprs, matchedPattern, isDelayed, parseResult); } - - @Nullable + @Override + @Nullable public String convert(Player player) { - if (mark == HEADER) - return player.getPlayerListHeader(); - else if (mark == FOOTER) - return player.getPlayerListFooter(); - assert false; - return null; + return isHeader ? player.getPlayerListHeader() : player.getPlayerListFooter(); } - + @Override @Nullable - public Class[] acceptChange(Changer.ChangeMode mode) { + public Class[] acceptChange(ChangeMode mode) { switch (mode) { case SET: case DELETE: case RESET: - return CollectionUtils.array(String.class); + return CollectionUtils.array(String[].class, Component.class); + default: + return null; } - return null; } - + @Override - public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { - final String text = delta == null ? "" : (String) delta[0]; - for (Player player : getExpr().getArray(e)) { - if (mark == HEADER) { - player.setPlayerListHeader(text); - } else if (mark == FOOTER) { - player.setPlayerListFooter(text); - } + public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + Component component; + if (delta == null) { + component = Component.empty(); + } else if (delta[0] instanceof Component) { + component = (Component) delta[0]; + } else { + component = ComponentHandler.parse(String.join("\n", (String[]) delta), false); + } + Audience audience = ComponentHandler.audienceFrom(getExpr().getArray(e)); + if (isHeader) { + audience.sendPlayerListHeader(component); + } else { + audience.sendPlayerListFooter(component); } } - + @Override public Class getReturnType() { return String.class; } - + @Override protected String getPropertyName() { - return "player list " + (mark == HEADER ? "header" : mark == FOOTER ? "footer" : ""); + return "player list " + (isHeader ? "header" : "footer"); } + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprRawString.java b/src/main/java/ch/njol/skript/expressions/ExprRawString.java index 6b9d907da1a..3a800609912 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRawString.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRawString.java @@ -29,8 +29,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.VariableString; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.LiteralUtils; import ch.njol.skript.util.SkriptColor; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -39,8 +41,10 @@ import java.util.regex.Pattern; @Name("Raw String") -@Description("Returns the string without formatting (colors etc.) and without stripping them from it, " + - "e.g. raw \"&aHello There!\" would output &aHello There!") +@Description( + "Returns the string without formatting (colors etc.) and without stripping them from it, " + + "e.g. raw \"&aHello There!\" would output &aHello There!" +) @Examples("send raw \"&aThis text is unformatted!\" to all players") @Since("2.7") public class ExprRawString extends SimpleExpression { @@ -52,21 +56,24 @@ public class ExprRawString extends SimpleExpression { } @SuppressWarnings("NotNullFieldNotInitialized") - private Expression expr; + private Expression messageExpr; @SuppressWarnings("NotNullFieldNotInitialized") private Expression[] messages; @Override @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - expr = (Expression) exprs[0]; - messages = expr instanceof ExpressionList ? - ((ExpressionList) expr).getExpressions() : new Expression[]{expr}; - for (Expression message : messages) { - if (message instanceof ExprColoured) { - Skript.error("The 'colored' expression may not be used in a 'raw string' expression"); - return false; + messageExpr = (Expression) exprs[0]; + messageExpr = LiteralUtils.defendExpression(exprs[0]); + if (messageExpr instanceof ExpressionList) { + ExpressionList exprList = (ExpressionList) messageExpr; + if (exprList.getAnd()) { + messages = exprList.getExpressions(); + } else { + messages = new Expression[]{CollectionUtils.getRandom(exprList.getExpressions())}; } + } else { + messages = new Expression[]{messageExpr}; } return true; } @@ -76,7 +83,7 @@ protected String[] get(Event event) { List strings = new ArrayList<>(); for (Expression message : messages) { if (message instanceof VariableString) { - strings.add(((VariableString) message).toUnformattedString(event)); + strings.add(((VariableString) message).toString(false, event)); continue; } for (String string : message.getArray(event)) { @@ -93,7 +100,7 @@ protected String[] get(Event event) { @Override public boolean isSingle() { - return expr.isSingle(); + return messageExpr.isSingle(); } @Override @@ -103,7 +110,7 @@ public Class getReturnType() { @Override public String toString(@Nullable Event e, boolean debug) { - return "raw " + expr.toString(e, debug); + return "raw " + messageExpr.toString(e, debug); } } diff --git a/src/main/java/ch/njol/skript/hooks/chat/expressions/ExprPrefixSuffix.java b/src/main/java/ch/njol/skript/hooks/chat/expressions/ExprPrefixSuffix.java index b5883a10024..fc9d43a5085 100644 --- a/src/main/java/ch/njol/skript/hooks/chat/expressions/ExprPrefixSuffix.java +++ b/src/main/java/ch/njol/skript/hooks/chat/expressions/ExprPrefixSuffix.java @@ -28,11 +28,11 @@ import ch.njol.skript.hooks.VaultHook; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; /** * @author Peter Güttinger @@ -63,7 +63,7 @@ public boolean init(final Expression[] exprs, final int matchedPattern, final @Override public String convert(final Player p) { - return Utils.replaceChatStyles(prefix ? "" + VaultHook.chat.getPlayerPrefix(p) : "" + VaultHook.chat.getPlayerSuffix(p)); + return ComponentHandler.toLegacyString(prefix ? "" + VaultHook.chat.getPlayerPrefix(p) : "" + VaultHook.chat.getPlayerSuffix(p), false); } @Override diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index f9ae8f4ab2f..b2ed1dc7279 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -18,24 +18,9 @@ */ package ch.njol.skript.lang; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.stream.Collectors; - -import org.bukkit.ChatColor; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; -import org.jetbrains.annotations.NotNull; -import org.skriptlang.skript.lang.script.Script; - -import com.google.common.collect.Lists; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.expressions.ExprColoured; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.ConvertedExpression; @@ -46,14 +31,25 @@ import ch.njol.skript.registrations.Classes; import ch.njol.skript.structures.StructVariables.DefaultVariables; import ch.njol.skript.util.StringMode; -import ch.njol.skript.util.Utils; -import ch.njol.skript.util.chat.ChatMessages; -import ch.njol.skript.util.chat.MessageComponent; import ch.njol.util.Checker; import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.SingleItemIterator; +import com.google.common.collect.Lists; +import net.kyori.adventure.text.Component; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.bukkit.chat.elements.ExprColoured; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; +import org.skriptlang.skript.lang.script.Script; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; /** * Represents a string that may contain expressions, and is thus "variable". @@ -64,25 +60,18 @@ public class VariableString implements Expression { private final Script script; private final String orig; - @Nullable - private final Object[] string; - - @Nullable - private Object[] stringUnformatted; - private final boolean isSimple; + private final Object @Nullable [] string; @Nullable private final String simple; @Nullable - private final String simpleUnformatted; - private final StringMode mode; + private final Component simpleComponent; + @Nullable + private final String simpleResolved; - /** - * Message components that this string consists of. Only simple parts have - * been evaluated here. - */ - private final MessageComponent[] components; + private final boolean isSimple; + private final StringMode mode; /** * Creates a new VariableString which does not contain variables. @@ -90,18 +79,17 @@ public class VariableString implements Expression { * @param input Content for string. */ private VariableString(String input) { - this.isSimple = true; - this.simpleUnformatted = input.replace("%%", "%"); // This doesn't contain variables, so this wasn't done in newInstance! - this.simple = Utils.replaceChatStyles(simpleUnformatted); - - this.orig = simple; - this.string = null; - this.mode = StringMode.MESSAGE; - + isSimple = true; + simple = input.replace("%%", "%"); // This doesn't contain variables, so this wasn't done in newInstance! + simpleComponent = ComponentHandler.parse(input, false); + simpleResolved = ComponentHandler.toLegacyString(simpleComponent); + + orig = simple; + string = null; + mode = StringMode.MESSAGE; + ParserInstance parser = getParser(); this.script = parser.isActive() ? parser.getCurrentScript() : null; - - this.components = new MessageComponent[] {ChatMessages.plainText(simpleUnformatted)}; } /** @@ -113,34 +101,18 @@ private VariableString(String input) { */ private VariableString(String orig, Object[] string, StringMode mode) { this.orig = orig; - this.string = new Object[string.length]; - this.stringUnformatted = new Object[string.length]; - - ParserInstance parser = getParser(); - this.script = parser.isActive() ? parser.getCurrentScript() : null; - - // Construct unformatted string and components - List components = new ArrayList<>(string.length); - for (int i = 0; i < string.length; i++) { - Object object = string[i]; - if (object instanceof String) { - this.string[i] = Utils.replaceChatStyles((String) object); - components.addAll(ChatMessages.parse((String) object)); - } else { - this.string[i] = object; - components.add(null); // Not known parse-time - } - - // For unformatted string, don't format stuff - this.stringUnformatted[i] = object; - } - this.components = components.toArray(new MessageComponent[0]); + this.string = string; + this.mode = mode; + + isSimple = false; + simple = null; + simpleComponent = null; + simpleResolved = null; - this.isSimple = false; - this.simple = null; - this.simpleUnformatted = null; + ParserInstance parser = getParser(); + this.script = parser.isActive() ? parser.getCurrentScript() : null; } /** @@ -384,152 +356,43 @@ public static VariableString[] makeStringsFromQuoted(List args) { } return strings; } - + /** * Parses all expressions in the string and returns it. - * Does not parse formatting codes! - * @param e Event to pass to the expressions. - * @return The input string with all expressions replaced. - */ - public String toUnformattedString(Event e) { - if (isSimple) { - assert simpleUnformatted != null; - return simpleUnformatted; - } - Object[] string = this.stringUnformatted; - assert string != null; - StringBuilder b = new StringBuilder(); - for (Object o : string) { - if (o instanceof Expression) { - b.append(Classes.toString(((Expression) o).getArray(e), true, mode)); - } else { - b.append(o); - } - } - return b.toString(); - } - - /** - * Gets message components from this string. Formatting is parsed only - * in simple parts for security reasons. - * @param e Currently running event. - * @return Message components. - */ - public List getMessageComponents(Event e) { - if (isSimple) { // Trusted, constant string in a script - assert simpleUnformatted != null; - return ChatMessages.parse(simpleUnformatted); - } - - // Parse formating - Object[] string = this.stringUnformatted; - assert string != null; - List message = new ArrayList<>(components.length); // At least this much space - int stringPart = -1; - MessageComponent previous = null; - for (MessageComponent component : components) { - if (component == null) { // This component holds place for variable part - // Go over previous expression part (stringPart >= 0) or take first part (stringPart == 0) - stringPart++; - if (previous != null) { // Also jump over literal part - stringPart++; - } - Object o = string[stringPart]; - previous = null; - - // Convert it to plain text - String text = null; - if (o instanceof ExprColoured && ((ExprColoured) o).isUnsafeFormat()) { // Special case: user wants to process formatting - String unformatted = Classes.toString(((ExprColoured) o).getArray(e), true, mode); - if (unformatted != null) { - message.addAll(ChatMessages.parse(unformatted)); - } - continue; - } else if (o instanceof Expression) { - text = Classes.toString(((Expression) o).getArray(e), true, mode); - } - - assert text != null; - List components = ChatMessages.fromParsedString(text); - if (!message.isEmpty()) { // Copy styles from previous component - int startSize = message.size(); - for (int i = 0; i < components.size(); i++) { - MessageComponent plain = components.get(i); - ChatMessages.copyStyles(message.get(startSize + i - 1), plain); - message.add(plain); - } - } else { - message.addAll(components); - } - } else { - MessageComponent componentCopy = component.copy(); - if (!message.isEmpty()) { // Copy styles from previous component - ChatMessages.copyStyles(message.get(message.size() - 1), componentCopy); - } - message.add(componentCopy); - previous = componentCopy; - } - } - - return message; - } - - /** - * Gets message components from this string. Formatting is parsed - * everywhere, which is a potential security risk. - * @param e Currently running event. - * @return Message components. - */ - public List getMessageComponentsUnsafe(Event e) { - if (isSimple) { // Trusted, constant string in a script - assert simpleUnformatted != null; - return ChatMessages.parse(simpleUnformatted); - } - - return ChatMessages.parse(toUnformattedString(e)); - } - - /** - * Parses all expressions in the string and returns it in chat JSON format. - * - * @param e Event to pass to the expressions. + * If this is a simple string, the event may be null. + * + * Simple formatting (colors) will be processed. + * + * @param event Event to pass to the expressions. * @return The input string with all expressions replaced. + * @see #toString(boolean, Event) */ - public String toChatString(Event e) { - return ChatMessages.toJson(getMessageComponents(e)); - } - - @Nullable - private static ChatColor getLastColor(CharSequence s) { - for (int i = s.length() - 2; i >= 0; i--) { - if (s.charAt(i) == ChatColor.COLOR_CHAR) { - ChatColor c = ChatColor.getByChar(s.charAt(i + 1)); - if (c != null && (c.isColor() || c == ChatColor.RESET)) - return c; - } - } - return null; - } - - @Override - public String toString() { - return toString(null, false); + public String toString(@Nullable Event event) { + return toString(true, event); } /** * Parses all expressions in the string and returns it. * If this is a simple string, the event may be null. - * + * + * @param format Whether simple formatting (colors) should be processed. * @param event Event to pass to the expressions. * @return The input string with all expressions replaced. */ - public String toString(@Nullable Event event) { + public String toString(boolean format, @Nullable Event event) { if (isSimple) { - assert simple != null; - return simple; + if (format) { + assert simpleResolved != null; + return simpleResolved; + } else { + assert simple != null; + return simple; + } } - if (event == null) + + if (event == null) { throw new IllegalArgumentException("Event may not be null in non-simple VariableStrings!"); + } Object[] string = this.string; assert string != null; @@ -538,20 +401,30 @@ public String toString(@Nullable Event event) { for (Object object : string) { if (object instanceof Expression) { Object[] objects = ((Expression) object).getArray(event); - if (objects != null && objects.length > 0) + if (objects.length > 0) types.add(objects[0].getClass()); - builder.append(Classes.toString(objects, true, mode)); + String expression = Classes.toString(objects, true, mode); + if (!(object instanceof ExprColoured) && format) + expression = ComponentHandler.escape(expression); + builder.append(expression); } else { builder.append(object); } } String complete = builder.toString(); + if (script != null && mode == StringMode.VARIABLE_NAME && !types.isEmpty()) { DefaultVariables data = script.getData(DefaultVariables.class); if (data != null) data.add(complete, types.toArray(new Class[0])); } - return complete; + + return format ? ComponentHandler.toLegacyString(builder.toString(), false) : builder.toString(); + } + + @Override + public String toString() { + return toString(null, false); } /** @@ -577,6 +450,18 @@ public String toString(@Nullable Event event, boolean debug) { return builder.toString(); } + public Component getAsComponent(@Nullable Event event) { + if (isSimple) { + assert simpleComponent != null; + return simpleComponent; + } + + if (event == null) + throw new IllegalArgumentException("Event may not be null in non-simple VariableStrings!"); + + return ComponentHandler.parse(toString(false, event)); + } + /** * Builds all possible default variable type hints based on the super type of the expression. * @@ -683,12 +568,39 @@ public boolean check(Event e, Checker c) { return SimpleExpression.check(getAll(e), c, false, false); } - @SuppressWarnings("unchecked") @Override @Nullable + @SuppressWarnings("unchecked") public Expression getConvertedExpression(Class... to) { if (CollectionUtils.containsSuperclass(to, String.class)) return (Expression) this; + if (CollectionUtils.containsSuperclass(to, Component.class)) + return new SimpleExpression() { + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + throw new UnsupportedOperationException(); + } + + @Override + protected @Nullable R[] get(Event e) { + return (R[]) new Component[]{getAsComponent(e)}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return (Class) Component.class; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return VariableString.this.toString(e, debug); + } + }; return ConvertedExpression.newInstance(this, to); } diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 12255d4d8d6..cf35839dc45 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -62,6 +62,7 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.EnumerationIterable; import net.md_5.bungee.api.ChatColor; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; /** * Utility class. @@ -570,7 +571,9 @@ public static String getChatStyle(final String s) { * * @param message * @return message with localised chat styles converted to Minecraft's format + * @deprecated Use {@link ComponentHandler#toLegacyString(String)} */ + @Deprecated public static String replaceChatStyles(final String message) { if (message.isEmpty()) return message; @@ -608,6 +611,7 @@ public String run(final Matcher m) { * * @param message * @return message with english chat styles converted to Minecraft's format + * @deprecated Use {@link ComponentHandler#toLegacyString(String)} */ public static String replaceEnglishChatStyles(final String message) { if (message.isEmpty()) diff --git a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java index dbe03940c76..795fb447daf 100644 --- a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java +++ b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java @@ -603,4 +603,5 @@ public static String stripStyles(String text) { return result; } + } diff --git a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java index 5ab2d37d596..29a33adbfbc 100644 --- a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java +++ b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java @@ -26,10 +26,10 @@ 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.util.NotifyingReference; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; import java.io.BufferedReader; import java.io.File; @@ -223,7 +223,7 @@ protected boolean load_i(SectionNode sectionNode) { // Legacy if (deserializedValue instanceof String && update2_0_beta3) { - deserializedValue = Utils.replaceChatStyles((String) deserializedValue); + deserializedValue = ComponentHandler.toLegacyString((String) deserializedValue, false); } Variables.variableLoaded(split[0], deserializedValue, this); diff --git a/src/main/java/org/skriptlang/skript/bukkit/chat/ChatModule.java b/src/main/java/org/skriptlang/skript/bukkit/chat/ChatModule.java new file mode 100644 index 00000000000..bae0ec87771 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/ChatModule.java @@ -0,0 +1,117 @@ +/** + * 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 org.skriptlang.skript.bukkit.chat; + +import ch.njol.skript.SkriptAddon; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.apache.commons.lang.StringEscapeUtils; + +import java.io.IOException; + +public class ChatModule { + + public void register(SkriptAddon addon) { + + try { + addon.loadClasses("org.skriptlang.skript.bukkit.chat.elements"); + } catch (IOException e) { + e.printStackTrace(); + } + + Converters.registerConverter(String.class, Component.class, ComponentHandler::parse); + Converters.registerConverter(Component.class, String.class, ComponentHandler::toLegacyString); + + Classes.registerClass(new ClassInfo<>(Component.class, "component") + .user("components?") + .name("Component") + .since("INSERT VERSION") + .parser(new Parser() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(Component component, int flags) { + return ComponentHandler.toLegacyString(component); + } + + @Override + public String toVariableNameString(Component component) { + return "component:" + component; + } + }) + ); + + // Just to initialize it now + ComponentHandler.getAdventure(); + + // TODO maybe find a better solution idk though + ComponentHandler.registerPlaceholder("dark_cyan", ""); + ComponentHandler.registerPlaceholder("dark_turquoise", ""); + ComponentHandler.registerPlaceholder("cyan", ""); + + ComponentHandler.registerPlaceholder("purple", ""); + + ComponentHandler.registerPlaceholder("dark_yellow", ""); + ComponentHandler.registerPlaceholder("orange", ""); + + ComponentHandler.registerPlaceholder("light_grey", ""); + ComponentHandler.registerPlaceholder("light_gray", ""); + ComponentHandler.registerPlaceholder("silver", ""); + + ComponentHandler.registerPlaceholder("dark_silver", ""); + + ComponentHandler.registerPlaceholder("light_blue", ""); + ComponentHandler.registerPlaceholder("indigo", ""); + + ComponentHandler.registerPlaceholder("light_green", ""); + ComponentHandler.registerPlaceholder("lime_green", ""); + ComponentHandler.registerPlaceholder("lime", ""); + + ComponentHandler.registerPlaceholder("light_cyan", ""); + ComponentHandler.registerPlaceholder("light_aqua", ""); + ComponentHandler.registerPlaceholder("turquoise", ""); + + ComponentHandler.registerPlaceholder("light_red", ""); + + + ComponentHandler.registerPlaceholder("pink", ""); + ComponentHandler.registerPlaceholder("magenta", ""); + + ComponentHandler.registerPlaceholder("light_yellow", ""); + + ComponentHandler.registerPlaceholder("underline", ""); + + ComponentHandler.registerResolver(TagResolver.resolver("unicode", (argumentQueue, context) -> { + String unicode = argumentQueue.popOr("A unicode tag must have an argument of the unicode").value(); + return Tag.selfClosingInserting(Component.text(StringEscapeUtils.unescapeJava("\\" + unicode))); + })); + + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffBroadcast.java b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/EffBroadcast.java similarity index 58% rename from src/main/java/ch/njol/skript/effects/EffBroadcast.java rename to src/main/java/org/skriptlang/skript/bukkit/chat/elements/EffBroadcast.java index 5e2b8fbdefb..63ebbbd848c 100644 --- a/src/main/java/ch/njol/skript/effects/EffBroadcast.java +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/EffBroadcast.java @@ -16,26 +16,22 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.effects; +package org.skriptlang.skript.bukkit.chat.elements; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.ExprColoured; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionList; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.VariableString; -import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.LiteralUtils; -import ch.njol.skript.util.chat.BungeeConverter; -import ch.njol.skript.util.chat.ChatMessages; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -import net.md_5.bungee.api.chat.BaseComponent; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; +import net.kyori.adventure.audience.Audience; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.CommandSender; @@ -54,67 +50,55 @@ @Since("1.0, 2.6 (broadcasting objects), 2.6.1 (using advanced formatting)") public class EffBroadcast extends Effect { - static { - Skript.registerEffect(EffBroadcast.class, "broadcast %objects% [(to|in) %-worlds%]"); - } - - @SuppressWarnings("NotNullFieldNotInitialized") - private Expression messageExpr; @SuppressWarnings("NotNullFieldNotInitialized") private Expression[] messages; + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression messageExpr; // Used in toString @Nullable private Expression worlds; - @SuppressWarnings("unchecked") + static { + Skript.registerEffect(EffBroadcast.class, "broadcast %objects% [(to|in) %-worlds%]"); + } + @Override + @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { messageExpr = LiteralUtils.defendExpression(exprs[0]); - messages = messageExpr instanceof ExpressionList ? - ((ExpressionList) messageExpr).getExpressions() : new Expression[] {messageExpr}; + + messageExpr = LiteralUtils.defendExpression(exprs[0]); + if (messageExpr instanceof ExpressionList) { + ExpressionList exprList = (ExpressionList) messageExpr; + if (exprList.getAnd()) { + messages = exprList.getExpressions(); + } else { + messages = new Expression[]{CollectionUtils.getRandom(exprList.getExpressions())}; + } + } else { + messages = new Expression[]{messageExpr}; + } + worlds = (Expression) exprs[1]; return LiteralUtils.canInitSafely(messageExpr); } - + @Override - @SuppressWarnings("deprecation") - public void execute(Event e) { - List receivers = new ArrayList<>(); + protected void execute(Event e) { + List recipients = new ArrayList<>(); if (worlds == null) { - receivers.addAll(Bukkit.getOnlinePlayers()); - receivers.add(Bukkit.getConsoleSender()); + recipients.addAll(Bukkit.getOnlinePlayers()); + recipients.add(Bukkit.getConsoleSender()); } else { for (World world : worlds.getArray(e)) - receivers.addAll(world.getPlayers()); - } - - for (Expression message : getMessages()) { - if (message instanceof VariableString) { - BaseComponent[] components = BungeeConverter.convert(((VariableString) message).getMessageComponents(e)); - receivers.forEach(receiver -> receiver.spigot().sendMessage(components)); - } else if (message instanceof ExprColoured && ((ExprColoured) message).isUnsafeFormat()) { // Manually marked as trusted - for (Object realMessage : message.getArray(e)) { - BaseComponent[] components = BungeeConverter.convert(ChatMessages.parse((String) realMessage)); - receivers.forEach(receiver -> receiver.spigot().sendMessage(components)); - } - } else { - for (Object messageObject : message.getArray(e)) { - String realMessage = messageObject instanceof String ? (String) messageObject : Classes.toString(messageObject); - receivers.forEach(receiver -> receiver.sendMessage(realMessage)); - } - } + recipients.addAll(world.getPlayers()); } - } - - private Expression[] getMessages() { - if (messageExpr instanceof ExpressionList && !messageExpr.getAnd()) { - return new Expression[] {CollectionUtils.getRandom(messages)}; - } - return messages; + Audience audience = ComponentHandler.audienceFrom(recipients); + ComponentHandler.parseFromExpressions(e, messages).forEach(audience::sendMessage); } @Override public String toString(@Nullable Event e, boolean debug) { return "broadcast " + messageExpr.toString(e, debug) + (worlds == null ? "" : " to " + worlds.toString(e, debug)); } - + } diff --git a/src/main/java/org/skriptlang/skript/bukkit/chat/elements/EffMessage.java b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/EffMessage.java new file mode 100644 index 00000000000..4997ae64dda --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/EffMessage.java @@ -0,0 +1,136 @@ +/** + * 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 org.skriptlang.skript.bukkit.chat.elements; + +import ch.njol.skript.Skript; +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.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionList; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.LiteralUtils; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.List; + +@Name("Send Message") +@Description({ + "Sends a message to the given player. Only styles written", + "in given string or in formatted expressions will be parsed.", + "Adding an optional sender allows the messages to be sent as if a specific player sent them.", + "This is useful with Minecraft 1.16.4's new chat ignore system, in which players can choose to ignore other players,", + "but for this to work, the message needs to be sent from a player." +}) +@Examples({ + "message \"A wild %player% appeared!\"", + "message \"This message is a distraction. Mwahaha!\"", + "send \"Your kill streak is %{kill streak::%uuid of player%}%.\" to player", + "if the targeted entity exists:", + "\tmessage \"You're currently looking at a %type of the targeted entity%!\"", + "on chat:", + "\tcancel event", + "\tsend \"[%player%] >> %message%\" to all players from player" +}) +@RequiredPlugins("Minecraft 1.16.4+ for optional sender") +@Since("1.0, 2.2-dev26 (advanced features), 2.5.2 (optional sender), 2.6 (sending objects)") +public class EffMessage extends Effect { + + static { + Skript.registerEffect(EffMessage.class, + "(message|send [the] [message[s]]) %objects% [to %commandsenders%] [from %-player%]", + "send [the] action bar [with text] %objects% [to %commandsenders%]" + ); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression[] messages; + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression messageExpr; // Used in toString + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression recipients; + + @Nullable + private Expression sender; + + boolean isChatMessage; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isChatMessage = matchedPattern == 0; + + messageExpr = LiteralUtils.defendExpression(exprs[0]); + if (messageExpr instanceof ExpressionList) { + ExpressionList exprList = (ExpressionList) messageExpr; + if (exprList.getAnd()) { + messages = exprList.getExpressions(); + } else { + messages = new Expression[]{CollectionUtils.getRandom(exprList.getExpressions())}; + } + } else { + messages = new Expression[]{messageExpr}; + } + + recipients = (Expression) exprs[1]; + + if (isChatMessage) + sender = (Expression) exprs[2]; + + return LiteralUtils.canInitSafely(messageExpr); + } + + @Override + protected void execute(Event e) { + Audience audience = ComponentHandler.audienceFrom(recipients.getArray(e)); + + List components = ComponentHandler.parseFromExpressions(e, messages); + + if (isChatMessage) { + Player from = sender != null ? sender.getSingle(e) : null; + Identity identity = from != null ? Identity.identity(from.getUniqueId()) : Identity.nil(); + for (Component component : components) + audience.sendMessage(identity, component); + } else { + for (Component component : components) + audience.sendActionBar(component); + } + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + if (isChatMessage) + return "send " + messageExpr.toString(e, debug) + " to " + recipients.toString(e, debug) + + (sender != null ? " from " + sender.toString(e, debug) : ""); + return "send action bar " + messageExpr.toString(e, debug) + " to " + recipients.toString(e, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/chat/elements/EffSendTitle.java b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/EffSendTitle.java new file mode 100644 index 00000000000..c64400cebc6 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/EffSendTitle.java @@ -0,0 +1,152 @@ +/** + * 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 org.skriptlang.skript.bukkit.chat.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.LiteralUtils; +import ch.njol.skript.util.Timespan; +import ch.njol.util.Kleenean; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.title.Title; +import net.kyori.adventure.title.Title.Times; +import net.kyori.adventure.util.Ticks; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.time.Duration; + +@Name("Send Title") +@Description({ + "Sends a title/subtitle to the given player(s) with optional fadein/stay/fadeout times for Minecraft versions 1.11 and above.", + "Note: if no input is given for the title/subtitle or the times, " + + "it will keep the ones from the last title sent, use the reset title effect to restore the default values." +}) +@Examples({ + "send title \"Competition Started\" with subtitle \"Have fun, Stay safe!\" to player for 5 seconds", + "send title \"Hi %player%\" to player", "send title \"Loot Drop\" with subtitle \"starts in 3 minutes\" to all players", + "send title \"Hello %player%!\" with subtitle \"Welcome to our server\" to player for 5 seconds with fadein 1 second and fade out 1 second", + "send subtitle \"Party!\" to all players" +}) +@Since("2.3, INSERT VERSION (sending objects)") +public class EffSendTitle extends Effect { + + static { + Skript.registerEffect(EffSendTitle.class, + "send title %object% [with subtitle %-object%] [to %commandsenders%] [for %-timespan%] [with fade[(-| )]in %-timespan%] [(and|with) fade[(-| )]out %-timespan%]", + "send subtitle %object% [to %commandsenders%] [for %-timespan%] [with fade[(-| )]in %-timespan%] [(and|with) fade[(-| )]out %-timespan%]" + ); + } + + @Nullable + private Expression title; + @Nullable + private Expression subtitle; + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression recipients; + @Nullable + private Expression fadeIn, stay, fadeOut; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + title = matchedPattern == 0 ? LiteralUtils.defendExpression(exprs[0]) : null; + subtitle = exprs[1 - matchedPattern]; + if (subtitle != null) + LiteralUtils.defendExpression(exprs[1 - matchedPattern]); + recipients = (Expression) exprs[2 - matchedPattern]; + stay = (Expression) exprs[3 - matchedPattern]; + fadeIn = (Expression) exprs[4 - matchedPattern]; + fadeOut = (Expression) exprs[5 - matchedPattern]; + if (title != null) { + if (subtitle != null) + return LiteralUtils.canInitSafely(title, subtitle); + return LiteralUtils.canInitSafely(title); + } + return LiteralUtils.canInitSafely(subtitle); + } + + @Override + protected void execute(Event e) { + Audience audience = ComponentHandler.audienceFrom(recipients.getArray(e)); + + Timespan fadeIn = this.fadeIn != null ? this.fadeIn.getSingle(e) : null; + Timespan stay = this.stay != null ? this.stay.getSingle(e) : null; + Timespan fadeOut = this.fadeOut != null ? this.fadeOut.getSingle(e) : null; + + Duration fadeInDuration = fadeIn != null ? Ticks.duration(fadeIn.getTicks_i()) : Title.DEFAULT_TIMES.fadeIn(); + Duration stayDuration = stay != null ? Ticks.duration(stay.getTicks_i()) : Title.DEFAULT_TIMES.stay(); + Duration fadeOutDuration = fadeOut != null ? Ticks.duration(fadeOut.getTicks_i()) : Title.DEFAULT_TIMES.fadeOut(); + + Times times = Times.times(fadeInDuration, stayDuration, fadeOutDuration); + Title title = Title.title(ComponentHandler.parseFromSingleExpression(e, this.title), ComponentHandler.parseFromSingleExpression(e, this.subtitle), times); + + audience.showTitle(title); + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + StringBuilder builder = new StringBuilder(); + if (title != null) { + builder.append("send title ").append(title.toString(e, debug)); + if (subtitle != null) + builder.append(" with subtitle ").append(subtitle.toString(e, debug)); + } else { + assert subtitle != null; + builder.append("send subtitle ").append(subtitle.toString(e, debug)); + } + + builder.append(" to ").append(recipients.toString(e, debug)); + + if (stay != null) { + builder.append(" for ").append(stay.toString(e, debug)); + } else { + long ticks = Title.DEFAULT_TIMES.stay().toMillis() / Ticks.SINGLE_TICK_DURATION_MS; + builder.append(" for ").append(ticks).append(" ticks"); + } + if (fadeIn != null) { + builder.append(" with fade in ").append(fadeIn.toString(e, debug)); + } else { + long ticks = Title.DEFAULT_TIMES.fadeIn().toMillis() / Ticks.SINGLE_TICK_DURATION_MS; + builder.append(" with fade in ").append(ticks).append(" ticks"); + } + if (fadeOut != null) { + builder.append(" with fade out ").append(fadeOut.toString(e, debug)); + } else { + if (fadeIn != null) { + builder.append(" and"); + } else { + builder.append(" with"); + } + long ticks = Title.DEFAULT_TIMES.fadeOut().toMillis() / Ticks.SINGLE_TICK_DURATION_MS; + builder.append(" fade out ").append(ticks).append(" ticks"); + } + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/chat/elements/ExprColoured.java b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/ExprColoured.java new file mode 100644 index 00000000000..8a76721ec37 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/ExprColoured.java @@ -0,0 +1,134 @@ +/** + * 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 org.skriptlang.skript.bukkit.chat.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +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.VariableString; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.skriptlang.skript.bukkit.chat.util.ComponentHandler; +import net.kyori.adventure.text.Component; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +@Name("Coloured / Uncoloured") +@Description({ + "Parses <colour>s and, optionally, chat styles in a message or removes", + "any colours and chat styles from the message. Parsing all", + "chat styles requires this expression to be used in same line with", + "the send effect." +}) +@Examples({ + "on chat:", + "\tset message to coloured message # Safe; only colors get parsed", + "command /fade <player>:", + "\ttrigger:", + "\t\tset display name of the player-argument to uncoloured display name of the player-argument", + "command /format <text>:", + "\ttrigger:", + "\t\tmessage formatted text-argument # Safe, because we're sending to whoever used this command" +}) +@Since("2.0") +public class ExprColoured extends SimpleExpression { + + static { + Skript.registerExpression(ExprColoured.class, Component.class, ExpressionType.COMBINED, + "(colo[u]r-|colo[u]red )%strings%", + "(format-|formatted )%strings%", + "(un|non)[-](colo[u]r-|colo[u]red |1¦format-|1¦formatted )%strings%" + ); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression strings; + + // Whether colors should be parsed + boolean color; + // Whether all formatting should be parsed + boolean format; + // Whether all formatting should be removed + boolean unformatted; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + strings = (Expression) exprs[0]; + color = matchedPattern <= 1; // colored and formatted + format = matchedPattern == 1; + unformatted = parseResult.mark == 1; + return true; + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + protected Component[] get(Event e) { + long start = System.nanoTime(); + List components = new ArrayList<>(); + + Expression[] expressions = strings instanceof ExpressionList ? + (Expression[]) ((ExpressionList) strings).getExpressions() : new Expression[]{strings}; + + for (Expression expr : expressions) { + if (expr instanceof VariableString) { // Avoid unnecessary parsing + // Although a VariableString may already be parsed into a component, we have to do this again because it's colored. + components.add(getComponent(((VariableString) expr).toString(false, e))); + } else { + for (String string : expr.getArray(e)) + components.add(getComponent(string)); + } + } + System.out.println("Finished Coloring: " + (1. * (System.nanoTime() - start) / 1000000.)); + + return components.toArray(new Component[0]); + } + + private Component getComponent(String string) { + return color ? ComponentHandler.parse(string, !format) : ComponentHandler.plain(ComponentHandler.stripFormatting(string, unformatted)); + } + + @Override + public boolean isSingle() { + return strings.isSingle(); + } + + @Override + public Class getReturnType() { + return Component.class; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + if (format) + return "formatted " + strings.toString(e, debug); + return (color ? "" : "un") + "coloured " + strings.toString(e, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/chat/elements/package-info.java b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/package-info.java new file mode 100644 index 00000000000..d05865be9d6 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/elements/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.bukkit.chat.elements; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/java/org/skriptlang/skript/bukkit/chat/package-info.java b/src/main/java/org/skriptlang/skript/bukkit/chat/package-info.java new file mode 100644 index 00000000000..09d974c28f3 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.bukkit.chat; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/java/org/skriptlang/skript/bukkit/chat/util/CodeConverter.java b/src/main/java/org/skriptlang/skript/bukkit/chat/util/CodeConverter.java new file mode 100644 index 00000000000..8c3affb2ab6 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/util/CodeConverter.java @@ -0,0 +1,62 @@ +/** + * 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 org.skriptlang.skript.bukkit.chat.util; + +import org.eclipse.jdt.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +class CodeConverter { + + private static final Map CODE_MAP = new HashMap<>(); + + static { + CODE_MAP.put('0', "black"); + CODE_MAP.put('1', "dark_blue"); + CODE_MAP.put('2', "dark_green"); + CODE_MAP.put('3', "dark_aqua"); + CODE_MAP.put('4', "dark_red"); + CODE_MAP.put('5', "dark_purple"); + CODE_MAP.put('6', "gold"); + CODE_MAP.put('7', "gray"); + CODE_MAP.put('8', "dark_gray"); + CODE_MAP.put('9', "blue"); + CODE_MAP.put('a', "green"); + CODE_MAP.put('b', "aqua"); + CODE_MAP.put('c', "red"); + CODE_MAP.put('d', "light_purple"); + CODE_MAP.put('e', "yellow"); + CODE_MAP.put('f', "white"); + + CODE_MAP.put('o', "italic"); + CODE_MAP.put('l', "bold"); + CODE_MAP.put('m', "strikethrough"); + CODE_MAP.put('n', "underlined"); + CODE_MAP.put('k', "obfuscated"); + + CODE_MAP.put('r', "reset"); + } + + @Nullable + public static String getColor(char code) { + return CODE_MAP.get(code); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/chat/util/ComponentHandler.java b/src/main/java/org/skriptlang/skript/bukkit/chat/util/ComponentHandler.java new file mode 100644 index 00000000000..1ae323b0a34 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/util/ComponentHandler.java @@ -0,0 +1,349 @@ +/** + * 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 org.skriptlang.skript.bukkit.chat.util; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.VariableString; +import ch.njol.skript.registrations.Classes; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.Context; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.ParsingException; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ComponentHandler { + + private static final Map SIMPLE_PLACEHOLDERS = new HashMap<>(); + private static final List RESOLVERS = new ArrayList<>(); + + /** + * Registers a simple key-value placeholder with Skript's message parsers. + * @param tag The name/key of the placeholder. + * @param result The result/value of the placeholder. + */ + public static void registerPlaceholder(String tag, String result) { + SIMPLE_PLACEHOLDERS.put(tag, Tag.preProcessParsed(result)); + } + + /** + * Unregisters a simple key-value placeholder from Skript's message parsers. + * @param tag The name of the placeholder to unregister. + */ + public static void unregisterPlaceholder(String tag) { + SIMPLE_PLACEHOLDERS.remove(tag); + } + + /** + * Registers a TagResolver with Skript's message parsers. + * @param resolver The TagResolver to register. + */ + public static void registerResolver(TagResolver resolver) { + RESOLVERS.add(resolver); + } + + /** + * Unregisters a TagResolver from Skript's message parsers. + * @param resolver The TagResolver to unregister. + */ + public static void unregisterResolver(TagResolver resolver) { + RESOLVERS.remove(resolver); + } + + private static final TagResolver SKRIPT_TAG_RESOLVER = new TagResolver() { + @Override + @Nullable + public Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException { + Tag simple = SIMPLE_PLACEHOLDERS.get(name); + if (simple != null) + return simple; + for (TagResolver resolver : RESOLVERS) { + Tag resolved = resolver.resolve(name, arguments, ctx); + if (resolved != null) + return resolved; + } + return null; + } + + @Override + public boolean has(@NotNull String name) { + if (SIMPLE_PLACEHOLDERS.containsKey(name)) + return true; + for (TagResolver resolver : RESOLVERS) { + if (resolver.has(name)) + return true; + } + return false; + } + }; + + // The normal parser will process any proper tags + private static final MiniMessage parser = MiniMessage.builder() + .strict(false) + .tags(TagResolver.builder() + .resolver(StandardTags.defaults()) + .resolver(SKRIPT_TAG_RESOLVER) + .build() + ) + .build(); + + // The safe parser only parses color/decoration/formatting related tags + private static final MiniMessage safeParser = MiniMessage.builder() + .strict(false) + .tags(TagResolver.builder() + .resolvers( + StandardTags.color(), StandardTags.decorations(), StandardTags.font(), + StandardTags.gradient(), StandardTags.rainbow(), StandardTags.newline(), + StandardTags.reset(), StandardTags.transition() + ) + .resolver(SKRIPT_TAG_RESOLVER) + .build() + ) + .build(); + + /** + * Parses a string using one of the MiniMessage parsers. + * @param message The message to parse. Will be parsed with the safe parser by default. + * @return An adventure component from the parsed message. + * @see #parse(Object, boolean) + */ + public static Component parse(Object message) { + return parse(message, true); + } + + /** + * Parses a string using one of the MiniMessage parsers. + * @param message The message to parse. + * @param safe Whether only color/decoration/formatting related tags should be parsed. + * @return An adventure component from the parsed message. + */ + public static Component parse(Object message, boolean safe) { + String realMessage = message instanceof String ? (String) message : Classes.toString(message); + + if (realMessage.isEmpty()) { + return Component.empty(); + } + + if (realMessage.contains("&") || realMessage.contains("§")) { + StringBuilder reconstructedMessage = new StringBuilder(); + char[] messageChars = realMessage.toCharArray(); + int length = messageChars.length; + for (int i = 0; i < length; i++) { + char current = messageChars[i]; + if (current == '§') + current = '&'; + char next = (i + 1 != length) ? messageChars[i + 1] : ' '; + boolean isCode = current == '&'; + if (isCode && next == 'x') { // Try to parse as hex -> &x&1&2&3&4&5&6 + reconstructedMessage.append("<#"); + for (int i2 = i + 3; i2 < i + 14; i2 += 2) // Isolate the specific numbers + reconstructedMessage.append(messageChars[i2]); + reconstructedMessage.append('>'); + i += 13; // Skip to the end + } else if (isCode) { + String color = CodeConverter.getColor(next); + if (color != null) { // This is a valid code + reconstructedMessage.append('<').append(color).append('>'); + i++; // Skip to the end + } else { // Not a valid color :( + reconstructedMessage.append(current); + } + } else { + reconstructedMessage.append(current); + } + } + realMessage = reconstructedMessage.toString(); + } + + // TODO find a better solution + // Really annoying backwards compatibility check + realMessage = realMessage.replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", ""); + + return safe ? safeParser.deserialize(realMessage) : parser.deserialize(realMessage); + } + + /** + * Constructs a list of components from the given expressions. + * @param e The event to get expression values with. + * @param expressions The expressions to parse from. + * @return A list of components parsed from the stringified expressions. + * @see #parseFromSingleExpression(Event, Expression) + */ + public static List parseFromExpressions(Event e, Expression... expressions) { + List components = new ArrayList<>(); + for (Expression expression : expressions) { + if (expression instanceof VariableString) { // Get the unformatted string since we'll be formatting it here + components.add(((VariableString) expression).getAsComponent(e)); + } else { // Might not be safe, only parse formatting + for (Object messageObject : expression.getArray(e)) { + if (messageObject instanceof Component) { // No point in doing anything + components.add((Component) messageObject); + } else { + components.add(parse(messageObject, true)); + } + } + } + } + return components; + } + + /** + * Constructs a component from the given expression. + * @param e The event to get expression values with. + * @param expression The expression to parse from. + * @return A component parsed from the stringified expression. + * Will return an empty component if the provided expression is null. + * @see #parseFromExpressions(Event, Expression[]) + */ + public static Component parseFromSingleExpression(Event e, @Nullable Expression expression) { + if (expression != null && expression.isSingle()) { + if (expression instanceof VariableString) + return ((VariableString) expression).getAsComponent(e); + Object object = expression.getSingle(e); + if (object != null) { + if (object instanceof Component) + return (Component) object; + return ComponentHandler.parse(object, true); + } + } + return Component.empty(); + } + + /** + * Creates a plain text component from an object. + * @param message The message to create a component from. + * @return An unprocessed component from the given message. + */ + public static Component plain(Object message) { + return Component.text(message instanceof String ? (String) message : Classes.toString(message)); + } + + /** + * Escapes all tags known to Skript in the given string. + * @param string The string to escape tags in. + * @return The string with tags escaped. + */ + public static String escape(String string) { + return parser.escapeTags(string); + } + + /** + * Strips all formatting from a string. + * @param string The string to strip formatting from. + * @param all Whether ALL formatting/tags should be stripped. + * If false, only safe tags like colors and decorations will be stripped. + * @return The stripped string. + */ + public static String stripFormatting(String string, boolean all) { + return stripFormatting(parse(string, !all)); + } + + /** + * Strips all formatting from a component. + * @param component The component to strip formatting from. + * @return A stripped string from a component. + */ + public static String stripFormatting(Component component) { + return PlainTextComponentSerializer.plainText().serialize(component); + } + + /** + * Converts a string into a legacy formatted string. + * @param string The string to convert. + * @param all Whether ALL formatting/tags should be converted to a legacy format. + * If false, only safe tags like colors and decorations will be converted. + * @return The legacy string. + */ + public static String toLegacyString(String string, boolean all) { + return toLegacyString(parse(string, !all)); + } + + /** + * Converts a component into a legacy formatted string. + * @param component The component to convert. + * @return The legacy string. + */ + public static String toLegacyString(Component component) { + return BukkitComponentSerializer.legacy().serialize(component); + } + + @Nullable + private static BukkitAudiences adventure = null; // Can't set here as we need an instance of Skript + + public static BukkitAudiences getAdventure() { + if (adventure == null) + adventure = BukkitAudiences.create(Skript.getInstance()); + // TODO we might need to close this ('adventure.close()') + return adventure; + } + + /** + * Constructs an audience from command senders. + * @param senders The members of this audience. + * @return An audience consisting of the provided command senders. + */ + public static Audience audienceFrom(Collection senders) { + List bukkitAudiences = new ArrayList<>(); + for (CommandSender sender : senders) + bukkitAudiences.add(getAdventure().sender(sender)); + return Audience.audience(bukkitAudiences); + } + + /** + * Constructs an audience from command senders. + * @param senders The members of this audience. + * @return An audience consisting of the provided command senders. + */ + public static Audience audienceFrom(CommandSender... senders) { + List bukkitAudiences = new ArrayList<>(); + for (CommandSender sender : senders) + bukkitAudiences.add(getAdventure().sender(sender)); + return Audience.audience(bukkitAudiences); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/chat/util/package-info.java b/src/main/java/org/skriptlang/skript/bukkit/chat/util/package-info.java new file mode 100644 index 00000000000..605ab1b7c18 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/chat/util/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.bukkit.chat.util; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 02909f887b2..8cb004e3b34 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2031,3 +2031,7 @@ types: # Hooks money: money region: region¦s + + # Other API + + component: component¦s @a