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 +
+ '}';
+ }
+}