Files
healer-man/docs/ENEMY_STANCES_2026-10-05.md
2026-10-06 22:25:34 -04:00

146 lines
7.9 KiB
Markdown

# Enemy Defensive and Battle Stance audit — 2026-10-05
This audit covers the Stockade enemy stance casts Defensive Stance 7164 and
Battle Stance 7165. Their form auras are state, not direct damage. The reviewed
profile-specific rows and the ActiveAura-supported subset are in
[`src/game/enemyStances.ts`](../src/game/enemyStances.ts).
## Pinned client rows
The Ascension source is the recipe compiler's pinned 3.3.5a patch-D table:
`../LadiksMPQEditor/client-current/area-52/patch-D/DBFilesClient/Spell.dbc`
SHA-256: `2f77f18b91ffbae32a19568e4bc05ec78ee384b6ba8dd2a6e05f0e97abbb2dce`.
Epoch uses the compiler's pinned table:
`../HealerMan-Storage/pipeline-work/dungeons/epoch-client/DBFilesClient/Spell.dbc`
SHA-256: `d5cce1a83550dcfa9eb2f0251dbb11fd24c272534b2b1a9b230924a44d817ab3`.
Both profiles use the compiler's shared patch-S `SpellDuration.dbc` fallback
for duration indices because neither client folder contains that table. Its
SHA-256 is `eeae44ef0185924f3d6ec57ab2701c66566db86e51ab0bae017a9449126eb3a0`.
The two NPC casts have the same Spell.dbc behavior in both profiles:
| Spell | Effect row (`effect/aura/base/misc/trigger`) | Duration | Stance masks |
| --- | --- | --- | --- |
| 7164 Defensive Stance | `6/36/0/18/0` | 180,000 ms | required `0/0`, excluded `0/0` |
| 7165 Battle Stance | `6/36/0/17/0` | permanent (`-1`) | required `0/0`, excluded `0/0` |
Both have range index 1, school mask 1, damage class 0 for 7164 and 2 for
7165, dispel 0, stack amount 0, and Attribute0 327696. Their equipped-item
class is -1 with subclass and inventory masks 0. The direct effects target self
(`targetA=1`, `targetB=0`), use radius index/value 0, mechanic 0, and period 0.
That attribute value does not include AzerothCore's `SPELL_ATTR0_NOT_SHAPESHIFT`
bit (`0x00010000`). The casts themselves therefore have no source stance mask
or “not while shapeshifted” restriction in these rows. This statement applies
to 7164 and 7165 only; it does not infer restrictions for unrelated abilities.
## Server-linked passive rows
The historical AzerothCore form handler links the forms to passive spells in
`src/server/game/Spells/Auras/SpellAuraEffects.cpp`:
- form 18 → Defensive Stance Passive 7376;
- form 17 → Battle Stance Passive 21156.
This is a server-side mapping. Neither stance cast has a nonzero
`EffectTriggerSpell` in either profile. Both child rows have Attribute0 327888,
the passive flag, and zero required/excluded stance masks. Their actual effect
rows differ by client profile:
| Child passive | Ascension patch-D | Epoch client |
| --- | --- | --- |
| 7376 Defensive Stance Passive | `6/87/-10/127/0`, `6/79/-5/127/0`, `6/4/+42/127/0` | `6/87/-10/127/0`, `6/79/-5/127/0`, `6/10/+45/127/0` |
| 21156 Battle Stance Passive | `6/10/0/127/0`, `6/280/0/0/0` | `6/10/-20/127/0` |
Rows use `effect/aura/base points/misc/trigger`. The final value in each row is
zero: these passive rows also have no triggered child spell. A separate player
stance spell such as 2457 is not a substitute for these NPC rows; its own
profile rows contain additional effects and are not the source cast IDs here.
The passive scalar fields are identical in both profiles: dispel 0, duration
index 0, range index 1, school mask 1, damage class 0, stack amount 0,
equipped-item class -1 with subclass and inventory masks 0, and required and
excluded stance masks 0/0. Each active effect is type 6, targets self
(`targetA=1`, `targetB=0`), has mechanic 0, radius index/value 0, period 0, and
trigger spell 0. The helper pins these scalar fields alongside each profile's
effect rows so the campaign compiler can compare the complete child source.
For compiler drift checks, the WDBC scalar field indices are: `Dispel=2`,
`Attribute0=4`, `ShapeshiftMask=12..13`, `ShapeshiftExclude=14..15`,
`DurationIndex=40`, `RangeIndex=46`, `StackAmount=49`,
`EquippedItemClass=68`, `EquippedItemSubclassMask=69`,
`EquippedItemInventoryMask=70`, `DamageClass=213`, and `SchoolMask=225`.
The three per-effect `EffectTriggerSpell` fields are `116..118`; active effect
rows also use `Effect=71+j`, `BasePoints=80+j`, `Mechanic=83+j`,
`ImplicitTargetA=86+j`, `ImplicitTargetB=89+j`, `RadiusIndex=92+j`,
`Aura=95+j`, `Period=98+j`, and `MiscValue=110+j`. In the campaign compiler,
array fields at index 77 or greater shift by six only when a Spell.dbc has at
least 239 fields. Both pinned client tables have 234 fields, so none of these
indices shift for the current Ascension or Epoch data; stance and equipped-item
fields are below 77 and never use that localized-string adjustment.
Aura 79 is percent damage done, aura 87 is percent damage taken, and aura 10
modifies threat. Their `miscValue` 127 covers all schools. The runtime can
therefore apply Defensive Stance's shared `-10%` damage taken and `-5%` damage
dealt in both profiles. Epoch's passive rows additionally supply `+45%` threat
in Defensive Stance and `-20%` threat in Battle Stance. HealerMan already has a
generic `stat: "threat"` resolver used by party and summon threat generation,
so these profile-specific scalars are retained in the helper. Current combat
threat tables record party and summon threat against hostile mobs; hostile NPCs
have no outgoing-threat consumer in the current combat runtime. The two threat
modifiers therefore do not change direct enemy hits, party threat, or the
current threat tables.
Ascension's aura 4 row on 7376 is `DUMMY` with base points +42. No
spell-specific `HandleAuraDummy` case, server script registration, linked spell,
or triggered child was found for 7376, so its meaning is not guessed. Aura 280
on Ascension's 21156 row has base points 0; it has no numerical delta and is
kept as source metadata without an unsupported-effect warning. Aura 10 with
base points 0 on that same row is also a sourced zero. No defensive or battle
stance row is treated as an NPC direct hit.
## Apply, replacement, expiry, and removal
In `SpellAuraEffects.cpp`, applying a new shapeshift removes an existing
shapeshift aura before setting the new form. The form boost handler applies the
mapped passive only when the form changes and removes that owned passive when
the stance aura is removed. The two stances are therefore mutually exclusive;
the helper uses the `enemy-warrior-stance` exclusive group to preserve that
state. Reapplying 7164 refreshes its timed form aura; 7165 remains until another
form replaces it or it is explicitly removed.
When a warrior stance aura is removed, AzerothCore caps Rage to its computed
stance removal value. Player-only spellbook bonuses contribute to that value;
an ordinary NPC has no player spellbook contribution, so the default cap is
zero. On expiry or removal with no replacement stance, the server also clears
the form. The current enemy helper records the sourced form and duration; it
does not invent a creature Rage baseline or treat the passive DUMMY row as
resource regeneration. Its aura uses the shared `enemy-source-spell-<id>` ID
format and carries utility tags for the form and linked passive ID, allowing
existing source-aura cleanup and telemetry to identify it.
## Historical source checked
Read-only reference: `../wow-pvt-server-backups-2/azerothcore-wotlk`.
- `src/server/game/Spells/Auras/SpellAuraEffects.cpp`: form-to-passive mapping,
child aura cleanup, replacement of existing shapeshift auras, and stance Rage
reset on removal.
- `src/server/game/Spells/SpellInfo.cpp`: `CheckShapeshift` reads
`ShapeshiftMask`, `ShapeshiftExclude`, and `NOT_SHAPESHIFT`.
- `src/server/game/Spells/Auras/SpellAuraDefines.h`: aura IDs 4, 10, 79, 87,
and 280.
- `src/server/game/Combat/ThreatManager.cpp`: aura 10 is consumed as a
multiplicative threat modifier by school mask.
- `src/server/game/Spells/Auras/SpellAuraEffects.cpp` and the server scripts
plus SQL update tree: no 7376-specific dummy effect or linked-spell consumer
was found.
The targeted helper tests check both profile rows, exclusivity, supported
damage/threat modifiers, and the permanent versus timed duration. No full test
suite, build, or live combat run is part of this source audit.