feat: remove inactive players after 60s disconnect timeout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package game
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/tolelom/catacombs/dungeon"
|
||||
"github.com/tolelom/catacombs/entity"
|
||||
@@ -12,6 +13,13 @@ func (s *GameSession) EnterRoom(roomIdx int) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for _, p := range s.state.Players {
|
||||
if p.Fingerprint != "" {
|
||||
s.lastActivity[p.Fingerprint] = now
|
||||
}
|
||||
}
|
||||
|
||||
s.state.Floor.CurrentRoom = roomIdx
|
||||
dungeon.UpdateVisibility(s.state.Floor)
|
||||
room := s.state.Floor.Rooms[roomIdx]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -68,6 +69,7 @@ type GameSession struct {
|
||||
actionCh chan playerActionMsg
|
||||
combatSignal chan struct{}
|
||||
done chan struct{}
|
||||
lastActivity map[string]time.Time // fingerprint -> last activity time
|
||||
}
|
||||
|
||||
type playerActionMsg struct {
|
||||
@@ -84,6 +86,7 @@ func NewGameSession() *GameSession {
|
||||
actionCh: make(chan playerActionMsg, 4),
|
||||
combatSignal: make(chan struct{}, 1),
|
||||
done: make(chan struct{}),
|
||||
lastActivity: make(map[string]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +108,10 @@ func (s *GameSession) StartGame() {
|
||||
}
|
||||
s.started = true
|
||||
s.state.SoloMode = len(s.state.Players) == 1
|
||||
now := time.Now()
|
||||
for _, p := range s.state.Players {
|
||||
s.lastActivity[p.Fingerprint] = now
|
||||
}
|
||||
s.mu.Unlock()
|
||||
s.StartFloor()
|
||||
go s.combatLoop()
|
||||
@@ -128,6 +135,36 @@ func (s *GameSession) combatLoop() {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove players inactive for >60 seconds
|
||||
s.mu.Lock()
|
||||
now := time.Now()
|
||||
changed := false
|
||||
remaining := make([]*entity.Player, 0, len(s.state.Players))
|
||||
for _, p := range s.state.Players {
|
||||
if p.Fingerprint != "" && !p.IsOut() {
|
||||
if last, ok := s.lastActivity[p.Fingerprint]; ok {
|
||||
if now.Sub(last) > 60*time.Second {
|
||||
s.addLog(fmt.Sprintf("%s removed (disconnected)", p.Name))
|
||||
changed = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
remaining = append(remaining, p)
|
||||
}
|
||||
if changed {
|
||||
s.state.Players = remaining
|
||||
if len(s.state.Players) <= 1 {
|
||||
s.state.SoloMode = true
|
||||
}
|
||||
if len(s.state.Players) == 0 {
|
||||
s.state.GameOver = true
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
if phase == PhaseCombat {
|
||||
s.RunTurn()
|
||||
} else {
|
||||
@@ -226,9 +263,18 @@ func (s *GameSession) GetState() GameState {
|
||||
}
|
||||
|
||||
func (s *GameSession) SubmitAction(playerID string, action PlayerAction) {
|
||||
s.mu.Lock()
|
||||
s.lastActivity[playerID] = time.Now()
|
||||
s.mu.Unlock()
|
||||
s.actionCh <- playerActionMsg{PlayerID: playerID, Action: action}
|
||||
}
|
||||
|
||||
func (s *GameSession) TouchActivity(fingerprint string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.lastActivity[fingerprint] = time.Now()
|
||||
}
|
||||
|
||||
// BuyItem handles shop purchases
|
||||
func (s *GameSession) BuyItem(playerID string, itemIdx int) bool {
|
||||
s.mu.Lock()
|
||||
|
||||
@@ -285,6 +285,9 @@ func (m Model) pollState() tea.Cmd {
|
||||
type tickMsg struct{}
|
||||
|
||||
func (m Model) updateGame(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.session != nil && m.fingerprint != "" {
|
||||
m.session.TouchActivity(m.fingerprint)
|
||||
}
|
||||
// Refresh state on every update
|
||||
if m.session != nil {
|
||||
m.gameState = m.session.GetState()
|
||||
|
||||
Reference in New Issue
Block a user