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,