feat(Scripts/ICC): script the Deathbringer Saurfang outro and victory… (#27112)

Co-authored-by: Skjalf <[email protected]>
This commit is contained in:
EricksOliveira
2026-09-16 03:55:01 -03:00
committed by GitHub
co-authored by Skjalf
parent f68a9759bd
commit 5b04ee5469
4 changed files with 1010 additions and 99 deletions
@@ -0,0 +1,13 @@
-- Deathbringer Saurfang outro: the camp props only spawned on 10N/25N.
UPDATE `gameobject` SET `spawnMask` = 15 WHERE `id` IN (1685, 191345, 187852) AND `guid` IN (12363, 55882, 81649);
-- Grip of Agony hangs Muradin/High Overlord and their guards in the air, and the script owns the
-- disable-gravity flag for that. Without 512 (CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE) the core
-- clears the flag again on the next terrain update and they drop back to the floor mid-intro.
UPDATE `creature_template` SET `flags_extra` = `flags_extra`|512 WHERE `entry` IN (37187, 37200, 37830, 37920);
-- Two emote beats of the Alliance outro that have no creature_text row.
DELETE FROM `creature_text` WHERE (`CreatureID` = 37187 AND `GroupID` = 19) OR (`CreatureID` = 37188 AND `GroupID` = 2);
INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES
(37187, 19, 0, '%s nods.', 16, 0, 0, 273, 0, 0, 37531, 0, 'High Overlord Saurfang - SAY_OUTRO_ALLIANCE_SAURFANG_NOD'),
(37188, 2, 0, '%s wipes her tears away and smiles at King Varian Wrynn.', 16, 0, 0, 0, 0, 0, 37544, 0, 'Lady Jaina Proudmoore - SAY_OUTRO_ALLIANCE_JAINA_SMILE');
@@ -61,12 +61,13 @@ enum ScriptTexts
SAY_OUTRO_HORDE_2 = 12,
SAY_OUTRO_HORDE_3 = 13,
SAY_OUTRO_HORDE_4 = 14,
SAY_OUTRO_ALLIANCE_SAURFANG_NOD = 19, // emote beat, outside the numbered 21
// Muradin Bronzebeard
SAY_INTRO_ALLIANCE_1 = 0,
SAY_INTRO_ALLIANCE_4 = 1,
SAY_INTRO_ALLIANCE_5 = 2,
SAY_OUTRO_ALLIANCE_1 = 3, /// @todo ALLIANCE OUTRO
SAY_OUTRO_ALLIANCE_1 = 3,
SAY_OUTRO_ALLIANCE_2 = 4,
SAY_OUTRO_ALLIANCE_3 = 5,
SAY_OUTRO_ALLIANCE_4 = 6,
@@ -80,6 +81,7 @@ enum ScriptTexts
// Lady Jaina Proudmoore
SAY_OUTRO_ALLIANCE_17 = 0,
SAY_OUTRO_ALLIANCE_19 = 1,
SAY_OUTRO_ALLIANCE_JAINA_SMILE = 2, // emote beat, outside the numbered 21
// King Varian Wrynn
SAY_OUTRO_ALLIANCE_11 = 0,
@@ -167,13 +169,22 @@ enum EventTypes
EVENT_OUTRO_ALLIANCE_18 = 40,
EVENT_OUTRO_ALLIANCE_19 = 41,
EVENT_OUTRO_ALLIANCE_20 = 42,
EVENT_OUTRO_ALLIANCE_21 = 43,
EVENT_OUTRO_HORDE_1 = 44,
EVENT_OUTRO_HORDE_2 = 45,
EVENT_OUTRO_HORDE_3 = 46,
EVENT_OUTRO_HORDE_4 = 47,
EVENT_OUTRO_HORDE_5 = 48,
EVENT_OUTRO_HORDE_GUARDS_KNEEL = 49,
EVENT_OUTRO_HORDE_PICKUP = 50,
EVENT_OUTRO_HORDE_LEAVE = 51,
EVENT_OUTRO_A_GUARDS_KNEEL = 52,
EVENT_OUTRO_A_SAURFANG_NOD = 53,
EVENT_OUTRO_A_JAINA_SMILE = 54,
EVENT_OUTRO_A_SAURFANG_LEAVE = 55,
EVENT_OUTRO_A_DISMISS = 56,
EVENT_OUTRO_A_ROYALS = 57,
EVENT_OUTRO_A_PORTAL_CLOSE = 58,
EVENT_OUTRO_A_SAURFANG_ADVANCE = 59,
EVENT_OUTRO_A_ZEPPELIN_RELEASE = 60,
};
enum Phases
@@ -191,11 +202,16 @@ enum Actions
ACTION_CONTINUE_INTRO = -3781301,
ACTION_CHARGE = -3781302,
ACTION_START_OUTRO = -3781303,
ACTION_DESPAWN = -3781304,
ACTION_INTRO_DONE = -3781305,
ACTION_EVADE = -3781306,
ACTION_GAIN_SCENT_OF_BLOOD = -3781307,
ACTION_MARK_OF_THE_FALLEN_CHAMPION = -72293,
ACTION_READY_WEAPONS = -3781308,
ACTION_OUTRO_DESCEND = -3781309,
ACTION_OUTRO_KNEEL = -3781310,
ACTION_OUTRO_RETREAT = -3781311,
ACTION_OUTRO_FALL_IN = -3781312,
ACTION_OUTRO_STAND_DOWN = -3781313,
};
#define DATA_MADE_A_MESS 45374613 // 4537, 4613 are achievement IDs
@@ -209,6 +225,15 @@ enum MovePoints
POINT_CHOKE = 3781303,
POINT_CORPSE = 3781304,
POINT_FINAL = 3781305,
POINT_TRANSPORTER = 3781306,
POINT_RETREAT = 3781307,
POINT_A_MURADIN_STAND = 3781308,
POINT_A_SAURFANG_MEET = 3781309,
POINT_A_CORPSE = 3781310,
POINT_A_VARIAN = 3781311,
POINT_A_EXIT = 3781312,
POINT_A_STAND_DOWN = 3781313,
POINT_A_MURADIN_HOME = 3781314,
};
Position const deathbringerPos = {-496.3542f, 2211.33f, 541.1138f, 0.0f};
@@ -224,17 +249,35 @@ Position const chargePos[6] =
{-509.0040f, 2211.743f, 539.2870f, 0.0f} // back right
};
// Grip of Agony hangs the whole group at one height, 12 yards over the floor at 539.287.
Position const chokePos[6] =
{
{-514.4834f, 2211.334f, 549.2887f, 0.0f}, // High Overlord Saurfang/Muradin Bronzebeard
{-510.1081f, 2211.592f, 546.3773f, 0.0f}, // front left
{-514.4834f, 2211.334f, 551.2882f, 0.0f}, // High Overlord Saurfang/Muradin Bronzebeard
{-510.1081f, 2211.592f, 551.2882f, 0.0f}, // front left
{-513.3210f, 2211.396f, 551.2882f, 0.0f}, // front right
{-507.3684f, 2210.353f, 545.7497f, 0.0f}, // back middle
{-507.0486f, 2212.999f, 545.5512f, 0.0f}, // back left
{-510.7041f, 2211.069f, 546.5298f, 0.0f} // back right
{-507.3684f, 2210.353f, 551.2882f, 0.0f}, // back middle
{-507.0486f, 2212.999f, 551.2882f, 0.0f}, // back left
{-510.7041f, 2211.069f, 551.2882f, 0.0f} // back right
};
//Position const finalPos = {-563.7552f, 2211.328f, 538.7848f, 0.0f};
Position const finalPos = {-563.7552f, 2211.328f, 538.7848f, 0.0f};
Position const transporterPos = {-549.0735f, 2211.289f, 539.2917f, 3.1415927f};
Position const allianceSaurfangPos = {-521.8657f, 2250.3455f, 539.29240f, 5.2911840f};
Position const allianceMuradinPos = {-518.2308f, 2232.5356f, 539.29095f, 1.8000813f};
Position const allianceVarianPos = {-521.0001f, 2238.3192f, 539.29095f, 5.6681833f};
Position const allianceJainaPos = {-522.2058f, 2236.3171f, 539.29095f, 5.7388660f};
Position const allianceSaurfangMeetPos = {-519.0500f, 2236.4000f, 539.29095f, 1.8000813f + 3.1415927f};
Position const allianceVarianMeetPos = {-518.5498f, 2236.5883f, 539.29095f, 2.5265906f};
float const allianceGuardOffset[6][2] =
{
{ 0.0f, 0.0f},
{ 2.6f, -1.4f},
{ 3.4f, 1.2f},
{ 0.8f, -3.2f},
{-1.6f, -2.4f},
{-2.2f, 1.0f}
};
class boss_deathbringer_saurfang : public CreatureScript
{
@@ -255,6 +298,7 @@ public:
me->SetReactState(REACT_DEFENSIVE);
events.Reset();
_introDone = false;
_outroStarted = false;
_frenzied = false;
_fallenChampionCastCount = 0;
_transportCheckTimer = 1000;
@@ -308,6 +352,7 @@ public:
void JustDied(Unit* /*killer*/) override
{
_JustDied();
_outroStarted = true;
DoCast(me, SPELL_ACHIEVEMENT, true);
Talk(SAY_DEATH);
@@ -497,6 +542,10 @@ public:
void EnterEvadeMode(EvadeReason why) override
{
// The outro revives the corpse to carry it, so evading here would roll DONE back to FAIL.
if (_outroStarted)
return;
BossAI::EnterEvadeMode(why);
if (Creature* creature = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SAURFANG_EVENT_NPC)))
creature->AI()->DoAction(ACTION_EVADE);
@@ -505,6 +554,7 @@ public:
private:
uint32 _fallenChampionCastCount;
bool _introDone;
bool _outroStarted;
bool _frenzied; // faster than iterating all auras to find Frenzy
uint16 _transportCheckTimer;
};
@@ -549,14 +599,17 @@ public:
if (!deathbringer || deathbringer->IsInEvadeMode())
return;
if (_guardList.empty())
{
GetCreatureListWithEntryInGrid(_guardList, me, NPC_SE_KOR_KRON_REAVER, 20.0f);
_guardList.sort(Acore::ObjectDistanceOrderPred(me));
}
// Rebuilt each run: the guards despawn during the outro.
_guardList.clear();
std::list<Creature*> guards;
GetCreatureListWithEntryInGrid(guards, me, NPC_SE_KOR_KRON_REAVER, 20.0f);
guards.sort(Acore::ObjectDistanceOrderPred(me));
uint32 x = 1;
for (std::list<Creature*>::iterator itr = _guardList.begin(); itr != _guardList.end(); ++itr)
(*itr)->AI()->SetData(0, x++);
for (Creature* guard : guards)
{
_guardList.push_back(guard->GetGUID());
guard->AI()->SetData(0, x++);
}
me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
Talk(SAY_INTRO_HORDE_1);
@@ -581,13 +634,15 @@ public:
me->RemoveAurasDueToSpell(SPELL_GRIP_OF_AGONY);
me->SetDisableGravity(false);
me->GetMotionMaster()->MovePoint(0, me->GetPositionX(), me->GetPositionY(), 539.2917f, FORCED_MOVEMENT_NONE, 10.0f);
for (std::list<Creature*>::iterator itr = _guardList.begin(); itr != _guardList.end(); ++itr)
(*itr)->AI()->DoAction(ACTION_DESPAWN);
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_OUTRO_DESCEND);
/*Talk(SAY_OUTRO_HORDE_1);
_instance->SetData(DATA_SAURFANG_CAMP, IN_PROGRESS);
Talk(SAY_OUTRO_HORDE_1);
_events.ScheduleEvent(EVENT_OUTRO_HORDE_GUARDS_KNEEL, 2s + 500ms);
_events.ScheduleEvent(EVENT_OUTRO_HORDE_1, 10s);
_events.ScheduleEvent(EVENT_OUTRO_HORDE_2, 18s);
_events.ScheduleEvent(EVENT_OUTRO_HORDE_3, 24s);*/
_events.ScheduleEvent(EVENT_OUTRO_HORDE_3, 18s);
}
break;
case ACTION_EVADE:
@@ -599,14 +654,18 @@ public:
me->StopMovingOnCurrentPos();
me->SetDisableGravity(false);
EnterEvadeMode();
for (std::list<Creature*>::iterator itr = _guardList.begin(); itr != _guardList.end(); ++itr)
for (ObjectGuid const& guid : _guardList)
{
(*itr)->GetMotionMaster()->Clear();
(*itr)->GetHomePosition(x, y, z, o);
(*itr)->SetPosition(x, y, z, o);
(*itr)->StopMovingOnCurrentPos();
(*itr)->SetDisableGravity(false);
(*itr)->AI()->EnterEvadeMode();
Creature* guard = ObjectAccessor::GetCreature(*me, guid);
if (!guard)
continue;
guard->GetMotionMaster()->Clear();
guard->GetHomePosition(x, y, z, o);
guard->SetPosition(x, y, z, o);
guard->StopMovingOnCurrentPos();
guard->SetDisableGravity(false);
guard->AI()->EnterEvadeMode();
}
}
break;
@@ -633,29 +692,38 @@ public:
case POINT_FIRST_STEP:
me->SetWalk(false);
Talk(SAY_INTRO_HORDE_3);
_events.ScheduleEvent(EVENT_INTRO_HORDE_4, 6500ms, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_5, 15s + 500ms, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_6, 29s + 500ms, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_7, 43s + 800ms, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_8, 47s, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_9, 48s + 200ms, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_FINISH, 56s + 700ms, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_4, 10s, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_5, 22s, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_6, 36s, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_7, 53s, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_8, 57s + 800ms, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_HORDE_9, 59s, 0, PHASE_INTRO_H);
_events.ScheduleEvent(EVENT_INTRO_FINISH, 68s, 0, PHASE_INTRO_H);
break;
/*case POINT_CORPSE:
case POINT_CORPSE:
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
{
deathbringer->CastSpell(me, SPELL_RIDE_VEHICLE, true);
deathbringer->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
deathbringer->setDeathState(DeathState::Alive);
}
_events.ScheduleEvent(EVENT_OUTRO_HORDE_4, 1s);
_events.ScheduleEvent(EVENT_OUTRO_HORDE_5, 4s);
me->SetFacingToObject(deathbringer);
me->SetStandState(UNIT_STAND_STATE_KNEEL);
Talk(SAY_OUTRO_HORDE_3);
_events.ScheduleEvent(EVENT_OUTRO_HORDE_PICKUP, 6s);
break;
case POINT_TRANSPORTER:
me->SetFacingTo(0.0f);
Talk(SAY_OUTRO_HORDE_4);
_events.ScheduleEvent(EVENT_OUTRO_HORDE_LEAVE, 8s);
break;
case POINT_FINAL:
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
deathbringer->DespawnOrUnsummon();
me->DespawnOrUnsummon();
break;*/
{
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
deathbringer->DespawnOrUnsummon();
float x, y, z, o;
me->GetHomePosition(x, y, z, o);
me->SetVisible(false);
me->NearTeleportTo(x, y, z, o);
_instance->SetData(DATA_SAURFANG_CAMP, DONE);
}
break;
default:
break;
}
@@ -674,6 +742,9 @@ public:
deathbringer->AI()->Talk(SAY_INTRO_HORDE_2);
break;
case EVENT_INTRO_HORDE_3:
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_READY_WEAPONS);
me->SetWalk(true);
me->GetMotionMaster()->MovePoint(POINT_FIRST_STEP, firstStepPos.GetPositionX(), firstStepPos.GetPositionY(), firstStepPos.GetPositionZ());
break;
@@ -692,8 +763,9 @@ public:
break;
case EVENT_INTRO_HORDE_8:
Talk(SAY_INTRO_HORDE_8);
for (std::list<Creature*>::iterator itr = _guardList.begin(); itr != _guardList.end(); ++itr)
(*itr)->AI()->DoAction(ACTION_CHARGE);
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_CHARGE);
me->GetMotionMaster()->MoveCharge(chargePos[0].GetPositionX(), chargePos[0].GetPositionY(), chargePos[0].GetPositionZ(), 8.5f, POINT_CHARGE);
break;
case EVENT_INTRO_HORDE_9:
@@ -712,37 +784,55 @@ public:
deathbringer->AI()->AttackStart(target);
}
break;
/*case EVENT_OUTRO_HORDE_1:
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
me->SetFacingToObject(deathbringer);
Talk(SAY_OUTRO_HORDE_2);
break;
case EVENT_OUTRO_HORDE_2:
Talk(SAY_OUTRO_HORDE_3);
break;
case EVENT_OUTRO_HORDE_3:
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
{
float x, y, z;
deathbringer->GetClosePoint(x, y, z, deathbringer->GetObjectSize());
me->SetWalk(true);
me->GetMotionMaster()->MovePoint(POINT_CORPSE, x, y, z);
}
break;
case EVENT_OUTRO_HORDE_4:
me->GetMotionMaster()->MovePoint(POINT_FINAL, finalPos);
break;
case EVENT_OUTRO_HORDE_5:
Talk(SAY_OUTRO_HORDE_4);
break;*/
case EVENT_OUTRO_HORDE_1:
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
me->SetFacingToObject(deathbringer);
Talk(SAY_OUTRO_HORDE_2);
break;
case EVENT_OUTRO_HORDE_GUARDS_KNEEL:
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_OUTRO_KNEEL);
break;
case EVENT_OUTRO_HORDE_3:
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
{
float x, y, z;
deathbringer->GetClosePoint(x, y, z, deathbringer->GetObjectSize());
me->SetWalk(true);
me->GetMotionMaster()->MovePoint(POINT_CORPSE, x, y, z);
}
break;
case EVENT_OUTRO_HORDE_PICKUP:
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
{
// Revive before the cast - Unit::_EnterVehicle refuses a dead passenger.
deathbringer->setDeathState(DeathState::Alive);
deathbringer->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
deathbringer->CastSpell(me, SPELL_RIDE_VEHICLE, true);
}
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_OUTRO_RETREAT);
_guardList.clear();
_events.ScheduleEvent(EVENT_OUTRO_HORDE_4, 3s);
break;
case EVENT_OUTRO_HORDE_4:
me->SetStandState(UNIT_STAND_STATE_STAND);
me->SetWalk(true);
me->GetMotionMaster()->MovePoint(POINT_TRANSPORTER, transporterPos);
break;
case EVENT_OUTRO_HORDE_LEAVE:
me->SetWalk(true);
me->GetMotionMaster()->MovePoint(POINT_FINAL, finalPos);
break;
}
}
private:
EventMap _events;
InstanceScript* _instance;
std::list<Creature*> _guardList;
GuidList _guardList;
};
bool OnGossipHello(Player* player, Creature* creature) override
@@ -787,11 +877,13 @@ public:
npc_muradin_bronzebeard_iccAI(Creature* creature) : ScriptedAI(creature)
{
_instance = me->GetInstanceScript();
_outroZeppelinWait = 0;
}
void Reset() override
{
_events.Reset();
_outroZeppelinWait = 0;
me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP);
me->SetReactState(REACT_PASSIVE);
}
@@ -810,17 +902,21 @@ public:
if (!deathbringer || deathbringer->IsInEvadeMode())
return;
if (_guardList.empty())
{
GetCreatureListWithEntryInGrid(_guardList, me, NPC_SE_SKYBREAKER_MARINE, 20.0f);
_guardList.sort(Acore::ObjectDistanceOrderPred(me));
}
// Rebuilt each run: the guards despawn during the outro.
_guardList.clear();
std::list<Creature*> guards;
GetCreatureListWithEntryInGrid(guards, me, NPC_SE_SKYBREAKER_MARINE, 20.0f);
guards.sort(Acore::ObjectDistanceOrderPred(me));
uint32 x = 1;
for (std::list<Creature*>::iterator itr = _guardList.begin(); itr != _guardList.end(); ++itr)
(*itr)->AI()->SetData(0, x++);
for (Creature* guard : guards)
{
_guardList.push_back(guard->GetGUID());
guard->AI()->SetData(0, x++);
}
me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
Talk(SAY_INTRO_ALLIANCE_1);
_outroZeppelinWait = 0;
_events.SetPhase(PHASE_INTRO_A);
_events.ScheduleEvent(EVENT_INTRO_ALLIANCE_2, 2500ms, 0, PHASE_INTRO_A);
_events.ScheduleEvent(EVENT_INTRO_ALLIANCE_3, 20s, 0, PHASE_INTRO_A);
@@ -843,10 +939,18 @@ public:
me->RemoveAurasDueToSpell(SPELL_GRIP_OF_AGONY);
me->SetDisableGravity(false);
me->GetMotionMaster()->MovePoint(0, me->GetPositionX(), me->GetPositionY(), 539.2917f, FORCED_MOVEMENT_NONE, 10.0f);
for (std::list<Creature*>::iterator itr = _guardList.begin(); itr != _guardList.end(); ++itr)
(*itr)->AI()->DoAction(ACTION_DESPAWN);
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_OUTRO_DESCEND);
//Talk(SAY_OUTRO_ALLIANCE_1);
_instance->SetData(DATA_SAURFANG_CAMP, IN_PROGRESS);
Talk(SAY_OUTRO_ALLIANCE_1);
_events.ScheduleEvent(EVENT_OUTRO_A_GUARDS_KNEEL, 2s + 500ms);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_1, 3s);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_2, 18s);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_3, 24s);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_4, 30s);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_5, 49s);
break;
}
case ACTION_EVADE:
@@ -858,14 +962,18 @@ public:
me->StopMovingOnCurrentPos();
me->SetDisableGravity(false);
EnterEvadeMode();
for (std::list<Creature*>::iterator itr = _guardList.begin(); itr != _guardList.end(); ++itr)
for (ObjectGuid const& guid : _guardList)
{
(*itr)->GetMotionMaster()->Clear();
(*itr)->GetHomePosition(x, y, z, o);
(*itr)->SetPosition(x, y, z, o);
(*itr)->StopMovingOnCurrentPos();
(*itr)->SetDisableGravity(false);
(*itr)->AI()->EnterEvadeMode();
Creature* guard = ObjectAccessor::GetCreature(*me, guid);
if (!guard)
continue;
guard->GetMotionMaster()->Clear();
guard->GetHomePosition(x, y, z, o);
guard->SetPosition(x, y, z, o);
guard->StopMovingOnCurrentPos();
guard->SetDisableGravity(false);
guard->AI()->EnterEvadeMode();
}
}
break;
@@ -897,6 +1005,15 @@ public:
_events.ScheduleEvent(EVENT_INTRO_ALLIANCE_7, 9s, 0, PHASE_INTRO_A);
_events.ScheduleEvent(EVENT_INTRO_FINISH, 14s, 0, PHASE_INTRO_A);
break;
case POINT_A_MURADIN_HOME:
{
float x, y, z, o;
me->GetHomePosition(x, y, z, o);
me->SetFacingTo(o);
me->SetSheath(SHEATH_STATE_UNARMED);
_instance->SetData(DATA_SAURFANG_CAMP, DONE);
}
break;
default:
break;
}
@@ -919,13 +1036,17 @@ public:
deathbringer->AI()->Talk(SAY_INTRO_ALLIANCE_3);
break;
case EVENT_INTRO_ALLIANCE_4:
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_READY_WEAPONS);
me->SetWalk(true);
me->GetMotionMaster()->MovePoint(POINT_FIRST_STEP, firstStepPos.GetPositionX(), firstStepPos.GetPositionY(), firstStepPos.GetPositionZ());
break;
case EVENT_INTRO_ALLIANCE_5:
Talk(SAY_INTRO_ALLIANCE_5);
for (std::list<Creature*>::iterator itr = _guardList.begin(); itr != _guardList.end(); ++itr)
(*itr)->AI()->DoAction(ACTION_CHARGE);
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_CHARGE);
me->GetMotionMaster()->MoveCharge(chargePos[0].GetPositionX(), chargePos[0].GetPositionY(), chargePos[0].GetPositionZ(), 8.5f, POINT_CHARGE);
break;
case EVENT_INTRO_ALLIANCE_6:
@@ -950,13 +1071,246 @@ public:
deathbringer->AI()->AttackStart(target);
}
break;
case EVENT_OUTRO_A_GUARDS_KNEEL:
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_OUTRO_KNEEL);
break;
case EVENT_OUTRO_ALLIANCE_1:
Talk(SAY_OUTRO_ALLIANCE_2);
break;
case EVENT_OUTRO_ALLIANCE_2:
Talk(SAY_OUTRO_ALLIANCE_3);
break;
case EVENT_OUTRO_ALLIANCE_3:
Talk(SAY_OUTRO_ALLIANCE_4);
_instance->SetData(DATA_SAURFANG_OUTRO_ZEPPELIN, IN_PROGRESS);
break;
case EVENT_OUTRO_ALLIANCE_4:
Talk(SAY_OUTRO_ALLIANCE_5);
me->SetWalk(false);
me->GetMotionMaster()->MovePoint(POINT_A_MURADIN_STAND, allianceMuradinPos);
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_OUTRO_FALL_IN);
break;
case EVENT_OUTRO_ALLIANCE_5:
if (_instance->GetData(DATA_SAURFANG_OUTRO_ZEPPELIN) != DONE)
{
// Bounded wait for the ship to dock, so a taxi path that never arrives cannot hang the scene.
if (++_outroZeppelinWait <= 60)
{
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_5, 1s);
break;
}
}
Talk(SAY_OUTRO_ALLIANCE_6);
if (Creature* saurfang = me->SummonCreature(NPC_SE_HIGH_OVERLORD_SAURFANG, allianceSaurfangPos))
{
_outroSaurfangGUID = saurfang->GetGUID();
saurfang->SetReactState(REACT_PASSIVE);
saurfang->SetSheath(SHEATH_STATE_MELEE);
saurfang->SetEmoteState(EMOTE_STATE_READY1H);
// He shares npc_high_overlord_saurfangAI, whose Reset() flags him for gossip.
saurfang->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
}
_events.ScheduleEvent(EVENT_OUTRO_A_SAURFANG_ADVANCE, 3s);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_6, 31s);
break;
case EVENT_OUTRO_A_SAURFANG_ADVANCE:
if (Creature* saurfang = ObjectAccessor::GetCreature(*me, _outroSaurfangGUID))
{
saurfang->SetWalk(true);
saurfang->GetMotionMaster()->MovePoint(POINT_A_SAURFANG_MEET, allianceSaurfangMeetPos);
}
break;
case EVENT_OUTRO_ALLIANCE_6:
Talk(SAY_OUTRO_ALLIANCE_7);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_7, 5s);
break;
case EVENT_OUTRO_ALLIANCE_7:
if (Creature* saurfang = ObjectAccessor::GetCreature(*me, _outroSaurfangGUID))
saurfang->AI()->Talk(SAY_OUTRO_ALLIANCE_8);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_8, 7s);
break;
case EVENT_OUTRO_ALLIANCE_8:
Talk(SAY_OUTRO_ALLIANCE_9);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_9, 15s);
break;
case EVENT_OUTRO_ALLIANCE_9:
Talk(SAY_OUTRO_ALLIANCE_10);
_instance->SetData(DATA_SAURFANG_OUTRO_PORTAL, IN_PROGRESS);
_events.ScheduleEvent(EVENT_OUTRO_A_ROYALS, 3s);
_events.ScheduleEvent(EVENT_OUTRO_A_PORTAL_CLOSE, 9s);
break;
case EVENT_OUTRO_A_ROYALS:
if (Creature* varian = me->SummonCreature(NPC_SE_KING_VARIAN_WRYNN, allianceVarianPos))
{
_outroVarianGUID = varian->GetGUID();
varian->SetReactState(REACT_PASSIVE);
}
if (Creature* jaina = me->SummonCreature(NPC_SE_JAINA_PROUDMOORE, allianceJainaPos))
{
_outroJainaGUID = jaina->GetGUID();
jaina->SetReactState(REACT_PASSIVE);
}
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_10, 5s);
break;
case EVENT_OUTRO_A_PORTAL_CLOSE:
_instance->SetData(DATA_SAURFANG_OUTRO_PORTAL, DONE);
break;
case EVENT_OUTRO_ALLIANCE_10:
if (Creature* varian = ObjectAccessor::GetCreature(*me, _outroVarianGUID))
varian->AI()->Talk(SAY_OUTRO_ALLIANCE_11);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_11, 6s);
break;
case EVENT_OUTRO_ALLIANCE_11:
{
// He carries the rest of the scene; without him, skip to the cleanup.
Creature* saurfang = ObjectAccessor::GetCreature(*me, _outroSaurfangGUID);
if (!saurfang)
{
_events.ScheduleEvent(EVENT_OUTRO_A_DISMISS, 1s);
break;
}
saurfang->AI()->Talk(SAY_OUTRO_ALLIANCE_12);
saurfang->SetEmoteState(EMOTE_ONESHOT_NONE);
saurfang->SetSheath(SHEATH_STATE_UNARMED);
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
{
float x, y, z;
deathbringer->GetClosePoint(x, y, z, deathbringer->GetObjectSize());
saurfang->SetWalk(true);
saurfang->GetMotionMaster()->MovePoint(POINT_A_CORPSE, x, y, z);
}
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_12, 14s);
break;
}
case EVENT_OUTRO_ALLIANCE_12:
if (Creature* saurfang = ObjectAccessor::GetCreature(*me, _outroSaurfangGUID))
{
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
saurfang->SetFacingToObject(deathbringer);
saurfang->SetStandState(UNIT_STAND_STATE_KNEEL);
saurfang->AI()->Talk(SAY_OUTRO_ALLIANCE_13);
}
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_13, 8s);
break;
case EVENT_OUTRO_ALLIANCE_13:
if (Creature* saurfang = ObjectAccessor::GetCreature(*me, _outroSaurfangGUID))
{
saurfang->AI()->Talk(SAY_OUTRO_ALLIANCE_14);
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
{
// Revive before the cast - Unit::_EnterVehicle refuses a dead passenger.
deathbringer->setDeathState(DeathState::Alive);
deathbringer->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
deathbringer->CastSpell(saurfang, SPELL_RIDE_VEHICLE, true);
}
saurfang->SetStandState(UNIT_STAND_STATE_STAND);
saurfang->SetWalk(true);
saurfang->GetMotionMaster()->MovePoint(POINT_A_VARIAN, allianceVarianMeetPos);
}
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_14, 18s);
break;
case EVENT_OUTRO_ALLIANCE_14:
if (Creature* saurfang = ObjectAccessor::GetCreature(*me, _outroSaurfangGUID))
saurfang->AI()->Talk(SAY_OUTRO_ALLIANCE_15);
_events.ScheduleEvent(EVENT_OUTRO_A_ZEPPELIN_RELEASE, 6s);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_15, 9s);
break;
case EVENT_OUTRO_A_ZEPPELIN_RELEASE:
// The transport only pulls away ~24s after release, timed to lift off as he boards.
_instance->SetData(DATA_SAURFANG_OUTRO_ZEPPELIN, DONE);
break;
case EVENT_OUTRO_ALLIANCE_15:
if (Creature* varian = ObjectAccessor::GetCreature(*me, _outroVarianGUID))
varian->AI()->Talk(SAY_OUTRO_ALLIANCE_16);
_events.ScheduleEvent(EVENT_OUTRO_A_SAURFANG_NOD, 6s);
_events.ScheduleEvent(EVENT_OUTRO_A_SAURFANG_LEAVE, 18s);
break;
case EVENT_OUTRO_A_SAURFANG_NOD:
if (Creature* saurfang = ObjectAccessor::GetCreature(*me, _outroSaurfangGUID))
saurfang->AI()->Talk(SAY_OUTRO_ALLIANCE_SAURFANG_NOD);
break;
case EVENT_OUTRO_ALLIANCE_16:
if (Creature* jaina = ObjectAccessor::GetCreature(*me, _outroJainaGUID))
jaina->AI()->Talk(SAY_OUTRO_ALLIANCE_17);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_17, 4s);
break;
case EVENT_OUTRO_ALLIANCE_17:
if (Creature* varian = ObjectAccessor::GetCreature(*me, _outroVarianGUID))
varian->AI()->Talk(SAY_OUTRO_ALLIANCE_18);
_events.ScheduleEvent(EVENT_OUTRO_A_JAINA_SMILE, 4s);
break;
case EVENT_OUTRO_A_JAINA_SMILE:
if (Creature* jaina = ObjectAccessor::GetCreature(*me, _outroJainaGUID))
jaina->AI()->Talk(SAY_OUTRO_ALLIANCE_JAINA_SMILE);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_18, 4s);
break;
case EVENT_OUTRO_ALLIANCE_18:
if (Creature* jaina = ObjectAccessor::GetCreature(*me, _outroJainaGUID))
jaina->AI()->Talk(SAY_OUTRO_ALLIANCE_19);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_19, 6s);
break;
case EVENT_OUTRO_A_SAURFANG_LEAVE:
if (Creature* saurfang = ObjectAccessor::GetCreature(*me, _outroSaurfangGUID))
{
saurfang->SetWalk(false);
saurfang->GetMotionMaster()->MovePoint(POINT_A_EXIT, allianceSaurfangPos);
saurfang->DespawnOrUnsummon(5s);
// The body goes with him: despawned later it would drop a revived Deathbringer on the rise.
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
deathbringer->DespawnOrUnsummon(5s);
}
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_16, 6s);
break;
case EVENT_OUTRO_ALLIANCE_19:
if (Creature* varian = ObjectAccessor::GetCreature(*me, _outroVarianGUID))
varian->AI()->Talk(SAY_OUTRO_ALLIANCE_20);
_events.ScheduleEvent(EVENT_OUTRO_ALLIANCE_20, 6s);
break;
case EVENT_OUTRO_ALLIANCE_20:
Talk(SAY_OUTRO_ALLIANCE_21);
for (ObjectGuid const& guid : _guardList)
if (Creature* guard = ObjectAccessor::GetCreature(*me, guid))
guard->AI()->DoAction(ACTION_OUTRO_STAND_DOWN);
_events.ScheduleEvent(EVENT_OUTRO_A_DISMISS, 8s);
break;
case EVENT_OUTRO_A_DISMISS:
{
if (Creature* deathbringer = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_DEATHBRINGER_SAURFANG)))
deathbringer->DespawnOrUnsummon();
if (Creature* varian = ObjectAccessor::GetCreature(*me, _outroVarianGUID))
varian->DespawnOrUnsummon();
if (Creature* jaina = ObjectAccessor::GetCreature(*me, _outroJainaGUID))
jaina->DespawnOrUnsummon();
_instance->SetData(DATA_SAURFANG_OUTRO_PORTAL, DONE);
_instance->SetData(DATA_SAURFANG_OUTRO_ZEPPELIN, DONE);
_outroSaurfangGUID.Clear();
_outroVarianGUID.Clear();
_outroJainaGUID.Clear();
float x, y, z, o;
me->GetHomePosition(x, y, z, o);
me->SetWalk(false);
me->GetMotionMaster()->MovePoint(POINT_A_MURADIN_HOME, x, y, z);
}
break;
}
}
private:
EventMap _events;
InstanceScript* _instance;
std::list<Creature*> _guardList;
GuidList _guardList;
ObjectGuid _outroSaurfangGUID;
ObjectGuid _outroVarianGUID;
ObjectGuid _outroJainaGUID;
uint8 _outroZeppelinWait;
};
bool OnGossipHello(Player* player, Creature* creature) override
@@ -1004,6 +1358,14 @@ public:
me->SetReactState(REACT_PASSIVE);
}
// The intro draws their weapons, so every reset path has to put them away again.
// _index is left alone: it is their identity, not encounter state.
void Reset() override
{
me->SetSheath(SHEATH_STATE_UNARMED);
me->SetEmoteState(EMOTE_ONESHOT_NONE);
}
void SetData(uint32 type, uint32 data) override
{
ASSERT(!type && data && data < 6);
@@ -1019,15 +1381,70 @@ public:
}
}
void MovementInform(uint32 type, uint32 id) override
{
if (type != POINT_MOTION_TYPE)
return;
if (id == POINT_RETREAT)
{
me->SetSheath(SHEATH_STATE_UNARMED);
me->SetVisible(false);
}
else if (id == POINT_A_STAND_DOWN)
{
float x, y, z, o;
me->GetHomePosition(x, y, z, o);
me->SetFacingTo(o);
me->SetSheath(SHEATH_STATE_UNARMED);
}
}
void DoAction(int32 action) override
{
if (action == ACTION_CHARGE && _index)
{
// Drop the guard stance, otherwise the looping emote overrides the run animation.
me->SetEmoteState(EMOTE_ONESHOT_NONE);
me->SetWalk(false);
me->GetMotionMaster()->MoveCharge(chargePos[_index].GetPositionX(), chargePos[_index].GetPositionY(), chargePos[_index].GetPositionZ(), 13.0f, POINT_CHARGE);
}
else if (action == ACTION_DESPAWN)
me->DespawnOrUnsummon(1ms);
else if (action == ACTION_READY_WEAPONS)
{
me->SetSheath(SHEATH_STATE_MELEE);
me->SetEmoteState(EMOTE_STATE_READY1H);
}
else if (action == ACTION_OUTRO_DESCEND)
{
me->RemoveAurasDueToSpell(SPELL_GRIP_OF_AGONY);
me->SetDisableGravity(false);
me->GetMotionMaster()->MovePoint(0, me->GetPositionX(), me->GetPositionY(), 539.2917f, FORCED_MOVEMENT_NONE, 10.0f);
me->SetEmoteState(EMOTE_STATE_READY1H);
}
else if (action == ACTION_OUTRO_FALL_IN)
{
me->SetStandState(UNIT_STAND_STATE_STAND);
me->SetSheath(SHEATH_STATE_MELEE);
me->SetEmoteState(EMOTE_STATE_READY1H);
me->SetWalk(false);
me->GetMotionMaster()->MovePoint(POINT_A_MURADIN_STAND, allianceMuradinPos.GetPositionX() + allianceGuardOffset[_index][0],
allianceMuradinPos.GetPositionY() + allianceGuardOffset[_index][1], allianceMuradinPos.GetPositionZ());
}
else if (action == ACTION_OUTRO_KNEEL)
{
// Kneeling is a stand state; the combat-ready emote would override it.
me->SetEmoteState(EMOTE_ONESHOT_NONE);
me->SetStandState(UNIT_STAND_STATE_KNEEL);
}
else if (action == ACTION_OUTRO_RETREAT || action == ACTION_OUTRO_STAND_DOWN)
{
me->SetStandState(UNIT_STAND_STATE_STAND);
me->SetEmoteState(EMOTE_ONESHOT_NONE);
me->SetWalk(false);
float x, y, z, o;
me->GetHomePosition(x, y, z, o);
me->GetMotionMaster()->MovePoint(action == ACTION_OUTRO_RETREAT ? POINT_RETREAT : POINT_A_STAND_DOWN, x, y, z);
}
}
private:
@@ -134,6 +134,9 @@ enum DataTypes
DATA_ENEMY_GUNSHIP = 40,
DATA_THE_SKYBREAKER = 41,
DATA_ORGRIMS_HAMMER = 42,
DATA_SAURFANG_CAMP = 43,
DATA_SAURFANG_OUTRO_ZEPPELIN = 44,
DATA_SAURFANG_OUTRO_PORTAL = 45,
// pussywizard:
DATA_BUFF_AVAILABLE = 251,
@@ -250,6 +253,13 @@ enum CreaturesIds
NPC_SE_HIGH_OVERLORD_SAURFANG = 37187,
NPC_SE_KOR_KRON_REAVER = 37920,
NPC_SE_SKYBREAKER_MARINE = 37830,
// Victory camp raised on Deathbringer's Rise once Saurfang is defeated
NPC_CAMP_ALLIANCE_MASON = 37902,
NPC_CAMP_SHELY_STEELBOWELS = 37903, // Alliance blacksmith
NPC_CAMP_BRAZIE_GETZ = 37904, // Alliance general goods
NPC_CAMP_WARSONG_PEON = 37930,
NPC_CAMP_APOTHECARY_CANDITH_TOMAS = 37935, // Horde general goods
NPC_CAMP_MORGAN_DAYBLAZE = 37936, // Horde blacksmith
NPC_FROST_FREEZE_TRAP = 37744,
// Festergut
@@ -425,6 +435,22 @@ enum GameObjectsIds
GO_DEATHBRINGER_S_CACHE_10H = 202238,
GO_DEATHBRINGER_S_CACHE_25H = 202241,
GO_SCOURGE_TRANSPORTER_SAURFANG = 202244,
// Victory camp. Forge, bonfire and anvil are the Horde camp's and have spawn rows - the script
// only raises them; everything else, the Alliance camp included, it summons.
GO_SAURFANG_CAMP_FORGE = 1685,
GO_SAURFANG_CAMP_BONFIRE = 187852,
GO_SAURFANG_CAMP_ANVIL = 191345,
GO_SAURFANG_CAMP_TENT_A = 201868,
GO_SAURFANG_CAMP_TENT_H1 = 201886,
GO_SAURFANG_CAMP_TENT_H2 = 201887,
GO_SAURFANG_CAMP_TELEPORTER_A = 201858,
GO_SAURFANG_CAMP_TELEPORTER_H = 201880,
GO_SAURFANG_CAMP_BANNER_A = 201869,
GO_SAURFANG_CAMP_ANVIL_A = 23303, // the Alliance camp uses its own anvil
// Zeppelin is a MO_TRANSPORT (taxi path 1834) created through TransportMgr, like the gunships.
// The portal is a SPELLCASTER, spawned non-selectable so players cannot click its spell.
GO_SAURFANG_OUTRO_ZEPPELIN = 201834,
GO_SAURFANG_OUTRO_PORTAL = 193207,
// Professor Putricide
GO_ORANGE_PLAGUE_MONSTER_ENTRANCE = 201371,
@@ -52,7 +52,20 @@ enum TimedEvents
EVENT_QUAKE_SHATTER = 2,
EVENT_REBUILD_PLATFORM = 3,
EVENT_RESPAWN_GUNSHIP = 4,
EVENT_RESPAWN_SINDRAGOSA = 5
EVENT_RESPAWN_SINDRAGOSA = 5,
EVENT_SPAWN_SAURFANG_EVENT = 6,
EVENT_SAURFANG_ZEPPELIN_DOCK = 7,
EVENT_SAURFANG_OUTRO_TIMEOUT = 8,
EVENT_SAURFANG_ZEPPELIN_REMOVE = 9,
// The camp is built on screen, stage by stage.
EVENT_SAURFANG_CAMP_TELEPORTERS = 10,
EVENT_SAURFANG_CAMP_WORKERS = 11,
EVENT_SAURFANG_CAMP_TENTS = 12,
EVENT_SAURFANG_CAMP_WORKERS_OUT = 13,
EVENT_SAURFANG_CAMP_VENDORS = 14,
EVENT_SAURFANG_CAMP_SMITH_ARRIVE = 15,
EVENT_SAURFANG_CAMP_WORKERS_BACK = 16,
EVENT_SAURFANG_CAMP_VENDOR_ARRIVE = 17
};
enum Spells
@@ -161,6 +174,46 @@ Position const UtherSpawnPos = { -26.58507f, 2211.524f, 30.19898f, 3.124139f
Position const SylvanasSpawnPos = { -41.45833f, 2222.891f, 27.98586f, 3.647738f };
Position const SindragosaSpawnPos = { 4818.6997f, 2483.7102f, 287.06497f, 3.286661f };
// The camps differ: Horde tents from the retail sniff, Alliance ones measured in game and much
// closer together, with their own forge, anvil and banner by the second tent, and no bonfire.
Position const SaurfangCampTentPosH[2] =
{
{ -532.86456f, 2229.0088f, 539.2921f, 2.530723f },
{ -524.55730f, 2238.0920f, 539.2920f, 0.13962449f }
};
Position const SaurfangCampTentPosA[2] =
{
{ -531.84283f, 2230.6853f, 539.2918f, 5.625422f },
{ -528.76000f, 2234.7815f, 539.2918f, 5.609714f }
};
Position const SaurfangCampBannerPosA = { -533.05540f, 2234.6326f, 539.2918f, 5.621495f };
Position const SaurfangCampBlacksmithPosA = { -526.41502f, 2232.9104f, 539.2918f, 5.609714f };
Position const SaurfangCampGeneralGoodsPosA = { -529.46875f, 2228.8513f, 539.2918f, 5.625422f };
Position const SaurfangCampAnvilPosA = { -525.71640f, 2236.1909f, 539.2918f, 5.609714f };
Position const SaurfangCampForgePosA = { -528.06140f, 2238.0620f, 539.2918f, 5.609714f };
// Detour: both Alliance vendors round the south side, on separate corners.
Position const SaurfangCampSmithDetourPosA = { -525.50000f, 2227.5000f, 539.2918f, 0.0f };
Position const SaurfangCampGoodsDetourPosA = { -528.50000f, 2225.0000f, 539.2918f, 0.0f };
Position const SaurfangCampTeleporterPos[2] =
{
{ -560.41840f, 2202.7500f, 539.28534f, 0.0f },
{ -560.29517f, 2220.2153f, 539.28540f, 0.0f }
};
Position const SaurfangCampBlacksmithPos = { -520.94100f, 2233.1077f, 539.3463f, 5.3756142f };
Position const SaurfangCampGeneralGoodsPos = { -530.17017f, 2226.2310f, 539.3463f, 5.4628806f };
// Travel time for the ~41y between a teleporter pad and its tent site.
Seconds const SaurfangCampWorkerTravel = 7s;
// The Horde smith would otherwise walk straight through the bonfire.
Position const SaurfangCampSmithDetourPos = { -529.00000f, 2233.0000f, 539.2920f, 0.0f };
Position const SaurfangOutroPortalPos = { -523.55963f, 2238.8900f, 539.29070f, 6.1102815f };
// Where the zeppelin comes to rest; it is frozen on arrival.
Position const SaurfangOutroZeppelinPos = { -527.66110f, 2254.6910f, 538.53300f, 0.6848107f };
float const SaurfangOutroZeppelinDockRange = 12.0f;
// Set position traps Spirit Alarm
std::vector<Position> GoSpiritAlarm_1 = { { -160.96f, 2210.46f, 35.24f, 0.0f }, { -176.27f, 2201.93f, 35.24f, 0.0f}, { -207.83f, 2207.38f, 35.24f, 0.0f } };
std::vector<Position> GoSpiritAlarm_2 = { { -178.41f, 2225.11f, 35.24f, 0.0f }, { -195.23f, 2221.55f, 35.24f, 0.0f}, { -209.94f, 2250.34f, 37.99f, 0.0f } };
@@ -216,6 +269,10 @@ public:
LichKingHeroicAvailable = true;
LichKingRandomWhisperTimer = 120 * IN_MILLISECONDS;
DarkwhisperElevatorTimer = DARKWHISPER_ELEVATOR_DWELL_TIME;
_saurfangCampSpawned = false;
_saurfangOutroRunning = false;
_saurfangZeppelinDocked = false;
_saurfangZeppelinLeaving = false;
SetHeaders(DataHeader);
SetBossNumber(MAX_ENCOUNTERS);
@@ -271,6 +328,14 @@ public:
if (GetBossState(DATA_LADY_DEATHWHISPER) == DONE && GetBossState(DATA_ICECROWN_GUNSHIP_BATTLE) != DONE)
SpawnGunship();
// Also covers an instance whose Gunship Battle state was set directly, which would
// otherwise keep them hidden for good.
if (GetBossState(DATA_ICECROWN_GUNSHIP_BATTLE) == DONE && !_saurfangOutroRunning)
RestoreHiddenSaurfangEventNpcs();
if (GetBossState(DATA_DEATHBRINGER_SAURFANG) == DONE && !_saurfangOutroRunning)
SpawnSaurfangCamp(false);
if (GetBossState(DATA_SINDRAGOSA) != DONE && IsSindragosaIntroDone && !GetCreature(DATA_SINDRAGOSA) && !Events.HasTimeUntilEvent(EVENT_RESPAWN_SINDRAGOSA))
{
Events.ScheduleEvent(EVENT_RESPAWN_SINDRAGOSA, 30s);
@@ -391,6 +456,11 @@ public:
DeathbringerSaurfangGUID = creature->GetGUID();
break;
case NPC_SE_HIGH_OVERLORD_SAURFANG:
// Only the static spawn is the event NPC - the Alliance outro summons another
// High Overlord Saurfang, which would otherwise clobber the guid.
if (creature->IsSummon())
break;
if (GetTeamIdInInstance() == TEAM_ALLIANCE)
{
creature->UpdateEntry(NPC_SE_MURADIN_BRONZEBEARD, true);
@@ -398,9 +468,14 @@ public:
}
DeathbringerSaurfangEventGUID = creature->GetGUID();
creature->LastUsedScriptID = creature->GetScriptId();
HideSaurfangEventNpc(creature);
break;
case NPC_SE_MURADIN_BRONZEBEARD:
if (creature->IsSummon())
break;
DeathbringerSaurfangEventGUID = creature->GetGUID();
HideSaurfangEventNpc(creature);
break;
case NPC_HIGH_OVERLORD_SAURFANG_DUMMY:
if (GetTeamIdInInstance() == TEAM_ALLIANCE)
@@ -410,8 +485,13 @@ public:
}
break;
case NPC_SE_KOR_KRON_REAVER:
if (creature->IsSummon())
break;
if (GetTeamIdInInstance() == TEAM_ALLIANCE)
creature->UpdateEntry(NPC_SE_SKYBREAKER_MARINE);
SaurfangEventGuardGUIDs.push_back(creature->GetGUID());
HideSaurfangEventNpc(creature);
break;
case NPC_FESTERGUT:
FestergutGUID = creature->GetGUID();
@@ -747,6 +827,18 @@ public:
case GO_SCOURGE_TRANSPORTER_SAURFANG:
SaurfangTeleportGUID = go->GetGUID();
break;
case GO_SAURFANG_CAMP_FORGE:
case GO_SAURFANG_CAMP_BONFIRE:
case GO_SAURFANG_CAMP_ANVIL:
// Camp props belong to the aftermath; their spawn rows are unconditional, hence the despawn.
SaurfangCampGUIDs.push_back(go->GetGUID());
if (GetBossState(DATA_DEATHBRINGER_SAURFANG) != DONE)
go->DespawnOrUnsummon(0ms, Seconds(WEEK));
// The despawn is persisted, so an instance restarted after the outro comes back
// with them hidden. Horde only - the Alliance camp summons its own props.
else if (GetTeamIdInInstance() == TEAM_HORDE)
go->Respawn();
break;
case GO_PLAGUE_SIGIL:
PlagueSigilGUID = go->GetGUID();
if (GetBossState(DATA_PROFESSOR_PUTRICIDE) == DONE)
@@ -904,6 +996,9 @@ public:
{
switch (type)
{
case DATA_SAURFANG_OUTRO_ZEPPELIN:
// DONE once it has come to rest, so the outro can wait for the ship instead of guessing.
return _saurfangZeppelinDocked ? DONE : IN_PROGRESS;
case DATA_BUFF_AVAILABLE:
return (IsBuffAvailable ? 1 : 0);
case DATA_WEEKLY_QUEST_ID:
@@ -1096,6 +1191,9 @@ public:
loot->SetLootRecipient(deathbringer);
loot->RemoveGameObjectFlag(GO_FLAG_LOCKED | GO_FLAG_NOT_SELECTABLE | GO_FLAG_NODESPAWN);
}
// Not raised here: DONE fires the moment the boss dies. The outro asks
// via SetData(DATA_SAURFANG_CAMP); OnPlayerEnter covers saved instances.
[[fallthrough]];
case NOT_STARTED:
if (GameObject* teleporter = instance->GetGameObject(SaurfangTeleportGUID))
@@ -1220,10 +1318,286 @@ public:
}
}
// Hidden rather than despawned: a despawned creature leaves the map and its guid would no
// longer resolve.
void HideSaurfangEventNpc(Creature* creature)
{
if (GetBossState(DATA_ICECROWN_GUNSHIP_BATTLE) == DONE)
return;
creature->SetVisible(false);
}
void RestoreSaurfangEventNpc(Creature* creature)
{
float x, y, z, o;
creature->GetHomePosition(x, y, z, o);
creature->NearTeleportTo(x, y, z, o);
creature->SetStandState(UNIT_STAND_STATE_STAND);
creature->SetEmoteState(EMOTE_ONESHOT_NONE);
creature->SetSheath(SHEATH_STATE_UNARMED);
creature->SetVisible(true);
}
void SpawnSaurfangEventNpcs()
{
if (Creature* captain = instance->GetCreature(DeathbringerSaurfangEventGUID))
RestoreSaurfangEventNpc(captain);
for (ObjectGuid const& guid : SaurfangEventGuardGUIDs)
if (Creature* guard = instance->GetCreature(guid))
RestoreSaurfangEventNpc(guard);
}
// Only the ones left hidden: a scene in progress has them visible and placed, and
// teleporting those to their spawn points would break it mid-run.
void RestoreHiddenSaurfangEventNpcs()
{
if (Creature* captain = instance->GetCreature(DeathbringerSaurfangEventGUID))
if (!captain->IsVisible())
RestoreSaurfangEventNpc(captain);
for (ObjectGuid const& guid : SaurfangEventGuardGUIDs)
if (Creature* guard = instance->GetCreature(guid))
if (!guard->IsVisible())
RestoreSaurfangEventNpc(guard);
}
// staged runs the on-screen build: teleporters, then workers raising the tents, then the
// vendors. Unstaged drops the finished camp at once, for an instance already DONE.
void SpawnSaurfangCamp(bool staged)
{
if (_saurfangCampSpawned || !instance->HavePlayers())
return;
_saurfangCampSpawned = true;
if (staged)
{
Events.ScheduleEvent(EVENT_SAURFANG_CAMP_TELEPORTERS, 3s);
return;
}
SummonSaurfangCampTeleporters();
SpawnSaurfangEventNpcs();
SpawnSaurfangCampTents();
SummonSaurfangCampVendor(true, false);
SummonSaurfangCampVendor(false, false);
}
void SummonSaurfangCampTeleporters()
{
uint32 const teleporter = GetTeamIdInInstance() == TEAM_HORDE ? GO_SAURFANG_CAMP_TELEPORTER_H : GO_SAURFANG_CAMP_TELEPORTER_A;
for (uint8 i = 0; i < 2; ++i)
{
// Summoned ready is the inert model; activating it is what turns the beam on.
if (GameObject* go = instance->SummonGameObject(teleporter, SaurfangCampTeleporterPos[i], 0.0f, 0.0f, 0.0f, 0.0f, WEEK))
go->SetGoState(GO_STATE_ACTIVE);
}
}
void SpawnSaurfangCampTeleporters()
{
SummonSaurfangCampTeleporters();
SpawnSaurfangEventNpcs();
Events.ScheduleEvent(EVENT_SAURFANG_CAMP_WORKERS, 5s);
}
void SpawnSaurfangCampWorkers()
{
bool const horde = GetTeamIdInInstance() == TEAM_HORDE;
uint32 const worker = horde ? NPC_CAMP_WARSONG_PEON : NPC_CAMP_ALLIANCE_MASON;
for (uint8 i = 0; i < 2; ++i)
{
if (Creature* builder = instance->SummonCreature(worker, SaurfangCampTeleporterPos[i]))
{
SaurfangCampWorkerGUIDs.push_back(builder->GetGUID());
builder->SetReactState(REACT_PASSIVE);
builder->SetWalk(false);
builder->GetMotionMaster()->MovePoint(0, (horde ? SaurfangCampTentPosH[i] : SaurfangCampTentPosA[i]));
builder->SetEmoteState(EMOTE_STATE_WORK_MINING);
}
}
// Timed from the summon, so the run out has to be paid for before the hammering starts.
Events.ScheduleEvent(EVENT_SAURFANG_CAMP_WORKERS_BACK, SaurfangCampWorkerTravel + (horde ? 3s : 15s));
}
void SpawnSaurfangCampTents()
{
bool const horde = GetTeamIdInInstance() == TEAM_HORDE;
uint32 const tents[2] =
{
static_cast<uint32>(horde ? GO_SAURFANG_CAMP_TENT_H1 : GO_SAURFANG_CAMP_TENT_A),
static_cast<uint32>(horde ? GO_SAURFANG_CAMP_TENT_H2 : GO_SAURFANG_CAMP_TENT_A)
};
Position const* tentPos = horde ? SaurfangCampTentPosH : SaurfangCampTentPosA;
for (uint8 i = 0; i < 2; ++i)
instance->SummonGameObject(tents[i], tentPos[i], 0.0f, 0.0f, 0.0f, 0.0f, WEEK);
if (horde)
{
for (ObjectGuid const& guid : SaurfangCampGUIDs)
if (GameObject* camp = instance->GetGameObject(guid))
camp->Respawn();
}
else
{
instance->SummonGameObject(GO_SAURFANG_CAMP_FORGE, SaurfangCampForgePosA, 0.0f, 0.0f, 0.0f, 0.0f, WEEK);
instance->SummonGameObject(GO_SAURFANG_CAMP_ANVIL_A, SaurfangCampAnvilPosA, 0.0f, 0.0f, 0.0f, 0.0f, WEEK);
instance->SummonGameObject(GO_SAURFANG_CAMP_BANNER_A, SaurfangCampBannerPosA, 0.0f, 0.0f, 0.0f, 0.0f, WEEK);
}
}
// They clear the site before the tent drops, or it lands on top of them.
void SendSaurfangCampWorkersBack()
{
uint8 i = 0;
for (ObjectGuid const& guid : SaurfangCampWorkerGUIDs)
{
if (Creature* builder = instance->GetCreature(guid))
{
builder->SetEmoteState(EMOTE_ONESHOT_NONE);
builder->SetWalk(false);
builder->GetMotionMaster()->MovePoint(0, SaurfangCampTeleporterPos[i % 2]);
}
++i;
}
Events.ScheduleEvent(EVENT_SAURFANG_CAMP_TENTS, 2s);
// They only vanish once they are back standing on the pad.
Events.ScheduleEvent(EVENT_SAURFANG_CAMP_WORKERS_OUT,
SaurfangCampWorkerTravel + (GetTeamIdInInstance() == TEAM_HORDE ? 2s : 5s));
}
void DespawnSaurfangCampWorkers()
{
for (ObjectGuid const& guid : SaurfangCampWorkerGUIDs)
if (Creature* builder = instance->GetCreature(guid))
builder->DespawnOrUnsummon();
SaurfangCampWorkerGUIDs.clear();
Events.ScheduleEvent(EVENT_SAURFANG_CAMP_VENDORS, 1s);
}
// walkIn false drops the vendor straight on his pitch, for a camp not built on screen.
// The detours exist because the direct line clips the Horde bonfire / the first Alliance tent.
void SummonSaurfangCampVendor(bool smith, bool walkIn)
{
bool const horde = GetTeamIdInInstance() == TEAM_HORDE;
uint32 const entry = smith ? (horde ? NPC_CAMP_MORGAN_DAYBLAZE : NPC_CAMP_SHELY_STEELBOWELS)
: (horde ? NPC_CAMP_APOTHECARY_CANDITH_TOMAS : NPC_CAMP_BRAZIE_GETZ);
Position const& pitch = smith ? (horde ? SaurfangCampBlacksmithPos : SaurfangCampBlacksmithPosA)
: (horde ? SaurfangCampGeneralGoodsPos : SaurfangCampGeneralGoodsPosA);
if (!walkIn)
{
instance->SummonCreature(entry, pitch);
return;
}
Creature* vendor = instance->SummonCreature(entry, SaurfangCampTeleporterPos[smith ? 0 : 1]);
if (!vendor)
return;
vendor->SetWalk(true);
if (horde && !smith)
{
vendor->GetMotionMaster()->MovePoint(0, pitch);
return;
}
Position const& detour = horde ? SaurfangCampSmithDetourPos
: (smith ? SaurfangCampSmithDetourPosA : SaurfangCampGoodsDetourPosA);
vendor->GetMotionMaster()->MovePoint(0, detour);
if (smith)
{
SaurfangCampSmithGUID = vendor->GetGUID();
Events.ScheduleEvent(EVENT_SAURFANG_CAMP_SMITH_ARRIVE, horde ? 19s : 17s);
}
else
{
SaurfangCampGoodsGUID = vendor->GetGUID();
Events.ScheduleEvent(EVENT_SAURFANG_CAMP_VENDOR_ARRIVE, 15s);
}
}
void SpawnSaurfangCampVendors()
{
SummonSaurfangCampVendor(true, true);
SummonSaurfangCampVendor(false, true);
}
void SetData(uint32 type, uint32 data) override
{
switch (type)
{
case DATA_SAURFANG_CAMP:
// The boss is DONE for the whole outro, so the encounter state alone cannot tell
// "outro playing" from "outro over".
if (data == IN_PROGRESS)
{
_saurfangOutroRunning = true;
// Safety net: an instance emptying mid-outro would leave the flag set and
// the camp could never be raised.
Events.ScheduleEvent(EVENT_SAURFANG_OUTRO_TIMEOUT, 8min);
}
else if (data == DONE)
{
Events.CancelEvent(EVENT_SAURFANG_OUTRO_TIMEOUT);
_saurfangOutroRunning = false;
SpawnSaurfangCamp(true);
}
break;
case DATA_SAURFANG_OUTRO_ZEPPELIN:
if (data == IN_PROGRESS)
{
if (!SaurfangZeppelinGUID)
{
_saurfangZeppelinDocked = false;
_saurfangZeppelinLeaving = false;
if (MotionTransport* zeppelin = sTransportMgr->CreateTransport(GO_SAURFANG_OUTRO_ZEPPELIN, 0, instance))
{
SaurfangZeppelinGUID = zeppelin->GetGUID();
zeppelin->setActive(true);
Events.ScheduleEvent(EVENT_SAURFANG_ZEPPELIN_DOCK, 1s);
}
}
}
// Asked for twice (departure, then cleanup); without the guard the second call
// would restart the removal timer.
else if (SaurfangZeppelinGUID && !_saurfangZeppelinLeaving)
{
Events.CancelEvent(EVENT_SAURFANG_ZEPPELIN_DOCK);
_saurfangZeppelinDocked = false;
_saurfangZeppelinLeaving = true;
if (GameObject* go = instance->GetGameObject(SaurfangZeppelinGUID))
if (MotionTransport* zeppelin = go->ToMotionTransport())
zeppelin->EnableMovement(true);
// Releasing _pendingStop does not launch it: it leaves when the stop frame
// DepartureTime comes round, ~24s. Removal is well past that.
Events.ScheduleEvent(EVENT_SAURFANG_ZEPPELIN_REMOVE, 60s);
}
break;
case DATA_SAURFANG_OUTRO_PORTAL:
if (data == IN_PROGRESS)
{
if (GameObject* portal = instance->SummonGameObject(GO_SAURFANG_OUTRO_PORTAL, SaurfangOutroPortalPos, 0.0f, 0.0f, 0.0f, 0.0f, HOUR))
{
// SPELLCASTER object carrying spell 59065; scenery here, so it must not be clickable.
portal->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE);
SaurfangPortalGUID = portal->GetGUID();
}
}
else
{
if (GameObject* portal = instance->GetGameObject(SaurfangPortalGUID))
portal->DespawnOrUnsummon();
SaurfangPortalGUID.Clear();
}
break;
case DATA_BUFF_AVAILABLE:
IsBuffAvailable = !!data;
if (!IsBuffAvailable)
@@ -1761,6 +2135,74 @@ public:
// Could happen more than once if more than one player enters before she respawns.
Events.CancelEvent(EVENT_RESPAWN_SINDRAGOSA);
break;
case EVENT_SPAWN_SAURFANG_EVENT:
SpawnSaurfangEventNpcs();
break;
case EVENT_SAURFANG_OUTRO_TIMEOUT:
_saurfangOutroRunning = false;
SetData(DATA_SAURFANG_OUTRO_PORTAL, DONE);
SetData(DATA_SAURFANG_OUTRO_ZEPPELIN, DONE);
if (GetBossState(DATA_DEATHBRINGER_SAURFANG) == DONE)
{
SpawnSaurfangCamp(false);
SpawnSaurfangEventNpcs();
}
break;
case EVENT_SAURFANG_CAMP_TELEPORTERS:
SpawnSaurfangCampTeleporters();
break;
case EVENT_SAURFANG_CAMP_WORKERS:
SpawnSaurfangCampWorkers();
break;
case EVENT_SAURFANG_CAMP_TENTS:
SpawnSaurfangCampTents();
break;
case EVENT_SAURFANG_CAMP_WORKERS_OUT:
DespawnSaurfangCampWorkers();
break;
case EVENT_SAURFANG_CAMP_WORKERS_BACK:
SendSaurfangCampWorkersBack();
break;
case EVENT_SAURFANG_CAMP_SMITH_ARRIVE:
if (Creature* smith = instance->GetCreature(SaurfangCampSmithGUID))
smith->GetMotionMaster()->MovePoint(0, GetTeamIdInInstance() == TEAM_HORDE ? SaurfangCampBlacksmithPos : SaurfangCampBlacksmithPosA);
break;
case EVENT_SAURFANG_CAMP_VENDOR_ARRIVE:
if (Creature* goods = instance->GetCreature(SaurfangCampGoodsGUID))
goods->GetMotionMaster()->MovePoint(0, SaurfangCampGeneralGoodsPosA);
break;
case EVENT_SAURFANG_CAMP_VENDORS:
SpawnSaurfangCampVendors();
break;
case EVENT_SAURFANG_ZEPPELIN_REMOVE:
if (GameObject* go = instance->GetGameObject(SaurfangZeppelinGUID))
{
// A MO_TRANSPORT is not despawned like an ordinary gameobject.
if (MotionTransport* zeppelin = go->ToMotionTransport())
{
zeppelin->EnableMovement(false);
zeppelin->UnloadStaticPassengers();
}
go->AddObjectToRemoveList();
}
_saurfangZeppelinLeaving = false;
SaurfangZeppelinGUID.Clear();
break;
case EVENT_SAURFANG_ZEPPELIN_DOCK:
{
// Reschedule unconditionally: giving up on a failed lookup would leave
// it looping its path forever.
GameObject* go = instance->GetGameObject(SaurfangZeppelinGUID);
MotionTransport* zeppelin = go ? go->ToMotionTransport() : nullptr;
if (zeppelin && zeppelin->GetExactDist2d(&SaurfangOutroZeppelinPos) <= SaurfangOutroZeppelinDockRange)
{
zeppelin->EnableMovement(false);
_saurfangZeppelinDocked = true;
}
else if (SaurfangZeppelinGUID)
Events.ScheduleEvent(EVENT_SAURFANG_ZEPPELIN_DOCK, 500ms);
}
break;
default:
break;
}
@@ -1803,6 +2245,8 @@ public:
}
if (Creature* captain = source->FindNearestCreature(GetTeamIdInInstance() == TEAM_HORDE ? NPC_IGB_HIGH_OVERLORD_SAURFANG : NPC_IGB_MURADIN_BRONZEBEARD, 200.0f))
captain->AI()->DoAction(ACTION_EXIT_SHIP);
// The captain despawns 18s into his walk off the ship; the event party appears with him.
Events.ScheduleEvent(EVENT_SPAWN_SAURFANG_EVENT, 18s);
break;
case EVENT_QUAKE:
@@ -1930,6 +2374,17 @@ public:
ObjectGuid DeathbringerSaurfangEventGUID; // Muradin Bronzebeard or High Overlord Saurfang
ObjectGuid DeathbringersCacheGUID;
ObjectGuid SaurfangTeleportGUID;
GuidList SaurfangCampGUIDs;
GuidList SaurfangEventGuardGUIDs;
GuidList SaurfangCampWorkerGUIDs;
ObjectGuid SaurfangCampSmithGUID;
ObjectGuid SaurfangCampGoodsGUID;
ObjectGuid SaurfangZeppelinGUID;
ObjectGuid SaurfangPortalGUID;
bool _saurfangCampSpawned;
bool _saurfangOutroRunning;
bool _saurfangZeppelinDocked;
bool _saurfangZeppelinLeaving;
ObjectGuid PlagueSigilGUID;
ObjectGuid BloodwingSigilGUID;
ObjectGuid FrostwingSigilGUID;