Command parse compound

This commit is contained in:
Tnze
2022-01-03 15:57:54 +08:00
parent 9433dd98de
commit be3a834696
13 changed files with 250 additions and 114 deletions

View File

@ -5,6 +5,7 @@ import (
_ "embed" _ "embed"
"flag" "flag"
"fmt" "fmt"
"github.com/Tnze/go-mc/server/command"
"image" "image"
_ "image/png" _ "image/png"
"log" "log"
@ -37,11 +38,31 @@ func main() {
log.Fatalf("Load chunks fail: %v", err) log.Fatalf("Load chunks fail: %v", err)
} }
commands := server.NewCommandGraph()
handleFunc := func(args []command.ParsedData) error {
log.Printf("Command: args: %v", args)
return nil
}
commands.AppendLiteral(commands.Literal("me").
AppendArgument(commands.Argument("action", command.StringParser(2)).
HandleFunc(handleFunc)).
Unhandle(),
).AppendLiteral(commands.Literal("help").
AppendArgument(commands.Argument("command", command.StringParser(0)).
HandleFunc(handleFunc)).
HandleFunc(handleFunc),
).AppendLiteral(commands.Literal("list").
AppendLiteral(commands.Literal("uuids").
HandleFunc(handleFunc)).
HandleFunc(handleFunc),
)
game := server.NewGame( game := server.NewGame(
defaultDimension, defaultDimension,
playerList, playerList,
server.NewKeepAlive(), server.NewKeepAlive(),
server.NewGlobalChat(), server.NewGlobalChat(),
commands,
) )
go game.Run(context.Background()) go game.Run(context.Background())

View File

