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
import (
"container/list"
_ "embed"
"log"
"sync"
"github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/data/packetid"
@ -16,29 +14,23 @@ import (
)
type MyServer struct {
PlayerList *list.List
PlayerListLock sync.Mutex
server.MojangLoginHandler
Settings struct {
Name string
MaxPlayer int
MOTD chat.Message
}
*server.PlayerList
}
const MaxPlayer = 20
func main() {
var ms MyServer
ms.PlayerList = list.New()
ms.MojangLoginHandler.OnlineMode = true
ms.MojangLoginHandler.Threshold = 256
ms.Settings.Name = "MyServer"
ms.Settings.MaxPlayer = 1
ms.Settings.MOTD = chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text: "Powered by go-mc", Color: "yellow"}}}
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{
ListPingHandler: &ms,
LoginHandler: &ms,
ListPingHandler: ms.PlayerList,
LoginHandler: &server.MojangLoginHandler{
OnlineMode: true,
Threshold: 256,
},
GamePlay: &ms,
}
if err := s.Listen(":25565"); err != nil {
@ -46,68 +38,22 @@ func main() {
}
}
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) {
// Add player into PlayerList
m.PlayerListLock.Lock()
if m.PlayerList.Len() >= m.Settings.MaxPlayer {
remove := m.TryInsert(server.PlayerSample{
Name: name,
ID: id,
})
if remove == nil {
err := conn.WritePacket(pk.Marshal(packetid.KickDisconnect,
chat.TranslateMsg("multiplayer.disconnect.server_full"),
))
if err != nil {
log.Printf("Write packet fail: %v", err)
}
m.PlayerListLock.Unlock()
return
}
elem := m.PlayerList.PushBack(server.PlayerSample{
Name: name,
ID: id,
})
m.PlayerListLock.Unlock()
defer func() {
m.PlayerListLock.Lock()
defer m.PlayerListLock.Unlock()
// remove player in PlayerList
m.PlayerList.Remove(elem)
}()
defer remove()
if err := m.joinGame(conn); err != nil {
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.Identifier("world"), // World Name
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), // Simulation Distance
pk.Boolean(false), // Reduced Debug Info

View File

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

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
import "github.com/Tnze/go-mc/net"