#pragma once #include #include #include #include #include #include namespace wowee { namespace pipeline { class AssetManager; } namespace rendering { class M2Renderer; class Renderer; class CharacterRenderer; class SpellVisualSystem { public: SpellVisualSystem() = default; ~SpellVisualSystem() = default; // Initialize with references to the M2 renderer and parent renderer void initialize(M2Renderer* m2Renderer, Renderer* renderer); void shutdown(); // Spawn a spell visual at a world position. // useImpactKit=false → CastKit path; useImpactKit=true → ImpactKit path void playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition, bool useImpactKit = false); // Spawn a precast visual effect at a world position. // castTimeMs: server cast time in milliseconds (0 = use anim duration). void playSpellVisualPrecast(uint32_t visualId, const glm::vec3& worldPosition, uint32_t castTimeMs = 0); // Advance lifetime timers and remove expired instances. void update(float deltaTime); // Remove all active precast visual instances (cast canceled/interrupted). void cancelAllPrecastVisuals(); // Remove all active spell visual instances and reset caches. // Called on map change / combat reset. void reset(); private: // Spell visual effects — transient M2 instances spawned by SMSG_PLAY_SPELL_VISUAL/IMPACT struct SpellVisualInstance { uint32_t instanceId; float elapsed; float duration; // per-instance lifetime in seconds (from M2 anim or default) bool isPrecast; // true for precast effects (removed on cancel/interrupt) uint32_t attachmentId; // character attachment point to track (0=none/static) }; void loadSpellVisualDbc(); M2Renderer* m2Renderer_ = nullptr; Renderer* renderer_ = nullptr; pipeline::AssetManager* cachedAssetManager_ = nullptr; std::vector activeSpellVisuals_; std::unordered_map spellVisualPrecastPath_; // visualId → precast M2 path std::unordered_map spellVisualCastPath_; // visualId → cast M2 path std::unordered_map spellVisualImpactPath_; // visualId → impact M2 path std::unordered_map spellVisualModelIds_; // M2 path → M2Renderer modelId std::unordered_set spellVisualFailedModels_; // modelIds that failed to load (negative cache) uint32_t nextSpellVisualModelId_ = 999000; // Reserved range 999000-999799 bool spellVisualDbcLoaded_ = false; static constexpr float SPELL_VISUAL_MAX_DURATION = 5.0f; static constexpr float SPELL_VISUAL_DEFAULT_DURATION = 2.0f; // Determine character attachment point from model path keywords static uint32_t classifyAttachmentId(const std::string& modelPath); // Apply height offset based on model path keywords (Hand → hands, Chest → chest, Base → ground) static glm::vec3 applyEffectHeightOffset(const glm::vec3& basePos, const std::string& modelPath); }; } // namespace rendering } // namespace wowee