Skip to content

Commit

Permalink
add command system & CommandExecuteEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
robinbraemer committed Aug 9, 2020
1 parent 993eb79 commit 2dac5bf
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 51 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/spf13/viper v1.7.0
github.com/stretchr/testify v1.6.1
github.com/valyala/fasthttp v1.15.1
go.minekube.com/common v0.0.0-20200804114822-9c4fad286696
go.minekube.com/common v0.0.0-20200809185449-de163b8050bf
go.uber.org/atomic v1.6.0
go.uber.org/zap v1.15.0
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.minekube.com/common v0.0.0-20200804114822-9c4fad286696 h1:BIrnH7iDE48hMJmEL/eZf0+zZuDik4ZWQbbQMMCKt9w=
go.minekube.com/common v0.0.0-20200804114822-9c4fad286696/go.mod h1:PCdSdTInlQv6ggDIbVjLFs7ehSRP4i9KqYsLAeeNUYU=
go.minekube.com/common v0.0.0-20200809185449-de163b8050bf h1:HVTUSpJlMZwcRiwKZ0nMDvqMGnPZRBdPC5BklO26iDo=
go.minekube.com/common v0.0.0-20200809185449-de163b8050bf/go.mod h1:PCdSdTInlQv6ggDIbVjLFs7ehSRP4i9KqYsLAeeNUYU=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
Expand Down
70 changes: 70 additions & 0 deletions pkg/proxy/builtin_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package proxy

import (
"context"
"fmt"
. "go.minekube.com/common/minecraft/color"
. "go.minekube.com/common/minecraft/component"
"time"
)

type serverCmd struct{ proxy *Proxy }

func (s *serverCmd) Invoke(c *Context) {
if len(c.Args) == 0 {
s.list(c)
return
}
s.connect(c)
}

// switch server
func (s *serverCmd) connect(c *Context) {
player, ok := c.Source.(Player)
if !ok {
_ = c.Source.SendMessage(&Text{Content: "Only players can connect to a server!", S: Style{Color: Red}})
return
}

server := c.Args[0]
rs := s.proxy.Server(server)
if rs == nil {
_ = c.Source.SendMessage(&Text{Content: fmt.Sprintf("Server %q not registered", server), S: Style{Color: Red}})
return
}

ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(s.proxy.config.ConnectionTimeout))
defer cancel()
player.CreateConnectionRequest(rs).ConnectWithIndication(ctx)
}

// list registered servers
func (s *serverCmd) list(c *Context) {
const maxEntries = 50
var servers []Component
proxyServers := s.proxy.Servers()
for i, s := range proxyServers {
if i+1 == maxEntries {
servers = append(servers, &Text{
Content: fmt.Sprintf("and %d more...", len(proxyServers)-i+1),
})
break
}
servers = append(servers, &Text{
Content: fmt.Sprintf(" %s - %s (%d players)\n",
s.ServerInfo().Name(), s.ServerInfo().Addr(), s.Players().Len()),
S: Style{ClickEvent: RunCommand(fmt.Sprintf("/server %s", s.ServerInfo().Name()))},
})
}
_ = c.Source.SendMessage(&Text{
Content: fmt.Sprintf("\nServers (%d):\n", len(proxyServers)),
S: Style{Color: Green},
Extra: []Component{&Text{
S: Style{
Color: Yellow,
HoverEvent: ShowText(&Text{Content: "Click to connect!", S: Style{Color: Green}}),
},
Extra: servers,
}},
})
}
132 changes: 132 additions & 0 deletions pkg/proxy/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package proxy

import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"sync"
)

type CommandManager struct {
mu sync.RWMutex
commands map[string]*registration
}

// newCommandManager returns a new CommandManager.
func newCommandManager() *CommandManager {
return &CommandManager{commands: map[string]*registration{}}
}

type registration struct {
cmd Command
aliases []string
}

// Register registers (and overrides) a command with the root literal name and optional aliases.
func (m *CommandManager) Register(cmd Command, name string, aliases ...string) {
if cmd == nil {
return
}
r := &registration{
cmd: cmd,
aliases: append(aliases, name),
}
m.mu.Lock()
defer m.mu.Unlock()
m.commands[name] = r
for _, name := range aliases {
m.commands[name] = r
}
}

// Unregister unregisters a command with its aliases.
func (m *CommandManager) Unregister(name string) {
m.mu.Lock()
r, ok := m.commands[name]
if ok {
for _, name := range r.aliases {
delete(m.commands, name)
}
}
delete(m.commands, name)
m.mu.Unlock()
}

