diff --git a/examples/frameworkServer/main.go b/examples/frameworkServer/main.go index a0a3e1e..eb16be9 100644 --- a/examples/frameworkServer/main.go +++ b/examples/frameworkServer/main.go @@ -14,7 +14,7 @@ import ( "os" ) -const MaxPlayer = 20 +const MaxPlayer = 1024 const IconPath = "./server-icon.png" var motd = chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text: "Powered by go-mc", Color: "yellow"}}} @@ -40,8 +40,9 @@ func main() { s := server.Server{ ListPingHandler: serverInfo, LoginHandler: &server.MojangLoginHandler{ - OnlineMode: false, - Threshold: 256, + OnlineMode: false, + Threshold: 256, + LoginChecker: playerList, }, GamePlay: game, } diff --git a/examples/pressureTest/main.go b/examples/pressureTest/main.go new file mode 100644 index 0000000..806ffd5 --- /dev/null +++ b/examples/pressureTest/main.go @@ -0,0 +1,99 @@ +package main + +import ( + "flag" + "log" + "strconv" + "time" + + "github.com/mattn/go-colorable" + + "github.com/Tnze/go-mc/bot" + "github.com/Tnze/go-mc/bot/basic" + "github.com/Tnze/go-mc/chat" +) + +var address = flag.String("address", "127.0.0.1", "The server address") +var number = flag.Int("number", 1023, "The number of clients") + +func main() { + flag.Parse() + log.SetOutput(colorable.NewColorableStdout()) + + for i := 0; i < *number; i++ { + go func(i int) { + for { + ind := newIndividual(i, "Player"+strconv.Itoa(i)) + ind.run(*address) + time.Sleep(time.Second * 3) + } + }(i) + } + select {} +} + +type individual struct { + id int + client *bot.Client + player *basic.Player +} + +func newIndividual(id int, name string) (i *individual) { + i = new(individual) + i.id = id + i.client = bot.NewClient() + i.client.Auth.Name = name + i.player = basic.NewPlayer(i.client, basic.DefaultSettings) + basic.EventsListener{ + GameStart: i.onGameStart, + Death: i.onDeath, + Disconnect: onDisconnect, + }.Attach(i.client) + return +} + +func (i *individual) run(address string) { + //Login + err := i.client.JoinServer(address) + if err != nil { + log.Printf("[%d]Login fail: %v", i.id, err) + return + } + log.Printf("[%d]Login success", i.id) + + //JoinGame + if err = i.client.HandleGame(); err == nil { + panic("HandleGame never return nil") + } + log.Printf("[%d] Handle game error: %v", i.id, err) +} + +func (i *individual) onDeath() error { + log.Printf("[%d]Died and Respawned", i.id) + // If we exclude Respawn(...) then the player won't press the "Respawn" button upon death + go func() { + time.Sleep(time.Second * 5) + err := i.player.Respawn() + if err != nil { + log.Print(err) + } + }() + return nil +} + +func (i *individual) onGameStart() error { + log.Printf("[%d]Game start", i.id) + return nil +} + +type DisconnectErr struct { + Reason chat.Message +} + +func (d DisconnectErr) Error() string { + return "disconnect: " + d.Reason.String() +} + +func onDisconnect(reason chat.Message) error { + return DisconnectErr{Reason: reason} +} diff --git a/server/chat.go b/server/chat.go index becd2c7..00b92b1 100644 --- a/server/chat.go +++ b/server/chat.go @@ -32,26 +32,51 @@ func NewGlobalChat() *GlobalChat { } } -func (g *GlobalChat) AddPlayer(p *Player) { - g.join <- p - p.AddHandler(PacketHandler{ +func (g *GlobalChat) Init(game *Game) { + game.AddHandler(&PacketHandler{ ID: packetid.ServerboundChat, - F: func(packet Packet757) error { + F: func(player *Player, packet Packet757) 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: p, text: text} + g.msg <- chatItem{p: player, text: text} return nil }, }) } -func (g *GlobalChat) RemovePlayer(p *Player) { - g.quit <- p +func (g *GlobalChat) Run(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case item := <-g.msg: + packet := Packet757(pk.Marshal( + packetid.ClientboundChat, + item.toMessage(), + pk.Byte(0), + pk.UUID(item.p.UUID), + )) + for _, p := range g.players { + err := p.WritePacket(packet) + if err != nil { + p.PutErr(err) + } + } + case p := <-g.join: + g.players[p.UUID] = p + case p := <-g.quit: + delete(g.players, p.UUID) + } + } } +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", @@ -86,29 +111,3 @@ func playerToSNBT(p *Player) string { return string(s) } - -func (g *GlobalChat) Run(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - case item := <-g.msg: - packet := Packet757(pk.Marshal( - packetid.ClientboundChat, - item.toMessage(), - pk.Byte(0), - pk.UUID(item.p.UUID), - )) - for _, p := range g.players { - err := p.WritePacket(packet) - if err != nil { - p.PutErr(err) - } - } - case p := <-g.join: - g.players[p.UUID] = p - case p := <-g.quit: - delete(g.players, p.UUID) - } - } -} diff --git a/server/gameplay.go b/server/gameplay.go index 3debebc..44c4c2c 100644 --- a/server/gameplay.go +++ b/server/gameplay.go @@ -25,16 +25,25 @@ type GamePlay interface { type Game struct { Dim Level components []Component + handlers map[int32][]*PacketHandler eid int32 } type Component interface { + Init(g *Game) Run(ctx context.Context) AddPlayer(p *Player) RemovePlayer(p *Player) } +type PacketHandler struct { + ID int32 + F packetHandlerFunc +} + +type packetHandlerFunc func(player *Player, packet Packet757) error + //go:embed DimensionCodec.snbt var dimensionCodecSNBT string @@ -42,10 +51,19 @@ var dimensionCodecSNBT string var dimensionSNBT string func NewGame(dim Level, components ...Component) *Game { - return &Game{ + g := &Game{ Dim: dim, components: components, + handlers: make(map[int32][]*PacketHandler), } + for _, v := range components { + v.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) { @@ -67,7 +85,6 @@ func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net UUID: id, EntityID: g.newEID(), Gamemode: 1, - handlers: make(map[int32][]packetHandlerFunc), errChan: make(chan error, 1), } dimInfo := g.Dim.Info() @@ -107,13 +124,11 @@ func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net var packet pk.Packet for { - err := p.ReadPacket(&packet) - if err != nil { + if err := p.ReadPacket(&packet); err != nil { return } - for _, ph := range p.handlers[packet.ID] { - err = ph(Packet757(packet)) - if err != nil { + for _, ph := range g.handlers[packet.ID] { + if err := ph.F(p, Packet757(packet)); err != nil { return } } diff --git a/server/keepalive.go b/server/keepalive.go index 4e55968..9473c3c 100644 --- a/server/keepalive.go +++ b/server/keepalive.go @@ -49,25 +49,22 @@ func NewKeepAlive() (k *KeepAlive) { } } -func (k *KeepAlive) AddPlayer(p *Player) { - k.join <- p - p.AddHandler(PacketHandler{ +// Init implement Component for KeepAlive +func (k *KeepAlive) Init(g *Game) { + g.AddHandler(&PacketHandler{ ID: packetid.ServerboundKeepAlive, - F: func(packet Packet757) error { + F: func(player *Player, packet Packet757) error { var KeepAliveID pk.Long if err := pk.Packet(packet).Scan(&KeepAliveID); err != nil { return err } - k.tick <- p + k.tick <- player return nil }, }) } -func (k *KeepAlive) RemovePlayer(p *Player) { - k.quit <- p -} - +// Run implement Component for KeepAlive func (k *KeepAlive) Run(ctx context.Context) { for { select { @@ -87,6 +84,12 @@ func (k *KeepAlive) Run(ctx context.Context) { } } +// AddPlayer implement Component for KeepAlive +func (k *KeepAlive) AddPlayer(player *Player) { k.join <- player } + +// RemovePlayer implement Component for KeepAlive +func (k *KeepAlive) RemovePlayer(p *Player) { k.quit <- p } + func (k KeepAlive) pushPlayer(p *Player) { k.listIndex[p.UUID] = k.pingList.PushBack( keepAliveItem{player: p, t: time.Now()}, diff --git a/server/player.go b/server/player.go index 4d8fb8e..95181b6 100644 --- a/server/player.go +++ b/server/player.go @@ -18,7 +18,6 @@ type Player struct { uuid.UUID EntityID int32 Gamemode byte - handlers map[int32][]packetHandlerFunc errChan chan error } @@ -51,20 +50,6 @@ func (s WritePacketError) Unwrap() error { return s.Err } -type PacketHandler struct { - ID int32 - F packetHandlerFunc -} - -type packetHandlerFunc func(packet Packet757) error - -func (p *Player) AddHandler(ph PacketHandler) { - if p.handlers == nil { - p.handlers = make(map[int32][]packetHandlerFunc) - } - p.handlers[ph.ID] = append(p.handlers[ph.ID], ph.F) -} - func (p *Player) PutErr(err error) { select { case p.errChan <- err: diff --git a/server/playerlist.go b/server/playerlist.go index 6e958c8..0e50b71 100644 --- a/server/playerlist.go +++ b/server/playerlist.go @@ -30,8 +30,13 @@ func NewPlayerList(maxPlayers int) *PlayerList { } } +// Init implement Component for PlayerList +func (p *PlayerList) Init(*Game) {} + +// Run implement Component for PlayerList func (p *PlayerList) Run(context.Context) {} +// AddPlayer implement Component for PlayerList func (p *PlayerList) AddPlayer(player *Player) { p.playersLock.Lock() defer p.playersLock.Unlock() @@ -52,12 +57,23 @@ func (p *PlayerList) AddPlayer(player *Player) { p.players[player.UUID] = player } +// RemovePlayer implement Component for PlayerList func (p *PlayerList) RemovePlayer(player *Player) { p.playersLock.Lock() defer p.playersLock.Unlock() delete(p.players, player.UUID) } +// CheckPlayer implement LoginChecker for PlayerList +func (p *PlayerList) CheckPlayer(name string, id uuid.UUID, protocol int32) (ok bool, reason chat.Message) { + p.playersLock.Lock() + defer p.playersLock.Unlock() + if len(p.players) >= p.maxPlayer { + return false, chat.TranslateMsg("multiplayer.disconnect.server_full") + } + return true, chat.Message{} +} + func (p *PlayerList) MaxPlayer() int { return p.maxPlayer } @@ -72,7 +88,11 @@ func (p *PlayerList) PlayerSamples() (sample []PlayerSample) { p.playersLock.Lock() defer p.playersLock.Unlock() // Up to 10 players can be returned - sample = make([]PlayerSample, len(p.players)) + length := len(p.players) + if length > 10 { + length = 10 + } + sample = make([]PlayerSample, length) var i int for _, v := range p.players { sample[i] = PlayerSample{ @@ -80,7 +100,7 @@ func (p *PlayerList) PlayerSamples() (sample []PlayerSample) { ID: v.UUID, } i++ - if i >= len(p.players) { + if i >= length { break } }