@ -82,7 +82,16 @@ func (a Ary) ReadFrom(r io.Reader) (n int64, err error) {
// //
// Warning: unstable API, may change in later version // Warning: unstable API, may change in later version
func Array(array interface{}) Field { func Array(array interface{}) Field {
length := VarInt(reflect.ValueOf(array).Len()) var length VarInt
value := reflect.ValueOf(array)
for value.Kind() == reflect.Ptr {
value = value.Elem()
}
if array != nil {
length = VarInt(value.Len())
}
return Tuple{ return Tuple{
&length, &length,
Ary{ Ary{

View File

@ -87,10 +87,7 @@ func (g *GlobalChat) Run(ctx context.Context) {
func (g *GlobalChat) broadcast(packet Packet757) { func (g *GlobalChat) broadcast(packet Packet757) {
for _, p := range g.players { for _, p := range g.players {
err := p.WritePacket(packet) p.WritePacket(packet)
if err != nil {
p.PutErr(err)
}
} }
} }

49
server/command.go Normal file
View File

@ -0,0 +1,49 @@
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) {}

View File

@ -6,22 +6,22 @@ func (g *Graph) AppendLiteral(child *Literal) *Graph {
} }
func (g *Graph) Literal(str string) LiteralBuilder { func (g *Graph) Literal(str string) LiteralBuilder {
index := len(g.nodes) index := int32(len(g.nodes))
g.nodes = append(g.nodes, Node{ g.nodes = append(g.nodes, Node{
g: g, g: g,
index: index, index: index,
flags: LiteralNode, kind: LiteralNode,
Name: str, Name: str,
}) })
return LiteralBuilder{g: g, current: index} return LiteralBuilder{g: g, current: index}
} }
func (g *Graph) Argument(name string, parser Parser) ArgumentBuilder { func (g *Graph) Argument(name string, parser Parser) ArgumentBuilder {
index := len(g.nodes) index := int32(len(g.nodes))
g.nodes = append(g.nodes, Node{ g.nodes = append(g.nodes, Node{
g: g, g: g,
index: index, index: index,
flags: ArgumentNode, kind: ArgumentNode,
Name: name, Name: name,
Parser: parser, Parser: parser,
}) })
@ -30,7 +30,7 @@ func (g *Graph) Argument(name string, parser Parser) ArgumentBuilder {
type LiteralBuilder struct { type LiteralBuilder struct {
g *Graph g *Graph
current int current int32
} }
func (n LiteralBuilder) AppendLiteral(node *Literal) LiteralBuilderWithLiteral { func (n LiteralBuilder) AppendLiteral(node *Literal) LiteralBuilderWithLiteral {
@ -57,7 +57,7 @@ func (n LiteralBuilder) Unhandle() *Literal {
type ArgumentBuilder struct { type ArgumentBuilder struct {
g *Graph g *Graph
current int current int32
} }
func (n ArgumentBuilder) AppendLiteral(node *Literal) ArgumentBuilderWithLiteral { func (n ArgumentBuilder) AppendLiteral(node *Literal) ArgumentBuilderWithLiteral {

View File

@ -18,8 +18,8 @@ type Graph struct {
func NewGraph() *Graph { func NewGraph() *Graph {
g := new(Graph) g := new(Graph)
root := Node{ root := Node{
g: g, g: g,
flags: RootNode, kind: RootNode,
} }
g.nodes = append(g.nodes, root) g.nodes = append(g.nodes, root)
return g return g
@ -43,7 +43,7 @@ func (g *Graph) Run(cmd string) error {
return nil return nil
} }
if next == 0 { if next == 0 {
panic("root node can't be child") return errors.New("command contains extra text: " + left)
} }
cmd = left cmd = left
@ -56,19 +56,20 @@ type ParsedData interface{}
type HandlerFunc func(args []ParsedData) error type HandlerFunc func(args []ParsedData) error
type Node struct { type Node struct {
g *Graph g *Graph
index int index int32
flags byte kind byte
Name string Name string
Children []int Children []int32
Parser Parser SuggestionsType string
Run HandlerFunc Parser Parser
Run HandlerFunc
} }
type Literal Node type Literal Node
type Argument Node type Argument Node
func (n *Node) parse(cmd string) (left string, value ParsedData, next int, err error) { func (n *Node) parse(cmd string) (left string, value ParsedData, next int32, err error) {
switch n.flags & 0x03 { switch n.kind & 0x03 {
case RootNode: case RootNode:
left = cmd left = cmd
value = nil value = nil
@ -90,7 +91,7 @@ func (n *Node) parse(cmd string) (left string, value ParsedData, next int, err e
// find next // find next
if len(n.Children) > 0 { if len(n.Children) > 0 {
// look up the first child's type // look up the first child's type
switch n.g.nodes[n.Children[0]].flags & 0x03 { switch n.g.nodes[n.Children[0]].kind & 0x03 {
case RootNode: case RootNode:
panic("root node can't be child") panic("root node can't be child")
default: default:

View File

@ -1,63 +1,32 @@
package command package command
import "testing" import (
"log"
"testing"
)
func TestRoot_Run(t *testing.T) { func TestRoot_Run(t *testing.T) {
handleFunc := func(args []ParsedData) error {
log.Printf("Command: args: %v", args)
return nil
}
g := NewGraph() g := NewGraph()
g.AppendLiteral(g.Literal("whitelist"). g.AppendLiteral(g.Literal("me").
AppendLiteral(g.Literal("add"). AppendArgument(g.Argument("action", StringParser(2)).
AppendArgument(g.Argument("targets", StringParser(0)). HandleFunc(handleFunc)).
HandleFunc(func(args []ParsedData) error {
t.Logf("whitelist add: %v", args)
return nil
})).Unhandle()).
AppendLiteral(g.Literal("remove").
AppendArgument(g.Argument("targets", StringParser(0)).
HandleFunc(func(args []ParsedData) error {
t.Logf("whitelist remove: %v", args)
return nil
})).Unhandle()).
AppendLiteral(g.Literal("on").
HandleFunc(func(args []ParsedData) error {
t.Logf("whitelist on: %v", args)
return nil
})).
AppendLiteral(g.Literal("off").
HandleFunc(func(args []ParsedData) error {
t.Logf("whitelist off: %v", args)
return nil
})).
Unhandle(), Unhandle(),
).AppendLiteral(g.Literal("help").
AppendArgument(g.Argument("command", StringParser(0)).
HandleFunc(handleFunc)).
HandleFunc(handleFunc),
).AppendLiteral(g.Literal("list").
AppendLiteral(g.Literal("uuids").
HandleFunc(handleFunc)).
HandleFunc(handleFunc),
) )
targetB := g.Argument("targetB", StringParser(0)). err := g.Run("me Tnze Xi_Xi_Mi")
HandleFunc(func(args []ParsedData) error {
t.Logf("tp A <from/to> B parsed: %v", args)
return nil
})
g.AppendLiteral(g.Literal("tp").AppendArgument(
g.Argument("targetA", StringParser(0)).
AppendLiteral(g.Literal("from").
AppendArgument(targetB).
Unhandle()).
AppendLiteral(g.Literal("to").
AppendArgument(targetB).
Unhandle()).
Unhandle(),
).Unhandle())
err := g.Run("tp Tnze to Xi_Xi_Mi")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
//g2 := NewGraph()
//g2.Then(
// g.Lite("whitelist").Then(
// // using reflect to generate all arguments nodes
// g.Lite("add").Then(g.Func(func(player GameProfile) {
//
// })),
// ),
//)
} }

View File

@ -0,0 +1,49 @@
package command
import (
"io"
"unsafe"
pk "github.com/Tnze/go-mc/net/packet"
)
const (
isExecutable = 1 << (iota + 2)
hasRedirect
hasSuggestionsType
)
func (g *Graph) WriteTo(w io.Writer) (int64, error) {
return pk.Tuple{
pk.Array(g.nodes),
pk.VarInt(0),
}.WriteTo(w)
}
func (n Node) WriteTo(w io.Writer) (int64, error) {
var flag byte
flag |= n.kind & 0x03
if n.Run != nil {
flag |= isExecutable
}
return pk.Tuple{
pk.Byte(flag),
pk.Array((*[]pk.VarInt)(unsafe.Pointer(&n.Children))),
pk.Opt{
Has: func() bool { return n.kind&hasRedirect != 0 },
Field: nil, // TODO: send redirect node
},
pk.Opt{
Has: func() bool { return n.kind == ArgumentNode || n.kind == LiteralNode },
Field: pk.String(n.Name),
},
pk.Opt{
Has: func() bool { return n.kind == ArgumentNode },
Field: n.Parser, // Parser identifier and Properties
},
pk.Opt{
Has: func() bool { return flag&hasSuggestionsType != 0 },
Field: nil, // TODO: send Suggestions type
},
}.WriteTo(w)
}

View File

@ -50,13 +50,10 @@ func (s *SimpleDim) PlayerJoin(p *Player) {
) )
column.Unlock() column.Unlock()
err := p.WritePacket(Packet757(packet)) p.WritePacket(Packet757(packet))
if err != nil {
return
}
} }
err := p.WritePacket(Packet757(pk.Marshal( p.WritePacket(Packet757(pk.Marshal(
packetid.ClientboundPlayerPosition, packetid.ClientboundPlayerPosition,
pk.Double(0), pk.Double(143), pk.Double(0), pk.Double(0), pk.Double(143), pk.Double(0),
pk.Float(0), pk.Float(0), pk.Float(0), pk.Float(0),
@ -64,9 +61,6 @@ func (s *SimpleDim) PlayerJoin(p *Player) {
pk.VarInt(0), pk.VarInt(0),
pk.Boolean(true), pk.Boolean(true),
))) )))
if err != nil {
return
}
} }
func (s *SimpleDim) PlayerQuit(p *Player) { func (s *SimpleDim) PlayerQuit(p *Player) {

View File

@ -80,15 +80,16 @@ func (g *Game) Run(ctx context.Context) {
func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) { func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) {
p := &Player{ p := &Player{
Conn: conn, Conn: conn,
Name: name, Name: name,
UUID: id, UUID: id,
EntityID: g.newEID(), EntityID: g.newEID(),
Gamemode: 1, Gamemode: 1,
errChan: make(chan error, 1), packetQueue: NewPacketQueue(),
errChan: make(chan error, 1),
} }
dimInfo := g.Dim.Info() dimInfo := g.Dim.Info()
err := p.WritePacket(Packet757(pk.Marshal( err := p.Conn.WritePacket(pk.Marshal(
packetid.ClientboundLogin, packetid.ClientboundLogin,
pk.Int(p.EntityID), // Entity ID pk.Int(p.EntityID), // Entity ID
pk.Boolean(false), // Is hardcore pk.Boolean(false), // Is hardcore
@ -106,10 +107,26 @@ func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net
pk.Boolean(true), // Enable respawn screen pk.Boolean(true), // Enable respawn screen
pk.Boolean(false), // Is Debug pk.Boolean(false), // Is Debug
pk.Boolean(true), // Is Flat pk.Boolean(true), // Is Flat
))) ))
if err != nil { if err != nil {
return return
} }
go func() {
for {
packet, ok := p.packetQueue.Pull()
if !ok {
break
}
err := p.Conn.WritePacket(packet)
if err != nil {
p.PutErr(err)
break
}
}
}()
defer p.packetQueue.Close()
g.Dim.PlayerJoin(p) g.Dim.PlayerJoin(p)
defer g.Dim.PlayerQuit(p) defer g.Dim.PlayerQuit(p)

View File

@ -121,14 +121,10 @@ func (k *KeepAlive) pingPlayer(now time.Time) {
if elem := k.pingList.Front(); elem != nil { if elem := k.pingList.Front(); elem != nil {
p := k.pingList.Remove(elem).(keepAliveItem).player p := k.pingList.Remove(elem).(keepAliveItem).player
// Send Clientbound KeepAlive packet. // Send Clientbound KeepAlive packet.
err := p.WritePacket(Packet757(pk.Marshal( p.WritePacket(Packet757(pk.Marshal(
packetid.ClientboundKeepAlive, packetid.ClientboundKeepAlive,
pk.Long(k.keepAliveID), pk.Long(k.keepAliveID),
))) )))
if err != nil {
p.PutErr(err)
return
}
k.keepAliveID++ k.keepAliveID++
// Clientbound KeepAlive packet is sent, move the player to waiting list. // Clientbound KeepAlive packet is sent, move the player to waiting list.
k.listIndex[p.UUID] = k.waitList.PushBack( k.listIndex[p.UUID] = k.waitList.PushBack(

View File

@ -1,25 +1,25 @@
package server package server
import ( import (
"container/list"
"github.com/google/uuid"
"strconv" "strconv"
"sync" "sync"
"github.com/google/uuid"
"github.com/Tnze/go-mc/net" "github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet" pk "github.com/Tnze/go-mc/net/packet"
) )
type Player struct { type Player struct {
*net.Conn *net.Conn
writeLock sync.Mutex
Name string Name string
uuid.UUID uuid.UUID
EntityID int32 EntityID int32
Gamemode byte Gamemode byte
errChan chan error packetQueue *PacketQueue
errChan chan error
} }
// Packet757 is a packet in protocol 757. // Packet757 is a packet in protocol 757.
@ -27,14 +27,8 @@ type Player struct {
type Packet757 pk.Packet type Packet757 pk.Packet
// WritePacket to player client. The type of parameter will update per version. // WritePacket to player client. The type of parameter will update per version.
func (p *Player) WritePacket(packet Packet757) error { func (p *Player) WritePacket(packet Packet757) {
p.writeLock.Lock() p.packetQueue.Push(pk.Packet(packet))
defer p.writeLock.Unlock()
err := p.Conn.WritePacket(pk.Packet(packet))
if err != nil {
return WritePacketError{Err: err, ID: packet.ID}
}
return nil
} }
type WritePacketError struct { type WritePacketError struct {
@ -66,3 +60,47 @@ func (p *Player) GetErr() error {
return nil return nil
} }
} }
type PacketQueue struct {
queue *list.List
closed bool
cond sync.Cond
}
func NewPacketQueue() (p *PacketQueue) {
p = &PacketQueue{
queue: list.New(),
cond: sync.Cond{L: new(sync.Mutex)},
}
return p
}
func (p *PacketQueue) Push(packet pk.Packet) {
p.cond.L.Lock()
if !p.closed {
p.queue.PushBack(packet)
}
p.cond.Signal()
p.cond.L.Unlock()
}
func (p *PacketQueue) Pull() (packet pk.Packet, ok bool) {
p.cond.L.Lock()
defer p.cond.L.Unlock()
for p.queue.Front() == nil && !p.closed {
p.cond.Wait()
}
if p.closed {
return pk.Packet{}, false
}
packet = p.queue.Remove(p.queue.Front()).(pk.Packet)
ok = true
return
}
func (p *PacketQueue) Close() {
p.cond.L.Lock()
p.closed = true
p.cond.Broadcast()
p.cond.L.Unlock()
}

View File

@ -42,15 +42,11 @@ func (p *PlayerList) AddPlayer(player *Player) {
defer p.playersLock.Unlock() defer p.playersLock.Unlock()
if len(p.players) >= p.maxPlayer { if len(p.players) >= p.maxPlayer {
err := player.WritePacket(Packet757(pk.Marshal( player.WritePacket(Packet757(pk.Marshal(
packetid.ClientboundDisconnect, packetid.ClientboundDisconnect,
chat.TranslateMsg("multiplayer.disconnect.server_full"), chat.TranslateMsg("multiplayer.disconnect.server_full"),
))) )))
if err != nil { player.PutErr(errors.New("playerlist: server full"))
player.PutErr(err)
} else {
player.PutErr(errors.New("playerlist: server full"))
}
return return
} }