diff --git a/.web/docs/guide/builtin-commands.md b/.web/docs/guide/builtin-commands.md index e8d49f70..a48c8dd1 100644 --- a/.web/docs/guide/builtin-commands.md +++ b/.web/docs/guide/builtin-commands.md @@ -11,7 +11,7 @@ If you want to add custom commands refer to the [Developers Guide](/developers/) |------------------|-----------------------|-----------------------------------------------------------------------------------------| | `/server` | `gate.command.server` | Players can use the command to view and switch to another server. | | `/glist` | `gate.command.glist` | View the number of players on the Gate instance. `/glist all` lists players per server. | - +| `/send` | `gate.command.send` | Send one or all players to another server. | ## Permission diff --git a/pkg/edition/java/proxy/builtin_cmd_send.go b/pkg/edition/java/proxy/builtin_cmd_send.go new file mode 100644 index 00000000..f0ec5f9d --- /dev/null +++ b/pkg/edition/java/proxy/builtin_cmd_send.go @@ -0,0 +1,71 @@ +package proxy + +import ( + "fmt" + "go.minekube.com/brigodier" + . "go.minekube.com/common/minecraft/color" + . "go.minekube.com/common/minecraft/component" + "go.minekube.com/gate/pkg/command" + "go.minekube.com/gate/pkg/command/suggest" + "strings" +) + +const sendCmdPermission = "gate.command.send" + +func newSendCmd(proxy *Proxy) brigodier.LiteralNodeBuilder { + const sendPlayerArg = "player" + const sendServerArg = "server" + return brigodier.Literal("send"). + Requires(hasCmdPerm(proxy, sendCmdPermission)). + Then(brigodier.Argument(sendPlayerArg, brigodier.String). + Suggests(playerSuggestionProvider(proxy, "all", "current")). + Then(brigodier.Argument(sendServerArg, brigodier.String). + Suggests(serverSuggestionProvider(proxy)). + Executes(command.Command(func(c *command.Context) error { + return sendToServer(proxy, c, c.String(sendPlayerArg), c.String(sendServerArg)) + })), + ), + ) +} + +func sendToServer(proxy *Proxy, c *command.Context, playerName, serverName string) error { + if strings.EqualFold(playerName, "all") { + return connectPlayersToServer(c, proxy, serverName, proxy.Players()...) + } + + if strings.EqualFold(playerName, "current") { + if player, ok := c.Source.(Player); ok { + if currentServer := player.CurrentServer(); currentServer != nil { + return connectPlayersToServer(c, proxy, serverName, PlayersToSlice[Player](currentServer.Server().Players())...) + } + } else { + return c.Source.SendMessage(&Text{S: Style{Color: Red}, Content: "Only players can use 'current'!"}) + } + return nil + } + + player := proxy.PlayerByName(playerName) + if player == nil { + return c.Source.SendMessage(&Text{S: Style{Color: Red}, Content: fmt.Sprintf("Player %q doesn't exist.", playerName)}) + } + + return connectPlayersToServer(c, proxy, serverName, player) +} +func playerSuggestionProvider(proxy *Proxy, additionalPlayers ...string) brigodier.SuggestionProvider { + return command.SuggestFunc(func( + _ *command.Context, + b *brigodier.SuggestionsBuilder, + ) *brigodier.Suggestions { + candidates := append(playerNames(proxy), additionalPlayers...) + return suggest.Similar(b, candidates).Build() + }) +} + +func playerNames(proxy *Proxy) []string { + list := proxy.Players() + n := make([]string, len(list)) + for i, player := range list { + n[i] = player.Username() + } + return n +} diff --git a/pkg/edition/java/proxy/builtin_cmd_server.go b/pkg/edition/java/proxy/builtin_cmd_server.go index 8d73d9f2..5c50ae3e 100644 --- a/pkg/edition/java/proxy/builtin_cmd_server.go +++ b/pkg/edition/java/proxy/builtin_cmd_server.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sort" + "sync" "time" "go.minekube.com/brigodier" @@ -17,6 +18,7 @@ const serverCmdPermission = "gate.command.server" // command to list and connect to registered servers func newServerCmd(proxy *Proxy) brigodier.LiteralNodeBuilder { + const serverNameArg = "name" return brigodier.Literal("server"). Requires(hasCmdPerm(proxy, serverCmdPermission)). // List registered server. @@ -24,7 +26,7 @@ func newServerCmd(proxy *Proxy) brigodier.LiteralNodeBuilder { return c.SendMessage(serversInfo(proxy, c.Source)) })). // Switch server - Then(brigodier.Argument("name", brigodier.String). + Then(brigodier.Argument(serverNameArg, brigodier.String). Suggests(serverSuggestionProvider(proxy)). Executes(command.Command(func(c *command.Context) error { player, ok := c.Source.(Player) @@ -33,22 +35,38 @@ func newServerCmd(proxy *Proxy) brigodier.LiteralNodeBuilder { Content: "Only players can connect to a server!"}) } - name := c.String("name") - rs := proxy.Server(name) - if rs == nil { - return c.Source.SendMessage(&Text{S: Style{Color: Red}, - Content: fmt.Sprintf("Server %q doesn't exist.", name)}) - } - - ctx, cancel := context.WithTimeout(context.Background(), - time.Millisecond*time.Duration(proxy.cfg.ConnectionTimeout)) - defer cancel() - player.CreateConnectionRequest(rs).ConnectWithIndication(ctx) - return nil + name := c.String(serverNameArg) + return connectPlayersToServer(c, proxy, name, player) })), ) } +func connectPlayersToServer(c *command.Context, proxy *Proxy, serverName string, players ...Player) error { + server := proxy.Server(serverName) + if server == nil { + return c.Source.SendMessage(&Text{S: Style{Color: Red}, + Content: fmt.Sprintf("Server %q doesn't exist.", serverName)}) + } + + go func() { + ctx, cancel := context.WithTimeout(context.Background(), + time.Millisecond*time.Duration(proxy.cfg.ConnectionTimeout)) + defer cancel() + + wg := new(sync.WaitGroup) + wg.Add(len(players)) + for _, player := range players { + go func(player Player) { + defer wg.Done() + player.CreateConnectionRequest(server).ConnectWithIndication(ctx) + }(player) + } + wg.Wait() + }() + + return nil +} + const maxServersToList = 50 func serversInfo(proxy *Proxy, s command.Source) (c Component) { diff --git a/pkg/edition/java/proxy/builtin_commands.go b/pkg/edition/java/proxy/builtin_commands.go index e61fd8cf..ea83773e 100644 --- a/pkg/edition/java/proxy/builtin_commands.go +++ b/pkg/edition/java/proxy/builtin_commands.go @@ -8,6 +8,7 @@ import ( func (p *Proxy) registerBuiltinCommands() { p.command.Register(newServerCmd(p)) p.command.Register(newGlistCmd(p)) + p.command.Register(newSendCmd(p)) } func hasCmdPerm(proxy *Proxy, perm string) brigodier.RequireFn {