Improved reconnect flow
This commit is contained in:
@ -2,6 +2,7 @@ package game
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Chat is used for chat messages.
|
||||
@ -12,9 +13,30 @@ func (game *Game) Chat(data []byte, address *net.UDPAddr) error {
|
||||
return ErrUnknownAddress
|
||||
}
|
||||
|
||||
message := string(data)
|
||||
|
||||
if game.ChatCommand(player, message) {
|
||||
return nil
|
||||
}
|
||||
|
||||
newData := []byte{}
|
||||
newData = AppendString(newData, player.ID)
|
||||
newData = AppendStringBytes(newData, data)
|
||||
newData = AppendString(newData, message)
|
||||
game.Broadcast(Chat, newData)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChatCommand executes chat commands and returns true if it was one.
|
||||
func (game *Game) ChatCommand(player *Player, message string) bool {
|
||||
if strings.HasPrefix(message, "/") {
|
||||
switch message {
|
||||
case "/logout":
|
||||
game.server.Send(Logout, nil, player.address)
|
||||
game.players.Remove(player)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
// Game represents the entire state of the game server.
|
||||
type Game struct {
|
||||
server *Server
|
||||
router Router
|
||||
router *Router
|
||||
players *PlayerManager
|
||||
}
|
||||
|
||||
@ -21,19 +21,26 @@ func New() *Game {
|
||||
players: NewPlayerManager(),
|
||||
}
|
||||
|
||||
game.router.Get(Ping, game.Ping)
|
||||
game.router.Get(Login, game.Login)
|
||||
game.router.Get(Move, game.Move)
|
||||
game.router.Get(Jump, game.Jump)
|
||||
game.router.Get(Chat, game.Chat)
|
||||
game.router = NewRouter(game)
|
||||
return game
|
||||
}
|
||||
|
||||
// NewRouter creates a new router.
|
||||
func NewRouter(game *Game) *Router {
|
||||
router := &Router{}
|
||||
router.Get(Ping, game.Ping)
|
||||
router.Get(Login, game.Login)
|
||||
router.Get(Move, game.Move)
|
||||
router.Get(Jump, game.Jump)
|
||||
router.Get(Chat, game.Chat)
|
||||
return router
|
||||
}
|
||||
|
||||
// Run starts all game systems.
|
||||
func (game *Game) Run() {
|
||||
physics := time.NewTicker(20 * time.Millisecond)
|
||||
statistics := time.NewTicker(time.Second)
|
||||
clean := time.NewTicker(time.Second)
|
||||
physics := time.NewTicker(20 * time.Millisecond).C
|
||||
statistics := time.NewTicker(time.Second).C
|
||||
clean := time.NewTicker(time.Second).C
|
||||
close := make(chan os.Signal, 1)
|
||||
signal.Notify(close, os.Interrupt)
|
||||
|
||||
@ -44,16 +51,16 @@ func (game *Game) Run() {
|
||||
case p := <-game.server.incoming:
|
||||
game.router.handle(p)
|
||||
|
||||
case <-physics.C:
|
||||
case <-physics:
|
||||
game.players.Each(func(c *Player) {
|
||||
c.Tick()
|
||||
})
|
||||
|
||||
case <-statistics.C:
|
||||
case <-statistics:
|
||||
fmt.Printf("%d players | %d packets\n", game.players.Count(), game.server.PacketCount())
|
||||
game.server.ResetPacketCount()
|
||||
|
||||
case <-clean.C:
|
||||
case <-clean:
|
||||
game.players.Clean(5 * time.Second)
|
||||
|
||||
case <-close:
|
||||
|
@ -50,21 +50,14 @@ func (game *Game) Login(data []byte, address *net.UDPAddr) error {
|
||||
player = game.players.ByAccount(account.ID)
|
||||
|
||||
if player != nil {
|
||||
game.reconnect(player, address)
|
||||
} else {
|
||||
game.connect(account, address)
|
||||
game.server.Send(Login, []byte{Failure}, address)
|
||||
return ErrAlreadyLoggedIn
|
||||
}
|
||||
|
||||
game.connect(account, address)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (game *Game) reconnect(player *Player, address *net.UDPAddr) {
|
||||
player.KeepAlive()
|
||||
game.players.ChangeAddress(player, address)
|
||||
game.sendLoginSuccess(player)
|
||||
player.OnConnect()
|
||||
}
|
||||
|
||||
func (game *Game) connect(account *Account, address *net.UDPAddr) {
|
||||
player := NewPlayer(address, account, game)
|
||||
player.authToken = createAuthToken()
|
||||
|
@ -48,6 +48,7 @@ func (player *Player) State() []byte {
|
||||
return state
|
||||
}
|
||||
|
||||
// OnConnect is executed when a connection has been established.
|
||||
func (player *Player) OnConnect() {
|
||||
fmt.Printf("%s connected.\n", player.Name)
|
||||
players := player.game.players
|
||||
@ -62,6 +63,7 @@ func (player *Player) OnConnect() {
|
||||
})
|
||||
}
|
||||
|
||||
// OnDisconnect is executed when the connection has been closed.
|
||||
func (player *Player) OnDisconnect() {
|
||||
fmt.Printf("%s disconnected.\n", player.Name)
|
||||
player.game.BroadcastOthers(PlayerRemove, []byte(player.ID), player)
|
||||
|
@ -2,92 +2,96 @@ package game
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PlayerManager keeps tracks of all players.
|
||||
type PlayerManager struct {
|
||||
players sync.Map
|
||||
accounts sync.Map
|
||||
count atomic.Int64
|
||||
players map[string]*Player
|
||||
accounts map[string]*Player
|
||||
count int
|
||||
}
|
||||
|
||||
// NewPlayerManager creates a new player manager.
|
||||
func NewPlayerManager() *PlayerManager {
|
||||
return &PlayerManager{}
|
||||
}
|
||||
|
||||
func (m *PlayerManager) Clean(timeout time.Duration) {
|
||||
now := time.Now()
|
||||
|
||||
m.players.Range(func(key, value interface{}) bool {
|
||||
player := value.(*Player)
|
||||
|
||||
if !player.lastPacket.IsZero() && now.After(player.lastPacket.Add(timeout)) {
|
||||
m.players.Delete(key)
|
||||
m.accounts.Delete(player.ID)
|
||||
m.count.Add(-1)
|
||||
player.OnDisconnect()
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
return &PlayerManager{
|
||||
players: map[string]*Player{},
|
||||
accounts: map[string]*Player{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a new player.
|
||||
func (m *PlayerManager) Add(player *Player) {
|
||||
m.players.Store(player.address.String(), player)
|
||||
m.accounts.Store(player.ID, player)
|
||||
m.count.Add(1)
|
||||
m.players[player.address.String()] = player
|
||||
m.accounts[player.ID] = player
|
||||
m.count++
|
||||
player.OnConnect()
|
||||
}
|
||||
|
||||
// ChangeAddress changes the address of a player.
|
||||
func (m *PlayerManager) ChangeAddress(player *Player, address *net.UDPAddr) {
|
||||
m.players.Delete(player.address.String())
|
||||
delete(m.players, player.address.String())
|
||||
player.address = address
|
||||
m.players.Store(player.address.String(), player)
|
||||
m.players[player.address.String()] = player
|
||||
}
|
||||
|
||||
// Clean checks for players who haven't responded in the timeout duration and disconnects them.
|
||||
func (m *PlayerManager) Clean(timeout time.Duration) {
|
||||
now := time.Now()
|
||||
|
||||
for _, player := range m.players {
|
||||
if player.lastPacket.IsZero() || now.Before(player.lastPacket.Add(timeout)) {
|
||||
continue
|
||||
}
|
||||
|
||||
m.Remove(player)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes a player.
|
||||
func (m *PlayerManager) Remove(player *Player) {
|
||||
delete(m.players, player.address.String())
|
||||
delete(m.accounts, player.ID)
|
||||
m.count--
|
||||
player.OnDisconnect()
|
||||
}
|
||||
|
||||
// Contains tells you whether the address is already a registered client.
|
||||
func (m *PlayerManager) Contains(address *net.UDPAddr) bool {
|
||||
_, exists := m.players.Load(address.String())
|
||||
_, exists := m.players[address.String()]
|
||||
return exists
|
||||
}
|
||||
|
||||
// Count returns the number of clients.
|
||||
func (m *PlayerManager) Count() int {
|
||||
return int(m.count.Load())
|
||||
return m.count
|
||||
}
|
||||
|
||||
// Each calls the callback function for each client.
|
||||
func (m *PlayerManager) Each(callback func(*Player)) {
|
||||
m.players.Range(func(key, value any) bool {
|
||||
callback(value.(*Player))
|
||||
return true
|
||||
})
|
||||
for _, player := range m.players {
|
||||
callback(player)
|
||||
}
|
||||
}
|
||||
|
||||
// ByAddress returns an existing client for the requested address.
|
||||
func (m *PlayerManager) ByAddress(address *net.UDPAddr) *Player {
|
||||
obj, exists := m.players.Load(address.String())
|
||||
player, exists := m.players[address.String()]
|
||||
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return obj.(*Player)
|
||||
return player
|
||||
}
|
||||
|
||||
// ByAccount returns the player with the given account ID.
|
||||
func (m *PlayerManager) ByAccount(id string) *Player {
|
||||
obj, exists := m.accounts.Load(id)
|
||||
player, exists := m.accounts[id]
|
||||
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return obj.(*Player)
|
||||
return player
|
||||
}
|
||||
|
Reference in New Issue
Block a user