diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index d71b31d..212a3e4 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -585,6 +585,20 @@ void LootTemplate::CopyConditions(LootItem* li) const // Rolls for every item in the template and adds the rolled items the the loot void LootTemplate::Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId, Player const* player /*= nullptr*/, bool specOnly /*= false*/) const { + // Sylvania: guard against cyclic loot references (e.g. a reference_loot_template + // entry that references itself). Without this, such a cycle recurses until the + // worldserver stack overflows and crashes (observed on ICC loot, ref 104). The + // load-time loot verification of this core does not reject self/cyclic references, + // so we cap recursion defensively. No legitimate loot chain nests this deep. + static uint32 const MAX_LOOT_REFERENCE_DEPTH = 50; + static thread_local uint32 lootProcessDepth = 0; + struct DepthGuard { uint32& d; explicit DepthGuard(uint32& r) : d(r) { ++d; } ~DepthGuard() { --d; } } depthGuard(lootProcessDepth); + if (lootProcessDepth > MAX_LOOT_REFERENCE_DEPTH) + { + TC_LOG_ERROR("sql.sql", "LootTemplate::Process: loot reference recursion exceeded %u levels (likely a self-referencing or cyclic reference_loot_template entry) - aborting to prevent a stack-overflow crash", MAX_LOOT_REFERENCE_DEPTH); + return; + } + if (groupId) // Group reference uses own processing of the group { if (groupId > Groups.size()) diff --git a/src/server/scripts/Kalimdor/Firelands/boss_ragnaros_firelands.cpp b/src/server/scripts/Kalimdor/Firelands/boss_ragnaros_firelands.cpp index 2c86a1e..6672cb0 100644 --- a/src/server/scripts/Kalimdor/Firelands/boss_ragnaros_firelands.cpp +++ b/src/server/scripts/Kalimdor/Firelands/boss_ragnaros_firelands.cpp @@ -421,7 +421,7 @@ class boss_ragnaros_firelands: public CreatureScript Creature* smash; Creature* splitting; uint8 sonOfFlameCount; - bool HeartCheck, introDone, intermission1, intermission1InProgress, intermission2, intermission2InProgress, phase3, inMeleeRange, heroicPhase, died; + bool HeartCheck, introDone, intermission1, intermission1InProgress, intermission2, intermission2InProgress, phase3, inMeleeRange, heroicPhase, heroicPhaseFour, died; void Reset() override { @@ -454,6 +454,7 @@ class boss_ragnaros_firelands: public CreatureScript phase3 = false; inMeleeRange = false; heroicPhase = false; + heroicPhaseFour = false; died = false; sonOfFlameCount = 0; @@ -706,6 +707,42 @@ class boss_ragnaros_firelands: public CreatureScript events.ScheduleEvent(EVENT_PHASE_FOUR, 1000); heroicPhase = true; } + // Sylvania fix: heroic phase 4 ("True Power of the Firelord") previously + // had NO completion path, leaving Ragnaros unkillable in heroic Firelands + // (every SetData(DONE)/EVENT_DIE site lived only in the !IsHeroic() branch). + // Mirror the normal-mode death once phase 4 is actually underway so the + // encounter completes (me->Kill -> _JustDied -> SetBossState DONE) and + // credit/achievement/loot are granted. Gated on heroicPhaseFour (set after + // the phase-4 heal) so a sub-10% burst during the ~1s entry window cannot + // short-circuit phase 4. + else if (HealthBelowPct(10) && IsHeroic() && heroicPhaseFour && !died) + { + Talk(SAY_DEATH_H); + + me->AttackStop(); + me->CastStop(); + me->SetReactState(REACT_PASSIVE); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + me->RemoveAllAuras(); + if (instance->GetData(DATA_TEAM) == ALLIANCE) + { + me->SummonGameObject(GO_CACHE_OF_THE_FIRELORD, 1016.043f, -57.436f, 55.333f, 3.151f, QuaternionData(), 30000); + } + else + { + me->SummonGameObject(GO_CACHE_OF_THE_FIRELORD_H, 1016.043f, -57.436f, 55.333f, 3.151f, QuaternionData(), 30000); + } + + if (instance) + { + instance->SetData(DATA_RAGNAROS, DONE); + instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); // Remove + } + + events.ScheduleEvent(EVENT_DIE, 2000); + me->HandleEmoteCommand(EMOTE_ONESHOT_SUBMERGE); + died = true; + } events.Update(diff); @@ -1032,6 +1069,7 @@ class boss_ragnaros_firelands: public CreatureScript Talk(SAY_PHASE_HEROIC_TEXT); me->RemoveAurasDueToSpell(SPELL_BASE_VISUAL); me->SetHealth(me->GetMaxHealth() / 2); + heroicPhaseFour = true; // Sylvania: phase 4 is now active -> arm the heroic death check me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_REMOVE_CLIENT_CONTROL); me->SetObjectScale(1.2f); events.ScheduleEvent(EVENT_SUPERHEATED, 10000);