diff --git a/server/chat.go b/server/chat.go new file mode 100644 index 0000000..6f83363 --- /dev/null +++ b/server/chat.go @@ -0,0 +1,114 @@ +package server + +import ( + "context" + + "github.com/Tnze/go-mc/chat" + "github.com/Tnze/go-mc/data/packetid" + "github.com/Tnze/go-mc/nbt" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/google/uuid" +) + +type GlobalChat struct { + msg chan chatItem + join chan *Player + quit chan *Player + + players map[uuid.UUID]*Player +} + +type chatItem struct { + p *Player + text string +} + +func NewGlobalChat() GlobalChat { + return GlobalChat{ + msg: make(chan chatItem), + join: make(chan *Player), + quit: make(chan *Player), + players: make(map[uuid.UUID]*Player), + } +} + +func (g *GlobalChat) AddPlayer(p *Player) { + g.join <- p + p.Add(PacketHandler{ + ID: packetid.ServerboundChat, + F: func(packet Packet757) error { + var msg pk.String + if err := pk.Packet(packet).Scan(&msg); err != nil { + return err + } + text, _ := chat.TransCtrlSeq(string(msg), false) + g.msg <- chatItem{p: p, text: text} + return nil + }, + }) +} + +func (g *GlobalChat) RemovePlayer(p *Player) { + g.quit <- p +} + +func (c chatItem) ToMessage() chat.Message { + return chat.TranslateMsg( + "chat.type.text", + chat.Message{ + Text: c.p.Name, + ClickEvent: chat.SuggestCommand("/msg " + c.p.Name), + HoverEvent: chat.ShowEntity(playerToSNBT(c.p)), + }, + chat.Text(c.text), + ) +} + +func playerToSNBT(p *Player) string { + var s nbt.StringifiedMessage + entity := struct { + ID string `nbt:"id"` + Name string `nbt:"name"` + }{ + ID: p.UUID.String(), + Name: p.Name, + } + + data, err := nbt.Marshal(entity) + if err != nil { + panic(err) + } + + err = nbt.Unmarshal(data, &s) + if err != nil { + panic(err) + } + + return string(s) +} + +func (g *GlobalChat) Run(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case item := <-g.msg: + packet := Packet757(pk.Marshal( + packetid.ClientboundChat, + item.ToMessage(), + pk.Byte(0), + pk.UUID(item.p.UUID), + )) + for _, p := range g.players { + err := p.WritePacket(packet) + if err != nil { + p.PutErr(err) + } + } + case p := <-g.join: + g.players[p.UUID] = p + case p := <-g.quit: + delete(g.players, p.UUID) + } + } +} diff --git a/server/gameplay.go b/server/gameplay.go index 47b8657..c339f97 100644 --- a/server/gameplay.go +++ b/server/gameplay.go @@ -26,7 +26,8 @@ type Game struct { eid int32 Dim Level *PlayerList - KeepAlive KeepAlive + KeepAlive KeepAlive + GlobalChat GlobalChat } //go:embed DimensionCodec.snbt @@ -40,11 +41,13 @@ func NewGame(dim Level, playerList *PlayerList) *Game { Dim: dim, PlayerList: playerList, KeepAlive: NewKeepAlive(), + GlobalChat: NewGlobalChat(), } } func (g *Game) Run(ctx context.Context) { go g.KeepAlive.Run(ctx) + go g.GlobalChat.Run(ctx) } func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) { @@ -59,6 +62,8 @@ func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net defer remove() p := &Player{ Conn: conn, + Name: name, + UUID: id, EntityID: g.newEID(), Gamemode: 1, handlers: make(map[int32][]packetHandlerFunc), @@ -92,6 +97,9 @@ func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net g.KeepAlive.AddPlayer(p) defer g.KeepAlive.RemovePlayer(p) + g.GlobalChat.AddPlayer(p) + defer g.GlobalChat.RemovePlayer(p) + var packet pk.Packet for { err := p.ReadPacket(&packet) diff --git a/server/player.go b/server/player.go index 6a77565..ccf01dd 100644 --- a/server/player.go +++ b/server/player.go @@ -13,6 +13,7 @@ type Player struct { *net.Conn writeLock sync.Mutex + Name string uuid.UUID EntityID int32 Gamemode byte