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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user