Issue.shadow cloak bug - #3672
Merged
Merged
Conversation
Shadow Cloak (kShadowCloak) gated incoming magic damage on GET_MR twice: the global CalcTotalSpellDmg gate AND its own scope="damage" absorption ward (chance="kMagicResist"), both reading GET_MR (incl. the cloak's +21). Result: P(land) = (1 - MR/100)^2 -- the cloak double-counted its own resist. Add ActionContext::mr_applied_ (mirrors ward_stop_): the first MR-derived gate to evaluate for a damage stage marks MR as spent, and any later MR gate that stage defers -- so MR gates AT MOST ONCE regardless of how many MR affects/wards are present (only one MR ward triggers). Reset per stage at CastDamage entry. CalcTotalSpellDmg takes ActionContext& and skips its roll when MR was already applied. MR is a damage-axis stat: the flag logic is confined to want==kDamage. The affect stage gates on GET_AR (affect resist), a separate axis, and is not touched. Shadow Cloak's +21 MR is intentionally kept -- this fixes only the double-gating, not the buff magnitude. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CastAffect gated a violent debuff on GET_AR twice for non-warcry spells: a pre-roll (number(1,999) <= GET_AR*10) AND a blanket block (number(1,100) <= GET_AR), both ~AR/100 and rolled independently. Net block was 1-(1-AR/100)^2 -- e.g. 36% at AR=20 instead of 20%. Add ActionContext::ar_applied_ (the affect-stage counterpart to mr_applied_): the first AR gate to evaluate marks AR spent for the delivery, and the later gate defers. Reset per stage at CastAffect entry. The pre-roll consumes the roll for non-warcry debuffs; a warcry (which skips the pre-roll) is gated by the blanket block instead -- so AR gates exactly once in every case. Balance note: this removes the compounding, so debuffs land more often against high-AR targets. The two axes now mirror: MR gates once per damage stage, AR gates once per affect stage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multi-hit damage spells (e.g. Magic Missile) resolved magic-resist, the damage ward, and the saving throw ONCE per stage: CalcTotalSpellDmg rolled MR and computed one amount, the save halved it once, and that single result was dealt to every hit. So one MR/save roll decided all hits together -- a resisted spell missed with all its attacks. Move the resolution into CastDamage's per-hit loop so each attack is independent (the point of multi-hit -- if one attack misses, others can still land): - CalcTotalSpellDmg now returns only the base per-hit amount (MR gate and its ctx param removed). - Per hit: reset mr_applied; roll the damage-scoped ward (Shadow Cloak absorb); else roll the global GET_MR gate; else roll the saving throw for half. The ward had to move per-hit too, or a per-stage ward + per-hit gate would re-introduce the double-gating this branch fixes. mr_applied still ensures at most one MR roll per hit (ward OR global gate). - Add a spell_trace tester line when a hit is MR-resisted (was silent, so testers could not tell MR from a saving throw). - saving.cpp: drop the 'Тестовое сообщение: ' prefix from the save tester line. The per-stage save roll (used by the instant-death gate) is kept; the damage half-save is now per hit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3657.
Problem
Shadow Cloak (
kShadowCloak) cut spell passage far more than its stats imply, because magic-resist was gated twice on the sameGET_MR(incl. the cloak's +21): the globalCalcTotalSpellDmggate and the cloak'sscope="damage"absorption ward. Net landing was(1 − MR/100)². The affect stage had the same shape onGET_AR.Changes (3 commits)
1. MR gated once per damage stage —
ActionContext::mr_applied_(mirrorsward_stop_): the first MR-derived gate to evaluate marks MR spent; any later MR gate defers. So MR gates at most once regardless of how many MR affects/wards are present (only one MR ward triggers). The +21 MR is intentionally kept — this fixes the double-count, not the buff magnitude.2. AR gated once per affect stage —
CastAffectrolledGET_ARtwice for a non-warcry debuff (a pre-rollnumber(1,999) <= AR*10and a blanketnumber(1,100) <= AR, both ~AR/100). Added the parallelar_applied_flag. Balance note: removes the compounding, so debuffs land more often vs high-AR targets. The two axes now mirror: MR once per damage stage, AR once per affect stage.3. Roll MR / ward / save per hit; add MR tester line — multi-hit spells (e.g. Magic Missile) resolved MR, the ward, and the save once per stage and dealt that single result to every hit, so a resisted spell missed with all its attacks. Moved the resolution into
CastDamage's per-hit loop: each attack is independently absorbed / MR-resisted / saved. The ward had to move per-hit too, or a per-stage ward + per-hit gate would re-introduce the double-gating. Also added aspell_traceline when a hit is MR-resisted (was silent — indistinguishable from a saving throw), and dropped theТестовое сообщение:prefix from the save tester line.Notes
GET_MR) is a damage-axis stat; affect-resist (GET_AR) is the affect axis — the flags are kept strictly per-axis.WardEligible), unchanged — test the cloak absorb as a mortal.