Provide official ListPingHandler implementation

This commit is contained in:
Tnze
2021-12-08 13:18:24 +08:00
parent 2274463c6c
commit 1c2332d678
4 changed files with 135 additions and 77 deletions

View File

@ -1,10 +1,8 @@
package main package main
import ( import (
"container/list"
_ "embed" _ "embed"
"log" "log"
"sync"
"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"
@ -16,98 +14,46 @@ import (
) )
type MyServer struct { type MyServer struct {
PlayerList *list.List *server.PlayerList
PlayerListLock sync.Mutex
server.MojangLoginHandler
Settings struct {
Name string
MaxPlayer int
MOTD chat.Message
}
} }
func main() { const MaxPlayer = 20
var ms MyServer
ms.PlayerList = list.New()
ms.MojangLoginHandler.OnlineMode = true
ms.MojangLoginHandler.Threshold = 256
ms.Settings.Name = "MyServer" func main() {
ms.Settings.MaxPlayer = 1 motd := &chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text: "Powered by go-mc", Color: "yellow"}}}
ms.Settings.MOTD = chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text: "Powered by go-mc", Color: "yellow"}}} ms := MyServer{
PlayerList: server.NewPlayerList("MyServer", server.ProtocolVersion, MaxPlayer, motd),
}
s := server.Server{ s := server.Server{
ListPingHandler: &ms, ListPingHandler: ms.PlayerList,
LoginHandler: &ms, LoginHandler: &server.MojangLoginHandler{
GamePlay: &ms, OnlineMode: true,
Threshold: 256,
},
GamePlay: &ms,
} }
if err := s.Listen(":25565"); err != nil { if err := s.Listen(":25565"); err != nil {
log.Fatalf("Listen error: %v", err) log.Fatalf("Listen error: %v", err)
} }
} }
func (m *MyServer) Name() string {
return m.Settings.Name
}
func (m *MyServer) Protocol() int {
return server.ProtocolVersion
}
func (m *MyServer) MaxPlayer() int {
return m.Settings.MaxPlayer
}
func (m *MyServer) OnlinePlayer() int {
m.PlayerListLock.Lock()
defer m.PlayerListLock.Unlock()
return m.PlayerList.Len()
}
func (m *MyServer) PlayerSamples() (sample []server.PlayerSample) {
m.PlayerListLock.Lock()
defer m.PlayerListLock.Unlock()
// get first 10 players
sample = make([]server.PlayerSample, 0, 10)
v := m.PlayerList.Front()
for i := 0; i < 10; i++ {
if v == nil {
break
}
sample = append(sample, v.Value.(server.PlayerSample))
v = v.Next()
}
return
}
func (m *MyServer) Description() chat.Message {
return m.Settings.MOTD
}
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
m.PlayerListLock.Lock() remove := m.TryInsert(server.PlayerSample{
if m.PlayerList.Len() >= m.Settings.MaxPlayer { Name: name,
ID: id,
})
if remove == nil {
err := conn.WritePacket(pk.Marshal(packetid.KickDisconnect, err := conn.WritePacket(pk.Marshal(packetid.KickDisconnect,
chat.TranslateMsg("multiplayer.disconnect.server_full"), chat.TranslateMsg("multiplayer.disconnect.server_full"),
)) ))
if err != nil { if err != nil {
log.Printf("Write packet fail: %v", err) log.Printf("Write packet fail: %v", err)
} }
m.PlayerListLock.Unlock()
return return
} }
elem := m.PlayerList.PushBack(server.PlayerSample{ defer remove()
Name: name,
ID: id,
})
m.PlayerListLock.Unlock()
defer func() {
m.PlayerListLock.Lock()
defer m.PlayerListLock.Unlock()
// remove player in PlayerList
m.PlayerList.Remove(elem)
}()
if err := m.joinGame(conn); err != nil { if err := m.joinGame(conn); err != nil {
log.Printf("Write packet fail: %v", err) log.Printf("Write packet fail: %v", err)
@ -148,7 +94,7 @@ func (m *MyServer) joinGame(conn *net.Conn) error {
pk.NBT(nbt.StringifiedMessage(dimensionSNBT)), // Dimension pk.NBT(nbt.StringifiedMessage(dimensionSNBT)), // Dimension
pk.Identifier("world"), // World Name pk.Identifier("world"), // World Name
pk.Long(0), // Hashed Seed pk.Long(0), // Hashed Seed
pk.VarInt(m.Settings.MaxPlayer), // Max Players pk.VarInt(MaxPlayer), // Max Players
pk.VarInt(15), // View Distance pk.VarInt(15), // View Distance
pk.VarInt(15), // Simulation Distance pk.VarInt(15), // Simulation Distance
pk.Boolean(false), // Reduced Debug Info pk.Boolean(false), // Reduced Debug Info

View File

@ -16,7 +16,7 @@ type ListPingHandler interface {
MaxPlayer() int MaxPlayer() int
OnlinePlayer() int OnlinePlayer() int
PlayerSamples() []PlayerSample PlayerSamples() []PlayerSample
Description() chat.Message Description() *chat.Message
} }
type PlayerSample struct { type PlayerSample struct {
@ -60,8 +60,8 @@ func (s *Server) listResp() ([]byte, error) {
Online int `json:"online"` Online int `json:"online"`
Sample []PlayerSample `json:"sample"` Sample []PlayerSample `json:"sample"`
} `json:"players"` } `json:"players"`
Description chat.Message `json:"description"` Description *chat.Message `json:"description"`
FavIcon string `json:"favicon,omitempty"` FavIcon string `json:"favicon,omitempty"`
} }
list.Version.Name = s.ListPingHandler.Name() list.Version.Name = s.ListPingHandler.Name()

90
server/playerlist.go Normal file
View File

@ -0,0 +1,90 @@
package server
import (
"container/list"
"sync"
"github.com/Tnze/go-mc/chat"
)
// PlayerList is an implement of ListPingHandler 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
// 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 {
return &PlayerList{
name: name,
protocol: protocol,
maxPlayer: maxPlayers,
description: motd,
players: list.New(),
}
}
// TryInsert trying to insert player into PlayerList.
// Return nil if the server is full (length of list larger than maxPlayers),
// otherwise return a function which is used to remove the player from PlayerList
func (p *PlayerList) TryInsert(player PlayerSample) (remove func()) {
p.playersLock.Lock()
defer p.playersLock.Unlock()
if p.players.Len() >= p.maxPlayer {
return nil
}
elem := p.players.PushBack(player)
return func() {
p.playersLock.Lock()
p.players.Remove(elem)
p.playersLock.Unlock()
}
}
func (p *PlayerList) Name() string {
return p.name
}
func (p *PlayerList) Protocol() int {
return p.protocol
}
func (p *PlayerList) MaxPlayer() int {
return p.maxPlayer
}
func (p *PlayerList) OnlinePlayer() int {
p.playersLock.Lock()
defer p.playersLock.Unlock()
return p.players.Len()
}
func (p *PlayerList) PlayerSamples() (sample []PlayerSample) {
p.playersLock.Lock()
defer p.playersLock.Unlock()
// Up to 10 players can be returned
length := p.players.Len()
if length > 10 {
length = 10
}
sample = make([]PlayerSample, length)
v := p.players.Front()
for i := 0; i < length; i++ {
sample[i] = v.Value.(PlayerSample)
v = v.Next()
}
return
}
func (p *PlayerList) Description() *chat.Message {
return p.description
}

View File

@ -1,3 +1,25 @@
// Package server provide a minecraft server framework.
// You can build the server you want by combining the various functional modules provided here.
// An example can be found in examples/frameworkServer.
//
// A server is roughly divided into two parts:
//
// +----------------------------------------------+
// | Go-MC Server Framework |
// +-----------------------+----------------------+
// | Gate | GamePlay |
// +--------------+--------+--------+-------------+
// | LoginHandler | ListPingHandler | Coming Soon |
// +--------------+-----------------+-------------+
//
// 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.
//
// Gameplay, which is used to handle all things after a player successfully logs in
// (that is, after the LoginSuccess package is sent),
// and is responsible for functions including player status, chunk management, keep alive, chat, etc.
//
// The implement of Gameplay will provide later.
package server package server
import "github.com/Tnze/go-mc/net" import "github.com/Tnze/go-mc/net"