aboutsummaryrefslogtreecommitdiff
path: root/internal/game/combat_mob.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 05:31:09 -0400
committerhistoria <[not public]>2026-09-18 05:31:09 -0400
commit153acb7f644d755c45533b7c5311e3ffa47e8973 (patch)
tree7645e6f7b65bc3830b078e3b9336d2e6b199d584 /internal/game/combat_mob.go
parent87d08f0151e643951769e323995f1c5ed89ee200 (diff)
downloadthehouseoficarus-153acb7f644d755c45533b7c5311e3ffa47e8973.tar.gz
fix: harden engine across casino, economy, concurrency, security, and content
Diffstat (limited to 'internal/game/combat_mob.go')
-rw-r--r--internal/game/combat_mob.go38
1 files changed, 29 insertions, 9 deletions
diff --git a/internal/game/combat_mob.go b/internal/game/combat_mob.go
index c53d847..ace02b5 100644
--- a/internal/game/combat_mob.go
+++ b/internal/game/combat_mob.go
@@ -213,16 +213,16 @@ func (g *Game) applyMobMiss(sess *net.Session, p *player.Player, mob *world.MobI
}
}
-func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInstance) {
- g.Combat.Leave(p.Name)
-
- if p.HP <= 0 {
- g.killPlayer(sess, p, mob)
- return
- }
-
+// resolveMobDeath runs the shared mob-death path for a mob whose HP has
+// reached zero: kill bookkeeping, the victory broadcast, drops, on-kill
+// triggers, and respawn scheduling. It returns false when the mob is nil or
+// still alive. Every path that can reduce a mob to zero HP — a player
+// attack, a finishing blow, and Dead Man's Switch retribution — must route
+// through here, or the zeroed instance lingers in MobStore, is filtered out
+// of MobsInRoom (HP > 0), and never respawns.
+func (g *Game) resolveMobDeath(sess *net.Session, p *player.Player, mob *world.MobInstance) bool {
if mob == nil || mob.HP > 0 {
- return
+ return false
}
isTask := mob.IsTask()
@@ -236,6 +236,20 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
// the player currently has that item equipped in a weapon-hand slot.
g.runTrigger(sess, p, mob.OnKill, p.RoomID, p.IsWielding, false)
g.scheduleMobRespawn(mob)
+ return true
+}
+
+func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInstance) {
+ g.Combat.Leave(p.Name)
+
+ if p.HP <= 0 {
+ g.killPlayer(sess, p, mob)
+ return
+ }
+
+ if !g.resolveMobDeath(sess, p, mob) {
+ return
+ }
if p.EscapeDir != "" {
g.beginEscapeMove(p)
@@ -360,6 +374,12 @@ func (g *Game) killPlayer(sess *net.Session, p *player.Player, mob *world.MobIns
}
}
}
+ // Retribution can zero the mob: run the same death path the normal
+ // attack uses so it still drops loot and respawns instead of lingering
+ // at HP 0 and being permanently removed from the room.
+ if mob != nil && mob.HP <= 0 {
+ g.resolveMobDeath(sess, p, mob)
+ }
p.DeactivateAllTechs()
p.Stats.RecordDeath()
sess.WriteLine(g.colorize(sess, "death", "Oh dear, you are dead!"))