feat(rendering): implement spell visual effects with bone-tracked ribbons and particles

Add complete spell visual pipeline resolving the DBC chain
(Spell → SpellVisual → SpellVisualKit → SpellVisualEffectName → M2)
with precast/cast/impact phases, bone-attached positioning, and
automatic dual-hand mirroring.

Ribbon rendering fixes:
- Parse visibility track as uint8 (was read as float, suppressing
  all ribbon edges due to ~1.4e-45 failing the >0.5 check)
- Filter garbage emitters with bone=UINT_MAX unconditionally
- Guard against NaN spine positions from corrupt bone data
- Resolve ribbon textures via direct index, not textureLookup table
- Fall back to bone 0 when ribbon bone index is out of range

Particle rendering fixes:
- Reduce spell particle scale from 5x to 1.5x (was oversized)
- Exempt spell effect instances from position-based deduplication

Spell handler integration:
- Trigger precast visuals on SMSG_SPELL_START with server castTimeMs
- Trigger cast/impact visuals on SMSG_SPELL_GO
- Cancel precast visuals on cast interrupt/failure/movement

M2 classifier expansion:
- Add AmbientEmitterType enum for sound system integration
- Add 20+ foliage tokens, 4 spell effect tokens, isSmallFoliage flag
- Add markModelAsSpellEffect() to override disableAnimation

DBC layouts:
- Add SpellVisualID field to Spell.dbc for all expansion configs

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
This commit is contained in:
Pavel Okhlopkov 2026-04-07 11:27:59 +03:00
parent 0a33e3081c
commit b79d9b8fea
18 changed files with 803 additions and 90 deletions

View file

@ -180,6 +180,7 @@
"RangeIndex": 33,
"Rank": 129,
"SchoolEnum": 1,
"SpellVisualID": 115,
"Tooltip": 147
},
"SpellIcon": {
@ -193,7 +194,8 @@
"CastKit": 2,
"ID": 0,
"ImpactKit": 3,
"MissileModel": 8
"MissileModel": 8,
"PrecastKit": 1
},
"SpellVisualEffectName": {
"FilePath": 2,
@ -201,7 +203,12 @@
},
"SpellVisualKit": {
"BaseEffect": 5,
"BreathEffect": 8,
"ChestEffect": 4,
"HeadEffect": 3,
"ID": 0,
"LeftHandEffect": 6,
"RightHandEffect": 7,
"SpecialEffect0": 11,
"SpecialEffect1": 12,
"SpecialEffect2": 13

View file

@ -219,6 +219,7 @@
"RangeIndex": 40,
"Rank": 136,
"SchoolMask": 215,
"SpellVisualID": 122,
"Tooltip": 154
},
"SpellIcon": {
@ -236,7 +237,8 @@
"CastKit": 2,
"ID": 0,
"ImpactKit": 3,
"MissileModel": 8
"MissileModel": 8,
"PrecastKit": 1
},
"SpellVisualEffectName": {
"FilePath": 2,
@ -244,7 +246,12 @@
},
"SpellVisualKit": {
"BaseEffect": 5,
"BreathEffect": 8,
"ChestEffect": 4,
"HeadEffect": 3,
"ID": 0,
"LeftHandEffect": 6,
"RightHandEffect": 7,
"SpecialEffect0": 11,
"SpecialEffect1": 12,
"SpecialEffect2": 13

View file

@ -213,6 +213,7 @@
"RangeIndex": 33,
"Rank": 129,
"SchoolEnum": 1,
"SpellVisualID": 115,
"Tooltip": 147
},
"SpellIcon": {
@ -230,7 +231,8 @@
"CastKit": 2,
"ID": 0,
"ImpactKit": 3,
"MissileModel": 8
"MissileModel": 8,
"PrecastKit": 1
},
"SpellVisualEffectName": {
"FilePath": 2,
@ -238,7 +240,12 @@
},
"SpellVisualKit": {
"BaseEffect": 5,
"BreathEffect": 8,
"ChestEffect": 4,
"HeadEffect": 3,
"ID": 0,
"LeftHandEffect": 6,
"RightHandEffect": 7,
"SpecialEffect0": 11,
"SpecialEffect1": 12,
"SpecialEffect2": 13

View file

@ -235,6 +235,7 @@
"RangeIndex": 49,
"Rank": 153,
"SchoolMask": 225,
"SpellVisualID": 131,
"Tooltip": 139
},
"SpellIcon": {
@ -252,7 +253,8 @@
"CastKit": 2,
"ID": 0,
"ImpactKit": 3,
"MissileModel": 8
"MissileModel": 8,
"PrecastKit": 1
},
"SpellVisualEffectName": {
"FilePath": 2,
@ -260,7 +262,12 @@
},
"SpellVisualKit": {
"BaseEffect": 5,
"BreathEffect": 8,
"ChestEffect": 4,
"HeadEffect": 3,
"ID": 0,
"LeftHandEffect": 6,
"RightHandEffect": 7,
"SpecialEffect0": 11,
"SpecialEffect1": 12,
"SpecialEffect2": 13