Move command component
This commit is contained in:
@ -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) {}
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
44
server/command/component.go
Normal file
44
server/command/component.go
Normal file
@ -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) {}
|
Reference in New Issue
Block a user