adjust PlayerList and KeepAlive in go-mc/server
This commit is contained in:
@ -5,34 +5,14 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/net"
|
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
*net.Conn
|
|
||||||
Protocol int32
|
|
||||||
packetQueue *PacketQueue
|
|
||||||
errChan chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Player struct {
|
|
||||||
uuid.UUID
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet758 is a packet in protocol 757.
|
// Packet758 is a packet in protocol 757.
|
||||||
// We are using type system to force programmers to update packets.
|
// We are using type system to force programmers to update packets.
|
||||||
type Packet758 pk.Packet
|
type Packet758 pk.Packet
|
||||||
type Packet757 pk.Packet
|
type Packet757 pk.Packet
|
||||||
|
|
||||||
// WritePacket to player client. The type of parameter will update per version.
|
|
||||||
func (c *Client) WritePacket(packet Packet758) {
|
|
||||||
c.packetQueue.Push(pk.Packet(packet))
|
|
||||||
}
|
|
||||||
|
|
||||||
type WritePacketError struct {
|
type WritePacketError struct {
|
||||||
Err error
|
Err error
|
||||||
ID int32
|
ID int32
|
||||||
@ -46,23 +26,6 @@ func (s WritePacketError) Unwrap() error {
|
|||||||
return s.Err
|
return s.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PutErr(err error) {
|
|
||||||
select {
|
|
||||||
case c.errChan <- err:
|
|
||||||
default:
|
|
||||||
// previous error exist, ignore this.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) GetErr() error {
|
|
||||||
select {
|
|
||||||
case err := <-c.errChan:
|
|
||||||
return err
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PacketQueue struct {
|
type PacketQueue struct {
|
||||||
queue *list.List
|
queue *list.List
|
||||||
closed bool
|
closed bool
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
|
||||||
"github.com/Tnze/go-mc/level"
|
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Level interface {
|
|
||||||
Init(g *Game)
|
|
||||||
Info() LevelInfo
|
|
||||||
PlayerJoin(p *Client)
|
|
||||||
PlayerQuit(p *Client)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LevelInfo struct {
|
|
||||||
Name string
|
|
||||||
HashedSeed uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type SimpleDim struct {
|
|
||||||
numOfSection int
|
|
||||||
columns map[level.ChunkPos]*level.Chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SimpleDim) Init(*Game) {}
|
|
||||||
|
|
||||||
func NewSimpleDim(secs int) *SimpleDim {
|
|
||||||
return &SimpleDim{
|
|
||||||
numOfSection: secs,
|
|
||||||
columns: make(map[level.ChunkPos]*level.Chunk),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SimpleDim) LoadChunk(pos level.ChunkPos, c *level.Chunk) {
|
|
||||||
s.columns[pos] = c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SimpleDim) Info() LevelInfo {
|
|
||||||
return LevelInfo{
|
|
||||||
Name: "minecraft:overworld",
|
|
||||||
HashedSeed: 1234567,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SimpleDim) PlayerJoin(p *Client) {
|
|
||||||
for pos, column := range s.columns {
|
|
||||||
packet := pk.Marshal(
|
|
||||||
packetid.ClientboundLevelChunkWithLight,
|
|
||||||
pos, column,
|
|
||||||
)
|
|
||||||
p.WritePacket(Packet758(packet))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SimpleDim) PlayerQuit(*Client) {}
|
|
@ -1,12 +1,8 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/net"
|
"github.com/Tnze/go-mc/net"
|
||||||
@ -19,59 +15,3 @@ type GamePlay interface {
|
|||||||
// You don't need to close the connection, but to keep not returning while the player is playing.
|
// You don't need to close the connection, but to keep not returning while the player is playing.
|
||||||
AcceptPlayer(name string, id uuid.UUID, profilePubKey *rsa.PublicKey, protocol int32, conn *net.Conn)
|
AcceptPlayer(name string, id uuid.UUID, profilePubKey *rsa.PublicKey, protocol int32, conn *net.Conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Game struct {
|
|
||||||
WorldLocker sync.Mutex
|
|
||||||
handlers map[int32][]*PacketHandler
|
|
||||||
components []Component
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Game) AcceptPlayer(name string, id uuid.UUID, profilePubKey *rsa.PublicKey, protocol int32, conn *net.Conn) {
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PacketHandler struct {
|
|
||||||
ID int32
|
|
||||||
F packetHandlerFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
type packetHandlerFunc func(client *Client, player *Player, packet Packet758) error
|
|
||||||
|
|
||||||
type Component interface {
|
|
||||||
Init(g *Game)
|
|
||||||
Run(ctx context.Context)
|
|
||||||
ClientJoin(c *Client, p *Player)
|
|
||||||
ClientLeft(c *Client, p *Player, reason error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGame(components ...Component) *Game {
|
|
||||||
g := &Game{
|
|
||||||
handlers: make(map[int32][]*PacketHandler),
|
|
||||||
components: components,
|
|
||||||
}
|
|
||||||
for _, c := range components {
|
|
||||||
c.Init(g)
|
|
||||||
}
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Game) AddHandler(ph *PacketHandler) {
|
|
||||||
g.handlers[ph.ID] = append(g.handlers[ph.ID], ph)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Game) Run(ctx context.Context) {
|
|
||||||
for _, c := range g.components {
|
|
||||||
go c.Run(ctx)
|
|
||||||
}
|
|
||||||
ticker := time.NewTicker(time.Second / 20)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
g.WorldLocker.Lock()
|
|
||||||
//g.Dispatcher.Run(g.World)
|
|
||||||
g.WorldLocker.Unlock()
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -4,10 +4,8 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/Tnze/go-mc/chat"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// keepAliveInterval represents the interval when the server sends keep alive
|
// keepAliveInterval represents the interval when the server sends keep alive
|
||||||
@ -16,59 +14,49 @@ const keepAliveInterval = time.Second * 15
|
|||||||
// keepAliveWaitInterval represents how long does the player expired
|
// keepAliveWaitInterval represents how long does the player expired
|
||||||
const keepAliveWaitInterval = time.Second * 30
|
const keepAliveWaitInterval = time.Second * 30
|
||||||
|
|
||||||
type ClientDelay struct {
|
type KeepAliveClient interface {
|
||||||
Delay time.Duration
|
SendKeepAlive(id int64)
|
||||||
|
SendDisconnect(reason chat.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeepAlive struct {
|
type KeepAlive struct {
|
||||||
join chan *Client
|
join chan KeepAliveClient
|
||||||
quit chan *Client
|
quit chan KeepAliveClient
|
||||||
tick chan *Client
|
tick chan KeepAliveClient
|
||||||
|
|
||||||
pingList *list.List
|
pingList *list.List
|
||||||
waitList *list.List
|
waitList *list.List
|
||||||
listIndex map[*Client]*list.Element
|
listIndex map[KeepAliveClient]*list.Element
|
||||||
listTimer *time.Timer
|
listTimer *time.Timer
|
||||||
waitTimer *time.Timer
|
waitTimer *time.Timer
|
||||||
// The Notchian server uses a system-dependent time in milliseconds to generate the keep alive ID value.
|
// The Notchian server uses a system-dependent time in milliseconds to generate the keep alive ID value.
|
||||||
// We don't do that here for security reason.
|
// We don't do that here for security reason.
|
||||||
keepAliveID int64
|
keepAliveID int64
|
||||||
|
|
||||||
updatePlayerDelay []func(p *Client, delay time.Duration)
|
updatePlayerDelay []func(p KeepAliveClient, delay time.Duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKeepAlive() (k *KeepAlive) {
|
func NewKeepAlive[C KeepAliveClient]() (k *KeepAlive) {
|
||||||
return &KeepAlive{
|
return &KeepAlive{
|
||||||
join: make(chan *Client),
|
join: make(chan KeepAliveClient),
|
||||||
quit: make(chan *Client),
|
quit: make(chan KeepAliveClient),
|
||||||
tick: make(chan *Client),
|
tick: make(chan KeepAliveClient),
|
||||||
pingList: list.New(),
|
pingList: list.New(),
|
||||||
waitList: list.New(),
|
waitList: list.New(),
|
||||||
listIndex: make(map[*Client]*list.Element),
|
listIndex: make(map[KeepAliveClient]*list.Element),
|
||||||
listTimer: time.NewTimer(keepAliveInterval),
|
listTimer: time.NewTimer(keepAliveInterval),
|
||||||
waitTimer: time.NewTimer(keepAliveWaitInterval),
|
waitTimer: time.NewTimer(keepAliveWaitInterval),
|
||||||
keepAliveID: 0,
|
keepAliveID: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KeepAlive) AddPlayerDelayUpdateHandler(f func(p *Client, delay time.Duration)) {
|
func (k *KeepAlive) AddPlayerDelayUpdateHandler(f func(c KeepAliveClient, delay time.Duration)) {
|
||||||
k.updatePlayerDelay = append(k.updatePlayerDelay, f)
|
k.updatePlayerDelay = append(k.updatePlayerDelay, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init implement Component for KeepAlive
|
func (k *KeepAlive) ClientJoin(client KeepAliveClient) { k.join <- client }
|
||||||
func (k *KeepAlive) Init(g *Game) {
|
func (k *KeepAlive) ClientTick(client KeepAliveClient) { k.tick <- client }
|
||||||
g.AddHandler(&PacketHandler{
|
func (k *KeepAlive) ClientLeft(client KeepAliveClient) { k.quit <- client }
|
||||||
ID: packetid.ServerboundKeepAlive,
|
|
||||||
F: func(client *Client, player *Player, packet Packet758) error {
|
|
||||||
var KeepAliveID pk.Long
|
|
||||||
if err := pk.Packet(packet).Scan(&KeepAliveID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
k.tick <- client
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run implement Component for KeepAlive
|
// Run implement Component for KeepAlive
|
||||||
func (k *KeepAlive) Run(ctx context.Context) {
|
func (k *KeepAlive) Run(ctx context.Context) {
|
||||||
@ -76,35 +64,29 @@ func (k *KeepAlive) Run(ctx context.Context) {
|
|||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case p := <-k.join:
|
case c := <-k.join:
|
||||||
k.pushPlayer(p)
|
k.pushPlayer(c)
|
||||||
case p := <-k.quit:
|
case c := <-k.quit:
|
||||||
k.removePlayer(p)
|
k.removePlayer(c)
|
||||||
case now := <-k.listTimer.C:
|
case now := <-k.listTimer.C:
|
||||||
k.pingPlayer(now)
|
k.pingPlayer(now)
|
||||||
case <-k.waitTimer.C:
|
case <-k.waitTimer.C:
|
||||||
k.kickPlayer()
|
k.kickPlayer()
|
||||||
case p := <-k.tick:
|
case c := <-k.tick:
|
||||||
k.tickPlayer(p)
|
k.tickPlayer(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientJoin implement Component for KeepAlive
|
func (k KeepAlive) pushPlayer(c KeepAliveClient) {
|
||||||
func (k *KeepAlive) ClientJoin(client *Client, _ *Player) { k.join <- client }
|
k.listIndex[c] = k.pingList.PushBack(
|
||||||
|
keepAliveItem{player: c, t: time.Now()},
|
||||||
// ClientLeft implement Component for KeepAlive
|
|
||||||
func (k *KeepAlive) ClientLeft(client *Client, _ *Player, _ error) { k.quit <- client }
|
|
||||||
|
|
||||||
func (k KeepAlive) pushPlayer(p *Client) {
|
|
||||||
k.listIndex[p] = k.pingList.PushBack(
|
|
||||||
keepAliveItem{player: p, t: time.Now()},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KeepAlive) removePlayer(p *Client) {
|
func (k *KeepAlive) removePlayer(c KeepAliveClient) {
|
||||||
elem := k.listIndex[p]
|
elem := k.listIndex[c]
|
||||||
delete(k.listIndex, p)
|
delete(k.listIndex, c)
|
||||||
if elem.Prev() == nil {
|
if elem.Prev() == nil {
|
||||||
// At present, it is difficult to distinguish
|
// At present, it is difficult to distinguish
|
||||||
// which linked list the player is in,
|
// which linked list the player is in,
|
||||||
@ -118,26 +100,23 @@ func (k *KeepAlive) removePlayer(p *Client) {
|
|||||||
|
|
||||||
func (k *KeepAlive) pingPlayer(now time.Time) {
|
func (k *KeepAlive) pingPlayer(now time.Time) {
|
||||||
if elem := k.pingList.Front(); elem != nil {
|
if elem := k.pingList.Front(); elem != nil {
|
||||||
p := k.pingList.Remove(elem).(keepAliveItem).player
|
c := k.pingList.Remove(elem).(keepAliveItem).player
|
||||||
// Send Clientbound KeepAlive packet.
|
// Send Clientbound KeepAlive packet.
|
||||||
p.WritePacket(Packet758(pk.Marshal(
|
c.SendKeepAlive(k.keepAliveID)
|
||||||
packetid.ClientboundKeepAlive,
|
|
||||||
pk.Long(k.keepAliveID),
|
|
||||||
)))
|
|
||||||
k.keepAliveID++
|
k.keepAliveID++
|
||||||
// Clientbound KeepAlive packet is sent, move the player to waiting list.
|
// Clientbound KeepAlive packet is sent, move the player to waiting list.
|
||||||
k.listIndex[p] = k.waitList.PushBack(
|
k.listIndex[c] = k.waitList.PushBack(
|
||||||
keepAliveItem{player: p, t: now},
|
keepAliveItem{player: c, t: now},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Wait for next earliest player
|
// Wait for next earliest player
|
||||||
keepAliveSetTimer(k.pingList, k.listTimer, keepAliveInterval)
|
keepAliveSetTimer(k.pingList, k.listTimer, keepAliveInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KeepAlive) tickPlayer(p *Client) {
|
func (k *KeepAlive) tickPlayer(c KeepAliveClient) {
|
||||||
elem, ok := k.listIndex[p]
|
elem, ok := k.listIndex[c]
|
||||||
if !ok {
|
if !ok {
|
||||||
p.PutErr(errors.New("keepalive: fail to tick player: client not found"))
|
panic(errors.New("keepalive: fail to tick player: client not found"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if elem.Prev() == nil {
|
if elem.Prev() == nil {
|
||||||
@ -150,19 +129,19 @@ func (k *KeepAlive) tickPlayer(p *Client) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
delay := now.Sub(k.waitList.Remove(elem).(keepAliveItem).t)
|
delay := now.Sub(k.waitList.Remove(elem).(keepAliveItem).t)
|
||||||
for _, f := range k.updatePlayerDelay {
|
for _, f := range k.updatePlayerDelay {
|
||||||
f(p, delay)
|
f(c, delay)
|
||||||
}
|
}
|
||||||
// move the player to ping list
|
// move the player to ping list
|
||||||
k.listIndex[p] = k.pingList.PushBack(
|
k.listIndex[c] = k.pingList.PushBack(
|
||||||
keepAliveItem{player: p, t: now},
|
keepAliveItem{player: c, t: now},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KeepAlive) kickPlayer() {
|
func (k *KeepAlive) kickPlayer() {
|
||||||
if elem := k.waitList.Front(); elem != nil {
|
if elem := k.waitList.Front(); elem != nil {
|
||||||
player := k.waitList.Remove(elem).(keepAliveItem).player
|
c := k.waitList.Remove(elem).(keepAliveItem).player
|
||||||
k.waitList.Remove(elem)
|
k.waitList.Remove(elem)
|
||||||
player.PutErr(errors.New("keepalive: client did not response"))
|
c.SendDisconnect(chat.TranslateMsg("disconnect.timeout"))
|
||||||
}
|
}
|
||||||
keepAliveSetTimer(k.waitList, k.waitTimer, keepAliveWaitInterval)
|
keepAliveSetTimer(k.waitList, k.waitTimer, keepAliveWaitInterval)
|
||||||
}
|
}
|
||||||
@ -180,6 +159,6 @@ func keepAliveSetTimer(l *list.List, timer *time.Timer, interval time.Duration)
|
|||||||
}
|
}
|
||||||
|
|
||||||
type keepAliveItem struct {
|
type keepAliveItem struct {
|
||||||
player *Client
|
player KeepAliveClient
|
||||||
t time.Time
|
t time.Time
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package server
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"image"
|
"image"
|
||||||
"image/png"
|
"image/png"
|
||||||
"strings"
|
"strings"
|
||||||
@ -97,36 +96,33 @@ func (s *Server) listResp() ([]byte, error) {
|
|||||||
type PingInfo struct {
|
type PingInfo struct {
|
||||||
name string
|
name string
|
||||||
protocol int
|
protocol int
|
||||||
*PlayerList
|
|
||||||
description chat.Message
|
description chat.Message
|
||||||
favicon string
|
favicon string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPingInfo crate a new PingInfo, the icon can be nil.
|
// 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) {
|
// Panic if icon's size is not 64x64.
|
||||||
|
func NewPingInfo(name string, protocol int, motd chat.Message, icon image.Image) (p *PingInfo) {
|
||||||
var favIcon string
|
var favIcon string
|
||||||
if icon != nil {
|
if icon != nil {
|
||||||
if !icon.Bounds().Size().Eq(image.Point{X: 64, Y: 64}) {
|
if !icon.Bounds().Size().Eq(image.Point{X: 64, Y: 64}) {
|
||||||
return nil, errors.New("icon size is not 64x64")
|
panic("icon size is not 64x64")
|
||||||
}
|
}
|
||||||
// Encode icon into string "data:image/png;base64,......" format
|
// Encode icon into string "data:image/png;base64,......" format
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString("data:image/png;base64,")
|
sb.WriteString("data:image/png;base64,")
|
||||||
w := base64.NewEncoder(base64.StdEncoding, &sb)
|
w := base64.NewEncoder(base64.StdEncoding, &sb)
|
||||||
err = png.Encode(w, icon)
|
if err := png.Encode(w, icon); err != nil {
|
||||||
if err != nil {
|
panic(err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
err = w.Close()
|
if err := w.Close(); err != nil {
|
||||||
if err != nil {
|
panic(err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
favIcon = sb.String()
|
favIcon = sb.String()
|
||||||
}
|
}
|
||||||
p = &PingInfo{
|
p = &PingInfo{
|
||||||
name: name,
|
name: name,
|
||||||
protocol: protocol,
|
protocol: protocol,
|
||||||
PlayerList: list,
|
|
||||||
description: motd,
|
description: motd,
|
||||||
favicon: favIcon,
|
favicon: favIcon,
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PlayerListClient interface {
|
||||||
|
SendDisconnect(reason chat.Message)
|
||||||
|
}
|
||||||
|
|
||||||
// PlayerList is a player list 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 {
|
||||||
maxPlayer int
|
maxPlayer int
|
||||||
clients map[uuid.UUID]*Player
|
players map[PlayerListClient]PlayerSample
|
||||||
// Only the field players is protected by this Mutex.
|
// Only the field players 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
|
||||||
@ -26,45 +26,33 @@ type PlayerList struct {
|
|||||||
func NewPlayerList(maxPlayers int) *PlayerList {
|
func NewPlayerList(maxPlayers int) *PlayerList {
|
||||||
return &PlayerList{
|
return &PlayerList{
|
||||||
maxPlayer: maxPlayers,
|
maxPlayer: maxPlayers,
|
||||||
clients: make(map[uuid.UUID]*Player),
|
players: make(map[PlayerListClient]PlayerSample),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init implement Component for PlayerList
|
|
||||||
func (p *PlayerList) Init(*Game) {}
|
|
||||||
|
|
||||||
// Run implement Component for PlayerList
|
|
||||||
func (p *PlayerList) Run(context.Context) {}
|
|
||||||
|
|
||||||
// ClientJoin implement Component for PlayerList
|
// ClientJoin implement Component for PlayerList
|
||||||
func (p *PlayerList) ClientJoin(client *Client, player *Player) {
|
func (p *PlayerList) ClientJoin(client PlayerListClient, player PlayerSample) {
|
||||||
p.playersLock.Lock()
|
p.playersLock.Lock()
|
||||||
defer p.playersLock.Unlock()
|
defer p.playersLock.Unlock()
|
||||||
|
|
||||||
if len(p.clients) >= p.maxPlayer {
|
if len(p.players) >= p.maxPlayer {
|
||||||
client.WritePacket(Packet758(pk.Marshal(
|
client.SendDisconnect(chat.TranslateMsg("multiplayer.disconnect.server_full"))
|
||||||
packetid.ClientboundDisconnect,
|
|
||||||
chat.TranslateMsg("multiplayer.disconnect.server_full"),
|
|
||||||
)))
|
|
||||||
client.PutErr(errors.New("playerlist: server full"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.clients[player.UUID] = player
|
p.players[client] = player
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientLeft implement Component for PlayerList
|
func (p *PlayerList) ClientLeft(client PlayerListClient) {
|
||||||
func (p *PlayerList) ClientLeft(_ *Client, player *Player, _ error) {
|
|
||||||
p.playersLock.Lock()
|
p.playersLock.Lock()
|
||||||
defer p.playersLock.Unlock()
|
defer p.playersLock.Unlock()
|
||||||
delete(p.clients, player.UUID)
|
delete(p.players, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPlayer implement LoginChecker for PlayerList
|
func (p *PlayerList) CheckPlayer(string, uuid.UUID, int32) (ok bool, reason chat.Message) {
|
||||||
func (p *PlayerList) CheckPlayer(name string, id uuid.UUID, protocol int32) (ok bool, reason chat.Message) {
|
|
||||||
p.playersLock.Lock()
|
p.playersLock.Lock()
|
||||||
defer p.playersLock.Unlock()
|
defer p.playersLock.Unlock()
|
||||||
if len(p.clients) >= p.maxPlayer {
|
if len(p.players) >= p.maxPlayer {
|
||||||
return false, chat.TranslateMsg("multiplayer.disconnect.server_full")
|
return false, chat.TranslateMsg("multiplayer.disconnect.server_full")
|
||||||
}
|
}
|
||||||
return true, chat.Message{}
|
return true, chat.Message{}
|
||||||
@ -77,24 +65,21 @@ func (p *PlayerList) MaxPlayer() int {
|
|||||||
func (p *PlayerList) OnlinePlayer() int {
|
func (p *PlayerList) OnlinePlayer() int {
|
||||||
p.playersLock.Lock()
|
p.playersLock.Lock()
|
||||||
defer p.playersLock.Unlock()
|
defer p.playersLock.Unlock()
|
||||||
return len(p.clients)
|
return len(p.players)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlayerList) PlayerSamples() (sample []PlayerSample) {
|
func (p *PlayerList) PlayerSamples() (sample []PlayerSample) {
|
||||||
p.playersLock.Lock()
|
p.playersLock.Lock()
|
||||||
defer p.playersLock.Unlock()
|
defer p.playersLock.Unlock()
|
||||||
// Up to 10 players can be returned
|
// Up to 10 players can be returned
|
||||||
length := len(p.clients)
|
length := len(p.players)
|
||||||
if length > 10 {
|
if length > 10 {
|
||||||
length = 10
|
length = 10
|
||||||
}
|
}
|
||||||
sample = make([]PlayerSample, length)
|
sample = make([]PlayerSample, length)
|
||||||
var i int
|
var i int
|
||||||
for _, v := range p.clients {
|
for _, player := range p.players {
|
||||||
sample[i] = PlayerSample{
|
sample[i] = player
|
||||||
Name: v.Name,
|
|
||||||
ID: v.UUID,
|
|
||||||
}
|
|
||||||
i++
|
i++
|
||||||
if i >= length {
|
if i >= length {
|
||||||
break
|
break
|
||||||
|
Reference in New Issue
Block a user