fix(loot+firelands): stop cyclic loot-ref stack overflow & make heroic Ragnaros killable

LootMgr: LootTemplate::Process gains a thread_local recursion-depth guard (RAII, cap 50). A self-referencing reference_loot_template row (ref 104 -> 104) recursed until the worldserver stack overflowed and crashed on ICC boss/trash loot; this core does not reject such cycles at load. DB row already removed live; this hardens against any future cycle.

boss_ragnaros_firelands: heroic phase 4 had no completion path (every SetData(DONE)/EVENT_DIE lived in the !IsHeroic branch), leaving Ragnaros unkillable in heroic. Add a heroic death branch gated on new flag heroicPhaseFour (set after the phase-4 heal) so the encounter completes and grants credit/loot. Normal mode unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
SylvaniaCore deploy
2026-06-22 21:24:18 +00:00
parent 0fd690e7c4
commit 72d1222d75
2 changed files with 53 additions and 1 deletions
+14
View File
@@ -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())
@@ -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);