diff --git a/server/command.go b/server/command.go deleted file mode 100644 index 930b050..0000000 --- a/server/command.go +++ /dev/null @@ -1,49 +0,0 @@ -package server - -import ( - "context" - "github.com/Tnze/go-mc/data/packetid" - pk "github.com/Tnze/go-mc/net/packet" - "github.com/Tnze/go-mc/server/command" - "strings" -) - -type CommandGraph struct { - *command.Graph -} - -func NewCommandGraph() *CommandGraph { - return &CommandGraph{ - Graph: command.NewGraph(), - } -} - -func (c *CommandGraph) Init(g *Game) { - g.AddHandler(&PacketHandler{ - ID: packetid.ServerboundChat, - F: func(player *Player, packet Packet757) error { - var msg pk.String - if err := pk.Packet(packet).Scan(&msg); err != nil { - return err - } - if cmd := string(msg); strings.HasPrefix(cmd, "/") { - cmderr := c.Graph.Run(strings.TrimPrefix(cmd, "/")) - if cmderr != nil { - // TODO: tell player that their command has error - } - } - return nil - }, - }) -} - -func (c *CommandGraph) Run(ctx context.Context) {} - -func (c *CommandGraph) AddPlayer(p *Player) { - p.WritePacket(Packet757(pk.Marshal( - packetid.ClientboundCommands, - c.Graph, - ))) -} - -func (c *CommandGraph) RemovePlayer(p *Player) {} diff --git a/server/command/builders.go b/server/command/builders.go index 5e9be29..0c3787c 100644 --- a/server/command/builders.go +++ b/server/command/builders.go @@ -5,50 +5,48 @@ func (g *Graph) AppendLiteral(child *Literal) *Graph { return g } +// Literal create a new LiteralNode in the Graph. func (g *Graph) Literal(str string) LiteralBuilder { index := int32(len(g.nodes)) - g.nodes = append(g.nodes, Node{ + g.nodes = append(g.nodes, &Node{ g: g, index: index, kind: LiteralNode, Name: str, }) - return LiteralBuilder{g: g, current: index} + return LiteralBuilder{current: g.nodes[index]} } +// Argument create a new ArgumentNode in the Graph. func (g *Graph) Argument(name string, parser Parser) ArgumentBuilder { index := int32(len(g.nodes)) - g.nodes = append(g.nodes, Node{ + g.nodes = append(g.nodes, &Node{ g: g, index: index, kind: ArgumentNode, Name: name, Parser: parser, }) - return ArgumentBuilder{g: g, current: index} + return ArgumentBuilder{current: g.nodes[index]} } type LiteralBuilder struct { - g *Graph - current int32 + current *Node } func (n LiteralBuilder) AppendLiteral(node *Literal) LiteralBuilderWithLiteral { - current := &n.g.nodes[n.current] - current.Children = append(current.Children, node.index) + n.current.Children = append(n.current.Children, node.index) return LiteralBuilderWithLiteral{n: n} } func (n LiteralBuilder) AppendArgument(node *Argument) LiteralBuilderWithArgument { - current := &n.g.nodes[n.current] - current.Children = append(current.Children, node.index) + n.current.Children = append(n.current.Children, node.index) return LiteralBuilderWithArgument{n: n} } func (n LiteralBuilder) HandleFunc(f HandlerFunc) *Literal { - current := &n.g.nodes[n.current] - current.Run = f - return (*Literal)(current) + n.current.Run = f + return (*Literal)(n.current) } func (n LiteralBuilder) Unhandle() *Literal { @@ -56,26 +54,22 @@ func (n LiteralBuilder) Unhandle() *Literal { } type ArgumentBuilder struct { - g *Graph - current int32 + current *Node } func (n ArgumentBuilder) AppendLiteral(node *Literal) ArgumentBuilderWithLiteral { - current := &n.g.nodes[n.current] - current.Children = append(current.Children, node.index) + n.current.Children = append(n.current.Children, node.index) return ArgumentBuilderWithLiteral{n: n} } func (n ArgumentBuilder) AppendArgument(node *Argument) ArgumentBuilderWithArgument { - current := &n.g.nodes[n.current] - current.Children = append(current.Children, node.index) + n.current.Children = append(n.current.Children, node.index) return ArgumentBuilderWithArgument{n: n} } func (n ArgumentBuilder) HandleFunc(f HandlerFunc) *Argument { - current := &n.g.nodes[n.current] - current.Run = f - return (*Argument)(current) + n.current.Run = f + return (*Argument)(n.current) } func (n ArgumentBuilder) Unhandle() *Argument { @@ -103,11 +97,11 @@ type LiteralBuilderWithArgument struct { } func (n LiteralBuilderWithArgument) HandleFunc(f HandlerFunc) *Literal { - return (*Literal)((*Argument)(n.n.HandleFunc(f))) + return n.n.HandleFunc(f) } func (n LiteralBuilderWithArgument) Unhandle() *Literal { - return (*Literal)((*Argument)(n.n.Unhandle())) + return n.n.Unhandle() } type ArgumentBuilderWithLiteral struct { diff --git a/server/command/command.go b/server/command/command.go index bed2756..48ea134 100644 --- a/server/command/command.go +++ b/server/command/command.go @@ -1,6 +1,7 @@ package command import ( + "context" "errors" "strings" ) @@ -11,54 +12,58 @@ const ( ArgumentNode ) +// Graph is a directed graph with a root node, representing all commands and how they are parsed. type Graph struct { - nodes []Node + // List of all nodes. The first element is the root node + nodes []*Node } func NewGraph() *Graph { - g := new(Graph) - root := Node{ - g: g, - kind: RootNode, - } - g.nodes = append(g.nodes, root) - return g + var g Graph + g.nodes = append(g.nodes, + &Node{g: &g, kind: RootNode}, + ) + return &g } -func (g *Graph) Run(cmd string) error { +func (g *Graph) Execute(ctx context.Context, cmd string) error { var args []ParsedData - node := &g.nodes[0] // root + node := g.nodes[0] // root for { - left, value, next, err := node.parse(cmd) + // parser command + left, value, err := node.parse(cmd) if err != nil { return err } args = append(args, value) left = strings.TrimSpace(left) if len(left) == 0 { - err := node.Run(args) - if err != nil { - return err - } - return nil + return node.Run(ctx, args) + } + // find next node + next, err := node.next(left) + if err != nil { + return err } if next == 0 { return errors.New("command contains extra text: " + left) } cmd = left - node = &g.nodes[next] + node = g.nodes[next] } } type ParsedData interface{} -type HandlerFunc func(args []ParsedData) error +type HandlerFunc func(ctx context.Context, args []ParsedData) error +// Node is the node of the Graph. There are 3 kinds of node: Root, Literal and Argument. type Node struct { - g *Graph - index int32 - kind byte + g *Graph + index int32 + kind byte + Name string Children []int32 SuggestionsType string @@ -68,54 +73,58 @@ type Node struct { type Literal Node type Argument Node -func (n *Node) parse(cmd string) (left string, value ParsedData, next int32, err error) { +func (n *Node) parse(cmd string) (left string, value ParsedData, err error) { switch n.kind & 0x03 { case RootNode: left = cmd value = nil err = nil + case LiteralNode: if !strings.HasPrefix(cmd, n.Name) { panic("expect " + cmd + " prefixed with " + n.Name) } left = strings.TrimPrefix(cmd, n.Name) value = LiteralData(n.Name) + case ArgumentNode: left, value, err = n.Parser.Parse(cmd) - if err != nil { - return "", nil, 0, err - } + default: panic("unreachable") } - // find next - if len(n.Children) > 0 { - // look up the first child's type - switch n.g.nodes[n.Children[0]].kind & 0x03 { - case RootNode: - panic("root node can't be child") - default: - panic("unreachable") - case LiteralNode: - _, value, err := StringParser(0).Parse(strings.TrimSpace(left)) - if err != nil { - return "", nil, 0, err - } - literal := value.(string) - for _, i := range n.Children { - if n.g.nodes[i].Name == literal { - next = i - break - } - } - case ArgumentNode: - next = n.Children[0] - } - } return } -func unhandledCmd([]ParsedData) error { +func (n *Node) next(left string) (next int32, err error) { + if len(n.Children) == 0 { + return 0, nil + } + // look up the first child's type + switch n.g.nodes[n.Children[0]].kind & 0x03 { + case RootNode: + panic("root node can't be child") + default: + panic("unreachable") + case LiteralNode: + _, value, err := StringParser(0).Parse(strings.TrimSpace(left)) + if err != nil { + return 0, err + } + literal := value.(string) + for _, i := range n.Children { + if n.g.nodes[i].Name == literal { + next = i + break + } + } + case ArgumentNode: + next = n.Children[0] + } + return +} + +func unhandledCmd(context.Context, []ParsedData) error { return errors.New("unhandled function") } diff --git a/server/command/command_test.go b/server/command/command_test.go index 40445eb..84de984 100644 --- a/server/command/command_test.go +++ b/server/command/command_test.go @@ -1,12 +1,13 @@ package command import ( + "context" "log" "testing" ) func TestRoot_Run(t *testing.T) { - handleFunc := func(args []ParsedData) error { + handleFunc := func(ctx context.Context, args []ParsedData) error { log.Printf("Command: args: %v", args) return nil } @@ -25,7 +26,7 @@ func TestRoot_Run(t *testing.T) { HandleFunc(handleFunc), ) - err := g.Run("me Tnze Xi_Xi_Mi") + err := g.Execute(context.TODO(), "me Tnze Xi_Xi_Mi") if err != nil { t.Fatal(err) } diff --git a/server/command/component.go b/server/command/component.go new file mode 100644 index 0000000..bd811e1 --- /dev/null +++ b/server/command/component.go @@ -0,0 +1,44 @@ +package command + +import ( + "context" + "strings" + + "github.com/Tnze/go-mc/data/packetid" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/Tnze/go-mc/server" +) + +// Init implement server.Component for Graph +func (g *Graph) Init(game *server.Game) { + game.AddHandler(&server.PacketHandler{ + ID: packetid.ServerboundChat, + F: func(player *server.Player, packet server.Packet757) error { + var msg pk.String + if err := pk.Packet(packet).Scan(&msg); err != nil { + return err + } + if cmd := string(msg); strings.HasPrefix(cmd, "/") { + ctx := context.WithValue(context.TODO(), "sender", player) + cmderr := g.Execute(ctx, strings.TrimPrefix(cmd, "/")) + if cmderr != nil { + // TODO: tell player that their command has error + } + } + return nil + }, + }) +} + +// Run implement server.Component for Graph +func (g *Graph) Run(ctx context.Context) {} + +// AddPlayer implement server.Component for Graph +func (g *Graph) AddPlayer(p *server.Player) { + p.WritePacket(server.Packet757(pk.Marshal( + packetid.ClientboundCommands, g, + ))) +} + +// RemovePlayer implement server.Component for Graph +func (g *Graph) RemovePlayer(p *server.Player) {}