diff --git a/data/sql/updates/pending_db_world/rev_1661470486331915500.sql b/data/sql/updates/pending_db_world/rev_1661470486331915500.sql
new file mode 100644
index 000000000..8186a602c
--- /dev/null
+++ b/data/sql/updates/pending_db_world/rev_1661470486331915500.sql
@@ -0,0 +1,43 @@
+--
+UPDATE `creature_template_addon` SET `bytes1` = 54432 WHERE `entry` = 15963;
+UPDATE `creature_template_addon` SET `auras` = '18943' WHERE `entry` = 15275;
+
+DELETE FROM `areatrigger_scripts` WHERE `ScriptName` = 'at_twin_emperors';
+INSERT INTO `areatrigger_scripts` (`entry`, `ScriptName`) VALUES
+(4047, 'at_twin_emperors');
+
+DELETE FROM `creature_text` WHERE `CreatureID` IN (15275, 15276) AND `GroupID` IN (0, 1, 2, 3, 4, 5, 6);
+DELETE FROM `creature_text` WHERE `CreatureID` = 15963;
+INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES
+(15275, 0, 0, 'Where are your manners, brother. Let us properly welcome our guests.', 14, 0, 100, 6, 0, 0, 11706, 0, 'Emperor Vek\'nilash INTRO_0'),
+(15275, 1, 0, 'Oh so much pain...', 14, 0, 100, 5, 0, 0, 11708, 0, 'Emperor Vek\'nilash INTRO_1'),
+(15275, 2, 0, 'The feast of souls begins now...', 14, 0, 100, 0, 0, 0, 11710, 0, 'Emperor Vek\'nilash INTRO_2'),
+(15275, 3, 0, 'Your fate is sealed!', 14, 0, 100, 0, 0, 8635, 11455, 0, 'Emperor Vek\'nilash KILL'),
+(15275, 4, 0, 'Vek\'lor, I feel your pain!', 14, 0, 100, 0, 0, 8636, 11454, 0, 'Emperor Vek\'nilash DEATH'),
+(15276, 0, 0, 'Only flesh and bone. Mortals are such easy prey...', 14, 0, 100, 1, 0, 0, 11702, 0, 'Emperor Vek\'lor INTRO_0'),
+(15276, 1, 0, 'There will be pain...', 14, 0, 100, 273, 0, 0, 11707, 0, 'Emperor Vek\'lor INTRO_1'),
+(15276, 2, 0, 'Come, little ones.', 14, 0, 100, 0, 0, 0, 11709, 0, 'Emperor Vek\'lor INTRO_2'),
+(15276, 3, 0, 'You will not escape death!', 14, 0, 100, 0, 0, 8629, 11453, 0, 'Emperor Vek\'lor KILL'),
+(15276, 4, 0, 'My brother, no!', 14, 0, 100, 0, 0, 8625, 11452, 0, 'Emperor Vek\'lor DEATH'),
+(15276, 5, 0, '%s goes into a berserker rage!', 16, 0, 100, 0, 0, 0, 34057, 0, 'Emperor Vek\'lor ENRAGE'),
+(15963, 0, 0, 'The massive floating eyeball in the center of the chamber turns its gaze upon you. You stand before a god.', 16, 0, 100, 0, 0, 0, 11700, 1, 'Masters Eye - On Areatrigger');
+
+UPDATE `creature_template_movement` SET `Ground` = 2, `Flight` = 1 WHERE `CreatureId` = 15963;
+
+UPDATE `creature_template` SET `unit_flags` = `unit_flags`|512|256 WHERE `entry` = 15963;
+UPDATE `creature_template` SET `spell_school_immune_mask` = `spell_school_immune_mask`|1 WHERE `entry` = 15276; -- Vek'lor immunity to phys
+UPDATE `creature_template` SET `spell_school_immune_mask` = `spell_school_immune_mask`|4|8|16|32|64 WHERE `entry` = 15275; -- Vek'nilash immunity to everything but holy and phys
+
+DELETE FROM `spell_script_names` WHERE `spell_id` IN (802, 804);
+INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
+(802, 'spell_mutate_explode_bug'),
+(804, 'spell_mutate_explode_bug');
+
+UPDATE `creature` SET `spawntimesecs` = 300 WHERE `id1` IN (15316, 15317);
+
+UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` IN (15316, 15317);
+
+DELETE FROM `smart_scripts` WHERE (`entryorguid` IN (15316, 15317)) AND (`source_type` = 0) AND (`id` IN (0));
+INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
+(15317, 0, 0, 0, 25, 0, 100, 0, 0, 0, 0, 0, 0, 2, 311, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Qiraji Scorpion - On Reset - Set Faction 311'),
+(15316, 0, 0, 0, 25, 0, 100, 0, 0, 0, 0, 0, 0, 2, 311, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Qiraji Scarab - On Reset - Set Faction 311');
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 584431749..78c282c29 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -19518,6 +19518,11 @@ bool Unit::CanSwim() const
return HasUnitFlag(UNIT_FLAG_RENAME | UNIT_FLAG_SWIMMING);
}
+void Unit::NearTeleportTo(Position& pos, bool casting /*= false*/, bool vehicleTeleport /*= false*/, bool withPet /*= false*/, bool removeTransport /*= false*/)
+{
+ NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), casting, vehicleTeleport, withPet, removeTransport);
+}
+
void Unit::NearTeleportTo(float x, float y, float z, float orientation, bool casting /*= false*/, bool vehicleTeleport /*= false*/, bool withPet /*= false*/, bool removeTransport /*= false*/)
{
DisableSpline();
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index ec2151596..8ece9e956 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -1731,6 +1731,7 @@ public:
void SendSpellDamageResist(Unit* target, uint32 spellId);
void SendSpellDamageImmune(Unit* target, uint32 spellId);
+ void NearTeleportTo(Position& pos, bool casting = false, bool vehicleTeleport = false, bool withPet = false, bool removeTransport = false);
void NearTeleportTo(float x, float y, float z, float orientation, bool casting = false, bool vehicleTeleport = false, bool withPet = false, bool removeTransport = false);
void SendTameFailure(uint8 result);
void SendTeleportPacket(Position& pos);
diff --git a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_twinemperors.cpp b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_twinemperors.cpp
index e496ef8a3..7ccba318e 100644
--- a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_twinemperors.cpp
+++ b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_twinemperors.cpp
@@ -15,368 +15,283 @@
* with this program. If not, see .
*/
-/* ScriptData
-SDName: Boss_Twinemperors
-SD%Complete: 95
-SDComment:
-SDCategory: Temple of Ahn'Qiraj
-EndScriptData */
-
-#include "Item.h"
+#include "Player.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
-#include "Spell.h"
-#include "WorldPacket.h"
+#include "SpellScript.h"
+#include "TaskScheduler.h"
#include "temple_of_ahnqiraj.h"
enum Spells
{
+ // Both
+ SPELL_TWIN_EMPATHY = 1177,
+ SPELL_TWIN_TELEPORT_1 = 800,
+ SPELL_TWIN_TELEPORT_VISUAL = 26638,
SPELL_HEAL_BROTHER = 7393,
- SPELL_TWIN_TELEPORT = 800, // CTRA watches for this spell to start its teleport timer
- SPELL_TWIN_TELEPORT_VISUAL = 26638, // visual
- SPELL_EXPLODEBUG = 804,
- SPELL_MUTATE_BUG = 802,
- SPELL_BERSERK = 26662,
+ // Vek'lor
+ SPELL_SHADOW_BOLT = 26006,
+ SPELL_BLIZZARD = 26607,
+ SPELL_FRENZY = 27897,
+ SPELL_ARCANE_BURST = 568,
+ SPELL_EXPLODE_BUG = 804,
+ SPELL_TWIN_TELEPORT_0 = 799,
+ // Vek'nilash
SPELL_UPPERCUT = 26007,
SPELL_UNBALANCING_STRIKE = 26613,
- SPELL_SHADOWBOLT = 26006,
- SPELL_BLIZZARD = 26607,
- SPELL_ARCANEBURST = 568,
+ SPELL_BERSERK = 27680,
+ SPELL_MUTATE_BUG = 802,
+ // Bugs
+ SPELL_VIRULENT_POISON_PROC = 22413
};
-enum Sound
+enum Actions
{
- SOUND_VL_AGGRO = 8657, //8657 - Aggro - To Late
- SOUND_VL_KILL = 8658, //8658 - Kill - You will not
- SOUND_VL_DEATH = 8659, //8659 - Death
- SOUND_VN_DEATH = 8660, //8660 - Death - Feel
- SOUND_VN_AGGRO = 8661, //8661 - Aggro - Let none
- SOUND_VN_KILL = 8662, //8661 - Kill - your fate
+ ACTION_START_INTRO = 0,
+ ACTION_CANCEL_INTRO = 1,
+ ACTION_AFTER_TELEPORT = 2
+};
+
+enum Say
+{
+ SAY_INTRO_0 = 0,
+ SAY_INTRO_1 = 1,
+ SAY_INTRO_2 = 2,
+ SAY_KILL = 3,
+ SAY_DEATH = 4,
+ EMOTE_ENRAGE = 5,
+
+ EMOTE_MASTERS_EYE_AT = 0,
+};
+
+enum Sounds
+{
+ SOUND_VK_AGGRO = 8657,
+ SOUND_VN_AGGRO = 8661
};
enum Misc
{
- PULL_RANGE = 50,
- ABUSE_BUG_RANGE = 20,
- VEKLOR_DIST = 20, // VL will not come to melee when attacking
- TELEPORTTIME = 30000
+ GROUP_INTRO = 0,
+
+ NPC_QIRAJI_SCARAB = 15316,
+ NPC_QIRAJI_SCORPION = 15317,
+
+ FACTION_HOSTILE = 16
};
+constexpr float veklorOrientationIntro = 2.241519f;
+constexpr float veknilashOrientationIntro = 1.144451f;
+
struct boss_twinemperorsAI : public BossAI
{
- boss_twinemperorsAI(Creature* creature): BossAI(creature, DATA_TWIN_EMPERORS) { }
-
- uint32 Heal_Timer;
- uint32 Teleport_Timer;
- bool AfterTeleport;
- uint32 AfterTeleportTimer;
- bool DontYellWhenDead;
- uint32 Abuse_Bug_Timer, BugsTimer;
- bool tspellcast;
- uint32 EnrageTimer;
-
- virtual bool IAmVeklor() = 0;
- void Reset() override = 0;
- virtual void CastSpellOnBug(Creature* target) = 0;
-
- void TwinReset()
+ boss_twinemperorsAI(Creature* creature): BossAI(creature, DATA_TWIN_EMPERORS), _introDone(false)
{
- Heal_Timer = 0; // first heal immediately when they get close together
- Teleport_Timer = TELEPORTTIME;
- AfterTeleport = false;
- tspellcast = false;
- AfterTeleportTimer = 0;
- Abuse_Bug_Timer = urand(10000, 17000);
- BugsTimer = 2000;
- me->ClearUnitState(UNIT_STATE_STUNNED);
- DontYellWhenDead = false;
- EnrageTimer = 15 * 60000;
+ me->SetStandState(UNIT_STAND_STATE_KNEEL);
- instance->HandleGameObject(instance->GetGuidData(AQ40_DOOR_1), true);
- }
-
- Creature* GetOtherBoss()
- {
- return ObjectAccessor::GetCreature(*me, instance->GetGuidData(IAmVeklor() ? DATA_VEKNILASH : DATA_VEKLOR));
- }
-
- void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
- {
- Unit* pOtherBoss = GetOtherBoss();
- if (pOtherBoss)
- {
- float dPercent = ((float)damage) / ((float)me->GetMaxHealth());
- int odmg = (int)(dPercent * ((float)pOtherBoss->GetMaxHealth()));
- int ohealth = pOtherBoss->GetHealth() - odmg;
- pOtherBoss->SetHealth(ohealth > 0 ? ohealth : 0);
- if (ohealth <= 0)
+ _scheduler.SetValidator([this]
{
- pOtherBoss->setDeathState(JUST_DIED);
- pOtherBoss->SetDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
+ return !me->HasUnitState(UNIT_STATE_CASTING);
+ });
+ }
+
+ Creature* GetTwin()
+ {
+ return instance->GetCreature(IAmVeklor() ? DATA_VEKNILASH : DATA_VEKLOR);
+ }
+
+ void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType, SpellSchoolMask) override
+ {
+ if (attacker)
+ {
+ if (attacker->GetEntry() == NPC_VEKLOR || attacker->GetEntry() == NPC_VEKNILASH)
+ {
+ me->LowerPlayerDamageReq(damage);
+ return;
+ }
+
+ if (Creature* twin = GetTwin())
+ {
+ float dmgPct = damage / (float)me->GetMaxHealth();
+ int32 actualDmg = dmgPct * twin->GetMaxHealth();
+ twin->CastCustomSpell(twin, SPELL_TWIN_EMPATHY, &actualDmg, nullptr, nullptr, true);
}
}
}
- void JustDied(Unit* /*killer*/) override
+ void KilledUnit(Unit* victim) override
{
- Creature* pOtherBoss = GetOtherBoss();
- if (pOtherBoss)
- {
- pOtherBoss->SetHealth(0);
- pOtherBoss->setDeathState(JUST_DIED);
- pOtherBoss->SetDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
- CAST_AI(boss_twinemperorsAI, pOtherBoss->AI())->DontYellWhenDead = true;
- }
- if (!DontYellWhenDead) // I hope AI is not threaded
- DoPlaySoundToSet(me, IAmVeklor() ? SOUND_VL_DEATH : SOUND_VN_DEATH);
-
- instance->HandleGameObject(instance->GetGuidData(AQ40_DOOR_1), true);
- instance->HandleGameObject(instance->GetGuidData(AQ40_DOOR_2), true);
+ if (victim && victim->GetTypeId() == TYPEID_PLAYER)
+ Talk(SAY_KILL);
}
- void KilledUnit(Unit* /*victim*/) override
+ void EnterEvadeMode(EvadeReason why) override
{
- DoPlaySoundToSet(me, IAmVeklor() ? SOUND_VL_KILL : SOUND_VN_KILL);
+ BossAI::EnterEvadeMode(why);
+
+ if (Creature* twin = GetTwin())
+ if (!twin->IsInEvadeMode())
+ twin->AI()->EnterEvadeMode(why);
+
+ _scheduler.CancelAll();
+ }
+
+ void JustDied(Unit* killer) override
+ {
+ if (Creature* twin = GetTwin())
+ if (twin->IsAlive())
+ Unit::Kill(me, twin);
+
+ Talk(SAY_DEATH);
+
+ BossAI::JustDied(killer);
+ }
+
+ void DoAction(int32 action) override
+ {
+ if (action == ACTION_CANCEL_INTRO)
+ {
+ _introDone = true;
+ _scheduler.CancelGroup(GROUP_INTRO);
+ return;
+ }
+
+ if (action == ACTION_AFTER_TELEPORT)
+ {
+ DoResetThreat();
+ me->SetReactState(REACT_PASSIVE);
+ DoCastSelf(SPELL_TWIN_TELEPORT_VISUAL, true);
+ _scheduler.DelayAll(2300ms);
+ _scheduler.Schedule(2s, [this](TaskContext /*context*/)
+ {
+ me->SetReactState(REACT_AGGRESSIVE);
+ me->SetControlled(false, UNIT_STATE_ROOT);
+ if (Unit* victim = me->SelectNearestTarget())
+ {
+ me->AddThreat(victim, 200000.f);
+ AttackStart(victim);
+ }
+ });
+ }
+
+ if (action != ACTION_START_INTRO)
+ return;
+
+ _scheduler.Schedule(5s, [this](TaskContext /*context*/)
+ {
+ me->SetStandState(UNIT_STAND_STATE_STAND);
+ me->LoadEquipment(1, true);
+ });
+
+ if (IAmVeklor())
+ {
+ _scheduler
+ .Schedule(12s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ Talk(SAY_INTRO_0);
+ })
+ .Schedule(20s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ Talk(SAY_INTRO_1);
+ })
+ .Schedule(28s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ me->HandleEmoteCommand(EMOTE_ONESHOT_ROAR);
+ })
+ .Schedule(30s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ me->SetFacingTo(veklorOrientationIntro);
+ Talk(SAY_INTRO_2);
+ })
+ .Schedule(33s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ me->HandleEmoteCommand(EMOTE_ONESHOT_POINT);
+ _introDone = true;
+ });
+ }
+ else
+ {
+ _scheduler
+ .Schedule(17s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ Talk(SAY_INTRO_0);
+ })
+ .Schedule(23s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ Talk(SAY_INTRO_1);
+ })
+ .Schedule(28s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ me->HandleEmoteCommand(EMOTE_ONESHOT_ROAR);
+ })
+ .Schedule(32s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ me->SetFacingTo(veknilashOrientationIntro);
+ Talk(SAY_INTRO_2);
+ })
+ .Schedule(33s, GROUP_INTRO, [this](TaskContext /*context*/)
+ {
+ me->HandleEmoteCommand(EMOTE_ONESHOT_POINT);
+ _introDone = true;
+ });
+ }
}
void EnterCombat(Unit* who) override
{
- DoZoneInCombat();
- Creature* pOtherBoss = GetOtherBoss();
- if (pOtherBoss)
+ BossAI::EnterCombat(who);
+
+ if (!_introDone)
{
- /// @todo we should activate the other boss location so he can start attackning even if nobody
- // is near I dont know how to do that
- ScriptedAI* otherAI = CAST_AI(ScriptedAI, pOtherBoss->AI());
- if (!pOtherBoss->IsInCombat())
+ DoAction(ACTION_CANCEL_INTRO);
+ if (Creature* twin = GetTwin())
+ twin->AI()->DoAction(ACTION_CANCEL_INTRO);
+ }
+
+ if (Creature* twin = GetTwin())
+ if (!twin->IsInCombat())
+ twin->AI()->AttackStart(who);
+
+ _scheduler
+ .Schedule(15min, [this](TaskContext /*context*/)
{
- DoPlaySoundToSet(me, IAmVeklor() ? SOUND_VL_AGGRO : SOUND_VN_AGGRO);
- otherAI->AttackStart(who);
- otherAI->DoZoneInCombat();
- }
- }
-
- instance->HandleGameObject(instance->GetGuidData(AQ40_DOOR_1), false);
- }
-
- void SpellHit(Unit* caster, SpellInfo const* entry) override
- {
- if (caster == me)
- return;
-
- Creature* pOtherBoss = GetOtherBoss();
- if (entry->Id != SPELL_HEAL_BROTHER || !pOtherBoss)
- return;
-
- // add health so we keep same percentage for both brothers
- uint32 mytotal = me->GetMaxHealth(), histotal = pOtherBoss->GetMaxHealth();
- float mult = ((float)mytotal) / ((float)histotal);
- if (mult < 1)
- mult = 1.0f / mult;
-#define HEAL_BROTHER_AMOUNT 30000.0f
- uint32 largerAmount = (uint32)((HEAL_BROTHER_AMOUNT * mult) - HEAL_BROTHER_AMOUNT);
-
- if (mytotal > histotal)
- {
- uint32 h = me->GetHealth() + largerAmount;
- me->SetHealth(std::min(mytotal, h));
- }
- else
- {
- uint32 h = pOtherBoss->GetHealth() + largerAmount;
- pOtherBoss->SetHealth(std::min(histotal, h));
- }
- }
-
- void TryHealBrother(uint32 diff)
- {
- if (IAmVeklor()) // this spell heals caster and the other brother so let VN cast it
- return;
-
- if (Heal_Timer <= diff)
- {
- Unit* pOtherBoss = GetOtherBoss();
- if (pOtherBoss && pOtherBoss->IsWithinDist(me, 60))
- {
- DoCast(pOtherBoss, SPELL_HEAL_BROTHER);
- Heal_Timer = 1000;
- }
- }
- else Heal_Timer -= diff;
- }
-
- void TeleportToMyBrother()
- {
- Teleport_Timer = TELEPORTTIME;
-
- if (IAmVeklor())
- return; // mechanics handled by veknilash so they teleport exactly at the same time and to correct coordinates
-
- Creature* pOtherBoss = GetOtherBoss();
- if (pOtherBoss)
- {
- //me->Yell("Teleporting ...", LANG_UNIVERSAL);
- Position thisPos;
- thisPos.Relocate(me);
- Position otherPos;
- otherPos.Relocate(pOtherBoss);
- pOtherBoss->SetPosition(thisPos);
- me->SetPosition(otherPos);
-
- SetAfterTeleport();
- CAST_AI(boss_twinemperorsAI, pOtherBoss->AI())->SetAfterTeleport();
- }
- }
-
- void SetAfterTeleport()
- {
- me->InterruptNonMeleeSpells(false);
- DoStopAttack();
- DoResetThreat();
- DoCast(me, SPELL_TWIN_TELEPORT_VISUAL);
- me->AddUnitState(UNIT_STATE_STUNNED);
- AfterTeleport = true;
- AfterTeleportTimer = 2000;
- tspellcast = false;
- }
-
- bool TryActivateAfterTTelep(uint32 diff)
- {
- if (AfterTeleport)
- {
- if (!tspellcast)
- {
- me->ClearUnitState(UNIT_STATE_STUNNED);
- DoCast(me, SPELL_TWIN_TELEPORT);
- me->AddUnitState(UNIT_STATE_STUNNED);
- }
-
- tspellcast = true;
-
- if (AfterTeleportTimer <= diff)
- {
- AfterTeleport = false;
- me->ClearUnitState(UNIT_STATE_STUNNED);
- if (Unit* nearu = me->SelectNearestTarget(100))
+ if (IAmVeklor())
{
- //DoYell(nearu->GetName(), LANG_UNIVERSAL);
- AttackStart(nearu);
- me->AddThreat(nearu, 10000);
- }
- return true;
- }
- else
- {
- AfterTeleportTimer -= diff;
- // update important timers which would otherwise get skipped
- if (EnrageTimer > diff)
- EnrageTimer -= diff;
- else
- EnrageTimer = 0;
- if (Teleport_Timer > diff)
- Teleport_Timer -= diff;
- else
- Teleport_Timer = 0;
- return false;
- }
- }
- else
- {
- return true;
- }
- }
-
- void MoveInLineOfSight(Unit* who) override
- {
- if (!who || me->GetVictim())
- return;
-
- if (me->CanCreatureAttack(who))
- {
- if (me->IsWithinDistInMap(who, PULL_RANGE, true, false) && me->GetDistanceZ(who) <= /*CREATURE_Z_ATTACK_RANGE*/7 /*there are stairs*/)
- {
- //if (who->HasStealthAura())
- // who->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH);
- AttackStart(who);
- }
- }
- }
-
- Creature* RespawnNearbyBugsAndGetOne()
- {
- std::list lUnitList;
- me->GetCreatureListWithEntryInGrid(lUnitList, 15316, 150.0f);
- me->GetCreatureListWithEntryInGrid(lUnitList, 15317, 150.0f);
-
- if (lUnitList.empty())
- return nullptr;
-
- Creature* nearb = nullptr;
-
- for (std::list::const_iterator iter = lUnitList.begin(); iter != lUnitList.end(); ++iter)
- {
- Creature* c = *iter;
- if (c)
- {
- if (c->isDead())
- {
- c->Respawn();
- c->SetFaction(FACTION_CREATURE);
- c->RemoveAllAuras();
- }
- if (c->IsWithinDistInMap(me, ABUSE_BUG_RANGE))
- {
- if (!nearb || (rand() % 4) == 0)
- nearb = c;
- }
- }
- }
- return nearb;
- }
-
- void HandleBugs(uint32 diff)
- {
- if (BugsTimer < diff || Abuse_Bug_Timer <= diff)
- {
- Creature* c = RespawnNearbyBugsAndGetOne();
- if (Abuse_Bug_Timer <= diff)
- {
- if (c)
- {
- CastSpellOnBug(c);
- Abuse_Bug_Timer = urand(10000, 17000);
+ DoCastSelf(SPELL_FRENZY, true);
+ Talk(EMOTE_ENRAGE);
}
else
- {
- Abuse_Bug_Timer = 1000;
- }
- }
- else
+ DoCastSelf(SPELL_BERSERK, true);
+ })
+ .Schedule(3600ms, [this](TaskContext context) // according to sniffs it should be casted by both emperors.
{
- Abuse_Bug_Timer -= diff;
- }
- BugsTimer = 2000;
- }
- else
- {
- BugsTimer -= diff;
- Abuse_Bug_Timer -= diff;
- }
+ if (Creature* twin = GetTwin())
+ {
+ if (me->IsWithinDist(twin, 60.f))
+ DoCast(twin, SPELL_HEAL_BROTHER, true);
+ }
+
+ context.Repeat();
+ });
}
- void CheckEnrage(uint32 diff)
+ void UpdateAI(uint32 diff) override
{
- if (EnrageTimer <= diff)
- {
- if (!me->IsNonMeleeSpellCast(true))
+ if (!UpdateVictim() && _introDone)
+ return;
+
+ _scheduler.Update(diff, [this]
{
- DoCast(me, SPELL_BERSERK);
- EnrageTimer = 60 * 60000;
- }
- else EnrageTimer = 0;
- }
- else EnrageTimer -= diff;
+ if (!IAmVeklor())
+ DoMeleeAttackIfReady();
+ });
}
+
+ virtual bool IAmVeklor() = 0;
+
+protected:
+ TaskScheduler _scheduler;
+ bool _introDone;
};
struct boss_veknilash : public boss_twinemperorsAI
@@ -385,192 +300,172 @@ struct boss_veknilash : public boss_twinemperorsAI
bool IAmVeklor() override { return false; }
- uint32 UpperCut_Timer;
- uint32 UnbalancingStrike_Timer;
- uint32 Scarabs_Timer;
- int Rand;
- int RandX;
- int RandY;
-
- Creature* Summoned;
-
- void Reset() override
+ void EnterCombat(Unit* who) override
{
- TwinReset();
- UpperCut_Timer = urand(14000, 29000);
- UnbalancingStrike_Timer = urand(8000, 18000);
- Scarabs_Timer = urand(7000, 14000);
+ boss_twinemperorsAI::EnterCombat(who);
- //Added. Can be removed if its included in DB.
- me->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_MAGIC, true);
- }
+ DoPlaySoundToSet(me, SOUND_VN_AGGRO);
- void CastSpellOnBug(Creature* target) override
- {
- target->SetFaction(FACTION_MONSTER);
- target->AI()->AttackStart(me->GetThreatMgr().getHostileTarget());
- target->AddAura(SPELL_MUTATE_BUG, target);
- target->SetFullHealth();
- }
-
- void UpdateAI(uint32 diff) override
- {
- //Return since we have no target
- if (!UpdateVictim())
- return;
-
- if (!TryActivateAfterTTelep(diff))
- return;
-
- //UnbalancingStrike_Timer
- if (UnbalancingStrike_Timer <= diff)
- {
- DoCastVictim(SPELL_UNBALANCING_STRIKE);
- UnbalancingStrike_Timer = 8000 + rand() % 12000;
- }
- else UnbalancingStrike_Timer -= diff;
-
- if (UpperCut_Timer <= diff)
- {
- Unit* randomMelee = SelectTarget(SelectTargetMethod::Random, 0, NOMINAL_MELEE_RANGE, true);
- if (randomMelee)
- DoCast(randomMelee, SPELL_UPPERCUT);
- UpperCut_Timer = 15000 + rand() % 15000;
- }
- else UpperCut_Timer -= diff;
-
- HandleBugs(diff);
-
- //Heal brother when 60yrds close
- TryHealBrother(diff);
-
- //Teleporting to brother
- if (Teleport_Timer <= diff)
- {
- TeleportToMyBrother();
- }
- else Teleport_Timer -= diff;
-
- CheckEnrage(diff);
-
- DoMeleeAttackIfReady();
+ _scheduler
+ .Schedule(14s, [this](TaskContext context)
+ {
+ DoCastRandomTarget(SPELL_UPPERCUT, 0, me->GetMeleeReach(), true);
+ context.Repeat(4s, 15s);
+ })
+ .Schedule(12s, [this](TaskContext context)
+ {
+ DoCastVictim(SPELL_UNBALANCING_STRIKE);
+ context.Repeat(8s, 20s);
+ })
+ .Schedule(16s, [this](TaskContext context)
+ {
+ DoCastAOE(SPELL_MUTATE_BUG);
+ context.Repeat(10s, 20s);
+ });
}
};
struct boss_veklor : public boss_twinemperorsAI
{
- boss_veklor(Creature* creature) : boss_twinemperorsAI(creature) { }
+ boss_veklor(Creature* creature) : boss_twinemperorsAI(creature)
+ {
+ me->SetFloatValue(UNIT_FIELD_COMBATREACH, 45.f);
+ }
bool IAmVeklor() override { return true; }
- uint32 ShadowBolt_Timer;
- uint32 Blizzard_Timer;
- uint32 ArcaneBurst_Timer;
- uint32 Scorpions_Timer;
- int Rand;
- int RandX;
-
- Creature* Summoned;
-
- void Reset() override
+ void EnterCombat(Unit* who) override
{
- TwinReset();
- ShadowBolt_Timer = 0;
- Blizzard_Timer = urand(15000, 20000);
- ArcaneBurst_Timer = 1000;
- Scorpions_Timer = urand(7000, 14000);
+ boss_twinemperorsAI::EnterCombat(who);
- //Added. Can be removed if its included in DB.
- me->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, true);
+ DoPlaySoundToSet(me, SOUND_VK_AGGRO);
+
+ _scheduler
+ .Schedule(4s, [this](TaskContext context)
+ {
+ DoCastVictim(SPELL_SHADOW_BOLT);
+ context.Repeat(2500ms);
+ })
+ .Schedule(10s, 15s, [this](TaskContext context)
+ {
+ DoCastRandomTarget(SPELL_BLIZZARD, 0, 45.f);
+ context.Repeat(5s, 12s);
+ })
+ .Schedule(1s, [this](TaskContext context)
+ {
+ if (me->SelectNearestPlayer(NOMINAL_MELEE_RANGE))
+ DoCastAOE(SPELL_ARCANE_BURST);
+ context.Repeat(7s, 12s);
+ })
+ .Schedule(30s, 40s, [this](TaskContext context)
+ {
+ DoCastSelf(SPELL_TWIN_TELEPORT_0);
+ context.Repeat();
+ })
+ .Schedule(5s, [this](TaskContext context)
+ {
+ DoCastAOE(SPELL_EXPLODE_BUG);
+ context.Repeat(4500ms, 10s);
+ });
}
- void CastSpellOnBug(Creature* target) override
+ void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override
{
- target->SetFaction(FACTION_MONSTER);
- target->AddAura(SPELL_EXPLODEBUG, target);
- target->SetFullHealth();
+ if (spellInfo->Id == SPELL_TWIN_TELEPORT_0)
+ {
+ if (Creature* veknilash = GetTwin())
+ {
+ DoCastSelf(SPELL_TWIN_TELEPORT_1, true);
+ me->SetControlled(true, UNIT_STATE_ROOT);
+
+ Position veklorPos = me->GetPosition();
+ Position veknilashPos = veknilash->GetPosition();
+ me->NearTeleportTo(veknilashPos);
+
+ veknilash->CastSpell(veknilash, SPELL_TWIN_TELEPORT_1, true);
+ veknilash->SetControlled(true, UNIT_STATE_ROOT);
+ veknilash->NearTeleportTo(veklorPos);
+
+ veknilash->AI()->DoAction(ACTION_AFTER_TELEPORT);
+ DoAction(ACTION_AFTER_TELEPORT);
+ }
+ }
+ }
+};
+
+class at_twin_emperors : public OnlyOnceAreaTriggerScript
+{
+public:
+ at_twin_emperors() : OnlyOnceAreaTriggerScript("at_twin_emperors") { }
+
+ bool _OnTrigger(Player* player, const AreaTrigger* /*at*/) override
+ {
+ if (InstanceScript* instance = player->GetInstanceScript())
+ {
+ if (instance->GetBossState(DATA_TWIN_EMPERORS) != DONE)
+ {
+ if (Creature* mastersEye = instance->GetCreature(DATA_MASTERS_EYE))
+ {
+ mastersEye->AI()->Talk(EMOTE_MASTERS_EYE_AT, player);
+ mastersEye->DespawnOrUnsummon(11000);
+ mastersEye->m_Events.AddEventAtOffset([mastersEye, player]()
+ {
+ mastersEye->SetFacingToObject(player);
+ }, 3s);
+ }
+
+ if (Creature* veklor = instance->GetCreature(DATA_VEKLOR))
+ veklor->AI()->DoAction(ACTION_START_INTRO);
+
+ if (Creature* veknilash = instance->GetCreature(DATA_VEKNILASH))
+ veknilash->AI()->DoAction(ACTION_START_INTRO);
+ }
+ }
+ return false;
+ }
+};
+
+class spell_mutate_explode_bug : public SpellScript
+{
+ PrepareSpellScript(spell_mutate_explode_bug);
+
+ void FilterTargets(std::list& targets)
+ {
+ targets.remove_if([&](WorldObject const* target) -> bool
+ {
+ if (target->GetEntry() != NPC_QIRAJI_SCARAB && target->GetEntry() != NPC_QIRAJI_SCORPION)
+ return true;
+ if (Creature const* creature = target->ToCreature())
+ if (creature->HasAura(SPELL_EXPLODE_BUG) || creature->HasAura(SPELL_MUTATE_BUG))
+ return true;
+
+ return false;
+ });
+
+ Acore::Containers::RandomResize(targets, 1);
}
- void UpdateAI(uint32 diff) override
+ void HandleOnHit()
{
- //Return since we have no target
- if (!UpdateVictim())
+ if (!GetHitUnit())
return;
- // reset arcane burst after teleport - we need to do this because
- // when VL jumps to VN's location there will be a warrior who will get only 2s to run away
- // which is almost impossible
- if (AfterTeleport)
- ArcaneBurst_Timer = 5000;
- if (!TryActivateAfterTTelep(diff))
+ Creature* target = GetHitUnit()->ToCreature();
+
+ if (!target)
return;
- //ShadowBolt_Timer
- if (ShadowBolt_Timer <= diff)
- {
- if (!me->IsWithinDist(me->GetVictim(), 45.0f))
- me->GetMotionMaster()->MoveChase(me->GetVictim(), VEKLOR_DIST, 0);
- else
- DoCastVictim(SPELL_SHADOWBOLT);
- ShadowBolt_Timer = 2000;
- }
- else ShadowBolt_Timer -= diff;
-
- //Blizzard_Timer
- if (Blizzard_Timer <= diff)
- {
- Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45, true);
- if (target)
- {
- DoCast(target, SPELL_BLIZZARD);
- }
- Blizzard_Timer = 15000 + rand() % 15000;
- }
- else Blizzard_Timer -= diff;
-
- if (ArcaneBurst_Timer <= diff)
- {
- Unit* mvic;
- if ((mvic = SelectTarget(SelectTargetMethod::MaxDistance, 0, NOMINAL_MELEE_RANGE, true)) != nullptr)
- {
- DoCast(mvic, SPELL_ARCANEBURST);
- ArcaneBurst_Timer = 5000;
- }
- }
- else ArcaneBurst_Timer -= diff;
-
- HandleBugs(diff);
-
- //Heal brother when 60yrds close
- TryHealBrother(diff);
-
- //Teleporting to brother
- if (Teleport_Timer <= diff)
- {
- TeleportToMyBrother();
- }
- else Teleport_Timer -= diff;
-
- CheckEnrage(diff);
-
- //VL doesn't melee
- //DoMeleeAttackIfReady();
+ if (m_scriptSpellId == SPELL_MUTATE_BUG)
+ target->CastSpell(target, SPELL_VIRULENT_POISON_PROC, true);
+ target->SetFaction(FACTION_HOSTILE);
+ target->SetReactState(REACT_AGGRESSIVE);
+ target->SetInCombatWithZone();
}
- void AttackStart(Unit* who) override
+ void Register() override
{
- if (!who)
- return;
-
- if (who->isTargetableForAttack())
- {
- // VL doesn't melee
- if (me->Attack(who, false))
- {
- me->GetMotionMaster()->MoveChase(who, VEKLOR_DIST, 0);
- me->AddThreat(who, 0.0f);
- }
- }
+ OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_mutate_explode_bug::FilterTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENTRY);
+ OnHit += SpellHitFn(spell_mutate_explode_bug::HandleOnHit);
}
};
@@ -578,4 +473,6 @@ void AddSC_boss_twinemperors()
{
RegisterTempleOfAhnQirajCreatureAI(boss_veknilash);
RegisterTempleOfAhnQirajCreatureAI(boss_veklor);
+ new at_twin_emperors();
+ RegisterSpellScript(spell_mutate_explode_bug);
}
diff --git a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/instance_temple_of_ahnqiraj.cpp b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/instance_temple_of_ahnqiraj.cpp
index 38097a791..aeab896c8 100644
--- a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/instance_temple_of_ahnqiraj.cpp
+++ b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/instance_temple_of_ahnqiraj.cpp
@@ -24,7 +24,10 @@ ObjectData const creatureData[] =
{
{ NPC_SARTURA, DATA_SARTURA },
{ NPC_EYE_OF_CTHUN, DATA_EYE_OF_CTHUN },
- { NPC_OURO_SPAWNER, DATA_OURO_SPAWNER }
+ { NPC_OURO_SPAWNER, DATA_OURO_SPAWNER },
+ { NPC_MASTERS_EYE, DATA_MASTERS_EYE },
+ { NPC_VEKLOR, DATA_VEKLOR },
+ { NPC_VEKNILASH, DATA_VEKNILASH }
};
class instance_temple_of_ahnqiraj : public InstanceMapScript
@@ -46,15 +49,10 @@ public:
SetBossNumber(MAX_BOSS_NUMBER);
}
- //If Vem is dead...
- bool IsBossDied[3];
-
ObjectGuid SkeramGUID;
ObjectGuid VemGUID;
ObjectGuid KriGUID;
ObjectGuid YaujGUID;
- ObjectGuid VeklorGUID;
- ObjectGuid VeknilashGUID;
ObjectGuid ViscidusGUID;
ObjectGuid CThunGUID;
GuidVector CThunGraspGUIDs;
@@ -65,9 +63,6 @@ public:
void Initialize() override
{
- IsBossDied[0] = false;
- IsBossDied[1] = false;
- IsBossDied[2] = false;
BugTrioDeathCount = 0;
CthunPhase = 0;
}
@@ -92,16 +87,6 @@ public:
case NPC_YAUJ:
YaujGUID = creature->GetGUID();
break;
- case NPC_VEKLOR:
- VeklorGUID = creature->GetGUID();
- if (!creature->IsAlive())
- {
- HandleGameObject(doorGUIDs[1], true);
- }
- break;
- case NPC_VEKNILASH:
- VeknilashGUID = creature->GetGUID();
- break;
case NPC_VISCIDUS:
ViscidusGUID = creature->GetGUID();
break;
@@ -109,6 +94,10 @@ public:
if (GetBossState(DATA_OURO) != DONE)
creature->Respawn();
break;
+ case NPC_MASTERS_EYE:
+ if (GetBossState(DATA_TWIN_EMPERORS) != DONE)
+ creature->Respawn(true);
+ break;
case NPC_CTHUN:
CThunGUID = creature->GetGUID();
if (!creature->IsAlive())
@@ -138,7 +127,7 @@ public:
break;
case AQ40_DOOR_2:
doorGUIDs[1] = go->GetGUID();
- if (Creature* veklor = instance->GetCreature(VeklorGUID))
+ if (Creature* veklor = GetCreature(DATA_VEKLOR))
{
if (!veklor->IsAlive())
{
@@ -177,16 +166,6 @@ public:
{
switch (type)
{
- case DATA_VEKLORISDEAD:
- if (IsBossDied[1])
- return 1;
- break;
-
- case DATA_VEKNILASHISDEAD:
- if (IsBossDied[2])
- return 1;
- break;
-
case DATA_BUG_TRIO_DEATH:
return BugTrioDeathCount;
@@ -208,10 +187,6 @@ public:
return KriGUID;
case DATA_YAUJ:
return YaujGUID;
- case DATA_VEKLOR:
- return VeklorGUID;
- case DATA_VEKNILASH:
- return VeknilashGUID;
case DATA_VISCIDUS:
return ViscidusGUID;
case AQ40_DOOR_1:
@@ -234,12 +209,6 @@ public:
else
BugTrioDeathCount = 0;
break;
- case DATA_VEKLOR_DEATH:
- IsBossDied[1] = true;
- break;
- case DATA_VEKNILASH_DEATH:
- IsBossDied[2] = true;
- break;
case DATA_CTHUN_PHASE:
CthunPhase = data;
if (data == PHASE_CTHUN_DONE)
@@ -253,6 +222,8 @@ public:
}
}
break;
+ default:
+ break;
}
}
diff --git a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.h b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.h
index e5d2ed286..1cba3f384 100644
--- a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.h
+++ b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.h
@@ -42,17 +42,15 @@ enum DataTypes
DATA_BUG_TRIO_DEATH = 13,
DATA_OURO_SPAWNER = 14,
DATA_VEKLOR = 15,
- DATA_VEKLORISDEAD = 16,
- DATA_VEKLOR_DEATH = 17,
- DATA_VEKNILASH = 18,
- DATA_VEKNILASHISDEAD = 19,
- DATA_VEKNILASH_DEATH = 20,
- DATA_CTHUN_PHASE = 21,
- DATA_EYE_OF_CTHUN = 22
+ DATA_VEKNILASH = 16,
+ DATA_CTHUN_PHASE = 17,
+ DATA_EYE_OF_CTHUN = 18,
+ DATA_MASTERS_EYE = 19
};
enum Creatures
{
+ NPC_MASTERS_EYE = 15963,
NPC_CTHUN = 15727,
NPC_EYE_OF_CTHUN = 15589,
NPC_CTHUN_PORTAL = 15896,