mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 00:52:40 +00:00
Review follow-up M: Grim Harvest fires in turn-based combat
The doc's item M claimed all three mage subclass spell hooks were dead on the
turn path because applyMageSubclassSpellHooks had one caller. It has three.
resolveTurnSpell has called it since 5cd343a, so Empowered Evocation and
Overchannel always worked -- they only move mods.SpellPreDamage, which
resolveTurnSpell returns as EnemyDamage.
Grim Harvest was the real defect, with a narrower cause: the hook wrote
mods.GrimHarvestSlot into a local CombatModifiers that resolveTurnSpell
discarded, because turnSpellOutcome had no field to carry it out. A Necromancy
Mage who killed with a spell in a manual fight never healed.
The stash can't ride on fight-start mods the way auto-resolve's does -- the
spell is cast mid-fight and the turn engine rebuilds combatants every round --
so it rides on the casting seat's ActorStatuses, like ArmedAbility. Each
damaging cast overwrites it; snapshotActor carries it across commit().
grimHarvestHeal also scanned for the *first* spell_cast event to ask whether
the spell landed the killing blow. Auto-resolve casts once, pre-combat, so
first == last there. A turn-based mage casts every round, so a non-lethal
opening cantrip vetoed the heal the killing spell had earned. Now scans for the
last spell_cast -- provably identical on the auto-resolve path, so the golden
corpus does not move.
Balance: a caster buff on the manual surface only, and the one the subclass was
written to have. Auto-resolve already paid it out.
Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
@@ -934,11 +934,22 @@ func grimHarvestHeal(c *DnDCharacter, result CombatResult, mods CombatModifiers)
|
||||
if !result.PlayerWon || mods.GrimHarvestSlot <= 0 {
|
||||
return 0
|
||||
}
|
||||
// The pre-combat spell killed the enemy iff the spell_cast event itself
|
||||
// dropped EnemyHP to 0. If a later round-event finished the kill, no heal.
|
||||
for _, ev := range result.Events {
|
||||
if ev.Action == "spell_cast" {
|
||||
if ev.EnemyHP > 0 {
|
||||
// The spell killed the enemy iff the spell_cast event itself dropped EnemyHP
|
||||
// to 0. If a later round-event (a weapon swing, the pet, a concentration
|
||||
// aura re-tick) finished the kill, no heal.
|
||||
//
|
||||
// It is the *last* spell_cast that has to be lethal, not the first. The
|
||||
// auto-resolve path casts once, pre-combat, so the two coincide there; the
|
||||
// turn-based path can cast every round, and there the stash on the seat's
|
||||
// statuses describes that last cast. Reading the first event would let a
|
||||
// non-lethal opening cantrip veto a heal the killing spell had earned.
|
||||
//
|
||||
// No spell_cast event at all cannot happen while the stash is set — both
|
||||
// surfaces emit one for every damaging cast — so the loop falling through
|
||||
// is not a case, just an absence.
|
||||
for i := len(result.Events) - 1; i >= 0; i-- {
|
||||
if result.Events[i].Action == "spell_cast" {
|
||||
if result.Events[i].EnemyHP > 0 {
|
||||
return 0
|
||||
}
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user