From 48d15fc653b5bcddee2b118cc4d2547e3fbcb0ed Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 19:41:01 -0700 Subject: [PATCH] fix: quest markers, level-up effect, and emote loop - renderer: construct QuestMarkerRenderer via make_unique (was never instantiated, causing getQuestMarkerRenderer() to always return null and all quest-marker updates to be silently skipped) - m2_renderer: add "levelup" to effectByName so LevelUp.m2 is treated as a spell effect (additive blend, no collision, particle-dominated) - renderer: auto-cancel non-looping emote animations when they reach end-of-sequence, transitioning player back to IDLE state --- src/rendering/m2_renderer.cpp | 3 ++- src/rendering/renderer.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index b079f50a..2c25cd77 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1148,7 +1148,8 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) { (lowerName.find("lavasplash") != std::string::npos) || (lowerName.find("lavabubble") != std::string::npos) || (lowerName.find("lavasteam") != std::string::npos) || - (lowerName.find("wisps") != std::string::npos); + (lowerName.find("wisps") != std::string::npos) || + (lowerName.find("levelup") != std::string::npos); gpuModel.isSpellEffect = effectByName || (hasParticles && model.vertices.size() <= 200 && model.particleEmitters.size() >= 3); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 71cb2a7c..56ce7c07 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -710,6 +710,8 @@ bool Renderer::initialize(core::Window* win) { levelUpEffect = std::make_unique(); + questMarkerRenderer = std::make_unique(); + LOG_INFO("Vulkan sub-renderers initialized (Phase 3)"); // LightingManager doesn't use GL — initialize for data-only use @@ -2222,6 +2224,14 @@ void Renderer::updateCharacterAnimation() { } else if (sitting) { cancelEmote(); newState = CharAnimState::SIT_DOWN; + } else if (!emoteLoop && characterRenderer && characterInstanceId > 0) { + // Auto-cancel non-looping emotes once animation completes + uint32_t curId = 0; float curT = 0.0f, curDur = 0.0f; + if (characterRenderer->getAnimationState(characterInstanceId, curId, curT, curDur) + && curDur > 0.1f && curT >= curDur - 0.05f) { + cancelEmote(); + newState = CharAnimState::IDLE; + } } break;