Records why the three identities are separate, the DBC mechanic table each [cc:type] now follows, and which DR pools the immunity safeguard may act on. Every vMaNGOS and nampower claim behind the preceding four commits is cited to the source line that establishes it, so the next person to touch this can check the premises instead of re-deriving them: the DRTYPE_ALL set is three stun pools and nothing else, Charge and Intercept are the only triggered stuns forced into the controlled pool, and SPELL_MISS puts the spell ID in arg3. Also notes the mouseover learning gap as designed behavior rather than a bug, since the failure on the other side is permanent bad SavedVariables data.
7.2 KiB
CC, Learned Immunity and Diminishing Returns
How SuperCleveRoidMacros classifies crowd control, what it learns about NPC
immunity, and why diminishing returns only ever acts as a brake on that
learning. Source citations are into C:\Git\vmangos and C:\Git\nampower.
Three identities, never conflated
ccType Exact DBC mechanic. Backs [cc:*] and CC landing verification.
Conditionals.lua, CleveRoids.CCMechanics
immunityType Exact DBC mechanic, recorded against an NPC name when a CC
spell comes back IMMUNE.
Utility.lua, MECHANIC_TO_IMMUNITY_TYPE / GetSpellImmunityType
DR type The real Vanilla diminishing-return pool. Internal only.
Utility.lua, GetSpellImmunityDRType
The first two are the same taxonomy used for different purposes; the third is a separate axis and is never exposed to macros. Folding any pair together is what produced the bugs this design replaces: a target that resisted Gouge got written down as stun-immune, and every later Kidney Shot was suppressed against a target that had never resisted one.
[cc:*] follows the DBC mechanic exactly
One conditional, one mechanic. Several names may alias a single mechanic, but no name spans two:
[cc:stun] -> mechanic 12 only
[cc:freeze] -> mechanic 13 only
[cc:knockout] -> mechanic 14 only
[cc:sap] -> mechanic 30 only
[cc:daze] -> mechanic 27 only
Bare [cc] and [cc:any] still mean "any loss-of-control effect" and are
driven by CleveRoids.CCTypesLossOfControl, which is deliberately independent
of the per-mechanic table. An aggregate does not require redefining the parts.
Current aliases:
slow -> snare
disoriented -> disorient
grip -> fumble (mechanic 6 is DBC Fumble)
incap / incapacitate / incapacitated -> knockout (immunity input only)
Aliases exist so published macros keep working. They resolve to the canonical mechanic name before anything is stored, so SavedVariables only ever hold canonical keys.
Vanilla DBC mechanic taxonomy
The identities SCRM names. Where a mechanic is named at all, it is named as itself:
| DBC mechanic | ID | Canonical ccType |
|---|---|---|
| Charm | 1 | charm |
| Disoriented | 2 | disorient |
| Disarm | 3 | disarm |
| Distract | 4 | distract |
| Fear | 5 | fear |
| Fumble | 6 | fumble |
| Root | 7 | root |
| Pacify | 8 | pacify |
| Silence | 9 | silence |
| Sleep | 10 | sleep |
| Snare | 11 | snare |
| Stun | 12 | stun |
| Freeze | 13 | freeze |
| Knockout | 14 | knockout |
| Bleed | 15 | bleed |
| Polymorph | 17 | polymorph |
| Banish | 18 | banish |
| Shackle | 20 | shackle |
| Turn | 23 | turn |
| Horror | 24 | horror |
| Interrupt | 26 | interrupt |
| Daze | 27 | daze |
| Sapped | 30 | sap |
Mechanics 16, 19, 21, 22, 25, 28 and 29 (Bandage, Shield, Mount, Persuade, Invulnerability, Discovery, Immune Shield) are real DBC values but are not crowd control, so they get no conditional.
Diminishing returns
DR matters here for exactly one reason: a target at DR level 4 returns
IMMUNE, and that must not be recorded as permanent immunity. Nothing else in
the addon consumes DR state, and there is no player-facing DR feature.
Only three pools can diminish a creature
GetDiminishingReturnsGroupType in
vmangos/src/game/Spells/SpellEntry.h:52-79 returns DRTYPE_ALL for exactly
three groups:
DIMINISHING_CONTROL_STUN
DIMINISHING_TRIGGER_STUN
DIMINISHING_KIDNEYSHOT
Every other staged group — sleep, both roots, fear, Warlock fear, charm,
polymorph, silence, disarm, Death Coil, freeze, banish, knockout — is
DRTYPE_PLAYER, and Unit.cpp:7881 applies those only when the victim is a
player. So on an NPC target they can never generate a DR immunity, and they
must never hold back immunity learning.
This is why GetSpellImmunityDRType returns nil for everything that is not
mechanic 12. It is not an approximation; it is the complete DRTYPE_ALL set.
Classifying a stun into its pool
SpellEntry::GetDiminishingReturnsGroup(bool triggered)
(vmangos/src/game/Spells/SpellEntry.cpp:281-432) resolves the pool, and the
triggered argument means the runtime fact of whether an aura triggered the
cast — this is not a pure DBC property. SCRM reconstructs it from nampower
SPELL_CAST_EVENT correlation, which only fires for client-initiated casts:
a spell ID with a recent CleveRoids.pendingCasts entry was cast deliberately,
one without reached SPELL_GO/SPELL_MISS as a proc.
Kidney Shot -> stun_kidneyshot
Charge / Intercept stun -> stun_control
other mechanic-12, client cast -> stun_control
other mechanic-12, proc -> stun_trigger
Kidney Shot is matched on rogue family bit 21 (0x00200000,
SpellClassMask.h:228) rather than a bare ID list, so custom ranks that keep
their DBC family data still classify correctly. Charge (7922) and Intercept
(20253, 20614, 20615) are internally triggered but explicitly returned as
controlled stun by SpellEntry.cpp:389-399; they are the only such exceptions
in 1.12.
Pools that exist but do not apply
Recorded so nobody re-adds them to the safeguard. Knockout and Sap are
distinct mechanics that share DIMINISHING_KNOCKOUT
(SpellEntry.cpp:424-425), and Horror maps to DIMINISHING_DEATHCOIL
(SpellEntry.cpp:428-429). Both are DRTYPE_PLAYER. DIMINISHING_LIMITONLY
is a PvP duration cap rather than a staged pool, and DIMINISHING_NONE is not
a pool at all.
The safeguard itself
recentCCHits[targetGUID][drType] counts landed CC per DR pool, so the three
stun pools keep separate histories. A hit more than DR_RESET_WINDOW (20s)
after the previous one in that pool restarts the count at 1, matching the DR
decay the skip check uses. Without that reset the counter only ever climbed,
so three stuns on a target permanently disqualified it from ever teaching the
addon anything again.
An IMMUNE result is treated as DR, and discarded, only while the pool holds
three or more hits inside the window. Otherwise it is learned.
nampower SPELL_MISS arguments
TriggerSpellMissEvent (nampower/spellevents.cpp:888-917) signals both
SPELL_MISS_SELF and SPELL_MISS_OTHER with the same layout:
arg1 = casterGuid arg2 = targetGuid arg3 = spellId arg4 = missInfo
SPELL_MISS_SELF carries casterGuid too, even though it is by definition
the player — the event is chosen by comparing the caster to the active player
GUID, not by changing the payload. Reading arg1 as the spell ID hands a GUID
string to the immunity recorder and silently loses every miss.
Known limitation: mouseover casts
ProcessSpellMissSelf refuses to learn permanent immunity when it cannot
resolve a queryable unit, because the temporary-immunity-buff check needs one
— an NPC under Divine Shield must not be recorded as permanently immune. A CC
cast at a mouseover that is not the current target can therefore return
IMMUNE without being learned.
This is the safeguard working as designed, not a known bug. Do not loosen it
without a reproduction showing a real failure, since the failure mode on the
other side is permanent bad data in CleveRoids_ImmunityData.