feat: use M2 animation duration for spell visual lifetime

Spell visual effects previously used a fixed 3.5s duration for all
effects, causing some to linger too long and overlap during combat.
Now queries the M2 model's default animation duration via the new
getInstanceAnimDuration() method and clamps it to 0.5-5s. Effects
without animations fall back to a 2s default. This makes spell impacts
feel more responsive and reduces visual clutter.
This commit is contained in:
Kelsi 2026-03-20 05:52:47 -07:00
parent 29c938dec2
commit 90edb3bc07
4 changed files with 28 additions and 5 deletions

View file

@ -3956,6 +3956,18 @@ void M2Renderer::setInstanceAnimationFrozen(uint32_t instanceId, bool frozen) {
}
}
float M2Renderer::getInstanceAnimDuration(uint32_t instanceId) const {
auto idxIt = instanceIndexById.find(instanceId);
if (idxIt == instanceIndexById.end()) return 0.0f;
const auto& inst = instances[idxIt->second];
if (!inst.cachedModel) return 0.0f;
const auto& seqs = inst.cachedModel->sequences;
if (seqs.empty()) return 0.0f;
int seqIdx = inst.currentSequenceIndex;
if (seqIdx < 0 || seqIdx >= static_cast<int>(seqs.size())) seqIdx = 0;
return seqs[seqIdx].duration; // in milliseconds
}
void M2Renderer::setInstanceTransform(uint32_t instanceId, const glm::mat4& transform) {
auto idxIt = instanceIndexById.find(instanceId);
if (idxIt == instanceIndexById.end()) return;

View file

@ -2892,16 +2892,21 @@ void Renderer::playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition
LOG_WARNING("SpellVisual: failed to create instance for visualId=", visualId);
return;
}
activeSpellVisuals_.push_back({instanceId, 0.0f});
// Determine lifetime from M2 animation duration (clamp to reasonable range)
float animDurMs = m2Renderer->getInstanceAnimDuration(instanceId);
float duration = (animDurMs > 100.0f)
? std::clamp(animDurMs / 1000.0f, 0.5f, SPELL_VISUAL_MAX_DURATION)
: SPELL_VISUAL_DEFAULT_DURATION;
activeSpellVisuals_.push_back({instanceId, 0.0f, duration});
LOG_DEBUG("SpellVisual: spawned visualId=", visualId, " instanceId=", instanceId,
" model=", modelPath);
" duration=", duration, "s model=", modelPath);
}
void Renderer::updateSpellVisuals(float deltaTime) {
if (activeSpellVisuals_.empty() || !m2Renderer) return;
for (auto it = activeSpellVisuals_.begin(); it != activeSpellVisuals_.end(); ) {
it->elapsed += deltaTime;
if (it->elapsed >= SPELL_VISUAL_DURATION) {
if (it->elapsed >= it->duration) {
m2Renderer->removeInstance(it->instanceId);
it = activeSpellVisuals_.erase(it);
} else {