Skip to content
Permalink
Browse files

Added Velocity support (#182)

This PR adds support for the upcoming proxy Velocity.
https://www.velocitypowered.org/

A few note worthy issues:
 - Velocity does not provide a static method for its ProxyServer instance, so the instance has to be passed in by the plugin with `new VelocityCommandManager(proxy, this)`

- Velocity does not provide a way to get the Plugin logger with the provided Plugin or PluginContainer, so it must be fetched using `Logger logger = LoggerFactory.getLogger(plugin.getClass());` UNTESTED

- Velocity uses an annotation to signify the main class, meaning it can't be passed around like the Bukkit and Bungeecord JavaPlugin and Plugin. An Object has to be passed in which the PluginContainer is extracted from it using the Proxy's PluginManager 
`proxy.getPluginManager().getPlugin(plugin.getClass().getAnnotation(Plugin.class).id()).get();`

Any and all feedback is welcomed and appreciated.

*This implementation is essentially a copy/paste/rename. All code belongs to the original author, except the `ACFVelocityUtil#matchPlayer` which was originally written by md_5 and modified to use Velocity classes.*
  • Loading branch information...
Crypnotic authored and aikar committed Feb 20, 2019
1 parent 0223070 commit 9ad64975bc98b4ae76647c115937dbc06b3b1b52
@@ -148,5 +148,6 @@
<module>bungee</module>
<module>sponge</module>
<module>jda</module>
<module>velocity</module>
</modules>
</project>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>acf-parent</artifactId>
<groupId>co.aikar</groupId>
<version>0.5.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>acf-velocity</artifactId>
<version><!--VERSION-->0.5.0-SNAPSHOT<!--VERSION--></version>

<name>ACF (Velocity)</name>

<repositories>
<repository>
<id>velocity-repo</id>
<url>https://repo.velocitypowered.com/snapshots/</url>
</repository>
</repositories>


<dependencies>
<dependency>
<groupId>co.aikar</groupId>
<artifactId>acf-core</artifactId>
<version><!--VERSION-->0.5.0-SNAPSHOT<!--VERSION--></version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.velocitypowered</groupId>
<artifactId>velocity-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${project.basedir}/../languages/minecraft/</directory>
</resource>
</resources>
</build>
</project>
@@ -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());
}
}
@@ -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<Player> 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<Player> 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<Player> 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> T validate(T object, String message, Object... values) {
if (object == null) {
throw new NullPointerException(String.format(message, values));
}
return object;
}
}
@@ -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;
}
}
@@ -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<VelocityCommandIssuer> {

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();
}
}
@@ -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<VelocityCommandCompletionContext> {

public VelocityCommandCompletions(ProxyServer server, CommandManager manager) {
super(manager);
registerAsyncCompletion("chatcolors", c -> {
Stream<TextFormat> 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<String> 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<String> 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;
});
}
}
Oops, something went wrong.

0 comments on commit 9ad6497

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.