Split the function of PlayerList and PingInfo
This commit is contained in:
@ -2,7 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"image"
|
||||||
|
_ "image/png"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
@ -14,19 +17,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MyServer struct {
|
type MyServer struct {
|
||||||
*server.PlayerList
|
playerList *server.PlayerList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ServerName = "MyServer"
|
||||||
const MaxPlayer = 20
|
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() {
|
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{
|
ms := MyServer{
|
||||||
PlayerList: server.NewPlayerList("MyServer", server.ProtocolVersion, MaxPlayer, motd),
|
playerList: playerList,
|
||||||
}
|
}
|
||||||
|
|
||||||
s := server.Server{
|
s := server.Server{
|
||||||
ListPingHandler: ms.PlayerList,
|
ListPingHandler: serverInfo,
|
||||||
LoginHandler: &server.MojangLoginHandler{
|
LoginHandler: &server.MojangLoginHandler{
|
||||||
OnlineMode: true,
|
OnlineMode: true,
|
||||||
Threshold: 256,
|
Threshold: 256,
|
||||||
@ -40,7 +51,7 @@ func main() {
|
|||||||
|
|
||||||
func (m *MyServer) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) {
|
func (m *MyServer) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) {
|
||||||
// Add player into PlayerList
|
// Add player into PlayerList
|
||||||
remove := m.TryInsert(server.PlayerSample{
|
remove := m.playerList.TryInsert(server.PlayerSample{
|
||||||
Name: name,
|
Name: name,
|
||||||
ID: id,
|
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
|
//go:embed DimensionCodec.snbt
|
||||||
var dimensionCodecSNBT string
|
var dimensionCodecSNBT string
|
||||||
|
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
"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"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListPingHandler interface {
|
type ListPingHandler interface {
|
||||||
@ -16,7 +20,9 @@ type ListPingHandler interface {
|
|||||||
MaxPlayer() int
|
MaxPlayer() int
|
||||||
OnlinePlayer() int
|
OnlinePlayer() int
|
||||||
PlayerSamples() []PlayerSample
|
PlayerSamples() []PlayerSample
|
||||||
|
|
||||||
Description() *chat.Message
|
Description() *chat.Message
|
||||||
|
FavIcon() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlayerSample struct {
|
type PlayerSample struct {
|
||||||
@ -64,12 +70,69 @@ func (s *Server) listResp() ([]byte, error) {
|
|||||||
FavIcon string `json:"favicon,omitempty"`
|
FavIcon string `json:"favicon,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Version.Name = s.ListPingHandler.Name()
|
list.Version.Name = s.Name()
|
||||||
list.Version.Protocol = s.ListPingHandler.Protocol()
|
list.Version.Protocol = s.Protocol()
|
||||||
list.Players.Max = s.ListPingHandler.MaxPlayer()
|
list.Players.Max = s.MaxPlayer()
|
||||||
list.Players.Online = s.ListPingHandler.OnlinePlayer()
|
list.Players.Online = s.OnlinePlayer()
|
||||||
list.Players.Sample = s.ListPingHandler.PlayerSamples()
|
list.Players.Sample = s.PlayerSamples()
|
||||||
list.Description = s.ListPingHandler.Description()
|
list.Description = s.Description()
|
||||||
|
list.FavIcon = s.FavIcon()
|
||||||
|
|
||||||
return json.Marshal(list)
|
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
|
||||||
|
}
|
||||||
|
36
server/ping_test.go
Normal file
36
server/ping_test.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -3,31 +3,23 @@ package server
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"sync"
|
"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.
|
// This struct should not be copied after used.
|
||||||
type PlayerList struct {
|
type PlayerList struct {
|
||||||
name string
|
maxPlayer int
|
||||||
protocol int
|
players *list.List
|
||||||
maxPlayer int
|
|
||||||
description *chat.Message
|
|
||||||
players *list.List
|
|
||||||
// Only the linked-list is protected by this Mutex.
|
// Only the linked-list is protected by this Mutex.
|
||||||
// Because others field never change after created.
|
// Because others field never change after created.
|
||||||
playersLock sync.Mutex
|
playersLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlayerList create a PlayerList which implement ListPingHandler.
|
// 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{
|
return &PlayerList{
|
||||||
name: name,
|
maxPlayer: maxPlayers,
|
||||||
protocol: protocol,
|
players: list.New(),
|
||||||
maxPlayer: maxPlayers,
|
|
||||||
description: motd,
|
|
||||||
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 {
|
func (p *PlayerList) MaxPlayer() int {
|
||||||
return p.maxPlayer
|
return p.maxPlayer
|
||||||
}
|
}
|
||||||
@ -84,7 +68,3 @@ func (p *PlayerList) PlayerSamples() (sample []PlayerSample) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlayerList) Description() *chat.Message {
|
|
||||||
return p.description
|
|
||||||
}
|
|
||||||
|
@ -4,13 +4,15 @@
|
|||||||
//
|
//
|
||||||
// A server is roughly divided into two parts:
|
// A server is roughly divided into two parts:
|
||||||
//
|
//
|
||||||
// +----------------------------------------------+
|
// +-----------------------------------------------------------------+
|
||||||
// | Go-MC Server Framework |
|
// | Go-MC Server Framework |
|
||||||
// +-----------------------+----------------------+
|
// +--------------------------------------+--------------------------+
|
||||||
// | Gate | GamePlay |
|
// | Gate | GamePlay |
|
||||||
// +--------------+--------+--------+-------------+
|
// +--------------------+-----------------+ |
|
||||||
// | LoginHandler | ListPingHandler | Coming Soon |
|
// | LoginHandler | ListPingHandler | |
|
||||||
// +--------------+-----------------+-------------+
|
// +--------------------+--------+--------+-----------+--------------+
|
||||||
|
// | MojangLoginHandler | Info | PlayerList | Others.... |
|
||||||
|
// +--------------------+--------+--------------------+--------------+
|
||||||
//
|
//
|
||||||
// Gate, which is used to respond to the client login request, provide login verification,
|
// 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.
|
// respond to the List Ping Request and providing the online players' information.
|
||||||
|
Reference in New Issue
Block a user