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

@ -6,6 +6,7 @@
#include "game/handler_types.hpp"
#include "audio/spell_sound_manager.hpp"
#include "network/packet.hpp"
#include <glm/glm.hpp>
#include <array>
#include <chrono>
#include <functional>
@ -283,6 +284,15 @@ private:
void playSpellCastSound(uint32_t spellId);
void playSpellImpactSound(uint32_t spellId);
// Resolve SpellVisualID from Spell.dbc cache for a given spellId.
uint32_t resolveSpellVisualId(uint32_t spellId);
// Resolve render-space position for a unit GUID (player or entity).
bool resolveUnitPosition(uint64_t guid, glm::vec3& outPos);
// Play the cast/precast visual effect at the caster's position.
void triggerCastVisual(uint32_t spellId, uint64_t casterGuid, uint32_t castTimeMs = 0);
// Play the impact visual effect at the target's position.
void triggerImpactVisual(uint32_t spellId, uint64_t targetGuid);
// --- handleSpellLogExecute per-effect parsers (extracted to reduce nesting) ---
void parseEffectPowerDrain(network::Packet& packet, uint32_t effectLogCount,
uint64_t caster, uint32_t spellId, bool isPlayerCaster,