player and dimension loader with ecs system
This commit is contained in:
@ -4,20 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"github.com/Tnze/go-mc/server/world"
|
||||||
"image"
|
"image"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
"github.com/Tnze/go-mc/level"
|
|
||||||
"github.com/Tnze/go-mc/save"
|
|
||||||
"github.com/Tnze/go-mc/save/region"
|
|
||||||
"github.com/Tnze/go-mc/server"
|
"github.com/Tnze/go-mc/server"
|
||||||
"github.com/Tnze/go-mc/server/command"
|
"github.com/Tnze/go-mc/server/command"
|
||||||
|
"github.com/Tnze/go-mc/server/player"
|
||||||
)
|
)
|
||||||
|
|
||||||
var motd = chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text: "Powered by go-mc", Color: "yellow"}}}
|
var motd = chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text: "Powered by go-mc", Color: "yellow"}}}
|
||||||
@ -35,11 +31,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keepAlive := server.NewKeepAlive()
|
keepAlive := server.NewKeepAlive()
|
||||||
playerInfo := server.NewPlayerInfo(time.Second, keepAlive)
|
//playerInfo := player.NewPlayerInfo(keepAlive)
|
||||||
defaultDimension, err := loadAllRegions(*regionPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Load chunks fail: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
commands := command.NewGraph()
|
commands := command.NewGraph()
|
||||||
handleFunc := func(ctx context.Context, args []command.ParsedData) error {
|
handleFunc := func(ctx context.Context, args []command.ParsedData) error {
|
||||||
@ -59,21 +51,21 @@ func main() {
|
|||||||
HandleFunc(handleFunc)).
|
HandleFunc(handleFunc)).
|
||||||
HandleFunc(handleFunc),
|
HandleFunc(handleFunc),
|
||||||
)
|
)
|
||||||
|
|
||||||
game := server.NewGame(
|
game := server.NewGame(
|
||||||
defaultDimension,
|
|
||||||
playerList,
|
playerList,
|
||||||
playerInfo,
|
//playerInfo,
|
||||||
keepAlive,
|
keepAlive,
|
||||||
server.NewGlobalChat(),
|
|
||||||
commands,
|
commands,
|
||||||
)
|
)
|
||||||
|
world.NewDimensionManager(game)
|
||||||
|
player.SpawnSystem(game, "./save/testdata/playerdata")
|
||||||
|
player.PosAndRotSystem(game)
|
||||||
go game.Run(context.Background())
|
go game.Run(context.Background())
|
||||||
|
|
||||||
s := server.Server{
|
s := server.Server{
|
||||||
ListPingHandler: serverInfo,
|
ListPingHandler: serverInfo,
|
||||||
LoginHandler: &server.MojangLoginHandler{
|
LoginHandler: &server.MojangLoginHandler{
|
||||||
OnlineMode: false,
|
OnlineMode: true,
|
||||||
Threshold: 256,
|
Threshold: 256,
|
||||||
LoginChecker: playerList,
|
LoginChecker: playerList,
|
||||||
},
|
},
|
||||||
@ -100,52 +92,3 @@ func readIcon() image.Image {
|
|||||||
}
|
}
|
||||||
return icon
|
return icon
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAllRegions(dir string) (*server.SimpleDim, error) {
|
|
||||||
mcafiles, err := filepath.Glob(filepath.Join(dir, "r.*.*.mca"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dim := server.NewSimpleDim(256)
|
|
||||||
for _, file := range mcafiles {
|
|
||||||
var rx, rz int
|
|
||||||
_, err := fmt.Sscanf(filepath.Base(file), "r.%d.%d.mca", &rx, &rz)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = loadAllChunks(dim, file, rx, rz)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dim, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadAllChunks(dim *server.SimpleDim, file string, rx, rz int) error {
|
|
||||||
r, err := region.Open(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
var c save.Chunk
|
|
||||||
for x := 0; x < 32; x++ {
|
|
||||||
for z := 0; z < 32; z++ {
|
|
||||||
if !r.ExistSector(x, z) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data, err := r.ReadSector(x, z)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.Load(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chunk, err := level.ChunkFromSave(&c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dim.LoadChunk(level.ChunkPos{X: rx<<5 + x, Z: rz<<5 + z}, chunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -253,13 +253,18 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
|
|||||||
vt := val.Type() //receiver must be []int or []int32
|
vt := val.Type() //receiver must be []int or []int32
|
||||||
if vt.Kind() == reflect.Interface {
|
if vt.Kind() == reflect.Interface {
|
||||||
vt = reflect.TypeOf([]int32{}) // pass
|
vt = reflect.TypeOf([]int32{}) // pass
|
||||||
} else if vt.Kind() != reflect.Slice {
|
} else if vt.Kind() == reflect.Array && vt.Len() != int(aryLen) {
|
||||||
|
return errors.New("cannot parse TagIntArray to " + vt.String() + ", length not match")
|
||||||
|
} else if k := vt.Kind(); k != reflect.Slice && k != reflect.Array {
|
||||||
return errors.New("cannot parse TagIntArray to " + vt.String() + ", it must be a slice")
|
return errors.New("cannot parse TagIntArray to " + vt.String() + ", it must be a slice")
|
||||||
} else if tk := val.Type().Elem().Kind(); tk != reflect.Int && tk != reflect.Int32 {
|
} else if tk := val.Type().Elem().Kind(); tk != reflect.Int && tk != reflect.Int32 {
|
||||||
return errors.New("cannot parse TagIntArray to " + vt.String())
|
return errors.New("cannot parse TagIntArray to " + vt.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := reflect.MakeSlice(vt, int(aryLen), int(aryLen))
|
buf := val
|
||||||
|
if vt.Kind() == reflect.Slice {
|
||||||
|
buf = reflect.MakeSlice(vt, int(aryLen), int(aryLen))
|
||||||
|
}
|
||||||
for i := 0; i < int(aryLen); i++ {
|
for i := 0; i < int(aryLen); i++ {
|
||||||
value, err := d.readInt()
|
value, err := d.readInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -267,7 +272,9 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
|
|||||||
}
|
}
|
||||||
buf.Index(i).SetInt(int64(value))
|
buf.Index(i).SetInt(int64(value))
|
||||||
}
|
}
|
||||||
val.Set(buf)
|
if vt.Kind() == reflect.Slice {
|
||||||
|
val.Set(buf)
|
||||||
|
}
|
||||||
|
|
||||||
case TagLongArray:
|
case TagLongArray:
|
||||||
aryLen, err := d.readInt()
|
aryLen, err := d.readInt()
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package save
|
package save
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"github.com/Tnze/go-mc/nbt"
|
"github.com/Tnze/go-mc/nbt"
|
||||||
"github.com/google/uuid"
|
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,8 +16,7 @@ type PlayerData struct {
|
|||||||
FallFlying byte
|
FallFlying byte
|
||||||
OnGround byte
|
OnGround byte
|
||||||
|
|
||||||
UUID uuid.UUID `nbt:"-"`
|
UUID [4]int32
|
||||||
UUIDLeast, UUIDMost int64
|
|
||||||
|
|
||||||
PlayerGameType int32 `nbt:"playerGameType"`
|
PlayerGameType int32 `nbt:"playerGameType"`
|
||||||
Air int16
|
Air int16
|
||||||
@ -80,8 +77,5 @@ type Item struct {
|
|||||||
|
|
||||||
func ReadPlayerData(r io.Reader) (data PlayerData, err error) {
|
func ReadPlayerData(r io.Reader) (data PlayerData, err error) {
|
||||||
_, err = nbt.NewDecoder(r).Decode(&data)
|
_, err = nbt.NewDecoder(r).Decode(&data)
|
||||||
//parse UUID from two int64s
|
|
||||||
binary.BigEndian.PutUint64(data.UUID[:], uint64(data.UUIDMost))
|
|
||||||
binary.BigEndian.PutUint64(data.UUID[8:], uint64(data.UUIDLeast))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
131
server/chat.go
131
server/chat.go
@ -1,131 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/chat"
|
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
|
||||||
"github.com/Tnze/go-mc/nbt"
|
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GlobalChat struct {
|
|
||||||
msg chan chatItem
|
|
||||||
join chan *Player
|
|
||||||
quit chan *Player
|
|
||||||
|
|
||||||
players map[uuid.UUID]*Player
|
|
||||||
}
|
|
||||||
|
|
||||||
type chatItem struct {
|
|
||||||
p *Player
|
|
||||||
text string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGlobalChat() *GlobalChat {
|
|
||||||
return &GlobalChat{
|
|
||||||
msg: make(chan chatItem),
|
|
||||||
join: make(chan *Player),
|
|
||||||
quit: make(chan *Player),
|
|
||||||
players: make(map[uuid.UUID]*Player),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GlobalChat) Init(game *Game) {
|
|
||||||
game.AddHandler(&PacketHandler{
|
|
||||||
ID: packetid.ServerboundChat,
|
|
||||||
F: func(player *Player, packet Packet758) error {
|
|
||||||
var msg pk.String
|
|
||||||
if err := pk.Packet(packet).Scan(&msg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
text, _ := chat.TransCtrlSeq(string(msg), false)
|
|
||||||
g.msg <- chatItem{p: player, text: text}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
chatPosChat = iota
|
|
||||||
chatPosSystem
|
|
||||||
chatPosGameInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
func (g *GlobalChat) Run(ctx context.Context) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case item := <-g.msg:
|
|
||||||
g.broadcast(Packet758(pk.Marshal(
|
|
||||||
packetid.ClientboundChat,
|
|
||||||
item.toMessage(),
|
|
||||||
pk.Byte(chatPosChat),
|
|
||||||
pk.UUID(item.p.UUID),
|
|
||||||
)))
|
|
||||||
case p := <-g.join:
|
|
||||||
g.broadcast(Packet758(pk.Marshal(
|
|
||||||
packetid.ClientboundChat,
|
|
||||||
chat.TranslateMsg("multiplayer.player.joined", chat.Text(p.Name)).SetColor(chat.Yellow),
|
|
||||||
pk.Byte(chatPosSystem),
|
|
||||||
pk.UUID(uuid.Nil),
|
|
||||||
)))
|
|
||||||
g.players[p.UUID] = p
|
|
||||||
case p := <-g.quit:
|
|
||||||
g.broadcast(Packet758(pk.Marshal(
|
|
||||||
packetid.ClientboundChat,
|
|
||||||
chat.TranslateMsg("multiplayer.player.left", chat.Text(p.Name)).SetColor(chat.Yellow),
|
|
||||||
pk.Byte(chatPosSystem),
|
|
||||||
pk.UUID(uuid.Nil),
|
|
||||||
)))
|
|
||||||
delete(g.players, p.UUID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GlobalChat) broadcast(packet Packet758) {
|
|
||||||
for _, p := range g.players {
|
|
||||||
p.WritePacket(packet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GlobalChat) AddPlayer(player *Player) { g.join <- player }
|
|
||||||
|
|
||||||
func (g *GlobalChat) RemovePlayer(p *Player) { g.quit <- p }
|
|
||||||
|
|
||||||
func (c chatItem) toMessage() chat.Message {
|
|
||||||
return chat.TranslateMsg(
|
|
||||||
"chat.type.text",
|
|
||||||
chat.Message{
|
|
||||||
Text: c.p.Name,
|
|
||||||
ClickEvent: chat.SuggestCommand("/msg " + c.p.Name),
|
|
||||||
HoverEvent: chat.ShowEntity(playerToSNBT(c.p)),
|
|
||||||
},
|
|
||||||
chat.Text(c.text),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func playerToSNBT(p *Player) string {
|
|
||||||
var s nbt.StringifiedMessage
|
|
||||||
entity := struct {
|
|
||||||
ID string `nbt:"id"`
|
|
||||||
Name string `nbt:"name"`
|
|
||||||
}{
|
|
||||||
ID: p.UUID.String(),
|
|
||||||
Name: p.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := nbt.Marshal(entity)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = nbt.Unmarshal(data, &s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(s)
|
|
||||||
}
|
|
@ -2,54 +2,27 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"github.com/google/uuid"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"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/Tnze/go-mc/server/ecs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Player struct {
|
type Client struct {
|
||||||
*net.Conn
|
*net.Conn
|
||||||
|
Protocol int32
|
||||||
Name string
|
ecs.Index
|
||||||
uuid.UUID
|
|
||||||
EntityID int32
|
|
||||||
Gamemode byte
|
|
||||||
|
|
||||||
packetQueue *PacketQueue
|
packetQueue *PacketQueue
|
||||||
errChan chan error
|
errChan chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlayer(conn *net.Conn, name string, id uuid.UUID, eid int32, gamemode byte) (p *Player) {
|
type Player struct {
|
||||||
p = &Player{
|
uuid.UUID
|
||||||
Conn: conn,
|
Name string
|
||||||
Name: name,
|
|
||||||
UUID: id,
|
|
||||||
EntityID: eid,
|
|
||||||
Gamemode: gamemode,
|
|
||||||
packetQueue: NewPacketQueue(),
|
|
||||||
errChan: make(chan error, 1),
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
packet, ok := p.packetQueue.Pull()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
err := p.Conn.WritePacket(packet)
|
|
||||||
if err != nil {
|
|
||||||
p.PutErr(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Close() {
|
|
||||||
p.packetQueue.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Packet758 is a packet in protocol 757.
|
// Packet758 is a packet in protocol 757.
|
||||||
@ -58,8 +31,8 @@ type Packet758 pk.Packet
|
|||||||
type Packet757 pk.Packet
|
type Packet757 pk.Packet
|
||||||
|
|
||||||
// WritePacket to player client. The type of parameter will update per version.
|
// WritePacket to player client. The type of parameter will update per version.
|
||||||
func (p *Player) WritePacket(packet Packet758) {
|
func (c *Client) WritePacket(packet Packet758) {
|
||||||
p.packetQueue.Push(pk.Packet(packet))
|
c.packetQueue.Push(pk.Packet(packet))
|
||||||
}
|
}
|
||||||
|
|
||||||
type WritePacketError struct {
|
type WritePacketError struct {
|
||||||
@ -75,17 +48,17 @@ func (s WritePacketError) Unwrap() error {
|
|||||||
return s.Err
|
return s.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) PutErr(err error) {
|
func (c *Client) PutErr(err error) {
|
||||||
select {
|
select {
|
||||||
case p.errChan <- err:
|
case c.errChan <- err:
|
||||||
default:
|
default:
|
||||||
// previous error exist, ignore this.
|
// previous error exist, ignore this.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) GetErr() error {
|
func (c *Client) GetErr() error {
|
||||||
select {
|
select {
|
||||||
case err := <-p.errChan:
|
case err := <-c.errChan:
|
||||||
return err
|
return err
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
@ -2,17 +2,14 @@ package clientinfo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/Tnze/go-mc/server/ecs"
|
||||||
"github.com/google/uuid"
|
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
"github.com/Tnze/go-mc/server"
|
"github.com/Tnze/go-mc/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientInformation struct {
|
type ClientInformation struct{}
|
||||||
Players map[uuid.UUID]*Info
|
|
||||||
}
|
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
Locale string
|
Locale string
|
||||||
@ -26,10 +23,25 @@ type Info struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientInformation) Init(g *server.Game) {
|
func (c *ClientInformation) Init(g *server.Game) {
|
||||||
c.Players = make(map[uuid.UUID]*Info)
|
infos := ecs.GetComponent[Info](g.World)
|
||||||
|
type updateData struct {
|
||||||
|
eid ecs.Index
|
||||||
|
info Info
|
||||||
|
}
|
||||||
|
updateChan := make(chan updateData)
|
||||||
|
g.Add(ecs.FuncSystem(func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case info := <-updateChan:
|
||||||
|
infos.SetValue(info.eid, info.info)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), "ClientInfoSystem", nil)
|
||||||
g.AddHandler(&server.PacketHandler{
|
g.AddHandler(&server.PacketHandler{
|
||||||
ID: packetid.ServerboundClientInformation,
|
ID: packetid.ServerboundClientInformation,
|
||||||
F: func(player *server.Player, p server.Packet758) error {
|
F: func(client *server.Client, player *server.Player, p server.Packet758) error {
|
||||||
var (
|
var (
|
||||||
Locale pk.String
|
Locale pk.String
|
||||||
ViewDistance pk.Byte
|
ViewDistance pk.Byte
|
||||||
@ -53,21 +65,25 @@ func (c *ClientInformation) Init(g *server.Game) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.Players[player.UUID] = &Info{
|
|
||||||
Locale: string(Locale),
|
updateChan <- updateData{
|
||||||
ViewDistance: int(ViewDistance),
|
eid: client.Index,
|
||||||
ChatMode: byte(ChatMode),
|
info: Info{
|
||||||
ChatColors: bool(ChatColors),
|
Locale: string(Locale),
|
||||||
DisplayedSkinParts: byte(DisplayedSkinParts),
|
ViewDistance: int(ViewDistance),
|
||||||
MainHand: byte(MainHand),
|
ChatMode: byte(ChatMode),
|
||||||
EnableTextFiltering: bool(EnableTextFiltering),
|
ChatColors: bool(ChatColors),
|
||||||
AllowServerListings: bool(AllowServerListings),
|
DisplayedSkinParts: byte(DisplayedSkinParts),
|
||||||
|
MainHand: byte(MainHand),
|
||||||
|
EnableTextFiltering: bool(EnableTextFiltering),
|
||||||
|
AllowServerListings: bool(AllowServerListings),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientInformation) Run(ctx context.Context) {}
|
func (c *ClientInformation) Run(ctx context.Context) {}
|
||||||
func (c *ClientInformation) AddPlayer(p *server.Player) {}
|
func (c *ClientInformation) ClientJoin(p *server.Client) {}
|
||||||
func (c *ClientInformation) RemovePlayer(p *server.Player) {}
|
func (c *ClientInformation) ClientLeft(p *server.Client) {}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
func (g *Graph) Init(game *server.Game) {
|
func (g *Graph) Init(game *server.Game) {
|
||||||
game.AddHandler(&server.PacketHandler{
|
game.AddHandler(&server.PacketHandler{
|
||||||
ID: packetid.ServerboundChat,
|
ID: packetid.ServerboundChat,
|
||||||
F: func(player *server.Player, packet server.Packet758) error {
|
F: func(client *server.Client, player *server.Player, packet server.Packet758) error {
|
||||||
var msg pk.String
|
var msg pk.String
|
||||||
if err := pk.Packet(packet).Scan(&msg); err != nil {
|
if err := pk.Packet(packet).Scan(&msg); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -33,12 +33,12 @@ func (g *Graph) Init(game *server.Game) {
|
|||||||
// Run implement server.Component for Graph
|
// Run implement server.Component for Graph
|
||||||
func (g *Graph) Run(ctx context.Context) {}
|
func (g *Graph) Run(ctx context.Context) {}
|
||||||
|
|
||||||
// AddPlayer implement server.Component for Graph
|
// ClientJoin implement server.Component for Graph
|
||||||
func (g *Graph) AddPlayer(p *server.Player) {
|
func (g *Graph) ClientJoin(client *server.Client, _ *server.Player) {
|
||||||
p.WritePacket(server.Packet758(pk.Marshal(
|
client.WritePacket(server.Packet758(pk.Marshal(
|
||||||
packetid.ClientboundCommands, g,
|
packetid.ClientboundCommands, g,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemovePlayer implement server.Component for Graph
|
// ClientLeft implement server.Component for Graph
|
||||||
func (g *Graph) RemovePlayer(p *server.Player) {}
|
func (g *Graph) ClientLeft(_ *server.Client, _ *server.Player) {}
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
type Level interface {
|
type Level interface {
|
||||||
Init(g *Game)
|
Init(g *Game)
|
||||||
Info() LevelInfo
|
Info() LevelInfo
|
||||||
PlayerJoin(p *Player)
|
PlayerJoin(p *Client)
|
||||||
PlayerQuit(p *Player)
|
PlayerQuit(p *Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LevelInfo struct {
|
type LevelInfo struct {
|
||||||
@ -43,7 +43,7 @@ func (s *SimpleDim) Info() LevelInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SimpleDim) PlayerJoin(p *Player) {
|
func (s *SimpleDim) PlayerJoin(p *Client) {
|
||||||
for pos, column := range s.columns {
|
for pos, column := range s.columns {
|
||||||
packet := pk.Marshal(
|
packet := pk.Marshal(
|
||||||
packetid.ClientboundLevelChunkWithLight,
|
packetid.ClientboundLevelChunkWithLight,
|
||||||
@ -53,4 +53,4 @@ func (s *SimpleDim) PlayerJoin(p *Player) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SimpleDim) PlayerQuit(*Player) {}
|
func (s *SimpleDim) PlayerQuit(*Client) {}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
package dimension
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Tnze/go-mc/server"
|
|
||||||
)
|
|
||||||
|
|
||||||
type World struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) Init(g *server.Game) {
|
|
||||||
//TODO implement me
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) Info() server.LevelInfo {
|
|
||||||
//TODO implement me
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) PlayerJoin(p *server.Player) {
|
|
||||||
//TODO implement me
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) PlayerQuit(p *server.Player) {
|
|
||||||
//TODO implement me
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) register() {
|
|
||||||
|
|
||||||
}
|
|
58
server/ecs/bitest.go
Normal file
58
server/ecs/bitest.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
type BitSetLike interface {
|
||||||
|
Set(i Index) (old bool)
|
||||||
|
Unset(i Index) (old bool)
|
||||||
|
Contains(i Index) bool
|
||||||
|
And(other BitSetLike) (result BitSetLike)
|
||||||
|
AndNot(other BitSetLike) (result BitSetLike)
|
||||||
|
Range(f func(eid Index))
|
||||||
|
}
|
||||||
|
|
||||||
|
type BitSet struct {
|
||||||
|
// TODO: this is not a BitSet, I'm just testing
|
||||||
|
values map[Index]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BitSet) Set(i Index) (old bool) {
|
||||||
|
_, old = b.values[i]
|
||||||
|
b.values[i] = struct{}{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BitSet) Unset(i Index) (old bool) {
|
||||||
|
_, old = b.values[i]
|
||||||
|
delete(b.values, i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BitSet) Contains(i Index) bool {
|
||||||
|
_, contains := b.values[i]
|
||||||
|
return contains
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BitSet) And(other BitSetLike) BitSetLike {
|
||||||
|
result := BitSet{values: make(map[Index]struct{})}
|
||||||
|
for i := range b.values {
|
||||||
|
if other.Contains(i) {
|
||||||
|
result.values[i] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BitSet) AndNot(other BitSetLike) BitSetLike {
|
||||||
|
result := BitSet{values: make(map[Index]struct{})}
|
||||||
|
for i := range b.values {
|
||||||
|
if !other.Contains(i) {
|
||||||
|
result.values[i] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BitSet) Range(f func(eid Index)) {
|
||||||
|
for i := range b.values {
|
||||||
|
f(i)
|
||||||
|
}
|
||||||
|
}
|
44
server/ecs/dispatcher.go
Normal file
44
server/ecs/dispatcher.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type Dispatcher struct {
|
||||||
|
waiters map[string][]*sync.WaitGroup
|
||||||
|
tasks []func(w *World, wg *sync.WaitGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDispatcher() *Dispatcher {
|
||||||
|
return &Dispatcher{
|
||||||
|
waiters: make(map[string][]*sync.WaitGroup),
|
||||||
|
tasks: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dispatcher) Add(s System, name string, deps []string) {
|
||||||
|
var start sync.WaitGroup
|
||||||
|
start.Add(len(deps))
|
||||||
|
for _, dep := range deps {
|
||||||
|
if wg, ok := d.waiters[dep]; ok {
|
||||||
|
d.waiters[dep] = append(wg, &start)
|
||||||
|
} else {
|
||||||
|
panic("Unknown deps: " + dep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.tasks = append(d.tasks, func(w *World, done *sync.WaitGroup) {
|
||||||
|
start.Wait()
|
||||||
|
defer done.Done()
|
||||||
|
s.Update(w)
|
||||||
|
for _, wg := range d.waiters[name] {
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dispatcher) Run(w *World) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(d.tasks))
|
||||||
|
for _, f := range d.tasks {
|
||||||
|
go f(w, &wg)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
34
server/ecs/ecs_test.go
Normal file
34
server/ecs/ecs_test.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func Test_common(t *testing.T) {
|
||||||
|
// W
|
||||||
|
w := NewWorld()
|
||||||
|
// C
|
||||||
|
type pos [2]int
|
||||||
|
type vel [2]int
|
||||||
|
Register[pos, *HashMapStorage[pos]](w)
|
||||||
|
Register[vel, *HashMapStorage[vel]](w)
|
||||||
|
// E
|
||||||
|
e1 := w.CreateEntity(pos{0, 0})
|
||||||
|
w.CreateEntity(vel{1, 2})
|
||||||
|
w.CreateEntity(pos{1, 2}, vel{2, 0})
|
||||||
|
// S
|
||||||
|
s1 := FuncSystem(func(p *pos) {
|
||||||
|
t.Log("system 1", p)
|
||||||
|
})
|
||||||
|
s2 := FuncSystem(func(p *pos, v *vel) {
|
||||||
|
t.Log("system 2", p, v)
|
||||||
|
})
|
||||||
|
s3 := FuncSystem(func(p pos, v *vel) {
|
||||||
|
t.Log("system 2", p, v)
|
||||||
|
})
|
||||||
|
// Run
|
||||||
|
s1.Update(w)
|
||||||
|
s2.Update(w)
|
||||||
|
s3.Update(w)
|
||||||
|
|
||||||
|
w.DeleteEntity(e1)
|
||||||
|
s1.Update(w)
|
||||||
|
}
|
78
server/ecs/storage.go
Normal file
78
server/ecs/storage.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Index uint32
|
||||||
|
|
||||||
|
type Storage[T any] interface {
|
||||||
|
Init()
|
||||||
|
GetValue(eid Index) *T
|
||||||
|
SetValue(eid Index, v T)
|
||||||
|
DelValue(eid Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HashMapStorage[T any] struct {
|
||||||
|
values map[Index]*T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashMapStorage[T]) Init() { h.values = make(map[Index]*T) }
|
||||||
|
func (h *HashMapStorage[T]) Len() int { return len(h.values) }
|
||||||
|
func (h *HashMapStorage[T]) GetValue(eid Index) *T { return h.values[eid] }
|
||||||
|
func (h *HashMapStorage[T]) SetValue(eid Index, v T) { h.values[eid] = &v }
|
||||||
|
func (h *HashMapStorage[T]) DelValue(eid Index) { delete(h.values, eid) }
|
||||||
|
func (h *HashMapStorage[T]) Range(f func(eid Index, value *T)) {
|
||||||
|
for i, v := range h.values {
|
||||||
|
f(i, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullStorage[T any] struct{}
|
||||||
|
|
||||||
|
func (NullStorage[T]) Init() {
|
||||||
|
var v T
|
||||||
|
if size := unsafe.Sizeof(v); size != 0 {
|
||||||
|
typeName := reflect.TypeOf(v).String()
|
||||||
|
typeSize := strconv.Itoa(int(size))
|
||||||
|
panic("NullStorage can only be used with ZST, " + typeName + " has size of " + typeSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (NullStorage[T]) GetValue(eid Index) *T { return nil }
|
||||||
|
func (NullStorage[T]) SetValue(eid Index, v T) {}
|
||||||
|
func (NullStorage[T]) DelValue(eid Index) {}
|
||||||
|
|
||||||
|
type MaskedStorage[T any] struct {
|
||||||
|
BitSetLike
|
||||||
|
Storage[T]
|
||||||
|
Len int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MaskedStorage[T]) Init() {
|
||||||
|
if m.BitSetLike == nil {
|
||||||
|
m.BitSetLike = BitSet{make(map[Index]struct{})}
|
||||||
|
}
|
||||||
|
m.Storage.Init()
|
||||||
|
}
|
||||||
|
func (m *MaskedStorage[T]) GetValue(eid Index) *T {
|
||||||
|
if m.Contains(eid) {
|
||||||
|
return m.Storage.GetValue(eid)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *MaskedStorage[T]) GetValueAny(eid Index) any { return m.GetValue(eid) }
|
||||||
|
func (m *MaskedStorage[T]) SetValue(eid Index, v T) {
|
||||||
|
if !m.BitSetLike.Set(eid) {
|
||||||
|
m.Len++
|
||||||
|
}
|
||||||
|
m.Storage.SetValue(eid, v)
|
||||||
|
}
|
||||||
|
func (m *MaskedStorage[T]) SetAny(eid Index, v any) { m.SetValue(eid, v.(T)) }
|
||||||
|
func (m *MaskedStorage[T]) DelValue(eid Index) {
|
||||||
|
if m.BitSetLike.Unset(eid) {
|
||||||
|
m.Len--
|
||||||
|
}
|
||||||
|
m.Storage.DelValue(eid)
|
||||||
|
}
|
66
server/ecs/system.go
Normal file
66
server/ecs/system.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type System interface {
|
||||||
|
Update(w *World)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcsystem struct {
|
||||||
|
update func(w *World)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuncSystem(F any) System {
|
||||||
|
type Storage interface {
|
||||||
|
BitSetLike
|
||||||
|
GetValueAny(eid Index) any
|
||||||
|
}
|
||||||
|
f := reflect.ValueOf(F)
|
||||||
|
in := f.Type().NumIn()
|
||||||
|
argTypes := make([]reflect.Type, in)
|
||||||
|
needCopy := make([]bool, in)
|
||||||
|
for i := 0; i < in; i++ {
|
||||||
|
if t := f.Type().In(i); t.Kind() == reflect.Pointer {
|
||||||
|
argTypes[i] = t.Elem()
|
||||||
|
} else {
|
||||||
|
argTypes[i] = t
|
||||||
|
needCopy[i] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &funcsystem{
|
||||||
|
update: func(w *World) {
|
||||||
|
storages := make([]Storage, in)
|
||||||
|
for i := 0; i < in; i++ {
|
||||||
|
storages[i] = w.GetResourceRaw(argTypes[i]).(Storage)
|
||||||
|
}
|
||||||
|
args := make([]reflect.Value, len(storages))
|
||||||
|
if len(storages) > 0 {
|
||||||
|
set := BitSetLike(storages[0])
|
||||||
|
for _, v := range storages[1:] {
|
||||||
|
set = set.And(v)
|
||||||
|
}
|
||||||
|
set.Range(func(eid Index) {
|
||||||
|
for i := range args {
|
||||||
|
arg := storages[i].GetValueAny(eid)
|
||||||
|
if arg == nil {
|
||||||
|
args[i] = reflect.Zero(argTypes[i])
|
||||||
|
} else if needCopy[i] {
|
||||||
|
args[i] = reflect.ValueOf(arg).Elem()
|
||||||
|
} else {
|
||||||
|
args[i] = reflect.ValueOf(arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.Call(args)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
f.Call(args)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *funcsystem) Update(w *World) {
|
||||||
|
f.update(w)
|
||||||
|
}
|
13
server/ecs/system_test.go
Normal file
13
server/ecs/system_test.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
type PositionComponent struct {
|
||||||
|
X, Y int
|
||||||
|
}
|
||||||
|
|
||||||
|
type MySystem1 struct {
|
||||||
|
*PositionComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySystem1) Update(w *World) {
|
||||||
|
|
||||||
|
}
|
90
server/ecs/world.go
Normal file
90
server/ecs/world.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type World struct {
|
||||||
|
resources map[reflect.Type]any
|
||||||
|
maxEID Index
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorld() *World {
|
||||||
|
return &World{resources: make(map[reflect.Type]any)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetResource[Res any](w *World, v Res) *Res {
|
||||||
|
w.resources[reflect.TypeOf(v)] = &v
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) Remove(resource any) any {
|
||||||
|
t := reflect.ValueOf(resource).Type()
|
||||||
|
resource = w.resources[t]
|
||||||
|
delete(w.resources, t)
|
||||||
|
return resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetResource[Res any](w *World) *Res {
|
||||||
|
var res Res
|
||||||
|
t := reflect.TypeOf(res)
|
||||||
|
if v, ok := w.resources[t]; ok {
|
||||||
|
return v.(*Res)
|
||||||
|
}
|
||||||
|
panic("Resource " + t.Name() + " not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) GetResourceRaw(t reflect.Type) any {
|
||||||
|
v, _ := w.resources[t]
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetComponent[T any](w *World) *MaskedStorage[T] {
|
||||||
|
var value T
|
||||||
|
t := reflect.ValueOf(value).Type()
|
||||||
|
if res, ok := w.resources[t]; ok {
|
||||||
|
return res.(*MaskedStorage[T])
|
||||||
|
}
|
||||||
|
panic("Component " + t.Name() + " not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the component with the storage.
|
||||||
|
//
|
||||||
|
// Will be changed to func (w *World) Register[C Component]() after Go support it
|
||||||
|
func Register[T any, S Storage[T]](w *World) {
|
||||||
|
var value T
|
||||||
|
t := reflect.TypeOf(value)
|
||||||
|
if _, ok := w.resources[t]; ok {
|
||||||
|
panic("Component " + t.Name() + " already exist")
|
||||||
|
}
|
||||||
|
var storage S
|
||||||
|
var storageInt Storage[T]
|
||||||
|
storageType := reflect.TypeOf(storage)
|
||||||
|
if storageType.Kind() == reflect.Pointer {
|
||||||
|
storageInt = reflect.New(storageType.Elem()).Interface().(Storage[T])
|
||||||
|
} else {
|
||||||
|
storageInt = storage
|
||||||
|
}
|
||||||
|
ms := MaskedStorage[T]{Storage: storageInt}
|
||||||
|
ms.Init()
|
||||||
|
w.resources[t] = &ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) CreateEntity(components ...any) (i Index) {
|
||||||
|
type Storage interface{ SetAny(Index, any) }
|
||||||
|
eid := Index(atomic.AddUint32((*uint32)(&w.maxEID), 1))
|
||||||
|
for _, c := range components {
|
||||||
|
w.resources[reflect.TypeOf(c)].(Storage).SetAny(eid, c)
|
||||||
|
}
|
||||||
|
return eid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) DeleteEntity(eid Index) {
|
||||||
|
type Storage interface{ Del(eid Index) }
|
||||||
|
for _, r := range w.resources {
|
||||||
|
if c, ok := r.(Storage); ok {
|
||||||
|
c.Del(eid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
server/entity.go
Normal file
4
server/entity.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
type Pos struct{ X, Y, Z float64 }
|
||||||
|
type Rot struct{ Yaw, Pitch float32 }
|
@ -3,15 +3,13 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"sync"
|
"time"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
|
||||||
"github.com/Tnze/go-mc/nbt"
|
|
||||||
"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/Tnze/go-mc/server/ecs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GamePlay interface {
|
type GamePlay interface {
|
||||||
@ -23,18 +21,10 @@ type GamePlay interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
Dim Level
|
*ecs.World
|
||||||
components []Component
|
*ecs.Dispatcher
|
||||||
handlers map[int32][]*PacketHandler
|
handlers map[int32][]*PacketHandler
|
||||||
|
components []Component
|
||||||
eid int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type Component interface {
|
|
||||||
Init(g *Game)
|
|
||||||
Run(ctx context.Context)
|
|
||||||
AddPlayer(p *Player)
|
|
||||||
RemovePlayer(p *Player)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PacketHandler struct {
|
type PacketHandler struct {
|
||||||
@ -42,24 +32,27 @@ type PacketHandler struct {
|
|||||||
F packetHandlerFunc
|
F packetHandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type packetHandlerFunc func(player *Player, packet Packet758) error
|
type packetHandlerFunc func(client *Client, player *Player, packet Packet758) error
|
||||||
|
|
||||||
//go:embed DimensionCodec.snbt
|
type Component interface {
|
||||||
var dimensionCodecSNBT nbt.StringifiedMessage
|
Init(g *Game)
|
||||||
|
Run(ctx context.Context)
|
||||||
|
ClientJoin(c *Client, p *Player)
|
||||||
|
ClientLeft(c *Client, p *Player)
|
||||||
|
}
|
||||||
|
|
||||||
//go:embed Dimension.snbt
|
func NewGame(components ...Component) *Game {
|
||||||
var dimensionSNBT nbt.StringifiedMessage
|
|
||||||
|
|
||||||
func NewGame(dim Level, components ...Component) *Game {
|
|
||||||
g := &Game{
|
g := &Game{
|
||||||
Dim: dim,
|
World: ecs.NewWorld(),
|
||||||
components: components,
|
Dispatcher: ecs.NewDispatcher(),
|
||||||
handlers: make(map[int32][]*PacketHandler),
|
handlers: make(map[int32][]*PacketHandler),
|
||||||
|
components: components,
|
||||||
}
|
}
|
||||||
dim.Init(g)
|
for _, c := range components {
|
||||||
for _, v := range components {
|
c.Init(g)
|
||||||
v.Init(g)
|
|
||||||
}
|
}
|
||||||
|
ecs.Register[Client, *ecs.HashMapStorage[Client]](g.World)
|
||||||
|
ecs.Register[Player, *ecs.HashMapStorage[Player]](g.World)
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,73 +61,68 @@ func (g *Game) AddHandler(ph *PacketHandler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Run(ctx context.Context) {
|
func (g *Game) Run(ctx context.Context) {
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(len(g.components))
|
|
||||||
for _, c := range g.components {
|
for _, c := range g.components {
|
||||||
go func(c Component) {
|
go c.Run(ctx)
|
||||||
defer wg.Done()
|
}
|
||||||
c.Run(ctx)
|
ticker := time.NewTicker(time.Second / 20)
|
||||||
}(c)
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
g.Dispatcher.Run(g.World)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
wg.Wait()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) {
|
func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) {
|
||||||
p := NewPlayer(conn, name, id, g.newEID(), 1)
|
eid := g.CreateEntity(
|
||||||
defer p.Close()
|
Client{
|
||||||
dimInfo := g.Dim.Info()
|
Conn: conn,
|
||||||
err := p.Conn.WritePacket(pk.Marshal(
|
Protocol: protocol,
|
||||||
packetid.ClientboundLogin,
|
packetQueue: NewPacketQueue(),
|
||||||
pk.Int(p.EntityID), // Entity ID
|
errChan: make(chan error, 1),
|
||||||
pk.Boolean(false), // Is hardcore
|
},
|
||||||
pk.Byte(p.Gamemode), // Gamemode
|
Player{
|
||||||
pk.Byte(-1), // Prev Gamemode
|
UUID: id,
|
||||||
pk.Array([]pk.Identifier{
|
Name: name,
|
||||||
pk.Identifier(dimInfo.Name),
|
},
|
||||||
}),
|
)
|
||||||
pk.NBT(dimensionCodecSNBT),
|
c := ecs.GetComponent[Client](g.World).GetValue(eid)
|
||||||
pk.NBT(dimensionSNBT),
|
p := ecs.GetComponent[Player](g.World).GetValue(eid)
|
||||||
pk.Identifier(dimInfo.Name), // World Name
|
defer c.packetQueue.Close()
|
||||||
pk.Long(dimInfo.HashedSeed), // Hashed seed
|
|
||||||
pk.VarInt(0), // Max Players (Ignored by client)
|
|
||||||
pk.VarInt(15), // View Distance
|
|
||||||
pk.VarInt(15), // Simulation Distance
|
|
||||||
pk.Boolean(false), // Reduced Debug Info
|
|
||||||
pk.Boolean(true), // Enable respawn screen
|
|
||||||
pk.Boolean(false), // Is Debug
|
|
||||||
pk.Boolean(true), // Is Flat
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Dim.PlayerJoin(p)
|
go func() {
|
||||||
defer g.Dim.PlayerQuit(p)
|
for {
|
||||||
|
packet, ok := c.packetQueue.Pull()
|
||||||
for _, c := range g.components {
|
if !ok {
|
||||||
c.AddPlayer(p)
|
break
|
||||||
if err := p.GetErr(); err != nil {
|
}
|
||||||
return
|
err := c.Conn.WritePacket(packet)
|
||||||
|
if err != nil {
|
||||||
|
c.PutErr(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer c.RemovePlayer(p)
|
}()
|
||||||
|
|
||||||
|
for _, component := range g.components {
|
||||||
|
component.ClientJoin(c, p)
|
||||||
|
defer component.ClientLeft(c, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
var packet pk.Packet
|
var packet pk.Packet
|
||||||
for {
|
for {
|
||||||
if err := p.ReadPacket(&packet); err != nil {
|
if err := c.ReadPacket(&packet); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, ph := range g.handlers[packet.ID] {
|
for _, ph := range g.handlers[packet.ID] {
|
||||||
if err := ph.F(p, Packet758(packet)); err != nil {
|
if err := ph.F(c, p, Packet758(packet)); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := p.GetErr(); err != nil {
|
if err := c.GetErr(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) newEID() int32 {
|
|
||||||
return atomic.AddInt32(&g.eid, 1)
|
|
||||||
}
|
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
type BitSet struct {
|
|
||||||
// TODO: this is not a bitset, I'm just testing
|
|
||||||
values map[Index]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BitSet) Set(i Index) (old bool) {
|
|
||||||
_, old = b.values[i]
|
|
||||||
b.values[i] = struct{}{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BitSet) Unset(i Index) (old bool) {
|
|
||||||
_, old = b.values[i]
|
|
||||||
delete(b.values, i)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BitSet) Contains(i Index) bool {
|
|
||||||
_, contains := b.values[i]
|
|
||||||
return contains
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BitSet) And(other BitSet) (result BitSet) {
|
|
||||||
result = BitSet{values: make(map[Index]struct{})}
|
|
||||||
for i := range b.values {
|
|
||||||
if other.Contains(i) {
|
|
||||||
result.values[i] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BitSet) Range(f func(eid Index)) {
|
|
||||||
for i := range b.values {
|
|
||||||
f(i)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func Test_common(t *testing.T) {
|
|
||||||
// W
|
|
||||||
w := NewWorld()
|
|
||||||
// C
|
|
||||||
type pos [2]int
|
|
||||||
type vel [2]int
|
|
||||||
Register(w, pos{})
|
|
||||||
Register(w, vel{})
|
|
||||||
// E
|
|
||||||
w.CreateEntity(pos{0, 0})
|
|
||||||
w.CreateEntity(vel{1, 2})
|
|
||||||
w.CreateEntity(pos{1, 2}, vel{2, 0})
|
|
||||||
// S
|
|
||||||
s1 := FuncSystem(func(p pos) {
|
|
||||||
t.Log("system 1", p)
|
|
||||||
})
|
|
||||||
s2 := FuncSystem(func(p pos, v vel) {
|
|
||||||
t.Log("system 2", p, v)
|
|
||||||
})
|
|
||||||
// Run
|
|
||||||
s1.Update(w)
|
|
||||||
s2.Update(w)
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
type Index uint32
|
|
||||||
|
|
||||||
type Storage interface {
|
|
||||||
Get(eid Index) any
|
|
||||||
Insert(eid Index, v any)
|
|
||||||
Remove(eid Index) any
|
|
||||||
BitSet() BitSet
|
|
||||||
}
|
|
||||||
|
|
||||||
type HashMapStorage[T any] struct {
|
|
||||||
keys BitSet
|
|
||||||
values map[Index]T
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHashMapStorage[T any]() *HashMapStorage[T] {
|
|
||||||
return &HashMapStorage[T]{
|
|
||||||
keys: BitSet{values: make(map[Index]struct{})},
|
|
||||||
values: make(map[Index]T),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (h *HashMapStorage[T]) Get(eid Index) any { return h.values[eid] }
|
|
||||||
func (h *HashMapStorage[T]) Insert(eid Index, v any) {
|
|
||||||
h.keys.Set(eid)
|
|
||||||
h.values[eid] = v.(T)
|
|
||||||
}
|
|
||||||
func (h *HashMapStorage[T]) Remove(eid Index) any {
|
|
||||||
h.keys.Unset(eid)
|
|
||||||
v := h.values[eid]
|
|
||||||
delete(h.values, eid)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
func (h *HashMapStorage[T]) BitSet() BitSet { return h.keys }
|
|
@ -1,49 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
type System interface {
|
|
||||||
Update(w *World)
|
|
||||||
}
|
|
||||||
|
|
||||||
type funcsystem struct {
|
|
||||||
update reflect.Value
|
|
||||||
args func(w *World) []Storage
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuncSystem(F any) System {
|
|
||||||
f := reflect.ValueOf(F)
|
|
||||||
in := f.Type().NumIn()
|
|
||||||
argTypes := make([]reflect.Type, in)
|
|
||||||
for i := 0; i < in; i++ {
|
|
||||||
argTypes[i] = f.Type().In(i)
|
|
||||||
}
|
|
||||||
return &funcsystem{
|
|
||||||
update: f,
|
|
||||||
args: func(w *World) (args []Storage) {
|
|
||||||
args = make([]Storage, in)
|
|
||||||
for i := 0; i < in; i++ {
|
|
||||||
args[i] = w.GetResourceRaw(argTypes[i]).(Storage)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *funcsystem) Update(w *World) {
|
|
||||||
storages := f.args(w)
|
|
||||||
if len(storages) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
eids := storages[0].BitSet()
|
|
||||||
for _, v := range storages[1:] {
|
|
||||||
eids = eids.And(v.BitSet())
|
|
||||||
}
|
|
||||||
args := make([]reflect.Value, len(storages))
|
|
||||||
eids.Range(func(eid Index) {
|
|
||||||
for i := range args {
|
|
||||||
args[i] = reflect.ValueOf(storages[i].Get(eid))
|
|
||||||
}
|
|
||||||
f.update.Call(args)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
type World struct {
|
|
||||||
resources map[reflect.Type]any
|
|
||||||
maxEID Index
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWorld() *World {
|
|
||||||
return &World{resources: make(map[reflect.Type]any)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) Insert(resource any) {
|
|
||||||
t := reflect.ValueOf(resource).Type()
|
|
||||||
w.resources[t] = resource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) Remove(resource any) any {
|
|
||||||
t := reflect.ValueOf(resource).Type()
|
|
||||||
resource = w.resources[t]
|
|
||||||
delete(w.resources, t)
|
|
||||||
return resource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) GetResource(resource any) any {
|
|
||||||
if resource == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t := reflect.ValueOf(resource).Type()
|
|
||||||
v, _ := w.resources[t]
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) GetResourceRaw(t reflect.Type) any {
|
|
||||||
v, _ := w.resources[t]
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func Register[T any](w *World, component T) {
|
|
||||||
t := reflect.TypeOf(component)
|
|
||||||
s := NewHashMapStorage[T]()
|
|
||||||
w.resources[t] = s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) CreateEntity(components ...any) (i Index) {
|
|
||||||
i = Index(atomic.AddUint32((*uint32)(&w.maxEID), 1))
|
|
||||||
for _, c := range components {
|
|
||||||
v := reflect.ValueOf(c)
|
|
||||||
t := v.Type()
|
|
||||||
storage := w.resources[t].(Storage)
|
|
||||||
storage.Insert(w.maxEID, c)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -4,11 +4,11 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/google/uuid"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"github.com/Tnze/go-mc/server/ecs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// keepAliveInterval represents the interval when the server sends keep alive
|
// keepAliveInterval represents the interval when the server sends keep alive
|
||||||
@ -17,54 +17,60 @@ 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 {
|
||||||
|
Delay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
type KeepAlive struct {
|
type KeepAlive struct {
|
||||||
join chan *Player
|
join chan *Client
|
||||||
quit chan *Player
|
quit chan *Client
|
||||||
tick chan *Player
|
tick chan *Client
|
||||||
|
|
||||||
pingList *list.List
|
pingList *list.List
|
||||||
waitList *list.List
|
waitList *list.List
|
||||||
listIndex map[uuid.UUID]*list.Element
|
listIndex map[*Client]*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 *Player, delay time.Duration)
|
updatePlayerDelay []func(p *Client, delay time.Duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKeepAlive() (k *KeepAlive) {
|
func NewKeepAlive() (k *KeepAlive) {
|
||||||
return &KeepAlive{
|
return &KeepAlive{
|
||||||
join: make(chan *Player),
|
join: make(chan *Client),
|
||||||
quit: make(chan *Player),
|
quit: make(chan *Client),
|
||||||
tick: make(chan *Player),
|
tick: make(chan *Client),
|
||||||
pingList: list.New(),
|
pingList: list.New(),
|
||||||
waitList: list.New(),
|
waitList: list.New(),
|
||||||
listIndex: make(map[uuid.UUID]*list.Element),
|
listIndex: make(map[*Client]*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 *Player, delay time.Duration)) {
|
func (k *KeepAlive) AddPlayerDelayUpdateHandler(f func(p *Client, delay time.Duration)) {
|
||||||
if k.updatePlayerDelay != nil {
|
k.updatePlayerDelay = append(k.updatePlayerDelay, f)
|
||||||
panic("add player update handler twice")
|
|
||||||
}
|
|
||||||
k.updatePlayerDelay = f
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init implement Component for KeepAlive
|
// Init implement Component for KeepAlive
|
||||||
func (k *KeepAlive) Init(g *Game) {
|
func (k *KeepAlive) Init(g *Game) {
|
||||||
|
ecs.Register[ClientDelay, *ecs.HashMapStorage[ClientDelay]](g.World)
|
||||||
|
k.AddPlayerDelayUpdateHandler(func(p *Client, delay time.Duration) {
|
||||||
|
c := ClientDelay{Delay: delay}
|
||||||
|
ecs.GetComponent[ClientDelay](g.World).SetValue(p.Index, c)
|
||||||
|
})
|
||||||
g.AddHandler(&PacketHandler{
|
g.AddHandler(&PacketHandler{
|
||||||
ID: packetid.ServerboundKeepAlive,
|
ID: packetid.ServerboundKeepAlive,
|
||||||
F: func(player *Player, packet Packet758) error {
|
F: func(client *Client, player *Player, packet Packet758) error {
|
||||||
var KeepAliveID pk.Long
|
var KeepAliveID pk.Long
|
||||||
if err := pk.Packet(packet).Scan(&KeepAliveID); err != nil {
|
if err := pk.Packet(packet).Scan(&KeepAliveID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
k.tick <- player
|
k.tick <- client
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -90,21 +96,21 @@ func (k *KeepAlive) Run(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPlayer implement Component for KeepAlive
|
// ClientJoin implement Component for KeepAlive
|
||||||
func (k *KeepAlive) AddPlayer(player *Player) { k.join <- player }
|
func (k *KeepAlive) ClientJoin(client *Client, _ *Player) { k.join <- client }
|
||||||
|
|
||||||
// RemovePlayer implement Component for KeepAlive
|
// ClientLeft implement Component for KeepAlive
|
||||||
func (k *KeepAlive) RemovePlayer(p *Player) { k.quit <- p }
|
func (k *KeepAlive) ClientLeft(client *Client, _ *Player) { k.quit <- client }
|
||||||
|
|
||||||
func (k KeepAlive) pushPlayer(p *Player) {
|
func (k KeepAlive) pushPlayer(p *Client) {
|
||||||
k.listIndex[p.UUID] = k.pingList.PushBack(
|
k.listIndex[p] = k.pingList.PushBack(
|
||||||
keepAliveItem{player: p, t: time.Now()},
|
keepAliveItem{player: p, t: time.Now()},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KeepAlive) removePlayer(p *Player) {
|
func (k *KeepAlive) removePlayer(p *Client) {
|
||||||
elem := k.listIndex[p.UUID]
|
elem := k.listIndex[p]
|
||||||
delete(k.listIndex, p.UUID)
|
delete(k.listIndex, p)
|
||||||
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,
|
||||||
@ -126,7 +132,7 @@ func (k *KeepAlive) pingPlayer(now time.Time) {
|
|||||||
)))
|
)))
|
||||||
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.UUID] = k.waitList.PushBack(
|
k.listIndex[p] = k.waitList.PushBack(
|
||||||
keepAliveItem{player: p, t: now},
|
keepAliveItem{player: p, t: now},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -134,10 +140,10 @@ func (k *KeepAlive) pingPlayer(now time.Time) {
|
|||||||
keepAliveSetTimer(k.pingList, k.listTimer, keepAliveInterval)
|
keepAliveSetTimer(k.pingList, k.listTimer, keepAliveInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KeepAlive) tickPlayer(p *Player) {
|
func (k *KeepAlive) tickPlayer(p *Client) {
|
||||||
elem, ok := k.listIndex[p.UUID]
|
elem, ok := k.listIndex[p]
|
||||||
if !ok {
|
if !ok {
|
||||||
p.PutErr(errors.New("keepalive: fail to tick player: " + p.UUID.String() + " not found"))
|
p.PutErr(errors.New("keepalive: fail to tick player: client not found"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if elem.Prev() == nil {
|
if elem.Prev() == nil {
|
||||||
@ -147,13 +153,13 @@ func (k *KeepAlive) tickPlayer(p *Player) {
|
|||||||
defer keepAliveSetTimer(k.waitList, k.waitTimer, keepAliveWaitInterval)
|
defer keepAliveSetTimer(k.waitList, k.waitTimer, keepAliveWaitInterval)
|
||||||
}
|
}
|
||||||
// update delay of player
|
// update delay of player
|
||||||
t := k.waitList.Remove(elem).(keepAliveItem).t
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if k.updatePlayerDelay != nil {
|
delay := now.Sub(k.waitList.Remove(elem).(keepAliveItem).t)
|
||||||
k.updatePlayerDelay(p, now.Sub(t))
|
for _, f := range k.updatePlayerDelay {
|
||||||
|
f(p, delay)
|
||||||
}
|
}
|
||||||
// move the player to ping list
|
// move the player to ping list
|
||||||
k.listIndex[p.UUID] = k.pingList.PushBack(
|
k.listIndex[p] = k.pingList.PushBack(
|
||||||
keepAliveItem{player: p, t: now},
|
keepAliveItem{player: p, t: now},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -180,6 +186,6 @@ func keepAliveSetTimer(l *list.List, timer *time.Timer, interval time.Duration)
|
|||||||
}
|
}
|
||||||
|
|
||||||
type keepAliveItem struct {
|
type keepAliveItem struct {
|
||||||
player *Player
|
player *Client
|
||||||
t time.Time
|
t time.Time
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,16 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"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"
|
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListPingHandler collect server running status info
|
// ListPingHandler collect server running status info
|
||||||
|
112
server/player/player.go
Normal file
112
server/player/player.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package player
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"github.com/Tnze/go-mc/server"
|
||||||
|
"github.com/Tnze/go-mc/server/ecs"
|
||||||
|
"github.com/Tnze/go-mc/server/world"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlayerProfile struct {
|
||||||
|
Dim ecs.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
type playerSpawnSystem struct {
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p playerSpawnSystem) Update(w *ecs.World) {
|
||||||
|
clients := ecs.GetComponent[server.Client](w)
|
||||||
|
players := ecs.GetComponent[server.Player](w)
|
||||||
|
profiles := ecs.GetComponent[PlayerProfile](w)
|
||||||
|
dimensionRes := ecs.GetResource[world.DimensionList](w)
|
||||||
|
players.AndNot(profiles.BitSetLike).Range(func(eid ecs.Index) {
|
||||||
|
player := players.GetValue(eid)
|
||||||
|
client := clients.GetValue(eid)
|
||||||
|
profile, err := p.GetPlayer(player.UUID)
|
||||||
|
if err != nil {
|
||||||
|
client.PutErr(fmt.Errorf("read player data fail: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("load player info successes", profile)
|
||||||
|
dim, ok := dimensionRes.Find(profile.Dimension)
|
||||||
|
if !ok {
|
||||||
|
panic("dimension " + profile.Dimension + " not found")
|
||||||
|
}
|
||||||
|
profiles.SetValue(eid, PlayerProfile{Dim: dim})
|
||||||
|
client.WritePacket(server.Packet758(pk.Marshal(
|
||||||
|
packetid.ClientboundLogin,
|
||||||
|
pk.Int(eid), // Entity ID
|
||||||
|
pk.Boolean(false), // Is hardcore
|
||||||
|
pk.Byte(profile.PlayerGameType), // Gamemode
|
||||||
|
pk.Byte(-1), // Prev Gamemode
|
||||||
|
dimensionRes,
|
||||||
|
pk.NBT(dimensionRes.DimCodecSNBT),
|
||||||
|
pk.NBT(dimensionRes.DimSNBT),
|
||||||
|
pk.Identifier(profile.Dimension), // World Name
|
||||||
|
pk.Long(1234567), // Hashed seed
|
||||||
|
pk.VarInt(0), // Max Players (Ignored by client)
|
||||||
|
pk.VarInt(15), // View Distance
|
||||||
|
pk.VarInt(15), // Simulation Distance
|
||||||
|
pk.Boolean(false), // Reduced Debug Info
|
||||||
|
pk.Boolean(true), // Enable respawn screen
|
||||||
|
pk.Boolean(false), // Is Debug
|
||||||
|
pk.Boolean(true), // Is Flat
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpawnSystem(g *server.Game, playerdataPath string) {
|
||||||
|
ecs.Register[PlayerProfile, *ecs.HashMapStorage[PlayerProfile]](g.World)
|
||||||
|
g.Dispatcher.Add(playerSpawnSystem{storage: storage{playerdataPath}}, "go-mc:player:SpawnSystem", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PosAndRotSystem add a system to g.Dispatcher that
|
||||||
|
// receive player movement packets and update Pos and Rot component
|
||||||
|
// Require component Pos and Rot to be registered before.
|
||||||
|
func PosAndRotSystem(g *server.Game) {
|
||||||
|
type posUpdate struct {
|
||||||
|
ecs.Index
|
||||||
|
server.Pos
|
||||||
|
}
|
||||||
|
updateChan := make(chan posUpdate)
|
||||||
|
ecs.Register[server.Pos, *ecs.HashMapStorage[server.Pos]](g.World)
|
||||||
|
ecs.Register[server.Rot, *ecs.HashMapStorage[server.Rot]](g.World)
|
||||||
|
g.Dispatcher.Add(ecs.FuncSystem(func() {
|
||||||
|
posStorage := ecs.GetComponent[server.Pos](g.World)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-updateChan:
|
||||||
|
if v := posStorage.GetValue(event.Index); v != nil {
|
||||||
|
*v = event.Pos
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), "go-mc:player:PosAndRotSystem", nil)
|
||||||
|
|
||||||
|
g.AddHandler(&server.PacketHandler{
|
||||||
|
ID: packetid.ServerboundMovePlayerPos,
|
||||||
|
F: func(client *server.Client, player *server.Player, packet server.Packet758) error {
|
||||||
|
var X, FeetY, Z pk.Double
|
||||||
|
var OnGround pk.Boolean
|
||||||
|
err := pk.Packet(packet).Scan(&X, &FeetY, &Z, &OnGround)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
updateChan <- posUpdate{
|
||||||
|
Index: client.Index,
|
||||||
|
Pos: server.Pos{
|
||||||
|
X: float64(X),
|
||||||
|
Y: float64(FeetY),
|
||||||
|
Z: float64(Z),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
163
server/player/playerinfo.go
Normal file
163
server/player/playerinfo.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package player
|
||||||
|
|
||||||
|
//import (
|
||||||
|
// "context"
|
||||||
|
// "io"
|
||||||
|
// "time"
|
||||||
|
//
|
||||||
|
// "github.com/Tnze/go-mc/data/packetid"
|
||||||
|
// pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
// "github.com/Tnze/go-mc/server"
|
||||||
|
// "github.com/Tnze/go-mc/server/ecs"
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//type PlayerInfo struct {
|
||||||
|
// updateDelay chan playerDelayUpdate
|
||||||
|
// quit chan clientAndPlayer
|
||||||
|
//}
|
||||||
|
//type clientAndPlayer struct {
|
||||||
|
// *server.Client
|
||||||
|
// *server.Player
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//type playerInfoList struct {
|
||||||
|
// players ecs.MaskedStorage[server.Player]
|
||||||
|
// delays ecs.MaskedStorage[server.ClientDelay]
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (p playerInfoList) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
// n, err = pk.VarInt(p.players.Len).WriteTo(w)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// var n1 int64
|
||||||
|
// p.players.And(p.delays.BitSetLike).Range(func(eid ecs.Index) {
|
||||||
|
// p := playerDelayUpdate{
|
||||||
|
// player: p.players.Get(eid),
|
||||||
|
// delay: p.delays.Get(eid).Delay,
|
||||||
|
// }
|
||||||
|
// n1, err = p.WriteTo(w)
|
||||||
|
// n += n1
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//type playerDelayUpdate struct {
|
||||||
|
// player *server.Player
|
||||||
|
// delay time.Duration
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (p playerDelayUpdate) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
// return pk.Tuple{
|
||||||
|
// pk.UUID(p.player.UUID),
|
||||||
|
// pk.VarInt(p.delay.Milliseconds()),
|
||||||
|
// }.WriteTo(w)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//const (
|
||||||
|
// actionAddPlayer = iota
|
||||||
|
// actionUpdateGamemode
|
||||||
|
// actionUpdateLatency
|
||||||
|
// actionUpdateDisplayName
|
||||||
|
// actionRemovePlayer
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//type DelaySource interface {
|
||||||
|
// AddPlayerDelayUpdateHandler(f func(c *server.Client, p *server.Player, delay time.Duration))
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func NewPlayerInfo(delaySource DelaySource) *PlayerInfo {
|
||||||
|
// updateChan := make(chan playerDelayUpdate)
|
||||||
|
// p := &PlayerInfo{
|
||||||
|
// updateDelay: updateChan,
|
||||||
|
// quit: make(chan clientAndPlayer),
|
||||||
|
// }
|
||||||
|
// if delaySource != nil {
|
||||||
|
// delaySource.AddPlayerDelayUpdateHandler(func(client *server.Client, player *server.Player, delay time.Duration) {
|
||||||
|
// updateChan <- playerDelayUpdate{player: player, delay: delay}
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// return p
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//type playerInfoSystemJoin struct{}
|
||||||
|
//
|
||||||
|
//func (p playerInfoSystemJoin) Update(w *ecs.World) {
|
||||||
|
// clients := ecs.GetComponent[server.Client](w)
|
||||||
|
// players := ecs.GetComponent[server.Player](w)
|
||||||
|
// delays := ecs.GetComponent[server.ClientDelay](w)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (p *PlayerInfo) Init(g *server.Game) {
|
||||||
|
// var delayBuffer []playerDelayUpdate
|
||||||
|
// clients := ecs.GetComponent[server.Client](g.World)
|
||||||
|
// players := ecs.GetComponent[server.Player](g.World)
|
||||||
|
// delays := ecs.GetComponent[server.ClientDelay](g.World)
|
||||||
|
// g.Dispatcher.Add(ecs.FuncSystem(func(client *server.Client, player *server.Player, delay server.ClientDelay) {
|
||||||
|
// info := server.ClientDelay{Delay: 0}
|
||||||
|
// pack := server.Packet758(pk.Marshal(
|
||||||
|
// packetid.ClientboundPlayerInfo,
|
||||||
|
// pk.VarInt(actionAddPlayer),
|
||||||
|
// pk.VarInt(1),
|
||||||
|
//
|
||||||
|
// pk.UUID(player.UUID),
|
||||||
|
// pk.String(player.Name),
|
||||||
|
// pk.Array([]pk.FieldEncoder{}),
|
||||||
|
// pk.VarInt(profile.Gamemode),
|
||||||
|
// pk.VarInt(0),
|
||||||
|
// pk.Boolean(false),
|
||||||
|
// ))
|
||||||
|
// delays.Set(client.Index, info)
|
||||||
|
// clients.Range(func(eid ecs.Index) {
|
||||||
|
// clients.Get(eid).WritePacket(pack)
|
||||||
|
// })
|
||||||
|
// client.WritePacket(server.Packet758(pk.Marshal(
|
||||||
|
// packetid.ClientboundPlayerInfo,
|
||||||
|
// pk.VarInt(actionAddPlayer),
|
||||||
|
// playerInfoList{players: players, delays: delays},
|
||||||
|
// )))
|
||||||
|
// }), "PlayerInfoSystem:Join", nil)
|
||||||
|
// g.Dispatcher.Add(ecs.FuncSystem(func() {
|
||||||
|
// for {
|
||||||
|
// select {
|
||||||
|
// case cp := <-p.quit:
|
||||||
|
// pack := server.Packet758(pk.Marshal(
|
||||||
|
// packetid.ClientboundPlayerInfo,
|
||||||
|
// pk.VarInt(actionRemovePlayer),
|
||||||
|
// pk.VarInt(1),
|
||||||
|
// pk.UUID(cp.UUID),
|
||||||
|
// ))
|
||||||
|
// for _, p := range players.list {
|
||||||
|
// cp.WritePacket(pack)
|
||||||
|
// }
|
||||||
|
// case change := <-p.updateDelay:
|
||||||
|
// delayBuffer = append(delayBuffer, change)
|
||||||
|
// default:
|
||||||
|
// if len(delayBuffer) > 0 {
|
||||||
|
// pack := server.Packet758(pk.Marshal(
|
||||||
|
// packetid.ClientboundPlayerInfo,
|
||||||
|
// pk.VarInt(actionUpdateLatency),
|
||||||
|
// pk.Array(&delayBuffer),
|
||||||
|
// ))
|
||||||
|
// players.Range(func(eid ecs.Index) {
|
||||||
|
// players.Get(eid).(*server.Client).WritePacket(pack)
|
||||||
|
// })
|
||||||
|
// delayBuffer = delayBuffer[:0]
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }), "PlayerInfoSystem", nil)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (p *PlayerInfo) Run(context.Context) {}
|
||||||
|
//func (p *PlayerInfo) ClientJoin(client *server.Client, player *server.Player) {}
|
||||||
|
//func (p *PlayerInfo) ClientLeft(client *server.Client, player *server.Player) {
|
||||||
|
// p.quit <- clientAndPlayer{
|
||||||
|
// Client: client,
|
||||||
|
// Player: player,
|
||||||
|
// }
|
||||||
|
//}
|
37
server/player/pool.go
Normal file
37
server/player/pool.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package player
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/level"
|
||||||
|
"github.com/Tnze/go-mc/save"
|
||||||
|
)
|
||||||
|
|
||||||
|
type storage struct {
|
||||||
|
playerdataDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *storage) GetPlayer(id uuid.UUID) (data save.PlayerData, err error) {
|
||||||
|
filename := id.String() + ".dat"
|
||||||
|
|
||||||
|
f, err := os.Open(filepath.Join(s.playerdataDir, filename))
|
||||||
|
if err != nil {
|
||||||
|
return save.PlayerData{}, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
r, err := gzip.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return save.PlayerData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return save.ReadPlayerData(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *storage) PutPlayer(pos level.ChunkPos, c *level.Chunk) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,148 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PlayerInfo struct {
|
|
||||||
updateDelay chan playerDelayInfo
|
|
||||||
join chan *Player
|
|
||||||
quit chan *Player
|
|
||||||
ticker *time.Ticker
|
|
||||||
}
|
|
||||||
|
|
||||||
type playerDelayInfo struct {
|
|
||||||
player *Player
|
|
||||||
delay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerDelayInfo) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
return pk.Tuple{
|
|
||||||
pk.UUID(p.player.UUID),
|
|
||||||
pk.String(p.player.Name),
|
|
||||||
pk.Array([]pk.FieldEncoder{}),
|
|
||||||
pk.VarInt(p.player.Gamemode),
|
|
||||||
pk.VarInt(p.delay),
|
|
||||||
pk.Boolean(false),
|
|
||||||
}.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
type playerInfoList struct {
|
|
||||||
list map[uuid.UUID]playerDelayInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playerInfoList) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
n, err = pk.VarInt(len(p.list)).WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var n1 int64
|
|
||||||
for _, p := range p.list {
|
|
||||||
n1, err = p.WriteTo(w)
|
|
||||||
n += n1
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type playerDelayUpdate playerDelayInfo
|
|
||||||
|
|
||||||
func (p playerDelayUpdate) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
return pk.Tuple{
|
|
||||||
pk.UUID(p.player.UUID),
|
|
||||||
pk.VarInt(p.delay.Milliseconds()),
|
|
||||||
}.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
actionAddPlayer = iota
|
|
||||||
actionUpdateGamemode
|
|
||||||
actionUpdateLatency
|
|
||||||
actionUpdateDisplayName
|
|
||||||
actionRemovePlayer
|
|
||||||
)
|
|
||||||
|
|
||||||
type PlayerDelaySource interface {
|
|
||||||
AddPlayerDelayUpdateHandler(f func(p *Player, delay time.Duration))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPlayerInfo(updateFreq time.Duration, delaySource PlayerDelaySource) *PlayerInfo {
|
|
||||||
p := &PlayerInfo{
|
|
||||||
updateDelay: make(chan playerDelayInfo),
|
|
||||||
join: make(chan *Player),
|
|
||||||
quit: make(chan *Player),
|
|
||||||
ticker: time.NewTicker(updateFreq),
|
|
||||||
}
|
|
||||||
if delaySource != nil {
|
|
||||||
delaySource.AddPlayerDelayUpdateHandler(p.onPlayerDelayUpdate)
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PlayerInfo) Init(*Game) {}
|
|
||||||
|
|
||||||
func (p *PlayerInfo) Run(ctx context.Context) {
|
|
||||||
players := &playerInfoList{list: make(map[uuid.UUID]playerDelayInfo)}
|
|
||||||
var delayBuffer []playerDelayUpdate
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case player := <-p.join:
|
|
||||||
info := playerDelayInfo{player: player, delay: 0}
|
|
||||||
pack := Packet758(pk.Marshal(
|
|
||||||
packetid.ClientboundPlayerInfo,
|
|
||||||
pk.VarInt(actionAddPlayer),
|
|
||||||
pk.VarInt(1),
|
|
||||||
&info,
|
|
||||||
))
|
|
||||||
players.list[player.UUID] = info
|
|
||||||
for _, p := range players.list {
|
|
||||||
p.player.WritePacket(pack)
|
|
||||||
}
|
|
||||||
player.WritePacket(Packet758(pk.Marshal(
|
|
||||||
packetid.ClientboundPlayerInfo,
|
|
||||||
pk.VarInt(actionAddPlayer),
|
|
||||||
players,
|
|
||||||
)))
|
|
||||||
case player := <-p.quit:
|
|
||||||
delete(players.list, player.UUID)
|
|
||||||
pack := Packet758(pk.Marshal(
|
|
||||||
packetid.ClientboundPlayerInfo,
|
|
||||||
pk.VarInt(actionRemovePlayer),
|
|
||||||
pk.VarInt(1),
|
|
||||||
pk.UUID(player.UUID),
|
|
||||||
))
|
|
||||||
for _, p := range players.list {
|
|
||||||
p.player.WritePacket(pack)
|
|
||||||
}
|
|
||||||
case change := <-p.updateDelay:
|
|
||||||
delayBuffer = append(delayBuffer, playerDelayUpdate(change))
|
|
||||||
case <-p.ticker.C:
|
|
||||||
pack := Packet758(pk.Marshal(
|
|
||||||
packetid.ClientboundPlayerInfo,
|
|
||||||
pk.VarInt(actionUpdateLatency),
|
|
||||||
pk.Array(&delayBuffer),
|
|
||||||
))
|
|
||||||
for _, p := range players.list {
|
|
||||||
p.player.WritePacket(pack)
|
|
||||||
}
|
|
||||||
delayBuffer = delayBuffer[:0]
|
|
||||||
case <-ctx.Done():
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PlayerInfo) AddPlayer(player *Player) { p.join <- player }
|
|
||||||
func (p *PlayerInfo) RemovePlayer(player *Player) { p.quit <- player }
|
|
||||||
func (p *PlayerInfo) onPlayerDelayUpdate(player *Player, delay time.Duration) {
|
|
||||||
p.updateDelay <- playerDelayInfo{player: player, delay: delay}
|
|
||||||
}
|
|
@ -16,7 +16,7 @@ import (
|
|||||||
// 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
|
||||||
players map[uuid.UUID]*Player
|
clients map[uuid.UUID]*Player
|
||||||
// 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,7 +26,7 @@ type PlayerList struct {
|
|||||||
func NewPlayerList(maxPlayers int) *PlayerList {
|
func NewPlayerList(maxPlayers int) *PlayerList {
|
||||||
return &PlayerList{
|
return &PlayerList{
|
||||||
maxPlayer: maxPlayers,
|
maxPlayer: maxPlayers,
|
||||||
players: make(map[uuid.UUID]*Player),
|
clients: make(map[uuid.UUID]*Player),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,35 +36,35 @@ func (p *PlayerList) Init(*Game) {}
|
|||||||
// Run implement Component for PlayerList
|
// Run implement Component for PlayerList
|
||||||
func (p *PlayerList) Run(context.Context) {}
|
func (p *PlayerList) Run(context.Context) {}
|
||||||
|
|
||||||
// AddPlayer implement Component for PlayerList
|
// ClientJoin implement Component for PlayerList
|
||||||
func (p *PlayerList) AddPlayer(player *Player) {
|
func (p *PlayerList) ClientJoin(client *Client, player *Player) {
|
||||||
p.playersLock.Lock()
|
p.playersLock.Lock()
|
||||||
defer p.playersLock.Unlock()
|
defer p.playersLock.Unlock()
|
||||||
|
|
||||||
if len(p.players) >= p.maxPlayer {
|
if len(p.clients) >= p.maxPlayer {
|
||||||
player.WritePacket(Packet758(pk.Marshal(
|
client.WritePacket(Packet758(pk.Marshal(
|
||||||
packetid.ClientboundDisconnect,
|
packetid.ClientboundDisconnect,
|
||||||
chat.TranslateMsg("multiplayer.disconnect.server_full"),
|
chat.TranslateMsg("multiplayer.disconnect.server_full"),
|
||||||
)))
|
)))
|
||||||
player.PutErr(errors.New("playerlist: server full"))
|
client.PutErr(errors.New("playerlist: server full"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.players[player.UUID] = player
|
p.clients[player.UUID] = player
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemovePlayer implement Component for PlayerList
|
// ClientLeft implement Component for PlayerList
|
||||||
func (p *PlayerList) RemovePlayer(player *Player) {
|
func (p *PlayerList) ClientLeft(_ *Client, player *Player) {
|
||||||
p.playersLock.Lock()
|
p.playersLock.Lock()
|
||||||
defer p.playersLock.Unlock()
|
defer p.playersLock.Unlock()
|
||||||
delete(p.players, player.UUID)
|
delete(p.clients, player.UUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPlayer implement LoginChecker for PlayerList
|
// CheckPlayer implement LoginChecker for PlayerList
|
||||||
func (p *PlayerList) CheckPlayer(name string, id uuid.UUID, protocol 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.players) >= p.maxPlayer {
|
if len(p.clients) >= 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,20 +77,20 @@ 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.players)
|
return len(p.clients)
|
||||||
}
|
}
|
||||||
|
|
||||||
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.players)
|
length := len(p.clients)
|
||||||
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.players {
|
for _, v := range p.clients {
|
||||||
sample[i] = PlayerSample{
|
sample[i] = PlayerSample{
|
||||||
Name: v.Name,
|
Name: v.Name,
|
||||||
ID: v.UUID,
|
ID: v.UUID,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package dimension
|
package world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
51
server/world/world.go
Normal file
51
server/world/world.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"github.com/Tnze/go-mc/server"
|
||||||
|
"io"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/nbt"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"github.com/Tnze/go-mc/server/ecs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed DimensionCodec.snbt
|
||||||
|
var dimensionCodecSNBT nbt.StringifiedMessage
|
||||||
|
|
||||||
|
//go:embed Dimension.snbt
|
||||||
|
var dimensionSNBT nbt.StringifiedMessage
|
||||||
|
|
||||||
|
type Dimension struct {
|
||||||
|
storage
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DimensionList struct {
|
||||||
|
Dims []ecs.Index
|
||||||
|
DimNames []string
|
||||||
|
DimCodecSNBT, DimSNBT nbt.StringifiedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DimensionList) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
return pk.Array(*(*[]pk.Identifier)(unsafe.Pointer(&d.DimNames))).WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DimensionList) Find(dim string) (ecs.Index, bool) {
|
||||||
|
for i, v := range d.DimNames {
|
||||||
|
if v == dim {
|
||||||
|
return d.Dims[i], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDimensionManager(g *server.Game) *DimensionList {
|
||||||
|
return ecs.SetResource(g.World, DimensionList{
|
||||||
|
Dims: nil,
|
||||||
|
DimNames: nil,
|
||||||
|
DimCodecSNBT: dimensionCodecSNBT,
|
||||||
|
DimSNBT: dimensionSNBT,
|
||||||
|
})
|
||||||
|
}
|
Reference in New Issue
Block a user