From f7bdf676ccebeef8ac9940a81636bbf688c76196 Mon Sep 17 00:00:00 2001 From: Tnze Date: Wed, 15 Dec 2021 12:24:30 +0800 Subject: [PATCH] Split the function of PlayerList and PingInfo --- examples/frameworkServer/main.go | 38 +++++++++++++--- server/ping.go | 77 +++++++++++++++++++++++++++++--- server/ping_test.go | 36 +++++++++++++++ server/playerlist.go | 32 +++---------- server/server.go | 16 ++++--- 5 files changed, 154 insertions(+), 45 deletions(-) create mode 100644 server/ping_test.go diff --git a/examples/frameworkServer/main.go b/examples/frameworkServer/main.go index b1acaa4..f359e09 100644 --- a/examples/frameworkServer/main.go +++ b/examples/frameworkServer/main.go @@ -2,7 +2,10 @@ package main import ( _ "embed" + "image" + _ "image/png" "log" + "os" "github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/data/packetid" @@ -14,19 +17,27 @@ import ( ) type MyServer struct { - *server.PlayerList + playerList *server.PlayerList } +const ServerName = "MyServer" const MaxPlayer = 20 +const IconPath = "./server-icon.png" + +var motd = chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text: "Powered by go-mc", Color: "yellow"}}} func main() { - motd := &chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text: "Powered by go-mc", Color: "yellow"}}} + playerList := server.NewPlayerList(MaxPlayer) + serverInfo, err := server.NewPingInfo(playerList, ServerName, server.ProtocolVersion, motd, readIcon()) + if err != nil { + log.Fatalf("Set server info error: %v", err) + } ms := MyServer{ - PlayerList: server.NewPlayerList("MyServer", server.ProtocolVersion, MaxPlayer, motd), + playerList: playerList, } s := server.Server{ - ListPingHandler: ms.PlayerList, + ListPingHandler: serverInfo, LoginHandler: &server.MojangLoginHandler{ OnlineMode: true, Threshold: 256, @@ -40,7 +51,7 @@ func main() { func (m *MyServer) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) { // Add player into PlayerList - remove := m.TryInsert(server.PlayerSample{ + remove := m.playerList.TryInsert(server.PlayerSample{ Name: name, ID: id, }) @@ -76,6 +87,23 @@ func (m *MyServer) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn } } +func readIcon() image.Image { + f, err := os.Open(IconPath) + // if the file doesn't exist, return nil + if os.IsNotExist(err) { + return nil + } else if err != nil { + log.Fatalf("Open icon file error: %v", err) + } + defer f.Close() + + icon, _, err := image.Decode(f) + if err != nil { + log.Fatalf("Decode image error: %v", err) + } + return icon +} + //go:embed DimensionCodec.snbt var dimensionCodecSNBT string diff --git a/server/ping.go b/server/ping.go index 576f192..ce23fa2 100644 --- a/server/ping.go +++ b/server/ping.go @@ -1,13 +1,17 @@ package server import ( + "encoding/base64" "encoding/json" - + "errors" "github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/data/packetid" "github.com/Tnze/go-mc/net" pk "github.com/Tnze/go-mc/net/packet" "github.com/google/uuid" + "image" + "image/png" + "strings" ) type ListPingHandler interface { @@ -16,7 +20,9 @@ type ListPingHandler interface { MaxPlayer() int OnlinePlayer() int PlayerSamples() []PlayerSample + Description() *chat.Message + FavIcon() string } type PlayerSample struct { @@ -64,12 +70,69 @@ func (s *Server) listResp() ([]byte, error) { FavIcon string `json:"favicon,omitempty"` } - list.Version.Name = s.ListPingHandler.Name() - list.Version.Protocol = s.ListPingHandler.Protocol() - list.Players.Max = s.ListPingHandler.MaxPlayer() - list.Players.Online = s.ListPingHandler.OnlinePlayer() - list.Players.Sample = s.ListPingHandler.PlayerSamples() - list.Description = s.ListPingHandler.Description() + list.Version.Name = s.Name() + list.Version.Protocol = s.Protocol() + list.Players.Max = s.MaxPlayer() + list.Players.Online = s.OnlinePlayer() + list.Players.Sample = s.PlayerSamples() + list.Description = s.Description() + list.FavIcon = s.FavIcon() return json.Marshal(list) } + +// PingInfo implement ListPingHandler. +type PingInfo struct { + name string + protocol int + *PlayerList + description chat.Message + favicon string +} + +// NewPingInfo crate a new PingInfo, the icon can be nil. +func NewPingInfo(list *PlayerList, name string, protocol int, motd chat.Message, icon image.Image) (p *PingInfo, err error) { + var favIcon string + if icon != nil { + if !icon.Bounds().Size().Eq(image.Point{X: 64, Y: 64}) { + return nil, errors.New("icon size is not 64x64") + } + // Encode icon into string "data:image/png;base64,......" format + var sb strings.Builder + sb.WriteString("data:image/png;base64,") + w := base64.NewEncoder(base64.StdEncoding, &sb) + err = png.Encode(w, icon) + if err != nil { + return nil, err + } + err = w.Close() + if err != nil { + return nil, err + } + favIcon = sb.String() + } + p = &PingInfo{ + name: name, + protocol: protocol, + PlayerList: list, + description: motd, + favicon: favIcon, + } + return +} + +func (p *PingInfo) Name() string { + return p.name +} + +func (p *PingInfo) Protocol() int { + return p.protocol +} + +func (p *PingInfo) FavIcon() string { + return p.favicon +} + +func (p *PingInfo) Description() *chat.Message { + return &p.description +} diff --git a/server/ping_test.go b/server/ping_test.go new file mode 100644 index 0000000..fc9c2ba --- /dev/null +++ b/server/ping_test.go @@ -0,0 +1,36 @@ +package server + +import ( + "github.com/Tnze/go-mc/chat" + "image" + "os" +) + +func ExamplePingInfo_standardUsage() { + // Read server icon + f, err := os.Open("./server-icon.png") + if err != nil { + panic(err) + } + defer f.Close() + icon, _, err := image.Decode(f) + if err != nil { + panic(err) + } + // Set server info + playerList := NewPlayerList(20) + pingInfo, err := NewPingInfo(playerList, "1.18", 757, chat.Text("A Minecraft Server"), icon) + if err != nil { + panic(err) + } + // Start listening + s := Server{ + ListPingHandler: pingInfo, + LoginHandler: nil, + GamePlay: nil, + } + err = s.Listen("0.0.0.0:25565") + if err != nil { + return + } +} diff --git a/server/playerlist.go b/server/playerlist.go index 913b9fe..00e1db9 100644 --- a/server/playerlist.go +++ b/server/playerlist.go @@ -3,31 +3,23 @@ package server import ( "container/list" "sync" - - "github.com/Tnze/go-mc/chat" ) -// PlayerList is an implement of ListPingHandler based on linked-list. +// PlayerList is a player list based on linked-list. // This struct should not be copied after used. type PlayerList struct { - name string - protocol int - maxPlayer int - description *chat.Message - players *list.List + maxPlayer int + players *list.List // Only the linked-list is protected by this Mutex. // Because others field never change after created. playersLock sync.Mutex } // NewPlayerList create a PlayerList which implement ListPingHandler. -func NewPlayerList(name string, protocol, maxPlayers int, motd *chat.Message) *PlayerList { +func NewPlayerList(maxPlayers int) *PlayerList { return &PlayerList{ - name: name, - protocol: protocol, - maxPlayer: maxPlayers, - description: motd, - players: list.New(), + maxPlayer: maxPlayers, + players: list.New(), } } @@ -50,14 +42,6 @@ func (p *PlayerList) TryInsert(player PlayerSample) (remove func()) { } } -func (p *PlayerList) Name() string { - return p.name -} - -func (p *PlayerList) Protocol() int { - return p.protocol -} - func (p *PlayerList) MaxPlayer() int { return p.maxPlayer } @@ -84,7 +68,3 @@ func (p *PlayerList) PlayerSamples() (sample []PlayerSample) { } return } - -func (p *PlayerList) Description() *chat.Message { - return p.description -} diff --git a/server/server.go b/server/server.go index 7c7bc9b..639f47d 100644 --- a/server/server.go +++ b/server/server.go @@ -4,13 +4,15 @@ // // A server is roughly divided into two parts: // -// +----------------------------------------------+ -// | Go-MC Server Framework | -// +-----------------------+----------------------+ -// | Gate | GamePlay | -// +--------------+--------+--------+-------------+ -// | LoginHandler | ListPingHandler | Coming Soon | -// +--------------+-----------------+-------------+ +// +-----------------------------------------------------------------+ +// | Go-MC Server Framework | +// +--------------------------------------+--------------------------+ +// | Gate | GamePlay | +// +--------------------+-----------------+ | +// | LoginHandler | ListPingHandler | | +// +--------------------+--------+--------+-----------+--------------+ +// | MojangLoginHandler | Info | PlayerList | Others.... | +// +--------------------+--------+--------------------+--------------+ // // Gate, which is used to respond to the client login request, provide login verification, // respond to the List Ping Request and providing the online players' information.