diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
index e5a4828e8..76d087fab 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
@@ -38,6 +38,7 @@ import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.ProxyVersion;
import com.velocitypowered.proxy.command.VelocityCommandManager;
import com.velocitypowered.proxy.command.builtin.GlistCommand;
+import com.velocitypowered.proxy.command.builtin.SendCommand;
import com.velocitypowered.proxy.command.builtin.ServerCommand;
import com.velocitypowered.proxy.command.builtin.ShutdownCommand;
import com.velocitypowered.proxy.command.builtin.VelocityCommand;
@@ -216,6 +217,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
commandManager.register("shutdown", ShutdownCommand.command(this),
"end", "stop");
new GlistCommand(this).register();
+ new SendCommand(this).register();
this.doStartupConfigLoad();
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java
index 52da4b405..f42a59722 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java
@@ -30,4 +30,6 @@ public class CommandMessages {
"velocity.command.players-only", NamedTextColor.RED);
public static final TranslatableComponent SERVER_DOES_NOT_EXIST = Component.translatable(
"velocity.command.server-does-not-exist", NamedTextColor.RED);
+ public static final TranslatableComponent PLAYER_NOT_FOUND = Component.translatable(
+ "velocity.command.player-not-found", NamedTextColor.RED);
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java
new file mode 100644
index 000000000..bf07e8a4c
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020-2023 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.command.builtin;
+
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.builder.RequiredArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.tree.ArgumentCommandNode;
+import com.mojang.brigadier.tree.LiteralCommandNode;
+import com.velocitypowered.api.command.BrigadierCommand;
+import com.velocitypowered.api.command.CommandSource;
+import com.velocitypowered.api.permission.Tristate;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.ProxyServer;
+import com.velocitypowered.api.proxy.server.RegisteredServer;
+import java.util.Objects;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+
+/**
+ * Implements the Velocity default {@code /send} command.
+ */
+public class SendCommand {
+ private final ProxyServer server;
+ private static final String SERVER_ARG = "server";
+ private static final String PLAYER_ARG = "player";
+
+ public SendCommand(ProxyServer server) {
+ this.server = server;
+ }
+
+ /**
+ * Registers this command.
+ */
+ public void register() {
+ LiteralCommandNode totalNode = LiteralArgumentBuilder
+ .literal("send")
+ .requires(source ->
+ source.getPermissionValue("velocity.command.send") == Tristate.TRUE)
+ .executes(this::usage)
+ .build();
+ ArgumentCommandNode playerNode = RequiredArgumentBuilder
+ .argument("player", StringArgumentType.word())
+ .suggests((context, builder) -> {
+ for (Player player : server.getAllPlayers()) {
+ builder.suggest(player.getUsername());
+ }
+ builder.suggest("all");
+ return builder.buildFuture();
+ })
+ .executes(this::usage)
+ .build();
+ ArgumentCommandNode serverNode = RequiredArgumentBuilder
+ .argument("server", StringArgumentType.word())
+ .suggests((context, builder) -> {
+ for (RegisteredServer server : server.getAllServers()) {
+ builder.suggest(server.getServerInfo().getName());
+ }
+ return builder.buildFuture();
+ })
+ .executes(this::send)
+ .build();
+ totalNode.addChild(playerNode);
+ playerNode.addChild(serverNode);
+ server.getCommandManager().register(new BrigadierCommand(totalNode));
+ }
+
+ private int usage(CommandContext context) {
+ context.getSource().sendMessage(
+ Component.translatable("velocity.command.send-usage", NamedTextColor.YELLOW)
+ );
+ return 1;
+ }
+
+ private int send(CommandContext context) {
+ String serverName = context.getArgument(SERVER_ARG, String.class);
+ String player = context.getArgument(PLAYER_ARG, String.class);
+
+ if (server.getServer(serverName).isEmpty()) {
+ context.getSource().sendMessage(
+ CommandMessages.SERVER_DOES_NOT_EXIST.args(Component.text(serverName))
+ );
+ return 0;
+ }
+
+ if (server.getPlayer(player).isEmpty() && !Objects.equals(player, "all")) {
+ context.getSource().sendMessage(
+ CommandMessages.PLAYER_NOT_FOUND.args(Component.text(player))
+ );
+ return 0;
+ }
+
+ if (Objects.equals(player, "all")) {
+ for (Player p : server.getAllPlayers()) {
+ p.createConnectionRequest(server.getServer(serverName).get()).fireAndForget();
+ }
+ return 1;
+ }
+
+ server.getPlayer(player).get().createConnectionRequest(
+ server.getServer(serverName).get()
+ ).fireAndForget();
+ return 1;
+ }
+}
diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties
index f2b4d8a7e..f5b6ae9b4 100644
--- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties
+++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages.properties
@@ -35,6 +35,7 @@ velocity.command.generic-error=An error occurred while running this command.
velocity.command.command-does-not-exist=This command does not exist.
velocity.command.players-only=Only players can run this command.
velocity.command.server-does-not-exist=The specified server {0} does not exist.
+velocity.command.player-not-found=The specified player {0} does not exist.
velocity.command.server-current-server=You are currently connected to {0}.
velocity.command.server-too-many=There are too many servers set up. Use tab completion to view all servers available.
velocity.command.server-available=Available servers:
@@ -59,5 +60,6 @@ velocity.command.dump-success=Created an anonymised report containing useful inf
velocity.command.dump-will-expire=This link will expire in a few days.
velocity.command.dump-server-error=An error occurred on the Velocity servers and the dump could not be completed. Please contact the Velocity staff about this problem and provide the details about this error from the Velocity console or server log.
velocity.command.dump-offline=Likely cause: Invalid system DNS settings or no internet connection
+velocity.command.send-usage=/send
# Kick
velocity.kick.shutdown=Proxy shutting down.
\ No newline at end of file
diff --git a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_es_ES.properties b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_es_ES.properties
index a980e0c83..5c73de752 100644
--- a/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_es_ES.properties
+++ b/proxy/src/main/resources/com/velocitypowered/proxy/l10n/messages_es_ES.properties
@@ -35,6 +35,7 @@ velocity.command.command-does-not-exist=Este comando no existe.
velocity.command.players-only=Solo los jugadores pueden ejecutar este comando.
velocity.command.server-does-not-exist=El servidor especificado {0} no existe.
velocity.command.server-current-server=Estás conectado a {0}.
+velocity.command.player-not-found=El jugador especificado {0} no existe.
velocity.command.server-too-many=Hay demasiados servidores registrados. Usa la finalización con tabulación para ver todos los servidores disponibles.
velocity.command.server-available=Servidores disponibles\:
velocity.command.server-tooltip-player-online={0} jugador conectado
@@ -57,4 +58,5 @@ velocity.command.dump-send-error=Se ha producido un error al comunicarse con los
velocity.command.dump-success=Se ha creado un informe anónimo que contiene información útil sobre este proxy. Si un desarrollador lo solicita, puedes compartir el siguiente enlace con él\:
velocity.command.dump-will-expire=Este enlace caducará en unos días.
velocity.command.dump-server-error=Se ha producido un error en los servidores de Velocity y la subida no se ha podido completar. Notifica al equipo de Velocity sobre este problema y proporciona los detalles sobre este error disponibles en el archivo de registro o la consola de tu servidor Velocity.
-velocity.command.dump-offline=Causa probable\: la configuración DNS del sistema no es válida o no hay conexión a internet
\ No newline at end of file
+velocity.command.dump-offline=Causa probable\: la configuración DNS del sistema no es válida o no hay conexión a internet
+velocity.command.send-usage=/send
\ No newline at end of file