diff options
| author | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
| commit | 153acb7f644d755c45533b7c5311e3ffa47e8973 (patch) | |
| tree | 7645e6f7b65bc3830b078e3b9336d2e6b199d584 /internal/game/tick.go | |
| parent | 87d08f0151e643951769e323995f1c5ed89ee200 (diff) | |
| download | thehouseoficarus-153acb7f644d755c45533b7c5311e3ffa47e8973.tar.gz | |
fix: harden engine across casino, economy, concurrency, security, and content
Diffstat (limited to 'internal/game/tick.go')
| -rw-r--r-- | internal/game/tick.go | 34 |
1 files changed, 32 insertions, 2 deletions
diff --git a/internal/game/tick.go b/internal/game/tick.go index cd9b2fd..4181622 100644 --- a/internal/game/tick.go +++ b/internal/game/tick.go @@ -5,10 +5,25 @@ import ( "math/rand" "thehouseoficarus/internal/behavior" + "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) +// disconnectCombatGraceTicks bounds how many ticks DisconnectTick defers the +// hard remove of a session that is still in combat, so an unresolvable fight +// cannot keep the character's registry entry (and its "logged in elsewhere" +// lockout) forever. +const disconnectCombatGraceTicks = 5 + +// disconnectCombatDeferrals counts, per session, how many ticks its pending +// disconnect has already been deferred for combat. DisconnectTick is its only +// reader/writer and every tick system runs on the owner goroutine, so the map +// needs no lock. An entry is dropped when this path completes the hard remove; +// a session hard-removed elsewhere (a second Hub.Remove, shutdown) leaves at +// most one stale int behind. +var disconnectCombatDeferrals = map[*net.Session]int{} + func (g *Game) DisconnectTick() { if g.Hub == nil { return @@ -22,10 +37,20 @@ func (g *Game) DisconnectTick() { g.Hub.Remove(sess) continue } + // An unresolvable fight (a task/worksite mob that takes no progress, a + // zero-damage stalemate) must not postpone the hard remove forever: + // the character registry entry only drops with the remove, so an + // unbounded deferral locks the player out ("logged in elsewhere") + // until a restart. Defer while the fight can still resolve, but only + // for a bounded grace period, then let the countdown proceed. if g.Combat.Get(p.Name) != nil { - continue + if deferred := disconnectCombatDeferrals[sess]; deferred < disconnectCombatGraceTicks { + disconnectCombatDeferrals[sess] = deferred + 1 + continue + } } if sess.TickDisconnectCountdown() { + delete(disconnectCombatDeferrals, sess) g.restoreGodPlayer(p) g.saveCharacter(p) g.Hub.HardRemove(sess) @@ -76,6 +101,11 @@ func (g *Game) WanderTick() { return } + // One session snapshot per system invocation: the destination broadcast + // below used to re-snapshot every session (lock, allocate, sort) once per + // wandering mob/object. + sessions := g.Hub.AllSessions() + type moveEvent struct { name string fromRoom int @@ -134,7 +164,7 @@ func (g *Game) WanderTick() { } for _, m := range moves { - for _, sess := range g.Hub.AllSessions() { + for _, sess := range sessions { p := sess.Player if p == nil { continue |
