diff --git a/src/main/java/de/strifel/VTools/VTools.java b/src/main/java/de/strifel/VTools/VTools.java index e3163b1..092ab76 100644 --- a/src/main/java/de/strifel/VTools/VTools.java +++ b/src/main/java/de/strifel/VTools/VTools.java @@ -40,6 +40,7 @@ public class VTools { server.getCommandManager().register("staffchat", new CommandStaffChat(server), "sc"); server.getCommandManager().register("restart", new CommandRestart(server)); server.getCommandManager().register("tps", new CommandTp(server), "jump"); + server.getCommandManager().register("server", new CommandServer(server)); server.getCommandManager().register("servers", new CommandServers(server), "allservers"); new TGBridge(this).register(); new PlayerStatus(this).register(); diff --git a/src/main/java/de/strifel/VTools/commands/CommandServer.java b/src/main/java/de/strifel/VTools/commands/CommandServer.java new file mode 100644 index 0000000..7231e9c --- /dev/null +++ b/src/main/java/de/strifel/VTools/commands/CommandServer.java @@ -0,0 +1,30 @@ +package de.strifel.VTools.commands; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import de.strifel.VTools.commands.utils.ServerUtil; + +public class CommandServer extends ServerUtil implements SimpleCommand { + + private final ProxyServer server; + + public CommandServer(ProxyServer server) { + super(server); + this.server = server; + } + + @Override + public boolean hasPermission(Invocation commandInvocation) { + CommandSource invocationSource = commandInvocation.source(); + if (invocationSource.hasPermission("vtools.servertp.auto") && invocationSource instanceof Player) { + Player player = (Player)invocationSource; + if (server.getAllPlayers().stream().anyMatch(p -> p.hasPermission("vtools.send") && p.hasPermission("vtools.send.revcmsg"))) { + return true; + } + return false; + } + else return invocationSource.hasPermission("vtools.servertp"); + } +} diff --git a/src/main/java/de/strifel/VTools/commands/utils/ServerUtil.java b/src/main/java/de/strifel/VTools/commands/utils/ServerUtil.java new file mode 100644 index 0000000..bcd2052 --- /dev/null +++ b/src/main/java/de/strifel/VTools/commands/utils/ServerUtil.java @@ -0,0 +1,176 @@ +package de.strifel.VTools.commands.utils; + +import static net.kyori.adventure.text.event.HoverEvent.showText; + +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.permission.Tristate; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerInfo; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; + +// https://github.com/PaperMC/Velocity/blob/40b76c633276fcd6aea165baeae74039b2d059c4/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ServerCommand.java + +/** + * Implements Velocity's {@code /server} command. + */ +public abstract class ServerUtil implements SimpleCommand { + + public static final int MAX_SERVERS_TO_LIST = 50; + private final ProxyServer server; + + public ServerUtil(ProxyServer server) { + this.server = server; + } + + @Override + public void execute(final SimpleCommand.Invocation invocation) { + final CommandSource source = invocation.source(); + final String[] args = invocation.arguments(); + + if (!(source instanceof Player)) { + source.sendMessage(CommandMessages.PLAYERS_ONLY); + return; + } + + Player player = (Player) source; + if (args.length == 1) { + // Trying to connect to a server. + String serverName = args[0]; + Optional toConnect = server.getServer(serverName); + if (toConnect.isEmpty()) { + player.sendMessage(CommandMessages.SERVER_DOES_NOT_EXIST.args(Component.text(serverName))); + return; + } + + player.createConnectionRequest(toConnect.get()).fireAndForget(); + } else { + outputServerInformation(player); + } + } + + private void outputServerInformation(Player executor) { + String currentServer = executor.getCurrentServer().map(ServerConnection::getServerInfo) + .map(ServerInfo::getName).orElse(""); + executor.sendMessage(Component.translatable( + "velocity.command.server-current-server", + NamedTextColor.YELLOW, + Component.text(currentServer))); + + List servers = BuiltinCommandUtil.sortedServerList(server); + if (servers.size() > MAX_SERVERS_TO_LIST) { + executor.sendMessage(Component.translatable( + "velocity.command.server-too-many", NamedTextColor.RED)); + return; + } + + // Assemble the list of servers as components + TextComponent.Builder serverListBuilder = Component.text() + .append(Component.translatable("velocity.command.server-available", + NamedTextColor.YELLOW)) + .append(Component.space()); + for (int i = 0; i < servers.size(); i++) { + RegisteredServer rs = servers.get(i); + serverListBuilder.append(formatServerComponent(currentServer, rs)); + if (i != servers.size() - 1) { + serverListBuilder.append(Component.text(", ", NamedTextColor.GRAY)); + } + } + + executor.sendMessage(serverListBuilder.build()); + } + + private TextComponent formatServerComponent(String currentPlayerServer, RegisteredServer server) { + ServerInfo serverInfo = server.getServerInfo(); + TextComponent serverTextComponent = Component.text(serverInfo.getName()); + + int connectedPlayers = server.getPlayersConnected().size(); + TranslatableComponent playersTextComponent; + if (connectedPlayers == 1) { + playersTextComponent = Component.translatable( + "velocity.command.server-tooltip-player-online"); + } else { + playersTextComponent = Component.translatable( + "velocity.command.server-tooltip-players-online"); + } + playersTextComponent = playersTextComponent.args(Component.text(connectedPlayers)); + if (serverInfo.getName().equals(currentPlayerServer)) { + serverTextComponent = serverTextComponent.color(NamedTextColor.GREEN) + .hoverEvent( + showText( + Component.translatable("velocity.command.server-tooltip-current-server") + .append(Component.newline()) + .append(playersTextComponent)) + ); + } else { + serverTextComponent = serverTextComponent.color(NamedTextColor.GRAY) + .clickEvent(ClickEvent.runCommand("/server " + serverInfo.getName())) + .hoverEvent( + showText( + Component.translatable("velocity.command.server-tooltip-offer-connect-server") + .append(Component.newline()) + .append(playersTextComponent)) + ); + } + return serverTextComponent; + } + + @Override + public List suggest(final SimpleCommand.Invocation invocation) { + final String[] currentArgs = invocation.arguments(); + Stream possibilities = server.getAllServers().stream() + .map(rs -> rs.getServerInfo().getName()); + + if (currentArgs.length == 0) { + return possibilities.collect(Collectors.toList()); + } else if (currentArgs.length == 1) { + return possibilities + .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) + .collect(Collectors.toList()); + } else { + return ImmutableList.of(); + } + } + + @Override + public boolean hasPermission(final SimpleCommand.Invocation invocation) { + return invocation.source().getPermissionValue("velocity.command.server") != Tristate.FALSE; + } +} + +// https://github.com/PaperMC/Velocity/blob/b0862d2d16c4ba7560d3f24c824d78793ac3d9e0/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java + +class CommandMessages { + + public static final TranslatableComponent PLAYERS_ONLY = Component.translatable( + "velocity.command.players-only", NamedTextColor.RED); + public static final TranslatableComponent SERVER_DOES_NOT_EXIST = Component.translatable( + "velocity.command.server-does-not-exist", NamedTextColor.RED); +} + +// https://github.com/PaperMC/Velocity/blob/b0862d2d16c4ba7560d3f24c824d78793ac3d9e0/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/BuiltinCommandUtil.java + +class BuiltinCommandUtil { + + private BuiltinCommandUtil() { + throw new AssertionError(); + } + + static List sortedServerList(ProxyServer proxy) { + List servers = new ArrayList<>(proxy.getAllServers()); + servers.sort(Comparator.comparing(RegisteredServer::getServerInfo)); + return Collections.unmodifiableList(servers); + } +} \ No newline at end of file diff --git a/src/main/java/de/strifel/VTools/listeners/utils/GlistUtil.java b/src/main/java/de/strifel/VTools/listeners/utils/GlistUtil.java index f1d9ac9..96cdb96 100644 --- a/src/main/java/de/strifel/VTools/listeners/utils/GlistUtil.java +++ b/src/main/java/de/strifel/VTools/listeners/utils/GlistUtil.java @@ -7,6 +7,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.NamedTextColor; import java.util.ArrayList; @@ -56,6 +57,7 @@ public class GlistUtil { NamedTextColor.DARK_AQUA)) .append(Component.text("(" + onServer.size() + ")", NamedTextColor.GRAY)) .append(Component.text(": ")) + .clickEvent(ClickEvent.runCommand("/server " + server.getServerInfo().getName())) .resetStyle(); for (int i = 0; i < onServer.size(); i++) {