// Has return true if the command is registered.
func (m *CommandManager) Has(command string) bool {
m.mu.RLock()
_, ok := m.commands[command]
m.mu.RUnlock()
return ok
}

// Invoke invokes a registered command.
func (m *CommandManager) Invoke(ctx *Context, command string) (found bool, err error) {
if len(command) == 0 {
return false, errors.New("command must not be empty")
}
if ctx == nil {
return false, errors.New("ctx must not be nil")
}
if ctx.Source == nil {
return false, errors.New("ctx source must not be nil")
}
if ctx.Context == nil {
ctx.Context = context.Background()
}
m.mu.RLock()
r, ok := m.commands[command]
m.mu.RUnlock()
if !ok {
return false, nil
}
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic while invoking command: %v", r)
}
}()
r.cmd.Invoke(ctx)
return true, err
}

// Command is an invokable command.
type Command interface {
Invoke(*Context)
}

// Func is a shorthand type that implements the Command interface.
type Func func(*Context)

// Invoke implements Command.
func (f Func) Invoke(c *Context) {
f(c)
}

// Context is a command invocation context.
type Context struct {
context.Context
Source CommandSource
Args []string
}

var spaceRegex = regexp.MustCompile(`\s+`)

// trimSpaces removes all spaces that are to much.
func trimSpaces(s string) string {
s = strings.TrimSpace(s)
return spaceRegex.ReplaceAllString(s, " ") // remove to much spaces in between
}

func extract(commandline string) (command string, args []string, ok bool) {
split := strings.Split(commandline, " ")
if len(split) != 0 {
command = split[0]
ok = true
}
if len(split) > 1 {
args = split[1:]
}
return
}
35 changes: 35 additions & 0 deletions pkg/proxy/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ func (s *PlayerSettingsChangedEvent) Settings() player.Settings {
//

// PlayerChatEvent is fired when a player sends a chat message.
// Note that messages with a leading "/" do not trigger this event, but instead CommandExecuteEvent.
type PlayerChatEvent struct {
player Player
message string
Expand All @@ -678,3 +679,37 @@ func (c *PlayerChatEvent) SetAllowed(allowed bool) {
func (c *PlayerChatEvent) Allowed() bool {
return !c.denied
}

//
//
//
//
//

// CommandExecuteEvent is fired when someone wants to execute a command.
type CommandExecuteEvent struct {
source CommandSource
commandline string

denied bool
}

// Source returns the command source that wants to run the command.
func (c *CommandExecuteEvent) Source() CommandSource {
return c.source
}

// Command returns the whole commandline without the leading "/".
func (c *CommandExecuteEvent) Command() string {
return c.commandline
}

// SetAllowed sets whether the command is allowed to be executed.
func (c *CommandExecuteEvent) SetAllowed(allowed bool) {
c.denied = !allowed
}

// Allowed returns true when the command is allowed to be executed.
func (c *CommandExecuteEvent) Allowed() bool {
return !c.denied
}
10 changes: 10 additions & 0 deletions pkg/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Proxy struct {
*connect
config *config.Config
event *event.Manager
command *CommandManager
channelRegistrar *ChannelRegistrar
authenticator *auth.Authenticator

Expand All @@ -54,6 +55,7 @@ func New(config config.Config) (s *Proxy) {
closed: make(chan struct{}),
config: &config,
event: event.NewManager(),
command: newCommandManager(),
channelRegistrar: NewChannelRegistrar(),
servers: map[string]RegisteredServer{},
authenticator: auth.NewAuthenticator(),
Expand Down Expand Up @@ -130,6 +132,9 @@ func (p *Proxy) preInit() (err error) {
if len(c.Servers) != 0 {
zap.S().Infof("Pre-registered %d servers", len(c.Servers))
}

// Register builtin commands
p.command.Register(&serverCmd{proxy: p}, "server")
return
}

Expand Down Expand Up @@ -174,6 +179,11 @@ func (p *Proxy) Event() *event.Manager {
return p.event
}

// Command returns the Proxy's command manager.
func (p *Proxy) Command() *CommandManager {
return p.command
}

// Config returns the config used by the Proxy.
func (p *Proxy) Config() config.Config {
return *p.config
Expand Down
Loading

0 comments on commit 2dac5bf

Please sign in to comment.