Skip to content

Commit

Permalink
feat: initial Velocity support
Browse files Browse the repository at this point in the history
  • Loading branch information
Siroshun09 committed Nov 19, 2023
1 parent a3e6820 commit 3bacdbf
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 356 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<name>ServerConnector</name>
<url>https://github.com/okocraft/ServerConnector</url>
<description>A utility Velocity plugin of connecting servers.</description>
<description>A Velocity plugin that provides utility functions related to connecting servers.</description>

<licenses>
<license>
Expand Down
114 changes: 67 additions & 47 deletions src/main/java/net/okocraft/serverconnector/ServerConnectorPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,56 @@
import com.github.siroshun09.configapi.api.util.ResourceUtils;
import com.github.siroshun09.configapi.yaml.YamlConfiguration;
import com.github.siroshun09.translationloader.directory.TranslationDirectory;
import com.google.inject.Inject;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import net.kyori.adventure.key.Key;
import net.md_5.bungee.api.plugin.Plugin;
import net.okocraft.serverconnector.command.SlashServerCommand;
import net.okocraft.serverconnector.config.ConfigValues;
import net.okocraft.serverconnector.listener.FirstJoinListener;
import net.okocraft.serverconnector.listener.PlayerListener;
import net.okocraft.serverconnector.listener.ServerListener;
import net.okocraft.serverconnector.listener.SnapshotClientListener;
import net.okocraft.serverconnector.util.AudienceUtil;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public final class ServerConnectorPlugin extends Plugin {
public final class ServerConnectorPlugin {

private final YamlConfiguration config = YamlConfiguration.create(getDataFolder().toPath().resolve("config.yml"));
private final TranslationDirectory translationDirectory =
TranslationDirectory.newBuilder()
.setDirectory(getDataFolder().toPath().resolve("languages"))
.setKey(Key.key("serverconnector", "language"))
.setDefaultLocale(Locale.ENGLISH)
.onDirectoryCreated(this::saveDefaultLanguages)
.build();
private final ProxyServer proxy;
private final Logger logger;
private final YamlConfiguration config;
private final TranslationDirectory translationDirectory;
private final List<SlashServerCommand> registeredSlashServerCommands = new ArrayList<>();

private FirstJoinListener firstJoinListener;

@Override
public void onLoad() {
@Inject
public ServerConnectorPlugin(@NotNull ProxyServer proxy, @NotNull Logger logger,
@DataDirectory Path dataDirectory) {
this.proxy = proxy;
this.logger = logger;

this.config = YamlConfiguration.create(dataDirectory.resolve("config.yml"));
this.translationDirectory =
TranslationDirectory.newBuilder()
.setDirectory(dataDirectory.resolve("languages"))
.setKey(Key.key("serverconnector", "language"))
.setDefaultLocale(Locale.ENGLISH)
.onDirectoryCreated(this::saveDefaultLanguages)
.build();
}

@Subscribe(order = PostOrder.FIRST)
public void onEnable(ProxyInitializeEvent ignored) {
try {
ResourceUtils.copyFromClassLoaderIfNotExists(getClass().getClassLoader(), "config.yml", config.getPath());
config.load();
Expand All @@ -45,74 +65,74 @@ public void onLoad() {
} catch (IOException e) {
throw new IllegalStateException("Failed to load languages", e);
}
}

@Override
public void onEnable() {
AudienceUtil.init(this);

enablePlayerListener();
enableServerListener();
enableSlashServer();
enableFirstJoinDetector();
enableSnapshotListenerIfConfigured();
}

@Override
public void onDisable() {
@Subscribe(order = PostOrder.LAST)
public void onDisable(ProxyShutdownEvent ignored) {
if (firstJoinListener != null) {
firstJoinListener.unsubscribe();
}

getProxy().getPluginManager().unregisterListeners(this);
getProxy().getPluginManager().unregisterCommands(this);
getProxy().getEventManager().unregisterListeners(this);

registeredSlashServerCommands.forEach(SlashServerCommand::unregister);

translationDirectory.unload();
}

@Subscribe
public void onReload(ProxyReloadEvent ignored) {
if (!registeredSlashServerCommands.isEmpty()) {
registeredSlashServerCommands.forEach(SlashServerCommand::unregister);
enableSlashServer();
}
}

public @NotNull ProxyServer getProxy() {
return proxy;
}

public @NotNull Logger getLogger() {
return logger;
}

public @NotNull YamlConfiguration getConfig() {
return config;
}

private void enablePlayerListener() {
var playerListener = new PlayerListener(this);
getProxy().getPluginManager().registerListener(this, playerListener);
}

private void enableServerListener() {
var serverListener = new ServerListener(this);
getProxy().getPluginManager().registerListener(this, serverListener);
getProxy().getEventManager().register(this, playerListener);
}

public void enableSlashServer() {
getProxy().getServers().values().stream()
.map(SlashServerCommand::new)
.forEach(cmd -> getProxy().getPluginManager().registerCommand(this, cmd));
getProxy().getAllServers().stream()
.map(server -> new SlashServerCommand(this, server))
.forEach(command -> {
command.register();
registeredSlashServerCommands.add(command);
});
}

private void enableFirstJoinDetector() {
if (getProxy().getPluginManager().getPlugin("LuckPerms") != null && config.get(ConfigValues.SEND_FIRST_JOIN_MESSAGE)) {
if (getProxy().getPluginManager().getPlugin("LuckPerms").isPresent() && config.get(ConfigValues.SEND_FIRST_JOIN_MESSAGE)) {
firstJoinListener = new FirstJoinListener(this);
}
}

private void enableSnapshotListenerIfConfigured() {
if (config.get(ConfigValues.ENABLE_SNAPSHOT_SERVER)) {
var snapshotListener = new SnapshotClientListener(this);
getProxy().getPluginManager().registerListener(this, snapshotListener);
}
}

private void saveDefaultLanguages(@NotNull Path directory) throws IOException {
var jarPath = getFile().toPath();

var defaultFileName = "en.yml";
var defaultFile = directory.resolve(defaultFileName);

ResourceUtils.copyFromJarIfNotExists(jarPath, defaultFileName, defaultFile);
ResourceUtils.copyFromClassLoaderIfNotExists(getClass().getClassLoader(), defaultFileName, defaultFile);

var japaneseFileName = "ja_JP.yml";
var japaneseFile = directory.resolve(japaneseFileName);

ResourceUtils.copyFromJarIfNotExists(jarPath, japaneseFileName, japaneseFile);
ResourceUtils.copyFromClassLoaderIfNotExists(getClass().getClassLoader(), japaneseFileName, japaneseFile);
}
}
Original file line number Diff line number Diff line change
@@ -1,102 +1,111 @@
package net.okocraft.serverconnector.command;

import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import net.okocraft.serverconnector.ServerConnectorPlugin;
import net.okocraft.serverconnector.lang.Messages;
import net.okocraft.serverconnector.util.AudienceUtil;

import java.util.Collections;
import java.util.Locale;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class SlashServerCommand extends Command implements TabExecutor {
public class SlashServerCommand implements SimpleCommand {

private static final String GLOBAL_OTHER_PLAYER_PERMISSION = "serverconnector.slashserver.other-players";

private final ServerInfo server;
private final ServerConnectorPlugin plugin;
private final RegisteredServer server;
private final ServerInfo serverInfo;
private final String permission;
private final String otherPermission;
private final CommandMeta meta;

public SlashServerCommand(ServerConnectorPlugin plugin, RegisteredServer server) {
this.plugin = plugin;
this.server = server;
this.serverInfo = server.getServerInfo();
this.permission = "serverconnector.slashserver." + serverInfo.getName();
this.otherPermission = permission + ".other";
this.meta = plugin.getProxy().getCommandManager().metaBuilder(serverInfo.getName()).plugin(plugin).build();
}

public SlashServerCommand(ServerInfo serverInfo) {
super(serverInfo.getName(), "serverconnector.slashserver." + serverInfo.getName());
server = serverInfo;
public void register() {
plugin.getProxy().getCommandManager().register(meta, this);
}

@Override
public void execute(CommandSender sender, String[] args) {
var audience = AudienceUtil.sender(sender);
public void unregister() {
plugin.getProxy().getCommandManager().unregister(meta);
}

if (!(sender instanceof ProxiedPlayer)) {
audience.sendMessage(Messages.SLASH_SERVER_ONLY_PLAYER);
return;
}
@Override
public void execute(Invocation invocation) {
var sender = invocation.source();

if (!sender.hasPermission(getPermission())) {
audience.sendMessage(Messages.SLASH_SERVER_NO_PERMISSION.apply(getPermission()));
if (!sender.hasPermission(permission)) {
sender.sendMessage(Messages.SLASH_SERVER_NO_PERMISSION.apply(permission));
return;
}

ProxiedPlayer player;
boolean otherPlayer;
Player target;
var args = invocation.arguments();

if (0 < args.length && !sender.getName().equalsIgnoreCase(args[0])) {
var permission = getPermission() + ".other";
if (!sender.hasPermission(GLOBAL_OTHER_PLAYER_PERMISSION) && !sender.hasPermission(permission)) {
audience.sendMessage(Messages.SLASH_SERVER_NO_PERMISSION.apply(permission));
if (0 < args.length) {
if (!sender.hasPermission(GLOBAL_OTHER_PLAYER_PERMISSION) && !sender.hasPermission(otherPermission)) {
sender.sendMessage(Messages.SLASH_SERVER_NO_PERMISSION.apply(otherPermission));
return;
}

player = ProxyServer.getInstance().getPlayer(args[0]);
otherPlayer = true;
target = plugin.getProxy().getPlayer(args[0]).orElse(null);

if (player == null) {
audience.sendMessage(Messages.SLASH_SERVER_PLAYER_NOT_FOUND.apply(args[0]));
if (target == null) {
sender.sendMessage(Messages.SLASH_SERVER_PLAYER_NOT_FOUND.apply(args[0]));
return;
}
} else {
player = (ProxiedPlayer) sender;
otherPlayer = false;
if (sender instanceof Player) {
target = (Player) sender;
} else {
sender.sendMessage(Messages.SLASH_SERVER_ONLY_PLAYER);
return;
}
}

var currentServerName = player.getServer().getInfo().getName();
var currentServer = target.getCurrentServer().map(ServerConnection::getServerInfo);

if (currentServerName.equalsIgnoreCase(server.getName())) {
audience.sendMessage(Messages.SLASH_SERVER_ALREADY_CONNECTED);
if (serverInfo.equals(currentServer.orElse(null))) {
sender.sendMessage(Messages.SLASH_SERVER_ALREADY_CONNECTED);
return;
}

if (!server.canAccess(player)) {
audience.sendMessage(Messages.SLASH_SERVER_COULD_NOT_CONNECT.apply(server.getName()));
return;
}
target.createConnectionRequest(server).fireAndForget();

if (otherPlayer) {
AudienceUtil.player(player).sendMessage(Messages.SLASH_SERVER_CONNECTING.apply(server.getName()));
audience.sendMessage(Messages.SLASH_SERVER_CONNECTING_OTHER.apply(player, server.getName()));
if (sender != target) {
target.sendMessage(Messages.SLASH_SERVER_CONNECTING.apply(serverInfo.getName()));
sender.sendMessage(Messages.SLASH_SERVER_CONNECTING_OTHER.apply(target, serverInfo.getName()));
} else {
audience.sendMessage(Messages.SLASH_SERVER_CONNECTING.apply(server.getName()));
sender.sendMessage(Messages.SLASH_SERVER_CONNECTING.apply(serverInfo.getName()));
}

player.connect(server);
}

@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
if (args.length == 1 &&
(sender.hasPermission(GLOBAL_OTHER_PLAYER_PERMISSION) || sender.hasPermission(getPermission() + ".other"))) {
var filter = args[0].toLowerCase(Locale.ENGLISH);
return ProxyServer.getInstance()
.getPlayers()
.stream()
.map(CommandSender::getName)
.map(name -> name.toLowerCase(Locale.ENGLISH))
.filter(name -> !name.equalsIgnoreCase(sender.getName()))
.filter(name -> name.startsWith(filter))
public List<String> suggest(Invocation invocation) {
var sender = invocation.source();
var args = invocation.arguments();

if (args.length <= 1 && (sender.hasPermission(GLOBAL_OTHER_PLAYER_PERMISSION) || sender.hasPermission(otherPermission))) {
return plugin.getProxy().getAllPlayers().stream()
.filter(Predicate.not(sender::equals))
.filter(Predicate.not(player -> serverInfo.equals(player.getCurrentServer().map(ServerConnection::getServerInfo).orElse(null))))
.map(Player::getUsername)
.filter(name -> args.length == 0 || name.regionMatches(true, 0, args[0], 0, args[0].length()))
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}

return Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,4 @@ public final class ConfigValues {
public static final Function<String, ConfigValue<String>> SERVER_CUSTOM_PERMISSION = serverName -> c -> c.getString("server-permission." + serverName);

public static final ConfigValue<String> PROXY_PERMISSION = SERVER_CUSTOM_PERMISSION.apply("proxy");

public static final ConfigValue<Boolean> ENABLE_SNAPSHOT_SERVER = c -> c.getBoolean("snapshot.enable");

public static final ConfigValue<String> SNAPSHOT_SERVER = c -> c.getString("snapshot.server-name", "snapshot");

public static final ConfigValue<Integer> SNAPSHOT_PROTOCOL_VERSION = c -> c.getInteger("snapshot.protocol-version");
}
Loading

0 comments on commit 3bacdbf

Please sign in to comment.