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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tolelom/catacombs/dungeon"
|
"github.com/tolelom/catacombs/dungeon"
|
||||||
"github.com/tolelom/catacombs/entity"
|
"github.com/tolelom/catacombs/entity"
|
||||||
@@ -12,6 +13,13 @@ func (s *GameSession) EnterRoom(roomIdx int) {
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
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
|
s.state.Floor.CurrentRoom = roomIdx
|
||||||
dungeon.UpdateVisibility(s.state.Floor)
|
dungeon.UpdateVisibility(s.state.Floor)
|
||||||
room := s.state.Floor.Rooms[roomIdx]
|
room := s.state.Floor.Rooms[roomIdx]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -68,6 +69,7 @@ type GameSession struct {
|
|||||||
actionCh chan playerActionMsg
|
actionCh chan playerActionMsg
|
||||||
combatSignal chan struct{}
|
combatSignal chan struct{}
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
lastActivity map[string]time.Time // fingerprint -> last activity time
|
||||||
}
|
}
|
||||||
|
|
||||||
type playerActionMsg struct {
|
type playerActionMsg struct {
|
||||||
@@ -84,6 +86,7 @@ func NewGameSession() *GameSession {
|
|||||||
actionCh: make(chan playerActionMsg, 4),
|
actionCh: make(chan playerActionMsg, 4),
|
||||||
combatSignal: make(chan struct{}, 1),
|
combatSignal: make(chan struct{}, 1),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
|
lastActivity: make(map[string]time.Time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +108,10 @@ func (s *GameSession) StartGame() {
|
|||||||
}
|
}
|
||||||
s.started = true
|
s.started = true
|
||||||
s.state.SoloMode = len(s.state.Players) == 1
|
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.mu.Unlock()
|
||||||
s.StartFloor()
|
s.StartFloor()
|
||||||
go s.combatLoop()
|
go s.combatLoop()
|
||||||
@@ -128,6 +135,36 @@ func (s *GameSession) combatLoop() {
|
|||||||
return
|
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 {
|
if phase == PhaseCombat {
|
||||||
s.RunTurn()
|
s.RunTurn()
|
||||||
} else {
|
} else {
|
||||||
@@ -226,9 +263,18 @@ func (s *GameSession) GetState() GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *GameSession) SubmitAction(playerID string, action PlayerAction) {
|
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}
|
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
|
// BuyItem handles shop purchases
|
||||||
func (s *GameSession) BuyItem(playerID string, itemIdx int) bool {
|
func (s *GameSession) BuyItem(playerID string, itemIdx int) bool {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
|||||||
@@ -285,6 +285,9 @@ func (m Model) pollState() tea.Cmd {
|
|||||||
type tickMsg struct{}
|
type tickMsg struct{}
|
||||||
|
|
||||||
func (m Model) updateGame(msg tea.Msg) (tea.Model, tea.Cmd) {
|
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
|
// Refresh state on every update
|
||||||
if m.session != nil {
|
if m.session != nil {
|
||||||
m.gameState = m.session.GetState()
|
m.gameState = m.session.GetState()
|
||||||
|
|||||||
Reference in New Issue
Block a user