Skip to content

Commit 9285813

Browse files
markus-waclaude
andcommitted
fix(events): cap fatal HealthDamageTaken with reported health, exactly
The fatal-hit cap summed raw dmg_health, which under-reports the true HP drop by 1 on some hits (the benign shotgun-pellet rounding), so a same-tick follow-up cap landed 1 HP high per affected prior hit. Track the victim's last reported post-hit health instead - the server's own HP value - so the cap is exact rather than exact-modulo-rounding. Multi-attacker behaviour is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b8d5538 commit 9285813

1 file changed

Lines changed: 20 additions & 11 deletions

File tree

‎pkg/demoinfocs/game_events.go‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,15 @@ func (p *parser) handleGameEvent(ge *msg.CMsgSource1LegacyGameEvent) {
7373
})
7474
}
7575

76-
// victimTickHealthDamage accumulates the health damage dealt to a victim within a single game tick.
77-
// It lets the fatal-hit HealthDamageTaken be capped at the victim's actual remaining HP instead of
78-
// the stale start-of-tick value, which otherwise over-reports same-tick multi-attacker kills.
76+
// victimTickHealthDamage records the victim's last reported health within a single game tick, so a
77+
// same-tick fatal follow-up hit can be capped at the victim's actual remaining HP instead of the
78+
// stale start-of-tick value (which over-reports same-tick multi-attacker kills). Tracking the
79+
// server's reported post-hit health is exact, whereas summing raw dmg_health can under-report the
80+
// true HP drop by 1 (the benign shotgun-pellet rounding artifact).
7981
type victimTickHealthDamage struct {
80-
tick int
81-
damage int
82+
tick int
83+
lastHealth int
84+
hasPrior bool
8285
}
8386

8487
type gameEventHandler struct {
@@ -501,11 +504,16 @@ func (geh gameEventHandler) playerHurt(data map[string]*msg.CMsgSource1LegacyGam
501504
if player != nil {
502505
if health == 0 {
503506
// Fatal hit: the damage actually taken is the victim's remaining HP right before this
504-
// hit. player.Health() reads the start-of-tick HP (entity state is applied only after
505-
// the tick's events are dispatched), so subtract any damage already dealt to this victim
506-
// earlier in the same tick - otherwise same-tick multi-attacker kills over-report the
507-
// finishing blow's HealthDamageTaken.
508-
healthDamageTaken = player.Health() - victimDamage.damage
507+
// hit. player.Health() reads the start-of-tick HP (entity state is applied only after the
508+
// tick's events are dispatched). When an earlier hit already landed on this victim this
509+
// same tick, use that hit's reported post-damage health - the server's own HP after it,
510+
// and exact - otherwise same-tick multi-attacker kills over-report the finishing blow.
511+
if victimDamage.hasPrior {
512+
healthDamageTaken = victimDamage.lastHealth
513+
} else {
514+
healthDamageTaken = player.Health()
515+
}
516+
509517
if healthDamageTaken < 0 {
510518
healthDamageTaken = 0
511519
}
@@ -516,7 +524,8 @@ func (geh gameEventHandler) playerHurt(data map[string]*msg.CMsgSource1LegacyGam
516524
}
517525
}
518526

519-
victimDamage.damage += healthDamage
527+
victimDamage.lastHealth = health
528+
victimDamage.hasPrior = true
520529

521530
wepType = geh.attackerWeaponType(wepType, userID)
522531

0 commit comments

Comments
 (0)