diff --git a/pom.xml b/pom.xml index 05d15a17..bb14d5e9 100644 --- a/pom.xml +++ b/pom.xml @@ -148,5 +148,6 @@ bungee sponge jda + velocity diff --git a/velocity/pom.xml b/velocity/pom.xml new file mode 100644 index 00000000..b2dbdbf5 --- /dev/null +++ b/velocity/pom.xml @@ -0,0 +1,45 @@ + + + + acf-parent + co.aikar + 0.5.0-SNAPSHOT + + 4.0.0 + + acf-velocity + 0.5.0-SNAPSHOT + + ACF (Velocity) + + + + velocity-repo + https://repo.velocitypowered.com/snapshots/ + + + + + + + co.aikar + acf-core + 0.5.0-SNAPSHOT + compile + + + com.velocitypowered + velocity-api + 1.0-SNAPSHOT + + + + + + ${project.basedir}/../languages/minecraft/ + + + + \ No newline at end of file diff --git a/velocity/src/main/java/co/aikar/commands/ACFVelocityListener.java b/velocity/src/main/java/co/aikar/commands/ACFVelocityListener.java new file mode 100644 index 00000000..8688dadf --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/ACFVelocityListener.java @@ -0,0 +1,45 @@ +package co.aikar.commands; + +import java.util.concurrent.TimeUnit; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; + +public class ACFVelocityListener { + + private final VelocityCommandManager manager; + private final PluginContainer plugin; + private final ProxyServer proxy; + + public ACFVelocityListener(VelocityCommandManager manager, PluginContainer plugin, ProxyServer proxy) { + this.manager = manager; + this.plugin = plugin; + this.proxy = proxy; + } + + @Subscribe + public void onPlayerJoin(PostLoginEvent loginEvent) { + Player player = loginEvent.getPlayer(); + + // the client settings are sent after a successful login + Runnable task = () -> manager.readLocale(player); + proxy.getScheduler().buildTask(plugin, task).delay(1, TimeUnit.SECONDS).schedule(); + } + + @Subscribe + public void onDisconnect(DisconnectEvent disconnectEvent) { + // cleanup + Player player = disconnectEvent.getPlayer(); + manager.issuersLocale.remove(player.getUniqueId()); + } + + @Subscribe + public void onSettingsChange(PlayerSettingsChangedEvent settingsEvent) { + manager.setIssuerLocale(settingsEvent.getPlayer(), settingsEvent.getPlayer().getPlayerSettings().getLocale()); + } +} diff --git a/velocity/src/main/java/co/aikar/commands/ACFVelocityUtil.java b/velocity/src/main/java/co/aikar/commands/ACFVelocityUtil.java new file mode 100644 index 00000000..f7478d1f --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/ACFVelocityUtil.java @@ -0,0 +1,83 @@ +package co.aikar.commands; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; + +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.ComponentSerializers; + +public class ACFVelocityUtil { + + @SuppressWarnings("deprecation") + public static TextComponent color(String message) { + return ComponentSerializers.LEGACY.deserialize(message); + } + + public static Player findPlayerSmart(ProxyServer server, CommandIssuer issuer, String search) { + CommandSource requester = issuer.getIssuer(); + String name = ACFUtil.replace(search, ":confirm", ""); + if (name.length() < 3) { + issuer.sendError(MinecraftMessageKeys.USERNAME_TOO_SHORT); + return null; + } + if (!isValidName(name)) { + issuer.sendError(MinecraftMessageKeys.IS_NOT_A_VALID_NAME, "{name}", name); + return null; + } + + List matches = new ArrayList<>(matchPlayer(server, name)); + + if (matches.size() > 1) { + String allMatches = matches.stream().map(Player::getUsername).collect(Collectors.joining(", ")); + issuer.sendError(MinecraftMessageKeys.MULTIPLE_PLAYERS_MATCH, "{search}", name, "{all}", allMatches); + return null; + } + + if (matches.isEmpty()) { + issuer.sendError(MinecraftMessageKeys.NO_PLAYER_FOUND_SERVER, "{search}", name); + return null; + } + + return matches.get(0); + } + + /* + * Original code written by md_5 + * + * Modified to work with Velocity by Crypnotic + */ + private static Collection matchPlayer(ProxyServer server, final String partialName) { + // A better error message might be nice. This just mimics the previous output + if (partialName == null) { + throw new NullPointerException("partialName"); + } + + Optional exactMatch = server.getPlayer(partialName); + if (exactMatch != null) { + return Collections.singleton(exactMatch.get()); + } + + return server.getAllPlayers().stream() + .filter(player -> player.getUsername().regionMatches(true, 0, partialName, 0, partialName.length())) + .collect(Collectors.toList()); + } + + public static boolean isValidName(String name) { + return name != null && !name.isEmpty() && ACFPatterns.VALID_NAME_PATTERN.matcher(name).matches(); + } + + public static T validate(T object, String message, Object... values) { + if (object == null) { + throw new NullPointerException(String.format(message, values)); + } + return object; + } +} diff --git a/velocity/src/main/java/co/aikar/commands/MinecraftMessageKeys.java b/velocity/src/main/java/co/aikar/commands/MinecraftMessageKeys.java new file mode 100644 index 00000000..4b3df34d --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/MinecraftMessageKeys.java @@ -0,0 +1,18 @@ +package co.aikar.commands; + +import co.aikar.locales.MessageKey; +import co.aikar.locales.MessageKeyProvider; + +public enum MinecraftMessageKeys implements MessageKeyProvider { + USERNAME_TOO_SHORT, + IS_NOT_A_VALID_NAME, + MULTIPLE_PLAYERS_MATCH, + NO_PLAYER_FOUND_SERVER, + NO_PLAYER_FOUND + ; + + private final MessageKey key = MessageKey.of("acf-minecraft." + this.name().toLowerCase()); + public MessageKey getMessageKey() { + return key; + } +} diff --git a/velocity/src/main/java/co/aikar/commands/VelocityCommandCompletionContext.java b/velocity/src/main/java/co/aikar/commands/VelocityCommandCompletionContext.java new file mode 100644 index 00000000..041351f0 --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityCommandCompletionContext.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; + +public class VelocityCommandCompletionContext extends CommandCompletionContext { + + VelocityCommandCompletionContext(RegisteredCommand command, VelocityCommandIssuer issuer, String input, String config, String[] args) { + super(command, issuer, input, config, args); + } + + public CommandSource getSender() { + return this.getIssuer().getIssuer(); + } + + public Player getPlayer() { + return this.issuer.getPlayer(); + } +} diff --git a/velocity/src/main/java/co/aikar/commands/VelocityCommandCompletions.java b/velocity/src/main/java/co/aikar/commands/VelocityCommandCompletions.java new file mode 100644 index 00000000..3ece1af6 --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityCommandCompletions.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; + +import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import net.kyori.text.format.TextFormat; + +public class VelocityCommandCompletions extends CommandCompletions { + + public VelocityCommandCompletions(ProxyServer server, CommandManager manager) { + super(manager); + registerAsyncCompletion("chatcolors", c -> { + Stream colors = Stream.of(TextColor.values()); + if (!c.hasConfig("colorsonly")) { + colors = Stream.concat(colors, Stream.of(TextDecoration.values())); + } + String filter = c.getConfig("filter"); + if (filter != null) { + Set filters = Arrays.stream(ACFPatterns.COLON.split(filter)).map(ACFUtil::simplifyString) + .collect(Collectors.toSet()); + + colors = colors.filter(color -> filters.contains(ACFUtil.simplifyString(color.toString()))); + } + + return colors.map(color -> ACFUtil.simplifyString(color.toString())).collect(Collectors.toList()); + }); + registerCompletion("players", c -> { + CommandSource sender = c.getSender(); + ACFVelocityUtil.validate(sender, "Sender cannot be null"); + String input = c.getInput(); + + ArrayList matchedPlayers = new ArrayList<>(); + for (Player player : server.getAllPlayers()) { + String name = player.getUsername(); + if (ApacheCommonsLangUtil.startsWithIgnoreCase(name, input)) { + matchedPlayers.add(name); + } + } + + matchedPlayers.sort(String.CASE_INSENSITIVE_ORDER); + return matchedPlayers; + }); + } +} diff --git a/velocity/src/main/java/co/aikar/commands/VelocityCommandContexts.java b/velocity/src/main/java/co/aikar/commands/VelocityCommandContexts.java new file mode 100644 index 00000000..14eb1fe7 --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityCommandContexts.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; + +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.contexts.OnlinePlayer; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import net.kyori.text.format.TextFormat; + +public class VelocityCommandContexts extends CommandContexts { + + VelocityCommandContexts(ProxyServer server, CommandManager manager) { + super(manager); + registerContext(OnlinePlayer.class, (c) -> { + Player proxiedPlayer = ACFVelocityUtil.findPlayerSmart(server, c.getIssuer(), c.popFirstArg()); + if (proxiedPlayer == null) { + if (c.hasAnnotation(Optional.class)) { + return null; + } + throw new InvalidCommandArgument(false); + } + return new OnlinePlayer(proxiedPlayer); + }); + registerIssuerAwareContext(CommandSource.class, VelocityCommandExecutionContext::getSender); + registerIssuerAwareContext(Player.class, (c) -> { + Player proxiedPlayer = c.getSender() instanceof Player ? (Player) c.getSender() : null; + if (proxiedPlayer == null && !c.hasAnnotation(Optional.class)) { + throw new InvalidCommandArgument(MessageKeys.NOT_ALLOWED_ON_CONSOLE, false); + } + return proxiedPlayer; + }); + + registerContext(TextFormat.class, c -> { + String first = c.popFirstArg(); + Stream colors = Stream.of(TextColor.values()); + if (!c.hasFlag("colorsonly")) { + colors = Stream.concat(colors, Stream.of(TextDecoration.values())); + } + String filter = c.getFlagValue("filter", (String) null); + if (filter != null) { + filter = ACFUtil.simplifyString(filter); + String finalFilter = filter; + colors = colors.filter(color -> finalFilter.equals(ACFUtil.simplifyString(color.toString()))); + } + + TextColor match = ACFUtil.simpleMatch(TextColor.class, first); + if (match == null) { + String valid = colors.map(color -> "" + ACFUtil.simplifyString(color.toString()) + "") + .collect(Collectors.joining(", ")); + + throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF, "{valid}", valid); + } + return match; + }); + } +} diff --git a/velocity/src/main/java/co/aikar/commands/VelocityCommandExecutionContext.java b/velocity/src/main/java/co/aikar/commands/VelocityCommandExecutionContext.java new file mode 100644 index 00000000..f4492c9f --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityCommandExecutionContext.java @@ -0,0 +1,22 @@ +package co.aikar.commands; + +import java.util.List; +import java.util.Map; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; + +public class VelocityCommandExecutionContext extends CommandExecutionContext { + + VelocityCommandExecutionContext(RegisteredCommand cmd, CommandParameter param, VelocityCommandIssuer sender, List args, int index, Map passedArgs) { + super(cmd, param, sender, args, index, passedArgs); + } + + public CommandSource getSender() { + return this.issuer.getIssuer(); + } + + public Player getPlayer() { + return this.issuer.getPlayer(); + } +} diff --git a/velocity/src/main/java/co/aikar/commands/VelocityCommandIssuer.java b/velocity/src/main/java/co/aikar/commands/VelocityCommandIssuer.java new file mode 100644 index 00000000..755cf35f --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityCommandIssuer.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.UUID; + +import org.jetbrains.annotations.NotNull; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; + +public class VelocityCommandIssuer implements CommandIssuer { + private final VelocityCommandManager manager; + private final CommandSource source; + + VelocityCommandIssuer(VelocityCommandManager manager, CommandSource source) { + this.manager = manager; + this.source = source; + } + + + @Override + public CommandSource getIssuer() { + return source; + } + + public Player getPlayer() { + return isPlayer() ? (Player) source : null; + } + + @Override + public CommandManager getManager() { + return manager; + } + + @Override + public boolean isPlayer() { + return source instanceof Player; + } + + @Override + public @NotNull UUID getUniqueId() { + if (isPlayer()) { + return ((Player) source).getUniqueId(); + } + + // TODO: Find a better solution for this + //generate a unique id based of the name (like for the console command sender) + return UUID.randomUUID(); + } + + @Override + public void sendMessageInternal(String message) { + source.sendMessage(ACFVelocityUtil.color(message)); + } + + @Override + public boolean hasPermission(String name) { + return source.hasPermission(name); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VelocityCommandIssuer that = (VelocityCommandIssuer) o; + return Objects.equals(source, that.source); + } + + @Override + public int hashCode() { + return Objects.hash(source); + } +} diff --git a/velocity/src/main/java/co/aikar/commands/VelocityCommandManager.java b/velocity/src/main/java/co/aikar/commands/VelocityCommandManager.java new file mode 100644 index 00000000..b16300e5 --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityCommandManager.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; + +import co.aikar.commands.apachecommonslang.ApacheCommonsExceptionUtil; +import net.kyori.text.format.TextColor; + +public class VelocityCommandManager extends + CommandManager { + + protected final ProxyServer proxy; + protected final PluginContainer plugin; + protected Map registeredCommands = new HashMap<>(); + protected VelocityCommandContexts contexts; + protected VelocityCommandCompletions completions; + protected VelocityLocales locales; + + public VelocityCommandManager(ProxyServer proxy, Object plugin) { + this.proxy = proxy; + this.plugin = proxy.getPluginManager().getPlugin(plugin.getClass().getAnnotation(Plugin.class).id()).get(); + this.formatters.put(MessageType.ERROR, defaultFormatter = new VelocityMessageFormatter(TextColor.RED, TextColor.YELLOW, TextColor.RED)); + this.formatters.put(MessageType.SYNTAX, new VelocityMessageFormatter(TextColor.YELLOW, TextColor.GREEN, TextColor.WHITE)); + this.formatters.put(MessageType.INFO, new VelocityMessageFormatter(TextColor.BLUE, TextColor.DARK_GREEN, TextColor.GREEN)); + this.formatters.put(MessageType.HELP, new VelocityMessageFormatter(TextColor.AQUA, TextColor.GREEN, TextColor.YELLOW)); + + getLocales(); + + proxy.getEventManager().register(plugin, new ACFVelocityListener(this, this.plugin, proxy)); + + registerDependency(plugin.getClass(), plugin); + registerDependency(Plugin.class, plugin); + registerDependency(ProxyServer.class, proxy); + } + + public ProxyServer getProxy() { + return this.proxy; + } + + public PluginContainer getPlugin() { + return this.plugin; + } + + @Override + public synchronized CommandContexts getCommandContexts() { + if (this.contexts == null) { + this.contexts = new VelocityCommandContexts(proxy, this); + } + return contexts; + } + + @Override + public synchronized CommandCompletions getCommandCompletions() { + if (this.completions == null) { + this.completions = new VelocityCommandCompletions(proxy, this); + } + return completions; + } + + @Override + public VelocityLocales getLocales() { + if (this.locales == null) { + this.locales = new VelocityLocales(this); + this.locales.loadLanguages(); + } + return locales; + } + + public void readLocale(Player player) { + if (!player.isActive()) { + return; + } + + //This can be null if we didn't received a settings packet + Locale locale = player.getPlayerSettings().getLocale(); + if (locale != null) { + setIssuerLocale(player, player.getPlayerSettings().getLocale()); + } + } + + @Override + public void registerCommand(BaseCommand command) { + command.onRegister(this); + for (Map.Entry entry : command.registeredCommands.entrySet()) { + String commandName = entry.getKey().toLowerCase(); + VelocityRootCommand velocityCommand = (VelocityRootCommand) entry.getValue(); + if (!velocityCommand.isRegistered) { + proxy.getCommandManager().register(velocityCommand, commandName); + } + velocityCommand.isRegistered = true; + registeredCommands.put(commandName, velocityCommand); + } + } + + public void unregisterCommand(BaseCommand command) { + for (Map.Entry entry : command.registeredCommands.entrySet()) { + String commandName = entry.getKey().toLowerCase(); + VelocityRootCommand velocityCommand = (VelocityRootCommand) entry.getValue(); + velocityCommand.getSubCommands().values().removeAll(command.subCommands.values()); + if (velocityCommand.getSubCommands().isEmpty() && velocityCommand.isRegistered) { + unregisterCommand(velocityCommand); + velocityCommand.isRegistered = false; + registeredCommands.remove(commandName); + } + } + } + + public void unregisterCommand(VelocityRootCommand command) { + proxy.getCommandManager().unregister(command.getCommandName()); + } + + public void unregisterCommands() { + for (Map.Entry entry : registeredCommands.entrySet()) { + unregisterCommand(entry.getValue()); + } + } + + @Override + public boolean hasRegisteredCommands() { + return !registeredCommands.isEmpty(); + } + + @Override + public boolean isCommandIssuer(Class aClass) { + return CommandSource.class.isAssignableFrom(aClass); + } + + @Override + public VelocityCommandIssuer getCommandIssuer(Object issuer) { + if (!(issuer instanceof CommandSource)) { + throw new IllegalArgumentException(issuer.getClass().getName() + " is not a Command Issuer."); + } + return new VelocityCommandIssuer(this, (CommandSource) issuer); + } + + @Override + public RootCommand createRootCommand(String cmd) { + return new VelocityRootCommand(this, cmd); + } + + @Override + public Collection getRegisteredRootCommands() { + return Collections.unmodifiableCollection(registeredCommands.values()); + } + + @Override + public VelocityCommandExecutionContext createCommandContext(RegisteredCommand command, CommandParameter parameter, CommandIssuer sender, List args, int i, Map passedArgs) { + return new VelocityCommandExecutionContext(command, parameter, (VelocityCommandIssuer) sender, args, i, passedArgs); + } + + @Override + public CommandCompletionContext createCompletionContext(RegisteredCommand command, CommandIssuer sender, String input, String config, String[] args) { + return new VelocityCommandCompletionContext(command, (VelocityCommandIssuer) sender, input, config, args); + } + + @Override + public RegisteredCommand createRegisteredCommand(BaseCommand command, String cmdName, Method method, String prefSubCommand) { + return new RegisteredCommand(command, cmdName, method, prefSubCommand); + } + + @Override + public VelocityConditionContext createConditionContext(CommandIssuer issuer, String config) { + return new VelocityConditionContext((VelocityCommandIssuer) issuer, config); + } + + @Override + public void log(LogLevel level, String message, Throwable throwable) { + // TODO: Find better solution + Logger logger = LoggerFactory.getLogger(plugin.getClass()); + if (level == LogLevel.INFO) { + logger.info(LogLevel.LOG_PREFIX + message); + } else { + logger.warn(LogLevel.LOG_PREFIX + message); + } + + if (throwable != null) { + for (String line : ACFPatterns.NEWLINE.split(ApacheCommonsExceptionUtil.getFullStackTrace(throwable))) { + if (level == LogLevel.INFO) { + logger.info(LogLevel.LOG_PREFIX + line); + } else { + logger.warn(LogLevel.LOG_PREFIX + line); + } + } + } + } + + + @Override + public String getCommandPrefix(CommandIssuer issuer) { + return issuer.isPlayer() ? "/" : ""; + } +} diff --git a/velocity/src/main/java/co/aikar/commands/VelocityConditionContext.java b/velocity/src/main/java/co/aikar/commands/VelocityConditionContext.java new file mode 100644 index 00000000..12b145b2 --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityConditionContext.java @@ -0,0 +1,19 @@ +package co.aikar.commands; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; + +public class VelocityConditionContext extends ConditionContext { + VelocityConditionContext(VelocityCommandIssuer issuer, String config) { + super(issuer, config); + } + + + public CommandSource getSender() { + return getIssuer().getIssuer(); + } + + public Player getPlayer() { + return getIssuer().getPlayer(); + } +} diff --git a/velocity/src/main/java/co/aikar/commands/VelocityLocales.java b/velocity/src/main/java/co/aikar/commands/VelocityLocales.java new file mode 100644 index 00000000..b77fd3ee --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityLocales.java @@ -0,0 +1,19 @@ +package co.aikar.commands; + +public class VelocityLocales extends Locales { + private final VelocityCommandManager manager; + + public VelocityLocales(VelocityCommandManager manager) { + super(manager); + + this.manager = manager; + this.addBundleClassLoader(this.manager.getPlugin().getClass().getClassLoader()); + } + + @Override + public void loadLanguages() { + super.loadLanguages(); + String pluginName = "acf-" + manager.plugin.getDescription().getName().get(); + addMessageBundles("acf-minecraft", pluginName, pluginName.toLowerCase()); + } +} diff --git a/velocity/src/main/java/co/aikar/commands/VelocityMessageFormatter.java b/velocity/src/main/java/co/aikar/commands/VelocityMessageFormatter.java new file mode 100644 index 00000000..1eb4645c --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityMessageFormatter.java @@ -0,0 +1,17 @@ +package co.aikar.commands; + +import net.kyori.text.format.TextColor; +import net.kyori.text.serializer.ComponentSerializers; + +public class VelocityMessageFormatter extends MessageFormatter { + + public VelocityMessageFormatter(TextColor... colors) { + super(colors); + } + + @Override + @SuppressWarnings("deprecation") + String format(TextColor color, String message) { + return ComponentSerializers.LEGACY.serialize(ComponentSerializers.LEGACY.deserialize(message).color(color)); + } +} \ No newline at end of file diff --git a/velocity/src/main/java/co/aikar/commands/VelocityRootCommand.java b/velocity/src/main/java/co/aikar/commands/VelocityRootCommand.java new file mode 100644 index 00000000..be17eab4 --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/VelocityRootCommand.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; +import com.velocitypowered.api.command.Command; +import com.velocitypowered.api.command.CommandSource; + +public class VelocityRootCommand implements Command, RootCommand { + + private final VelocityCommandManager manager; + private final String name; + private BaseCommand defCommand; + private SetMultimap subCommands = HashMultimap.create(); + private List children = new ArrayList<>(); + boolean isRegistered = false; + + VelocityRootCommand(VelocityCommandManager manager, String name) { + this.manager = manager; + this.name = name; + } + + @Override + public String getCommandName() { + return name; + } + + @Override + public void addChild(BaseCommand command) { + if (this.defCommand == null || !command.subCommands.get(BaseCommand.DEFAULT).isEmpty()) { + this.defCommand = command; + + } + this.addChildShared(this.children, this.subCommands, command); + } + + @Override + public CommandManager getManager() { + return manager; + } + + @Override + public SetMultimap getSubCommands() { + return subCommands; + } + + @Override + public List getChildren() { + return children; + } + + @Override + public void execute(CommandSource source, String[] args) { + execute(manager.getCommandIssuer(source), getCommandName(), args); + } + + @Override + public List suggest(CommandSource source, String[] args) { + return getTabCompletions(manager.getCommandIssuer(source), getCommandName(), args); + } + + @Override + public BaseCommand getDefCommand() { + return defCommand; + } +} diff --git a/velocity/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java b/velocity/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java new file mode 100644 index 00000000..e175cd00 --- /dev/null +++ b/velocity/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands.contexts; + +import java.util.Objects; + +import com.velocitypowered.api.proxy.Player; + +public class OnlinePlayer { + + public final Player player; + + public OnlinePlayer(Player player) { + this.player = player; + } + + public Player getPlayer(){ + return player; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OnlinePlayer that = (OnlinePlayer) o; + return Objects.equals(player, that.player); + } + + @Override + public int hashCode() { + return Objects.hash(player); + } + + @Override + public String toString() { + return "OnlinePlayer{" + + "player=" + player + + '}'; + } +}