diff options
Diffstat (limited to 'src')
20 files changed, 934 insertions, 471 deletions
diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index 3b7c489e018..f250a79ea25 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -80,7 +80,7 @@ class CreatureAI : public UnitAI Creature* DoSummonFlyer(uint32 entry, WorldObject* obj, float flightZ, float radius = 5.0f, uint32 despawnTime = 30000, TempSummonType summonType = TEMPSUMMON_CORPSE_TIMED_DESPAWN); bool CheckBoundary(Position* who = nullptr) const; - void SetBoundary(CreatureBoundary const* boundary) { _boundary = boundary; CheckInRoom(); } + void SetBoundary(CreatureBoundary const* boundary) { _boundary = boundary; me->DoImmediateBoundaryCheck(); } public: enum EvadeReason { diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp index cff02234ab6..52e0deaf86f 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp @@ -365,7 +365,7 @@ bool BattlegroundIC::SetupBattleground() // setting correct factions for Keep Cannons for (uint8 i = BG_IC_NPC_KEEP_CANNON_1; i <= BG_IC_NPC_KEEP_CANNON_12; ++i) GetBGCreature(i)->setFaction(BG_IC_Factions[0]); - for (uint8 i = BG_IC_NPC_KEEP_CANNON_13; i <= BG_IC_NPC_KEEP_CANNON_25; ++i) + for (uint8 i = BG_IC_NPC_KEEP_CANNON_13; i <= BG_IC_NPC_KEEP_CANNON_24; ++i) GetBGCreature(i)->setFaction(BG_IC_Factions[1]); // correcting spawn time for keeps bombs diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundIC.h b/src/server/game/Battlegrounds/Zones/BattlegroundIC.h index 72e9990b7c1..44666819feb 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundIC.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundIC.h @@ -390,7 +390,6 @@ enum BG_IC_NPCs BG_IC_NPC_KEEP_CANNON_22, BG_IC_NPC_KEEP_CANNON_23, BG_IC_NPC_KEEP_CANNON_24, - BG_IC_NPC_KEEP_CANNON_25, BG_IC_NPC_SIEGE_ENGINE_A, BG_IC_NPC_SIEGE_ENGINE_H, @@ -444,7 +443,7 @@ enum BannersTypes enum BG_IC_MaxSpawns { MAX_NORMAL_GAMEOBJECTS_SPAWNS = BG_IC_GO_DOODAD_ND_WINTERORC_WALL_GATEFX_DOOR03+1, - MAX_NORMAL_NPCS_SPAWNS = BG_IC_NPC_KEEP_CANNON_25+1, + MAX_NORMAL_NPCS_SPAWNS = BG_IC_NPC_KEEP_CANNON_24+1, MAX_WORKSHOP_SPAWNS = 10, MAX_DOCKS_SPAWNS = 12, MAX_SPIRIT_GUIDES_SPAWNS = 7, @@ -506,9 +505,8 @@ const ICNpc BG_IC_NpcSpawnlocs[MAX_NORMAL_NPCS_SPAWNS] = {BG_IC_NPC_KEEP_CANNON_20, NPC_KEEP_CANNON, TEAM_HORDE, 1137.72f, -688.517f, 88.4023f, 3.9619f}, // 30 {BG_IC_NPC_KEEP_CANNON_21, NPC_KEEP_CANNON, TEAM_HORDE, 1135.29f, -840.878f, 88.0252f, 2.30383f}, // 31 {BG_IC_NPC_KEEP_CANNON_22, NPC_KEEP_CANNON, TEAM_HORDE, 1144.33f, -833.309f, 87.9268f, 2.14675f}, // 32 - {BG_IC_NPC_KEEP_CANNON_23, NPC_KEEP_CANNON, TEAM_HORDE, 1135.29f, -840.878f, 88.0252f, 2.30383f}, // 33 - {BG_IC_NPC_KEEP_CANNON_24, NPC_KEEP_CANNON, TEAM_HORDE, 1142.59f, -691.946f, 87.9756f, 3.9619f}, // 34 - {BG_IC_NPC_KEEP_CANNON_25, NPC_KEEP_CANNON, TEAM_HORDE, 1166.13f, -858.391f, 87.9653f, 5.63741f} // 35 + {BG_IC_NPC_KEEP_CANNON_23, NPC_KEEP_CANNON, TEAM_HORDE, 1142.59f, -691.946f, 87.9756f, 3.9619f}, // 33 + {BG_IC_NPC_KEEP_CANNON_24, NPC_KEEP_CANNON, TEAM_HORDE, 1166.13f, -858.391f, 87.9653f, 5.63741f} // 34 }; const Position BG_IC_WorkshopVehicles[5] = diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 3e67f709816..31320e4d65e 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -2739,7 +2739,7 @@ bool Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target) } } if (shouldDelay) - shouldDelay = (!focusSpell->IsTriggered() && !focusSpell->GetCastTime()); + shouldDelay = !(focusSpell->IsTriggered() || focusSpell->GetCastTime() || focusSpell->GetSpellInfo()->IsChanneled()); } } diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index b0318ed33e2..3ee1ba7db7b 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -605,6 +605,7 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject float GetRespawnRadius() const { return m_respawnradius; } void SetRespawnRadius(float dist) { m_respawnradius = dist; } + void DoImmediateBoundaryCheck() { m_boundaryCheckTime = 0; } uint32 GetCombatPulseDelay() const { return m_combatPulseDelay; } void SetCombatPulseDelay(uint32 delay) // (secs) interval at which the creature pulses the entire zone into combat (only works in dungeons) { diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 24ddcd358ea..a826f841058 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -157,6 +157,28 @@ ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget, _damageInfo(damageInfo), _healInfo(healInfo) { } +SpellInfo const* ProcEventInfo::GetSpellInfo() const +{ + if (_spell) + return _spell->GetSpellInfo(); + if (_damageInfo) + return _damageInfo->GetSpellInfo(); + if (_healInfo) + return _healInfo->GetSpellInfo(); + return nullptr; +} + +SpellSchoolMask ProcEventInfo::GetSchoolMask() const +{ + if (_spell) + return _spell->GetSpellInfo()->GetSchoolMask(); + if (_damageInfo) + return _damageInfo->GetSchoolMask(); + if (_healInfo) + return _healInfo->GetSchoolMask(); + return SPELL_SCHOOL_MASK_NONE; +} + Unit::Unit(bool isWorldObject) : WorldObject(isWorldObject), m_movedPlayer(NULL), m_lastSanctuaryTime(0), IsAIEnabled(false), NeedChangeAI(false), LastCharmerGUID(), @@ -6365,54 +6387,6 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere } case SPELLFAMILY_PALADIN: { - // Light's Beacon - Beacon of Light - if (dummySpell->Id == 53651) - { - if (!victim || !procSpell) - return false; - triggered_spell_id = 0; - Unit* beaconTarget = NULL; - if (GetTypeId() != TYPEID_PLAYER) - { - beaconTarget = triggeredByAura->GetBase()->GetCaster(); - if (!beaconTarget || beaconTarget == this || !(beaconTarget->GetAura(53563, victim->GetGUID()))) - return false; - basepoints0 = int32(damage); - triggered_spell_id = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(635)) ? 53652 : 53654; - } - else - { // Check Party/Raid Group - if (Group* group = ToPlayer()->GetGroup()) - { - for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) - { - if (Player* member = itr->GetSource()) - { - // check if it was heal by paladin which cast this beacon of light - if (member->GetAura(53563, victim->GetGUID())) - { - // do not proc when target of beacon of light is healed - if (member == this) - return false; - - beaconTarget = member; - basepoints0 = int32(damage); - triggered_spell_id = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(635)) ? 53652 : 53654; - break; - } - } - } - } - } - - if (triggered_spell_id && beaconTarget) - { - victim->CastCustomSpell(beaconTarget, triggered_spell_id, &basepoints0, NULL, NULL, true); - return true; - } - - return false; - } // Judgements of the Wise if (dummySpell->SpellIconID == 3017) { @@ -14187,8 +14161,8 @@ void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, u Unit* actionTarget = !isVictim ? target : this; DamageInfo damageInfo = DamageInfo(actor, actionTarget, damage, procSpell, procSpell ? SpellSchoolMask(procSpell->SchoolMask) : SPELL_SCHOOL_MASK_NORMAL, SPELL_DIRECT_DAMAGE); - HealInfo healInfo = HealInfo(damage); - ProcEventInfo eventInfo = ProcEventInfo(actor, actionTarget, target, procFlag, 0, 0, procExtra, NULL, &damageInfo, &healInfo); + HealInfo healInfo = HealInfo(actor, actionTarget, damage, procSpell, procSpell ? SpellSchoolMask(procSpell->SchoolMask) : SPELL_SCHOOL_MASK_NORMAL); + ProcEventInfo eventInfo = ProcEventInfo(actor, actionTarget, target, procFlag, 0, 0, procExtra, nullptr, &damageInfo, &healInfo); ProcTriggeredList procTriggered; // Fill procTriggered list diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index c64667feeca..8572c791d67 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -863,22 +863,30 @@ public: class HealInfo { private: - uint32 m_heal; - uint32 m_absorb; + Unit* const _healer; + Unit* const _target; + uint32 _heal; + uint32 _absorb; + SpellInfo const* const _spellInfo; + SpellSchoolMask const _schoolMask; + public: - explicit HealInfo(uint32 heal) - : m_heal(heal) - { - m_absorb = 0; - } + explicit HealInfo(Unit* healer, Unit* target, uint32 heal, SpellInfo const* spellInfo, SpellSchoolMask schoolMask) + : _healer(healer), _target(target), _heal(heal), _absorb(0), _spellInfo(spellInfo), _schoolMask(schoolMask) { } + void AbsorbHeal(uint32 amount) { amount = std::min(amount, GetHeal()); - m_absorb += amount; - m_heal -= amount; + _absorb += amount; + _heal -= amount; } - uint32 GetHeal() const { return m_heal; } + Unit* GetHealer() const { return _healer; } + Unit* GetTarget() const { return _target; } + uint32 GetHeal() const { return _heal; } + uint32 GetAbsorb() const { return _absorb; } + SpellInfo const* GetSpellInfo() const { return _spellInfo; }; + SpellSchoolMask GetSchoolMask() const { return _schoolMask; }; }; class ProcEventInfo @@ -897,14 +905,8 @@ public: uint32 GetSpellPhaseMask() const { return _spellPhaseMask; } uint32 GetHitMask() const { return _hitMask; } - SpellInfo const* GetSpellInfo() const { return NULL; } - SpellInfo const* EnsureSpellInfo() const - { - SpellInfo const* spellInfo = GetSpellInfo(); - ASSERT(spellInfo); - return spellInfo; - } - SpellSchoolMask GetSchoolMask() const { return SPELL_SCHOOL_MASK_NONE; } + SpellInfo const* GetSpellInfo() const; + SpellSchoolMask GetSchoolMask() const; DamageInfo* GetDamageInfo() const { return _damageInfo; } HealInfo* GetHealInfo() const { return _healInfo; } diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index eb6d9a187d6..1575b50098f 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -30,6 +30,12 @@ #include "WorldSession.h" #include "Opcodes.h" +BossBoundaryData::~BossBoundaryData() +{ + for (const_iterator it = begin(); it != end(); ++it) + delete it->Boundary; +} + void InstanceScript::SaveToDB() { std::string data = GetSaveData(); @@ -99,8 +105,8 @@ void InstanceScript::SetHeaders(std::string const& dataHeaders) void InstanceScript::LoadBossBoundaries(const BossBoundaryData& data) { for (BossBoundaryEntry const& entry : data) - if (entry.bossId < bosses.size()) - bosses[entry.bossId].boundary.insert(entry.boundary); + if (entry.BossId < bosses.size()) + bosses[entry.BossId].boundary.insert(entry.Boundary); } void InstanceScript::LoadMinionData(const MinionData* data) diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index 8285eaa7346..ce83061e162 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -75,10 +75,23 @@ struct DoorData struct BossBoundaryEntry { - uint32 const bossId; - AreaBoundary const* const boundary; + uint32 BossId; + AreaBoundary const* Boundary; +}; + +struct BossBoundaryData +{ + typedef std::vector<BossBoundaryEntry> StorageType; + typedef StorageType::const_iterator const_iterator; + + BossBoundaryData(std::initializer_list<BossBoundaryEntry> data) : _data(data) { } + ~BossBoundaryData(); + const_iterator begin() const { return _data.begin(); } + const_iterator end() const { return _data.end(); } + + private: + StorageType _data; }; -typedef std::list<BossBoundaryEntry> BossBoundaryData; struct MinionData { diff --git a/src/server/game/Maps/AreaBoundary.h b/src/server/game/Maps/AreaBoundary.h index 7df72c3dc12..a134b783ca6 100644 --- a/src/server/game/Maps/AreaBoundary.h +++ b/src/server/game/Maps/AreaBoundary.h @@ -32,15 +32,16 @@ class AreaBoundary BOUNDARY_PARALLELOGRAM, BOUNDARY_Z_RANGE, }; + virtual ~AreaBoundary() { } BoundaryType GetBoundaryType() const { return m_boundaryType; } bool IsWithinBoundary(const Position* pos) const { return (IsWithinBoundaryArea(pos) != m_isInvertedBoundary); } struct DoublePosition : Position { double d_positionX, d_positionY, d_positionZ; - DoublePosition(double x = 0, double y = 0, double z = 0, float o = 0) - : Position((float)x, (float)y, (float)z, o), d_positionX(x), d_positionY(y), d_positionZ(z) { } - DoublePosition(float x = 0, float y = 0, float z = 0, float o = 0) + DoublePosition(double x = 0.0, double y = 0.0, double z = 0.0, float o = 0.0f) + : Position(x, y, z, o), d_positionX(x), d_positionY(y), d_positionZ(z) { } + DoublePosition(float x, float y = 0.0f, float z = 0.0f, float o = 0.0f) : Position(x, y, z, o), d_positionX(x), d_positionY(y), d_positionZ(z) { } DoublePosition(const Position& pos) : DoublePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()) { } diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index e96ed8c6799..e0b9bf53b63 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -969,10 +969,12 @@ bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcE // check spell family name/flags (if set) for spells if (eventInfo.GetTypeMask() & (PERIODIC_PROC_FLAG_MASK | SPELL_PROC_FLAG_MASK | PROC_FLAG_DONE_TRAP_ACTIVATION)) { - if (procEntry.spellFamilyName && eventInfo.GetSpellInfo() && (procEntry.spellFamilyName != eventInfo.EnsureSpellInfo()->SpellFamilyName)) + SpellInfo const* eventSpellInfo = eventInfo.GetSpellInfo(); + + if (procEntry.spellFamilyName && eventSpellInfo && (procEntry.spellFamilyName != eventSpellInfo->SpellFamilyName)) return false; - if (procEntry.spellFamilyMask && eventInfo.GetSpellInfo() && !(procEntry.spellFamilyMask & eventInfo.EnsureSpellInfo()->SpellFamilyFlags)) + if (procEntry.spellFamilyMask && eventSpellInfo && !(procEntry.spellFamilyMask & eventSpellInfo->SpellFamilyFlags)) return false; } @@ -3332,6 +3334,8 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Attributes |= SPELL_ATTR0_PASSIVE; break; case 17364: // Stormstrike + case 48278: // Paralyze + case 53651: // Light's Beacon spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS; break; case 51798: // Brewfest - Relay Race - Intro - Quest Complete @@ -3565,7 +3569,6 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->AreaGroupId = 0; // originally, these require area 4522, which is... outside of Icecrown Citadel break; case 70602: // Corruption - case 48278: // Paralyze spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS; break; case 70715: // Column of Frost (visual marker) diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp index 269e115189a..d5ed2421efc 100644 --- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp +++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp @@ -77,7 +77,7 @@ public: Talk(SAY_RESPAWN); } - bool CheckInRoom() + bool CheckInRoom() override { if (me->GetDistance2d(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY()) > 50) { diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp index 93583364ef6..238942c15f6 100644 --- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp +++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp @@ -78,7 +78,7 @@ public: Talk(SAY_BUFF); } - bool CheckInRoom() + bool CheckInRoom() override { if (me->GetDistance2d(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY()) > 50) { diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp index e5ac2676ad0..b1a0f0217c4 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp @@ -22,12 +22,11 @@ #include "Player.h" #include "TemporarySummon.h" -AreaBoundary const* const mainBoundary = new CircleBoundary(Position(563.26f, 139.6f), 75.0); BossBoundaryData const boundaries = { - { BOSS_BEASTS, mainBoundary }, - { BOSS_JARAXXUS, mainBoundary }, - { BOSS_CRUSADERS, mainBoundary }, - { BOSS_VALKIRIES, mainBoundary }, + { BOSS_BEASTS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) }, + { BOSS_JARAXXUS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) }, + { BOSS_CRUSADERS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) }, + { BOSS_VALKIRIES, new CircleBoundary(Position(563.26f, 139.6f), 75.0) }, { BOSS_ANUBARAK, new EllipseBoundary(Position(746.0f, 135.0f), 100.0, 75.0) } }; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp index 9bdbce81dbf..ac094588d35 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp @@ -265,7 +265,7 @@ class boss_sindragosa : public CreatureScript { if (!instance->CheckRequiredBosses(DATA_SINDRAGOSA, victim->ToPlayer())) { - EnterEvadeMode(); + EnterEvadeMode(EVADE_REASON_SEQUENCE_BREAK); instance->DoCastSpellOnPlayers(LIGHT_S_HAMMER_TELEPORT); return; } @@ -276,6 +276,13 @@ class boss_sindragosa : public CreatureScript Talk(SAY_AGGRO); } + void EnterEvadeMode(EvadeReason why) override + { + if (_isInAirPhase && why == EVADE_REASON_BOUNDARY) + return; + BossAI::EnterEvadeMode(why); + } + void JustReachedHome() override { BossAI::JustReachedHome(); diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp index c9684cf10cf..c875a336aaf 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp @@ -20,30 +20,70 @@ #include "SpellScript.h" #include "GridNotifiers.h" #include "CombatAI.h" +#include "AreaBoundary.h" #include "naxxramas.h" +/* Constants */ enum Yells { - SAY_SPEECH = 0, - SAY_KILL = 1, - SAY_DEATH = 2, - SAY_TELEPORT = 3 + SAY_INTRO_1 = 0, + SAY_INTRO_2 = 1, + SAY_INTRO_3 = 2, + SAY_INTRO_4 = 3, + SAY_PHASE_TWO = 4, + SAY_DEATH = 5, + SAY_KILL = 6, + + EMOTE_PHASE_TWO = 7, + EMOTE_GATE_OPENED = 8 }; -//Gothik enum Spells { + /* living trainee spells */ + SPELL_DEATH_PLAGUE = 55604, + + /* living knight spells */ + SPELL_SHADOW_MARK = 27825, + + /* living rider spells */ + SPELL_SHADOW_BOLT_VOLLEY = 27831, + + /* spectral trainee spells */ + SPELL_ARCANE_EXPLOSION = 27989, + + /* spectral knight spells */ + SPELL_WHIRLWIND = 56408, + + /* spectral rider spells */ + SPELL_DRAIN_LIFE = 27994, + SPELL_UNHOLY_FRENZY = 55648, + + /* spectral horse spells */ + SPELL_STOMP = 27993, + + /* gothik phase two spells */ SPELL_HARVEST_SOUL = 28679, SPELL_SHADOW_BOLT = 29317, - SPELL_INFORM_LIVE_TRAINEE = 27892, - SPELL_INFORM_LIVE_KNIGHT = 27928, - SPELL_INFORM_LIVE_RIDER = 27935, - SPELL_INFORM_DEAD_TRAINEE = 27915, - SPELL_INFORM_DEAD_KNIGHT = 27931, - SPELL_INFORM_DEAD_RIDER = 27937, - - SPELL_SHADOW_MARK = 27825 + + /* visual spells */ + SPELL_ANCHOR_1_TRAINEE = 27892, + SPELL_ANCHOR_1_DK = 27928, + SPELL_ANCHOR_1_RIDER = 27935, + + SPELL_ANCHOR_2_TRAINEE = 27893, + SPELL_ANCHOR_2_DK = 27929, + SPELL_ANCHOR_2_RIDER = 27936, + + SPELL_SKULLS_TRAINEE = 27915, + SPELL_SKULLS_DK = 27931, + SPELL_SKULLS_RIDER = 27937, + + /* teleport spells */ + SPELL_TELEPORT_DEAD = 28025, + SPELL_TELEPORT_LIVE = 28026 }; +#define SPELLHELPER_UNHOLY_FRENZY RAID_MODE<uint32>(SPELL_UNHOLY_FRENZY,27995) enum Creatures { @@ -53,108 +93,205 @@ enum Creatures NPC_DEAD_TRAINEE = 16127, NPC_DEAD_KNIGHT = 16148, NPC_DEAD_RIDER = 16150, - NPC_DEAD_HORSE = 16149 + NPC_DEAD_HORSE = 16149, + + NPC_TRIGGER = 16137 }; -struct Waves { uint32 entry, time, mode; }; -// wave setups are not the same in heroic and normal difficulty, -// mode is 0 only normal, 1 both and 2 only heroic -// but this is handled in DoGothikSummon function -const Waves waves[] = +enum Phases { - {NPC_LIVE_TRAINEE, 20000, 1}, - {NPC_LIVE_TRAINEE, 20000, 1}, - {NPC_LIVE_TRAINEE, 10000, 1}, - {NPC_LIVE_KNIGHT, 10000, 1}, - {NPC_LIVE_TRAINEE, 15000, 1}, - {NPC_LIVE_KNIGHT, 5000, 1}, - {NPC_LIVE_TRAINEE, 20000, 1}, - {NPC_LIVE_TRAINEE, 0, 1}, - {NPC_LIVE_KNIGHT, 10000, 1}, - {NPC_LIVE_TRAINEE, 10000, 2}, - {NPC_LIVE_RIDER, 10000, 0}, - {NPC_LIVE_RIDER, 5000, 2}, - {NPC_LIVE_TRAINEE, 5000, 0}, - {NPC_LIVE_TRAINEE, 15000, 2}, - {NPC_LIVE_KNIGHT, 15000, 0}, - {NPC_LIVE_TRAINEE, 0, 0}, - {NPC_LIVE_RIDER, 10000, 1}, - {NPC_LIVE_KNIGHT, 10000, 1}, - {NPC_LIVE_TRAINEE, 10000, 0}, - {NPC_LIVE_RIDER, 10000, 2}, - {NPC_LIVE_TRAINEE, 0, 2}, - {NPC_LIVE_RIDER, 5000, 1}, - {NPC_LIVE_TRAINEE, 0, 2}, - {NPC_LIVE_KNIGHT, 5000, 1}, - {NPC_LIVE_RIDER, 0, 2}, - {NPC_LIVE_TRAINEE, 20000, 1}, - {NPC_LIVE_RIDER, 0, 1}, - {NPC_LIVE_KNIGHT, 0, 1}, - {NPC_LIVE_TRAINEE, 25000, 2}, - {NPC_LIVE_TRAINEE, 15000, 0}, - {NPC_LIVE_TRAINEE, 25000, 0}, - {0, 0, 1}, + PHASE_ONE = 1, + PHASE_TWO = 2 }; -#define POS_Y_GATE -3360.78f -#define POS_Y_WEST -3285.0f -#define POS_Y_EAST -3434.0f -#define POS_X_NORTH 2750.49f -#define POS_X_SOUTH 2633.84f - -#define IN_LIVE_SIDE(who) (who->GetPositionY() < POS_Y_GATE) - enum Events { - EVENT_NONE, + EVENT_INTRO_2 = 1, + EVENT_INTRO_3, + EVENT_INTRO_4, + EVENT_PHASE_TWO, EVENT_SUMMON, + EVENT_DOORS_UNLOCK, + EVENT_TELEPORT, EVENT_HARVEST, EVENT_BOLT, - EVENT_TELEPORT -}; -enum Pos -{ - POS_LIVE = 6, - POS_DEAD = 5 + EVENT_RESUME_ATTACK }; -const Position PosSummonLive[POS_LIVE] = +enum Actions { - {2669.7f, -3428.76f, 268.56f, 1.6f}, - {2692.1f, -3428.76f, 268.56f, 1.6f}, - {2714.4f, -3428.76f, 268.56f, 1.6f}, - {2669.7f, -3431.67f, 268.56f, 1.6f}, - {2692.1f, -3431.67f, 268.56f, 1.6f}, - {2714.4f, -3431.67f, 268.56f, 1.6f}, + ACTION_GATE_OPENED = 1, + ACTION_MINION_EVADE, + ACTION_ACQUIRE_TARGET }; -const Position PosSummonDead[POS_DEAD] = + +/* Room side checking logic */ +static AreaBoundary* const livingSide = new RectangleBoundary(2633.84f, 2750.49f, -3434.0f, -3360.78f); +static AreaBoundary* const deadSide = new RectangleBoundary(2633.84f, 2750.49f, -3360.78f, -3285.0f); +enum Side { - {2725.1f, -3310.0f, 268.85f, 3.4f}, - {2699.3f, -3322.8f, 268.60f, 3.3f}, - {2733.1f, -3348.5f, 268.84f, 3.1f}, - {2682.8f, -3304.2f, 268.85f, 3.9f}, - {2664.8f, -3340.7f, 268.23f, 3.7f}, + SIDE_NONE = 0, + SIDE_LIVING, + SIDE_DEAD }; +inline static Side GetSide(Position const* who) +{ + if (livingSide->IsWithinBoundary(who)) + return SIDE_LIVING; + if (deadSide->IsWithinBoundary(who)) + return SIDE_DEAD; + return SIDE_NONE; +} +inline static bool IsOnSameSide(Position const* who, Position const* other) +{ + return (GetSide(who) == GetSide(other)); +} +static Player* FindEligibleTarget(Creature const* me, bool isGateOpen) +{ + Map::PlayerList const& players = me->GetMap()->GetPlayers(); + for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it) + { + Player* player = it->GetSource(); + if (player && (isGateOpen || IsOnSameSide(me, player)) && me->CanSeeOrDetect(player) && me->IsValidAttackTarget(player) && player->isInAccessiblePlaceFor(me)) + { + return player; + } + } -float const PosGroundLiveSide[4] = {2691.2f, -3387.0f, 267.68f, 1.52f}; -float const PosGroundDeadSide[4] = {2693.5f, -3334.6f, 267.68f, 4.67f}; -float const PosPlatform[4] = {2640.5f, -3360.6f, 285.26f, 0.0f}; + return nullptr; +} -// Predicate function to check that the r efzr unit is NOT on the same side as the source. -struct NotOnSameSide : public std::unary_function<Unit*, bool> -{ - NotOnSameSide(Unit* source) : _onLiveSide(IN_LIVE_SIDE(source)) { } - bool operator() (Unit const* target) +/* Wave data */ +typedef std::pair<uint32, uint8> GothikWaveEntry; // (npcEntry, npcCount) +typedef std::set<GothikWaveEntry> GothikWave; +typedef std::pair<GothikWave, uint8> GothikWaveInfo; // (wave, secondsToNext) +typedef std::vector<GothikWaveInfo> GothikWaveData; +const GothikWaveData waves10 = +{ { - return (_onLiveSide != IN_LIVE_SIDE(target)); - } + {{NPC_LIVE_TRAINEE, 2}}, + 20}, + { + {{NPC_LIVE_TRAINEE, 2}}, + 20}, + { + {{NPC_LIVE_TRAINEE, 2}}, + 10}, + { + {{NPC_LIVE_KNIGHT, 1}}, + 10}, + { + {{NPC_LIVE_TRAINEE, 2}}, + 15}, + { + {{NPC_LIVE_KNIGHT, 1}}, + 5}, + { + {{NPC_LIVE_TRAINEE, 2}}, + 20}, + { + {{NPC_LIVE_TRAINEE, 2}, {NPC_LIVE_KNIGHT, 1}}, + 10}, + { + {{NPC_LIVE_RIDER, 1}}, + 10}, + { + {{NPC_LIVE_TRAINEE, 2}}, + 5}, + { + {{NPC_LIVE_KNIGHT, 1}}, + 15}, + { + {{NPC_LIVE_TRAINEE, 2}, {NPC_LIVE_RIDER, 1}}, + 10}, + { + {{NPC_LIVE_KNIGHT, 2}}, + 10}, + { + {{NPC_LIVE_TRAINEE, 2}}, + 10}, + { + {{NPC_LIVE_RIDER, 1}}, + 5}, + { + {{NPC_LIVE_KNIGHT, 1}}, + 5}, + { + {{NPC_LIVE_TRAINEE, 2}}, + 20}, + { + {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_KNIGHT, 1}, {NPC_LIVE_TRAINEE, 2}}, + 15}, + { + {{NPC_LIVE_TRAINEE, 2}}, + 0} +}; - private: - bool _onLiveSide; +const GothikWaveData waves25 = +{ + { + {{NPC_LIVE_TRAINEE, 3}}, + 20}, + { + {{NPC_LIVE_TRAINEE, 3}}, + 20}, + { + {{NPC_LIVE_TRAINEE, 3}}, + 10}, + { + {{NPC_LIVE_KNIGHT, 2}}, + 10}, + { + {{NPC_LIVE_TRAINEE, 3}}, + 15}, + { + {{NPC_LIVE_KNIGHT, 2}}, + 5}, + { + {{NPC_LIVE_TRAINEE, 3}}, + 20}, + { + {{NPC_LIVE_TRAINEE, 3}, {NPC_LIVE_KNIGHT, 2}}, + 10}, + { + {{NPC_LIVE_TRAINEE, 3}}, + 10}, + { + {{NPC_LIVE_RIDER, 1}}, + 5}, + { + {{NPC_LIVE_TRAINEE, 3}}, + 15}, + { + {{NPC_LIVE_RIDER, 1}}, + 10}, + { + {{NPC_LIVE_KNIGHT, 2}}, + 10}, + { + {{NPC_LIVE_RIDER, 1}}, + 10}, + { + {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_TRAINEE, 3}}, + 5}, + { + {{NPC_LIVE_KNIGHT, 1}, {NPC_LIVE_TRAINEE, 3}}, + 5}, + { + {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_TRAINEE, 3}}, + 20}, + { + {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_KNIGHT, 2}, {NPC_LIVE_TRAINEE, 3}}, + 0} }; + +// GUID of first trigger NPC (used as offset for guid checks) +// 0-1 are living side soul triggers, 2-3 are spectral side soul triggers, 4 is living rider spawn trigger, 5-7 are living other spawn trigger, 8-12 are skull pile triggers +const uint32 CGUID_TRIGGER = 127618; +/* Creature AI */ class boss_gothik : public CreatureScript { public: @@ -165,29 +302,18 @@ class boss_gothik : public CreatureScript boss_gothikAI(Creature* creature) : BossAI(creature, BOSS_GOTHIK) { Initialize(); - waveCount = 0; } void Initialize() { - mergedSides = false; - phaseTwo = false; - thirtyPercentReached = false; + _waveCount = 0; + _gateCanOpen = false; + _gateIsOpen = true; + _lastTeleportDead = false; } - uint32 waveCount; - bool mergedSides; - bool phaseTwo; - bool thirtyPercentReached; - - GuidVector LiveTriggerGUID; - GuidVector DeadTriggerGUID; - void Reset() override { - LiveTriggerGUID.clear(); - DeadTriggerGUID.clear(); - me->SetReactState(REACT_PASSIVE); instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE); _Reset(); @@ -196,43 +322,29 @@ class boss_gothik : public CreatureScript void EnterCombat(Unit* /*who*/) override { - for (uint32 i = 0; i < POS_LIVE; ++i) - if (Creature* trigger = DoSummon(WORLD_TRIGGER, PosSummonLive[i])) - LiveTriggerGUID.push_back(trigger->GetGUID()); - for (uint32 i = 0; i < POS_DEAD; ++i) - if (Creature* trigger = DoSummon(WORLD_TRIGGER, PosSummonDead[i])) - DeadTriggerGUID.push_back(trigger->GetGUID()); - - if (LiveTriggerGUID.size() < POS_LIVE || DeadTriggerGUID.size() < POS_DEAD) - { - TC_LOG_ERROR("scripts", "Script Gothik: cannot summon triggers!"); - EnterEvadeMode(); - return; - } - _EnterCombat(); - waveCount = 0; - events.ScheduleEvent(EVENT_SUMMON, 30000); - DoTeleportTo(PosPlatform); - Talk(SAY_SPEECH); + events.SetPhase(PHASE_ONE); + events.ScheduleEvent(EVENT_SUMMON, 25 * IN_MILLISECONDS, 0, PHASE_ONE); + events.ScheduleEvent(EVENT_DOORS_UNLOCK, 205 * IN_MILLISECONDS, 0, PHASE_ONE); + events.ScheduleEvent(EVENT_PHASE_TWO, 270 * IN_MILLISECONDS, 0, PHASE_ONE); + Talk(SAY_INTRO_1); + events.ScheduleEvent(EVENT_INTRO_2, 4 * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_INTRO_3, 9 * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_INTRO_4, 14 * IN_MILLISECONDS); instance->SetData(DATA_GOTHIK_GATE, GO_STATE_READY); + _gateIsOpen = false; } void JustSummoned(Creature* summon) override { - if (summon->GetEntry() == WORLD_TRIGGER) - summon->setActive(true); - else if (!mergedSides) + summons.Summon(summon); + if (me->IsInCombat()) { - summon->AI()->DoAction(me->HasReactState(REACT_PASSIVE) ? 1 : 0); - summon->AI()->EnterEvadeMode(); + summon->AI()->DoAction(_gateIsOpen ? ACTION_GATE_OPENED : ACTION_ACQUIRE_TARGET); + summon->SetCombatPulseDelay(5); } else - { - summon->AI()->DoAction(0); - summon->AI()->DoZoneInCombat(); - } - summons.Summon(summon); + summon->DespawnOrUnsummon(); } void SummonedCreatureDespawn(Creature* summon) override @@ -240,256 +352,202 @@ class boss_gothik : public CreatureScript summons.Despawn(summon); } - void KilledUnit(Unit* /*victim*/) override + void KilledUnit(Unit* victim) override { - if (!(rand32() % 5)) + if (victim && victim->GetTypeId() == TYPEID_PLAYER) Talk(SAY_KILL); } void JustDied(Unit* /*killer*/) override { - LiveTriggerGUID.clear(); - DeadTriggerGUID.clear(); _JustDied(); Talk(SAY_DEATH); instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE); + _gateIsOpen = false; } - void DoGothikSummon(uint32 entry) + void OpenGate() { - if (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL) - { - switch (entry) - { - case NPC_LIVE_TRAINEE: - { - if (Creature* liveTrigger = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[0])) - DoSummon(NPC_LIVE_TRAINEE, liveTrigger, 1); - if (Creature* liveTrigger1 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[1])) - DoSummon(NPC_LIVE_TRAINEE, liveTrigger1, 1); - if (Creature* liveTrigger2 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[2])) - DoSummon(NPC_LIVE_TRAINEE, liveTrigger2, 1); - break; - } - case NPC_LIVE_KNIGHT: - { - if (Creature* liveTrigger3 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[3])) - DoSummon(NPC_LIVE_KNIGHT, liveTrigger3, 1); - if (Creature* liveTrigger5 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[5])) - DoSummon(NPC_LIVE_KNIGHT, liveTrigger5, 1); - break; - } - case NPC_LIVE_RIDER: - { - if (Creature* liveTrigger4 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) - DoSummon(NPC_LIVE_RIDER, liveTrigger4, 1); - break; - } - } - } - else - { - switch (entry) - { - case NPC_LIVE_TRAINEE: - { - if (Creature* liveTrigger = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) - DoSummon(NPC_LIVE_TRAINEE, liveTrigger, 1); - if (Creature* liveTrigger2 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) - DoSummon(NPC_LIVE_TRAINEE, liveTrigger2, 1); - break; - } - case NPC_LIVE_KNIGHT: - { - if (Creature* liveTrigger5 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) - DoSummon(NPC_LIVE_KNIGHT, liveTrigger5, 1); - break; - } - case NPC_LIVE_RIDER: - { - if (Creature* liveTrigger4 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) - DoSummon(NPC_LIVE_RIDER, liveTrigger4, 1); - break; - } - } - } - } - - bool CheckGroupSplitted() - { - bool checklife = false; - bool checkdead = false; - Map::PlayerList const &PlayerList = me->GetMap()->GetPlayers(); - for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i) - { - if (i->GetSource() && i->GetSource()->IsAlive() && - i->GetSource()->GetPositionX() <= POS_X_NORTH && - i->GetSource()->GetPositionX() >= POS_X_SOUTH && - i->GetSource()->GetPositionY() <= POS_Y_GATE && - i->GetSource()->GetPositionY() >= POS_Y_EAST) - { - checklife = true; - } - else if (i->GetSource() && i->GetSource()->IsAlive() && - i->GetSource()->GetPositionX() <= POS_X_NORTH && - i->GetSource()->GetPositionX() >= POS_X_SOUTH && - i->GetSource()->GetPositionY() >= POS_Y_GATE && - i->GetSource()->GetPositionY() <= POS_Y_WEST) - { - checkdead = true; - } - - if (checklife && checkdead) - return true; - } - - return false; - } - - void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override - { - uint32 spellId = 0; - switch (spell->Id) - { - case SPELL_INFORM_LIVE_TRAINEE: spellId = SPELL_INFORM_DEAD_TRAINEE; break; - case SPELL_INFORM_LIVE_KNIGHT: spellId = SPELL_INFORM_DEAD_KNIGHT; break; - case SPELL_INFORM_LIVE_RIDER: spellId = SPELL_INFORM_DEAD_RIDER; break; - } - if (spellId && me->IsInCombat()) - { - me->HandleEmoteCommand(EMOTE_ONESHOT_SPELL_CAST); - if (Creature* pRandomDeadTrigger = ObjectAccessor::GetCreature(*me, DeadTriggerGUID[rand32() % POS_DEAD])) - me->CastSpell(pRandomDeadTrigger, spellId, true); - } + if (_gateIsOpen) + return; + instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE); + Talk(EMOTE_GATE_OPENED); + _gateIsOpen = true; + + for (ObjectGuid summonGuid : summons) + if (Creature* summon = ObjectAccessor::GetCreature(*me, summonGuid)) + summon->AI()->DoAction(ACTION_GATE_OPENED); } void DamageTaken(Unit* /*who*/, uint32& damage) override { - if (!phaseTwo) + if (!events.IsInPhase(PHASE_TWO)) damage = 0; } - void SpellHitTarget(Unit* target, SpellInfo const* spell) override + void DoAction(int32 action) override { - if (!me->IsInCombat()) - return; - - switch (spell->Id) + switch (action) { - case SPELL_INFORM_DEAD_TRAINEE: - DoSummon(NPC_DEAD_TRAINEE, target, 0); - break; - case SPELL_INFORM_DEAD_KNIGHT: - DoSummon(NPC_DEAD_KNIGHT, target, 0); - break; - case SPELL_INFORM_DEAD_RIDER: - DoSummon(NPC_DEAD_RIDER, target, 1.0f); - DoSummon(NPC_DEAD_HORSE, target, 1.0f); + case ACTION_MINION_EVADE: + if (_gateIsOpen || me->getThreatManager().isThreatListEmpty()) + return EnterEvadeMode(EVADE_REASON_NO_HOSTILES); + if (_gateCanOpen) + OpenGate(); break; } } + void EnterEvadeMode(EvadeReason why) override + { + BossAI::EnterEvadeMode(why); + Position const& home = me->GetHomePosition(); + me->NearTeleportTo(home.GetPositionX(), home.GetPositionY(), home.GetPositionZ(), home.GetOrientation()); + } + void UpdateAI(uint32 diff) override { if (!UpdateVictim()) return; - events.Update(diff); - - if (!thirtyPercentReached && HealthBelowPct(30) && phaseTwo) + if (me->HasReactState(REACT_AGGRESSIVE) && !_gateIsOpen && !IsOnSameSide(me, me->GetVictim())) { - thirtyPercentReached = true; - instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE); + // NBD: this should only happen in practice if there is nobody left alive on our side (we should open gate) + // thus we only do a cursory check to make sure (edge cases?) + if (Player* newTarget = FindEligibleTarget(me, _gateIsOpen)) + { + me->getThreatManager().resetAllAggro(); + me->AddThreat(newTarget, 1.0f); + AttackStart(newTarget); + } + else + OpenGate(); } - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; + events.Update(diff); + + if (!_gateIsOpen && HealthBelowPct(30) && events.IsInPhase(PHASE_TWO)) + OpenGate(); while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { case EVENT_SUMMON: - if (waves[waveCount].entry) + { + if (RAID_MODE(waves10,waves25).size() <= _waveCount) // bounds check { - if ((waves[waveCount].mode == 2) && (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL)) - DoGothikSummon(waves[waveCount].entry); - else if ((waves[waveCount].mode == 0) && (GetDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL)) - DoGothikSummon(waves[waveCount].entry); - else if (waves[waveCount].mode == 1) - DoGothikSummon(waves[waveCount].entry); - - // if group is not splitted, open gate and merge both sides at ~ 2 minutes (wave 11) - if (waveCount == 11) + TC_LOG_INFO("scripts", "GothikAI: Wave count %d is out of range for difficulty %d.", _waveCount, GetDifficulty()); + break; + } + + std::list<Creature*> triggers; + me->GetCreatureListWithEntryInGrid(triggers, NPC_TRIGGER, 150.0f); + for (GothikWaveEntry entry : RAID_MODE(waves10, waves25)[_waveCount].first) + for (uint8 i = 0; i < entry.second; ++i) { - if (!CheckGroupSplitted()) + // GUID layout is as follows: + // CGUID+4: center (back of platform) - primary rider spawn + // CGUID+5: north (back of platform) - primary knight spawn + // CGUID+6: center (front of platform) - second spawn + // CGUID+7: south (front of platform) - primary trainee spawn + uint32 targetDBGuid; + switch (entry.first) { - instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE); - DummyEntryCheckPredicate pred; - summons.DoAction(0, pred); //! Magic numbers fail - summons.DoZoneInCombat(); - mergedSides = true; + case NPC_LIVE_RIDER: // only spawns from center (back) > north + targetDBGuid = (CGUID_TRIGGER + 4) + (i % 2); + break; + case NPC_LIVE_KNIGHT: // spawns north > center (front) > south + targetDBGuid = (CGUID_TRIGGER + 5) + (i % 3); + break; + case NPC_LIVE_TRAINEE: // spawns south > center (front) > north + targetDBGuid = (CGUID_TRIGGER + 7) - (i % 3); + break; } + + for (Creature* trigger : triggers) + if (trigger && trigger->GetSpawnId() == targetDBGuid) + { + DoSummon(entry.first, trigger, 1.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); + break; + } } - if (waves[waveCount].mode == 1) - events.ScheduleEvent(EVENT_SUMMON, waves[waveCount].time); - else if ((waves[waveCount].mode == 2) && (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL)) - events.ScheduleEvent(EVENT_SUMMON, waves[waveCount].time); - else if ((waves[waveCount].mode == 0) && (GetDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL)) - events.ScheduleEvent(EVENT_SUMMON, waves[waveCount].time); - else - events.ScheduleEvent(EVENT_SUMMON, 0); + if (uint8 timeToNext = RAID_MODE(waves10, waves25)[_waveCount].second) + events.ScheduleEvent(EVENT_SUMMON, timeToNext * IN_MILLISECONDS, 0, PHASE_ONE); - ++waveCount; - } - else - { - phaseTwo = true; - Talk(SAY_TELEPORT); - DoTeleportTo(PosGroundLiveSide); - me->SetReactState(REACT_AGGRESSIVE); - DummyEntryCheckPredicate pred; - summons.DoAction(0, pred); //! Magic numbers fail - summons.DoZoneInCombat(); - events.ScheduleEvent(EVENT_BOLT, 1000); - events.ScheduleEvent(EVENT_HARVEST, urand(3000, 15000)); - events.ScheduleEvent(EVENT_TELEPORT, 20000); - } + ++_waveCount; break; - case EVENT_BOLT: - DoCastVictim(SPELL_SHADOW_BOLT); - events.ScheduleEvent(EVENT_BOLT, 1000); + } + case EVENT_DOORS_UNLOCK: + _gateCanOpen = true; + for (ObjectGuid summonGuid : summons) + if (Creature* summon = ObjectAccessor::GetCreature(*me, summonGuid)) + if (summon->IsAlive() && (!summon->IsInCombat() || summon->IsInEvadeMode())) + { + OpenGate(); + break; + } break; - case EVENT_HARVEST: - DoCastVictim(SPELL_HARVEST_SOUL, true); - events.ScheduleEvent(EVENT_HARVEST, urand(20000, 25000)); + case EVENT_PHASE_TWO: + events.SetPhase(PHASE_TWO); + events.ScheduleEvent(EVENT_TELEPORT, 20 * IN_MILLISECONDS, 0, PHASE_TWO); + events.ScheduleEvent(EVENT_HARVEST, 15 * IN_MILLISECONDS, 0, PHASE_TWO); + events.ScheduleEvent(EVENT_RESUME_ATTACK, 2 * IN_MILLISECONDS, 0, PHASE_TWO); + Talk(SAY_PHASE_TWO); + Talk(EMOTE_PHASE_TWO); + me->SetReactState(REACT_PASSIVE); + me->getThreatManager().resetAllAggro(); + DoCastAOE(SPELL_TELEPORT_LIVE); break; case EVENT_TELEPORT: - if (!thirtyPercentReached) + if (!HealthBelowPct(30)) { + me->CastStop(); me->AttackStop(); - if (IN_LIVE_SIDE(me)) - DoTeleportTo(PosGroundDeadSide); - else - DoTeleportTo(PosGroundLiveSide); - - me->getThreatManager().resetAggro(NotOnSameSide(me)); - if (Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0)) - { - me->getThreatManager().addThreat(target, 100.0f); - AttackStart(target); - } - - events.ScheduleEvent(EVENT_TELEPORT, 20000); + me->StopMoving(); + me->SetReactState(REACT_PASSIVE); + me->getThreatManager().resetAllAggro(); + DoCastAOE(_lastTeleportDead ? SPELL_TELEPORT_LIVE : SPELL_TELEPORT_DEAD); + _lastTeleportDead = !_lastTeleportDead; + + events.CancelEvent(EVENT_BOLT); + events.ScheduleEvent(EVENT_TELEPORT, 20 * IN_MILLISECONDS, 0, PHASE_TWO); + events.ScheduleEvent(EVENT_RESUME_ATTACK, 2 * IN_MILLISECONDS, 0, PHASE_TWO); } break; + + case EVENT_HARVEST: + DoCastAOE(SPELL_HARVEST_SOUL, true); // triggered allows this to go "through" shadow bolt + events.ScheduleEvent(EVENT_HARVEST, 15 * IN_MILLISECONDS, 0, PHASE_TWO); + break; + case EVENT_RESUME_ATTACK: + me->SetReactState(REACT_AGGRESSIVE); + events.ScheduleEvent(EVENT_BOLT, 0, 0, PHASE_TWO); + // return to the start of this method so victim side etc is re-evaluated + return UpdateAI(0u); // tail recursion for efficiency + case EVENT_BOLT: + DoCastVictim(SPELL_SHADOW_BOLT); + events.ScheduleEvent(EVENT_BOLT, 1 * IN_MILLISECONDS, 0, PHASE_TWO); + break; + case EVENT_INTRO_2: + Talk(SAY_INTRO_2); + break; + case EVENT_INTRO_3: + Talk(SAY_INTRO_3); + break; + case EVENT_INTRO_4: + Talk(SAY_INTRO_4); + break; } } - - if (!phaseTwo) - DoMeleeAttackIfReady(); } + + private: + uint32 _waveCount; + bool _gateCanOpen; + bool _gateIsOpen; + bool _lastTeleportDead; }; CreatureAI* GetAI(Creature* creature) const override @@ -498,86 +556,407 @@ class boss_gothik : public CreatureScript } }; -class npc_gothik_minion : public CreatureScript +struct npc_gothik_minion_baseAI : public ScriptedAI { public: - npc_gothik_minion() : CreatureScript("npc_gothik_minion") { } + npc_gothik_minion_baseAI(Creature* creature, uint32 deathNotify=0) : ScriptedAI(creature), _deathNotify(deathNotify), _gateIsOpen(false) { } + + void JustDied(Unit* /*killer*/) override + { + if (_deathNotify) + DoCastAOE(_deathNotify, true); + } + + inline bool isOnSameSide(Unit const* who) const + { + return IsOnSameSide(me, who); + } - struct npc_gothik_minionAI : public CombatAI + void DamageTaken(Unit* attacker, uint32 &damage) override + { // do not allow minions to take damage before the gate is opened + if (!_gateIsOpen && !isOnSameSide(attacker)) + damage = 0; + } + + void DoAction(int32 action) override { - npc_gothik_minionAI(Creature* creature) : CombatAI(creature) + switch (action) { - liveSide = IN_LIVE_SIDE(me); - gateClose = false; + case ACTION_GATE_OPENED: + _gateIsOpen = true; + // intentional missing break + case ACTION_ACQUIRE_TARGET: + if (Player* target = FindEligibleTarget(me, _gateIsOpen)) + { + me->AddThreat(target, 1.0f); + AttackStart(target); + } + else + EnterEvadeMode(EVADE_REASON_NO_HOSTILES); + break; + } + } + + void EnterEvadeMode(EvadeReason why) override + { + ScriptedAI::EnterEvadeMode(why); + + if (InstanceScript* instance = me->GetInstanceScript()) + if (Creature* gothik = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_GOTHIK))) + gothik->AI()->DoAction(ACTION_MINION_EVADE); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + if (!_gateIsOpen && !isOnSameSide(me->GetVictim())) + { // reset threat, then try to find someone on same side as us to attack + if (Player* newTarget = FindEligibleTarget(me, _gateIsOpen)) + { + me->RemoveAurasByType(SPELL_AURA_MOD_TAUNT); + me->getThreatManager().resetAllAggro(); + me->AddThreat(newTarget, 1.0f); + AttackStart(newTarget); + } + else + EnterEvadeMode(EVADE_REASON_NO_HOSTILES); } - bool liveSide; - bool gateClose; + _UpdateAI(diff); + } + + virtual void _UpdateAI(uint32 diff) { ScriptedAI::UpdateAI(diff); }; - bool isOnSameSide(Unit const* who) const + private: + uint32 _deathNotify; + bool _gateIsOpen; +}; + +class npc_gothik_minion_livingtrainee : public CreatureScript +{ + public: + npc_gothik_minion_livingtrainee() : CreatureScript("npc_gothik_minion_livingtrainee") { } + + struct npc_gothik_minion_livingtraineeAI : public npc_gothik_minion_baseAI + { + npc_gothik_minion_livingtraineeAI(Creature* creature) : npc_gothik_minion_baseAI(creature, SPELL_ANCHOR_1_TRAINEE), _deathPlagueTimer(urandms(5,20)) { } + + void _UpdateAI(uint32 diff) { - return (liveSide == IN_LIVE_SIDE(who)); + if (diff < _deathPlagueTimer) + _deathPlagueTimer -= diff; + else + { + DoCastAOE(SPELL_DEATH_PLAGUE); + _deathPlagueTimer = urandms(5, 20); + } + DoMeleeAttackIfReady(); } + uint32 _deathPlagueTimer; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_gothik_minion_livingtraineeAI>(creature); + } +}; + +class npc_gothik_minion_livingknight : public CreatureScript +{ + public: + npc_gothik_minion_livingknight() : CreatureScript("npc_gothik_minion_livingknight") { } - void DoAction(int32 param) override + struct npc_gothik_minion_livingknightAI : public npc_gothik_minion_baseAI + { + npc_gothik_minion_livingknightAI(Creature* creature) : npc_gothik_minion_baseAI(creature, SPELL_ANCHOR_1_DK), _whirlwindTimer(urandms(5,10)) { } + + void _UpdateAI(uint32 diff) { - gateClose = param != 0; + if (diff < _whirlwindTimer) + _whirlwindTimer -= diff; + else + { + DoCastAOE(SPELL_SHADOW_MARK); + _whirlwindTimer = urandms(15, 20); + } + DoMeleeAttackIfReady(); } + uint32 _whirlwindTimer; + }; - void DamageTaken(Unit* attacker, uint32 &damage) override + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_gothik_minion_livingknightAI>(creature); + } +}; + +class npc_gothik_minion_livingrider : public CreatureScript +{ + public: + npc_gothik_minion_livingrider() : CreatureScript("npc_gothik_minion_livingrider") { } + + struct npc_gothik_minion_livingriderAI : public npc_gothik_minion_baseAI + { + npc_gothik_minion_livingriderAI(Creature* creature) : npc_gothik_minion_baseAI(creature, SPELL_ANCHOR_1_RIDER), _boltVolleyTimer(urandms(5,10)) { } + + void _UpdateAI(uint32 diff) { - if (gateClose && !isOnSameSide(attacker)) - damage = 0; + if (diff < _boltVolleyTimer) + _boltVolleyTimer -= diff; + else + { + DoCastAOE(SPELL_SHADOW_BOLT_VOLLEY); + _boltVolleyTimer = urandms(10, 15); + } + if (!me->HasUnitState(UNIT_STATE_CASTING)) + DoMeleeAttackIfReady(); } + uint32 _boltVolleyTimer; + }; - void JustDied(Unit* /*killer*/) override + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_gothik_minion_livingriderAI>(creature); + } +}; + +class npc_gothik_minion_spectraltrainee : public CreatureScript +{ + public: + npc_gothik_minion_spectraltrainee() : CreatureScript("npc_gothik_minion_spectraltrainee") { } + + struct npc_gothik_minion_spectraltraineeAI : public npc_gothik_minion_baseAI + { + npc_gothik_minion_spectraltraineeAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _explosionTimer(2 * IN_MILLISECONDS) { } + + void _UpdateAI(uint32 diff) + { + if (diff < _explosionTimer) + _explosionTimer -= diff; + else { - if (me->IsSummon()) - if (Unit* owner = me->ToTempSummon()->GetSummoner()) - CombatAI::JustDied(owner); + DoCastAOE(SPELL_ARCANE_EXPLOSION); + _explosionTimer = 2 * IN_MILLISECONDS; } + DoMeleeAttackIfReady(); + } + uint32 _explosionTimer; + }; - void EnterEvadeMode(EvadeReason why) override + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_gothik_minion_spectraltraineeAI>(creature); + } +}; + +class npc_gothik_minion_spectralknight : public CreatureScript +{ + public: + npc_gothik_minion_spectralknight() : CreatureScript("npc_gothik_minion_spectralknight") { } + + struct npc_gothik_minion_spectralknightAI : public npc_gothik_minion_baseAI + { + npc_gothik_minion_spectralknightAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _whirlwindTimer(urandms(15,25)) { } + + void _UpdateAI(uint32 diff) + { + if (diff < _whirlwindTimer) + _whirlwindTimer -= diff; + else { - if (!gateClose) - { - CombatAI::EnterEvadeMode(why); - return; - } + DoCastAOE(SPELL_WHIRLWIND); + _whirlwindTimer = urandms(20, 25); + } + DoMeleeAttackIfReady(); + } + uint32 _whirlwindTimer; + }; - if (!_EnterEvadeMode()) - return; + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_gothik_minion_spectralknightAI>(creature); + } +}; + +class npc_gothik_minion_spectralrider : public CreatureScript +{ + public: + npc_gothik_minion_spectralrider() : CreatureScript("npc_gothik_minion_spectralrider") { } + + struct npc_gothik_minion_spectralriderAI : public npc_gothik_minion_baseAI + { + npc_gothik_minion_spectralriderAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _frenzyTimer(urandms(2,5)), _drainTimer(urandms(8,12)) { } - Map::PlayerList const &PlayerList = me->GetMap()->GetPlayers(); - for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i) + void _UpdateAI(uint32 diff) + { + if (diff < _frenzyTimer) + _frenzyTimer -= diff; + else if (me->HasUnitState(UNIT_STATE_CASTING)) + _frenzyTimer = 0; + else + { // target priority: knight > other rider > horse > gothik + std::list<Creature*> potentialTargets = DoFindFriendlyMissingBuff(30.0, SPELLHELPER_UNHOLY_FRENZY); + Creature *knightTarget = nullptr, *riderTarget = nullptr, *horseTarget = nullptr, *gothikTarget = nullptr; + for (Creature* pTarget : potentialTargets) { - if (i->GetSource() && i->GetSource()->IsAlive() && isOnSameSide(i->GetSource())) + switch (pTarget->GetEntry()) { - AttackStart(i->GetSource()); - return; + case NPC_DEAD_KNIGHT: + knightTarget = pTarget; + break; + case NPC_DEAD_RIDER: + riderTarget = pTarget; + break; + case NPC_DEAD_HORSE: + horseTarget = pTarget; + break; + case NPC_GOTHIK: + gothikTarget = pTarget; + break; } + if (knightTarget) + break; } + Creature* target = knightTarget ? knightTarget : riderTarget ? riderTarget : horseTarget ? horseTarget : gothikTarget ? gothikTarget : nullptr; + if (target) + DoCast(target, SPELL_UNHOLY_FRENZY); + _frenzyTimer = 20 * IN_MILLISECONDS; + } - me->GetMotionMaster()->MoveIdle(); - Reset(); + if (diff < _drainTimer) + _drainTimer -= diff; + else + { + DoCastVictim(SPELL_DRAIN_LIFE); + _drainTimer = urandms(10,15); } - void UpdateAI(uint32 diff) override + if (!me->HasUnitState(UNIT_STATE_CASTING)) + DoMeleeAttackIfReady(); + } + uint32 _frenzyTimer, _drainTimer; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_gothik_minion_spectralriderAI>(creature); + } +}; + +class npc_gothik_minion_spectralhorse : public CreatureScript +{ + public: + npc_gothik_minion_spectralhorse() : CreatureScript("npc_gothik_minion_spectralhorse") { } + + struct npc_gothik_minion_spectralhorseAI : public npc_gothik_minion_baseAI + { + npc_gothik_minion_spectralhorseAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _stompTimer(urandms(10,15)) { } + + void _UpdateAI(uint32 diff) + { + if (diff < _stompTimer) + _stompTimer -= diff; + else { - if (gateClose && (!isOnSameSide(me) || (me->GetVictim() && !isOnSameSide(me->GetVictim())))) - { - EnterEvadeMode(EVADE_REASON_OTHER); - return; - } + DoCastAOE(SPELL_STOMP); + _stompTimer = urandms(14, 18); + } + DoMeleeAttackIfReady(); + } + uint32 _stompTimer; + }; - CombatAI::UpdateAI(diff); + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_gothik_minion_spectralhorseAI>(creature); + } +}; + +class npc_gothik_trigger : public CreatureScript +{ +public: + npc_gothik_trigger() : CreatureScript("npc_gothik_trigger") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_gothik_triggerAI>(creature); + } + + struct npc_gothik_triggerAI : public ScriptedAI + { + npc_gothik_triggerAI(Creature* creature) : ScriptedAI(creature) { creature->SetDisableGravity(true); } + + void EnterEvadeMode(EvadeReason /*why*/) override { } + void UpdateAI(uint32 /*diff*/) override { } + void EnterCombat(Unit* /*who*/) override { } + void DamageTaken(Unit* /*who*/, uint32& damage) override { damage = 0; } + + Creature* SelectRandomSkullPile() + { + std::list<Creature*> triggers; + me->GetCreatureListWithEntryInGrid(triggers, NPC_TRIGGER, 150.0f); + uint32 targetDBGuid = CGUID_TRIGGER + urand(8, 12); // CGUID+8 to CGUID+12 are the triggers for the skull piles on dead side + for (Creature* trigger : triggers) + if (trigger && trigger->GetSpawnId() == targetDBGuid) + return trigger; + + return nullptr; + } + void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override + { + if (!spell) + return; + + switch (spell->Id) + { + case SPELL_ANCHOR_1_TRAINEE: + DoCastAOE(SPELL_ANCHOR_2_TRAINEE, true); + break; + case SPELL_ANCHOR_1_DK: + DoCastAOE(SPELL_ANCHOR_2_DK, true); + break; + case SPELL_ANCHOR_1_RIDER: + DoCastAOE(SPELL_ANCHOR_2_RIDER, true); + break; + case SPELL_ANCHOR_2_TRAINEE: + if (Creature* target = SelectRandomSkullPile()) + DoCast(target, SPELL_SKULLS_TRAINEE, true); + break; + case SPELL_ANCHOR_2_DK: + if (Creature* target = SelectRandomSkullPile()) + DoCast(target, SPELL_SKULLS_DK, true); + break; + case SPELL_ANCHOR_2_RIDER: + if (Creature* target = SelectRandomSkullPile()) + DoCast(target, SPELL_SKULLS_RIDER, true); + break; + case SPELL_SKULLS_TRAINEE: + DoSummon(NPC_DEAD_TRAINEE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); + break; + case SPELL_SKULLS_DK: + DoSummon(NPC_DEAD_KNIGHT, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); + break; + case SPELL_SKULLS_RIDER: + DoSummon(NPC_DEAD_RIDER, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); + DoSummon(NPC_DEAD_HORSE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); + break; } - }; + } - CreatureAI* GetAI(Creature* creature) const override + // dead side summons are "owned" by gothik + void JustSummoned(Creature* summon) override + { + if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK))) + gothik->AI()->JustSummoned(summon); + } + void SummonedCreatureDespawn(Creature* summon) override { - return new npc_gothik_minionAI(creature); + if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK))) + gothik->AI()->SummonedCreatureDespawn(summon); } + }; }; class spell_gothik_shadow_bolt_volley : public SpellScriptLoader @@ -609,6 +988,13 @@ class spell_gothik_shadow_bolt_volley : public SpellScriptLoader void AddSC_boss_gothik() { new boss_gothik(); - new npc_gothik_minion(); + new npc_gothik_minion_livingtrainee(); + new npc_gothik_minion_livingknight(); + new npc_gothik_minion_livingrider(); + new npc_gothik_minion_spectraltrainee(); + new npc_gothik_minion_spectralknight(); + new npc_gothik_minion_spectralrider(); + new npc_gothik_minion_spectralhorse(); + new npc_gothik_trigger(); new spell_gothik_shadow_bolt_volley(); } diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp index 35557b2301b..c8a4eb7fbc8 100644 --- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp +++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp @@ -167,6 +167,9 @@ class instance_naxxramas : public InstanceMapScript case NPC_RAZUVIOUS: RazuviousGUID = creature->GetGUID(); break; + case NPC_GOTHIK: + GothikGUID = creature->GetGUID(); + break; case NPC_THANE: ThaneGUID = creature->GetGUID(); break; @@ -378,6 +381,8 @@ class instance_naxxramas : public InstanceMapScript return FaerlinaGUID; case DATA_RAZUVIOUS: return RazuviousGUID; + case DATA_GOTHIK: + return GothikGUID; case DATA_THANE: return ThaneGUID; case DATA_LADY: @@ -655,6 +660,7 @@ class instance_naxxramas : public InstanceMapScript // Instructor Razuvious ObjectGuid RazuviousGUID; // Gothik the Harvester + ObjectGuid GothikGUID; ObjectGuid GothikGateGUID; // The Four Horsemen ObjectGuid ThaneGUID; diff --git a/src/server/scripts/Northrend/Naxxramas/naxxramas.h b/src/server/scripts/Northrend/Naxxramas/naxxramas.h index aef8e8cd52b..c0caa86e93f 100644 --- a/src/server/scripts/Northrend/Naxxramas/naxxramas.h +++ b/src/server/scripts/Northrend/Naxxramas/naxxramas.h @@ -64,6 +64,7 @@ enum Data64 DATA_ANUBREKHAN, DATA_FAERLINA, DATA_RAZUVIOUS, + DATA_GOTHIK, DATA_THANE, DATA_LADY, DATA_BARON, @@ -86,6 +87,7 @@ enum CreaturesIds NPC_ANUBREKHAN = 15956, NPC_FAERLINA = 15953, NPC_RAZUVIOUS = 16061, + NPC_GOTHIK = 16060, NPC_THANE = 16064, NPC_LADY = 16065, NPC_BARON = 30549, diff --git a/src/server/scripts/Outland/zone_zangarmarsh.cpp b/src/server/scripts/Outland/zone_zangarmarsh.cpp index 48bbb7bad4a..6f38cce0e5b 100644 --- a/src/server/scripts/Outland/zone_zangarmarsh.cpp +++ b/src/server/scripts/Outland/zone_zangarmarsh.cpp @@ -48,8 +48,6 @@ EndContentData */ enum AshyenAndKeleth { - GOSSIP_REWARD_BLESS = 0, - NPC_ASHYEN = 17900, NPC_KELETH = 17901, @@ -117,7 +115,6 @@ public: if (spell) { creature->CastSpell(player, spell, true); - creature->AI()->Talk(GOSSIP_REWARD_BLESS); } } @@ -145,7 +142,6 @@ public: if (spell) { creature->CastSpell(player, spell, true); - creature->AI()->Talk(GOSSIP_REWARD_BLESS); } } player->CLOSE_GOSSIP_MENU(); diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp index 9f6b4947328..13e7697910b 100644 --- a/src/server/scripts/Spells/spell_paladin.cpp +++ b/src/server/scripts/Spells/spell_paladin.cpp @@ -43,6 +43,12 @@ enum PaladinSpells SPELL_PALADIN_BLESSING_OF_LOWER_CITY_PRIEST = 37880, SPELL_PALADIN_BLESSING_OF_LOWER_CITY_SHAMAN = 37881, + SPELL_PALADIN_BEACON_OF_LIGHT = 53563, + SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1 = 53652, + SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_2 = 53653, + SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3 = 53654, + SPELL_PALADIN_HOLY_LIGHT = 635, + SPELL_PALADIN_DIVINE_STORM = 53385, SPELL_PALADIN_DIVINE_STORM_DUMMY = 54171, SPELL_PALADIN_DIVINE_STORM_HEAL = 54172, @@ -1153,6 +1159,68 @@ class spell_pal_lay_on_hands : public SpellScriptLoader } }; +// 53651 - Light's Beacon - Beacon of Light +class spell_pal_light_s_beacon : public SpellScriptLoader +{ + public: + spell_pal_light_s_beacon() : SpellScriptLoader("spell_pal_light_s_beacon") { } + + class spell_pal_light_s_beacon_AuraScript : public AuraScript + { + PrepareAuraScript(spell_pal_light_s_beacon_AuraScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT) + || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1) + || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_2) + || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3) + || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_HOLY_LIGHT)) + return false; + return true; + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (GetTarget()->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT, eventInfo.GetActor()->GetGUID())) + return false; + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + if (!procSpell) + return; + + uint32 healSpellId = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(SPELL_PALADIN_HOLY_LIGHT)) ? SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1 : SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3; + uint32 heal = CalculatePct(eventInfo.GetHealInfo()->GetHeal(), aurEff->GetAmount()); + + Unit* beaconTarget = GetCaster(); + if (!beaconTarget || !beaconTarget->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT, eventInfo.GetActor()->GetGUID())) + return; + + /// @todo: caster must be the healed unit to perform distance checks correctly + /// but that will break animation on clientside + /// caster in spell packets must be the healing unit + eventInfo.GetActor()->CastCustomSpell(healSpellId, SPELLVALUE_BASE_POINT0, heal, beaconTarget, true); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pal_light_s_beacon_AuraScript::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pal_light_s_beacon_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_pal_light_s_beacon_AuraScript(); + } +}; + // 31789 - Righteous Defense class spell_pal_righteous_defense : public SpellScriptLoader { @@ -1338,6 +1406,7 @@ void AddSC_paladin_spell_scripts() new spell_pal_judgement("spell_pal_judgement_of_wisdom", SPELL_PALADIN_JUDGEMENT_OF_WISDOM); new spell_pal_judgement_of_command(); new spell_pal_lay_on_hands(); + new spell_pal_light_s_beacon(); new spell_pal_righteous_defense(); new spell_pal_sacred_shield(); new spell_pal_seal_of_righteousness(); |