package server import ( "fmt" "sort" "strconv" "strings" ) type CommandSender interface { OnCommandOutput(from *Server, output string) OnCommandError(from *Server, err string) } type commandCtx struct { server *Server sender CommandSender arg arguments auth authLevel } type commandHandler func(commandCtx) (err string) func isCommand(message string) (isCmd bool, text string) { text = message if message[0] == '/' { text = message[1:] // an additional slash escapes the second //not command -> /not command isCmd = len(text) != 0 && text[0] != '/' } return } func executeCommand( s *Server, auth authLevel, from CommandSender, command string) { ctx := commandCtx { server: s, sender: from, arg: parseArgs(command), auth: auth, } cmd, _ := ctx.arg.nextArg() handler := commands[cmd] var err string if commandAuth[cmd] > auth { err = "Permission denied" } else if handler == nil { err = "Unknown command: /" + cmd } else { err = handler(ctx) } if err != "" { from.OnCommandError(s, err) } } func usage(cmd string) string { if help[cmd] != nil { return "Usage: " + help[cmd][0] } else { return "Invalid usage" } } var commands = map[string]commandHandler { "tp": func(ctx commandCtx) string { var ( sourcePlayer string destPlayer string ) ctx.arg.flags["-p"] = func(arg *arguments) { sourcePlayer, _ = arg.nextArg() } ctx.arg.flags["-t"] = func(arg *arguments) { destPlayer, _ = arg.nextArg() } for name, pl := range(ctx.server.players) { if ctx.sender == pl { sourcePlayer = name } } args := ctx.arg.allArgs() if sourcePlayer == "" { return "Teleport who?" } if sourcePlayer != "" && ctx.server.players[sourcePlayer] == nil { return "Unknown player: " + sourcePlayer } if destPlayer != "" && ctx.server.players[destPlayer] == nil { return "Unknown player: " + destPlayer } if len(args) == 0 { if destPlayer == "" { return usage("tp") } var ( pl1 = ctx.server.players[sourcePlayer] pl2 = ctx.server.players[destPlayer] ) pl1.GetInfo(ctx.server, func(_ string, st1 playerState) { pl2.GetInfo(ctx.server, func(_ string, st2 playerState) { if st1.LevelId == st2.LevelId { pl1.MovePlayer(ctx.server, st2.Pos, st2.Facing) } else { pl1.ChangeLevel(ctx.server, st2.LevelId, st2.Pos) } }) }) } else { if destPlayer != "" { return usage("tp") } var values []float64 for _, value := range args { n, err := strconv.ParseFloat(value, 64) if err != nil { return usage("tp") } values = append(values, n) } if len(values) < 3 { return usage("tp") } pos := entityPos { fromFloat(values[0]), fromFloat(values[1]), fromFloat(values[2]), } pl := ctx.server.players[sourcePlayer] if len(values) == 3 { pl.MovePlayer(ctx.server, pos, entityFacing {}) } else if len(values) == 4 { pl.ChangeLevel(ctx.server, levelId(values[3]), pos) } else { return usage("tp") } } return "" }, "createLevel": func(ctx commandCtx) string { var ( ok = true size = blockPos {256, 256, 256} genType = "flat" ) ctx.arg.flags["--size"] = func(arg *arguments) { var coords []int for i := 0; i < 3; i++ { coord, k := arg.nextArg() i, err := strconv.Atoi(coord) ok = ok && k && err == nil coords = append(coords, i) } if !ok { return } size = blockPos { X: blockCoord(coords[0]), Y: blockCoord(coords[1]), Z: blockCoord(coords[2]), } } ctx.arg.flags["--gen"] = func(arg *arguments) { var k bool genType, k = arg.nextArg() ok = ok && k } ok = ok && len(ctx.arg.allArgs()) == 0 if !ok { return usage("createLevel") } switch genType { case "flat", "empty": default: return "Unknown generation type: " + genType } id, lvl := ctx.server.newLevel(levelInfo {Size: size}) lvl.Act(nil, func() { ctx.sender.OnCommandOutput(ctx.server, "Creating level...") switch genType { case "flat": lvl.generateFlat() case "empty": lvl.generateEmpty() } ctx.sender.OnCommandOutput(ctx.server, fmt.Sprintf("Generated level id %d.", id), ) }) switch pl := ctx.sender.(type) { case *player: pl.ChangeLevel(ctx.server, id, entityPos { X: entityCoord(size.X) * blockSize / 2, Y: entityCoord(size.Y) * blockSize / 2 + blockSize, Z: entityCoord(size.Z) * blockSize / 2, }) } return "" }, "op": func(ctx commandCtx) string { name, ok := ctx.arg.nextArg() if !ok { return usage("op") } if !playerNameRegex.Match([]byte(name)) { return "Unknown player." } ctx.server.changePlayerAuth(name, opAuth, func(ok bool) { if ok { ctx.sender.OnCommandOutput(ctx.server, "Opped.") } else { ctx.sender.OnCommandError(ctx.server, "Unknown player") } }) return "" }, "deop": func(ctx commandCtx) string { name, ok := ctx.arg.nextArg() if !ok { return usage("op") } ctx.server.changePlayerAuth(name, defaultAuth, func(ok bool) { if ok { ctx.sender.OnCommandOutput(ctx.server, "Deopped.") } else { ctx.sender.OnCommandError(ctx.server, "Unknown player") } }) return "" }, "stop": func(ctx commandCtx) string { ctx.server.Stop(nil) return "" }, "save": func(ctx commandCtx) string { ctx.server.Save(nil) return "" }, "help": func(ctx commandCtx) string { cmd, ok := ctx.arg.nextArg() if !ok { var usages []string for cmd, helpMessages := range help { if commandAuth[cmd] <= ctx.auth { usages = append(usages, helpMessages[0]) } } sort.Strings(usages) ctx.sender.OnCommandOutput(ctx.server, "Available commands:") for _, usage := range usages { ctx.sender.OnCommandOutput(ctx.server, "* " + usage) } return "" } if help[cmd] == nil { return "Unknown command: /" + cmd } for n, line := range help[cmd] { if n == 0 { line = "Usage: " + line } else { line = " " + line } ctx.sender.OnCommandOutput(ctx.server, line) } return "" }, } var commandAuth = map[string]authLevel { "tp": cheatAuth, "createLevel": opAuth, "op": opAuth, "deop": opAuth, "save": opAuth, "stop": opAuth, } var help = map[string][]string { "tp": []string { "/tp [-p player] <-t player | [level]>", "Teleport", "* -p: Player to teleport (default: you)", "* -t: Teleport to player", "Examples:", "* /tp 128 128 128 4", "* /tp -p alice -t eve", "* /tp -p bob 0 -1000000 0", }, "createLevel": []string { "/createLevel [--gen genType] [--size size]", "Create a level", "* --gen: Level generation type (flat, empty)", "* --size: Level dimensions", }, "op": []string { "/op ", "Grant operator status", }, "deop": []string { "/deop ", "Revoke operator status", }, "save": []string { "/save", "Save all levels", }, "stop": []string { "/stop", "Stop the server", }, "help": []string { "/help [command]", }, } type arguments struct { rd *strings.Reader flags map[string]func(*arguments) } func parseArgs(command string) arguments { return arguments { strings.NewReader(command), make(map[string]func(*arguments)), } } func (arg *arguments) nextArg() (string, bool) { var out strings.Builder for { b, err := arg.rd.ReadByte() if err != nil { return "", false } if b != ' ' { arg.rd.UnreadByte() break } } var splitChar byte = ' ' b, err := arg.rd.ReadByte() if err != nil { return "", false } if b == '"' { splitChar = '"' } else { arg.rd.UnreadByte() } for { b, err = arg.rd.ReadByte() if err != nil { if out.Len() > 0 { break } return "", false } if b == splitChar { break } if b == '\\' { b, err = arg.rd.ReadByte() if err != nil { b = '\\' } } out.WriteByte(b) } if arg.flags[out.String()] != nil { arg.flags[out.String()](arg) return arg.nextArg() } return out.String(), true } func (arg *arguments) allArgs() []string { var (a string; ok bool; args []string) for { a, ok = arg.nextArg() if !ok { break } args = append(args, a) } return args }