diff --git a/game/event.go b/game/event.go index 5e78a9e..1c4c6c5 100644 --- a/game/event.go +++ b/game/event.go @@ -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] diff --git a/game/session.go b/game/session.go index a378e04..84c219d 100644 --- a/game/session.go +++ b/game/session.go @@ -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() diff --git a/ui/model.go b/ui/model.go index 47ef114..cbb2816 100644 --- a/ui/model.go +++ b/ui/model.go @@ -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()