diff options
-rw-r--r-- | sql/updates/world/2015_10_10_00_world.sql | 9 | ||||
-rw-r--r-- | sql/updates/world/2015_10_10_01_world.sql | 7 | ||||
-rw-r--r-- | sql/updates/world/2015_10_10_02_world.sql | 12 | ||||
-rw-r--r-- | sql/updates/world/2015_10_10_03_world.sql | 2 | ||||
-rw-r--r-- | sql/updates/world/2015_10_10_04_world.sql | 2 | ||||
-rw-r--r-- | src/server/game/AI/CoreAI/UnitAI.cpp | 8 | ||||
-rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedCreature.cpp | 2 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartAI.cpp | 36 | ||||
-rw-r--r-- | src/server/game/Entities/Creature/Creature.cpp | 36 | ||||
-rw-r--r-- | src/server/game/Entities/Creature/Creature.h | 10 | ||||
-rw-r--r-- | src/server/game/Spells/SpellMgr.cpp | 4 | ||||
-rw-r--r-- | src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp | 147 | ||||
-rw-r--r-- | src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp | 84 | ||||
-rw-r--r-- | src/server/scripts/Northrend/Naxxramas/boss_noth.cpp | 284 | ||||
-rw-r--r-- | src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp | 14 | ||||
-rw-r--r-- | src/server/scripts/Spells/spell_generic.cpp | 2 |
16 files changed, 440 insertions, 219 deletions
diff --git a/sql/updates/world/2015_10_10_00_world.sql b/sql/updates/world/2015_10_10_00_world.sql new file mode 100644 index 00000000000..c26303006b7 --- /dev/null +++ b/sql/updates/world/2015_10_10_00_world.sql @@ -0,0 +1,9 @@ +-- +UPDATE `creature_text` SET `probability`=20 WHERE `entry`=15954 AND `groupid`=2; + +DELETE FROM `creature_text` WHERE `entry`=15954 AND `groupid` in (4,5,6,7); +INSERT INTO `creature_text` (`entry`,`groupid`,`id`,`text`,`type`,`probability`,`BroadcastTextId`,`TextRange`,`comment`) VALUES +(15954,4,0,"%s summons forth Skeletal Warriors!",41,100,32974,3,"Noth EMOTE_SUMMON"), +(15954,5,0,"%s raises more skeletons!",41,100,32977,3,"Noth EMOTE_SUMMON_WAVE"), +(15954,6,0,"%s teleports to the balcony above!",41,100,32331,3,"Noth EMOTE_TELEPORT_1"), +(15954,7,0,"%s teleports back into the battle!",41,100,32976,3,"Noth EMOTE_TELEPORT_2"); diff --git a/sql/updates/world/2015_10_10_01_world.sql b/sql/updates/world/2015_10_10_01_world.sql new file mode 100644 index 00000000000..4088a33b837 --- /dev/null +++ b/sql/updates/world/2015_10_10_01_world.sql @@ -0,0 +1,7 @@ +-- +UPDATE `creature_text` SET `probability`=20 WHERE `entry`=15936 AND `groupid`=1; + +DELETE FROM `creature_text` WHERE `entry`=15936 AND `groupid` IN (4,5); +INSERT INTO `creature_text` (`entry`,`groupid`,`id`,`text`,`type`,`probability`,`BroadcastTextId`,`TextRange`,`comment`) VALUES +(15936,4,0,"%s teleports and begins to channel a spell!",41,100,32332,3,"Heigan EMOTE_DANCE"), +(15936,5,0,"%s rushes to attack once more!",41,100,32333,3,"Heigan EMOTE_DANCE_END"); diff --git a/sql/updates/world/2015_10_10_02_world.sql b/sql/updates/world/2015_10_10_02_world.sql new file mode 100644 index 00000000000..068255d4e0c --- /dev/null +++ b/sql/updates/world/2015_10_10_02_world.sql @@ -0,0 +1,12 @@ +-- +DELETE FROM `smart_scripts` WHERE `entryorguid`=16286 AND `source_type`=0; +UPDATE `creature_template` SET `AIName`="" WHERE `entry`=16286; + +DELETE FROM `spell_script_names` WHERE `spell_id` IN (29865,55053); +INSERT INTO `spell_script_names` (`spell_id`,`ScriptName`) VALUES +(29865,"spell_loatheb_deathbloom"), +(55053,"spell_loatheb_deathbloom"); + +DELETE FROM `spelldifficulty_dbc` WHERE `id` IN (55594); +INSERT INTO `spelldifficulty_dbc` (`id`,`spellid0`,`spellid1`) VALUES +(55594,55594,55601); diff --git a/sql/updates/world/2015_10_10_03_world.sql b/sql/updates/world/2015_10_10_03_world.sql new file mode 100644 index 00000000000..57dc5a2f29b --- /dev/null +++ b/sql/updates/world/2015_10_10_03_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `locales_quest` SET `Title_loc2` = "Le nettoyage du camp des kobolds", `Title_loc3` = "Säuberung im Koboldlager", `Title_loc7` = "La Limpieza del Campamento Kóbold", `Title_loc8` = "Нападение на лагерь кобольдов" WHERE `Id` = 7; diff --git a/sql/updates/world/2015_10_10_04_world.sql b/sql/updates/world/2015_10_10_04_world.sql new file mode 100644 index 00000000000..b1bab808d96 --- /dev/null +++ b/sql/updates/world/2015_10_10_04_world.sql @@ -0,0 +1,2 @@ +-- +DELETE FROM `gossip_menu_option` WHERE `menu_id`=344 AND `id`=1; diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp index 5aa6ea8ea7a..f0d9d34db69 100644 --- a/src/server/game/AI/CoreAI/UnitAI.cpp +++ b/src/server/game/AI/CoreAI/UnitAI.cpp @@ -29,7 +29,15 @@ void UnitAI::AttackStart(Unit* victim) { if (victim && me->Attack(victim, true)) + { + // Clear distracted state on attacking + if (me->HasUnitState(UNIT_STATE_DISTRACTED)) + { + me->ClearUnitState(UNIT_STATE_DISTRACTED); + me->GetMotionMaster()->Clear(); + } me->GetMotionMaster()->MoveChase(victim); + } } void UnitAI::AttackStartCaster(Unit* victim, float dist) diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index 8e2a82dbfd3..7b145268d22 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -470,6 +470,7 @@ void BossAI::_Reset() if (!me->IsAlive()) return; + me->SetCombatPulseDelay(0); me->ResetLootMode(); events.Reset(); summons.DespawnAll(); @@ -500,6 +501,7 @@ void BossAI::_EnterCombat() instance->SetBossState(_bossId, IN_PROGRESS); } + me->SetCombatPulseDelay(5); me->setActive(true); DoZoneInCombat(); ScheduleTasks(); diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index 46122697094..46cf934356d 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -453,45 +453,15 @@ void SmartAI::MoveInLineOfSight(Unit* who) GetScript()->OnMoveInLineOfSight(who); - if (me->HasReactState(REACT_PASSIVE) || AssistPlayerInCombat(who)) + if (AssistPlayerInCombat(who)) return; - if (!CanAIAttack(who)) - return; - - if (!me->CanStartAttack(who, false)) - return; - - if (me->IsHostileTo(who)) - { - float fAttackRadius = me->GetAttackDistance(who); - if (me->IsWithinDistInMap(who, fAttackRadius) && me->IsWithinLOSInMap(who)) - { - if (!me->GetVictim()) - { - // Clear distracted state on combat - if (me->HasUnitState(UNIT_STATE_DISTRACTED)) - { - me->ClearUnitState(UNIT_STATE_DISTRACTED); - me->GetMotionMaster()->Clear(); - } - - AttackStart(who); - } - else/* if (me->GetMap()->IsDungeon())*/ - { - who->SetInCombatWith(me); - me->AddThreat(who, 0.0f); - } - } - } + CreatureAI::MoveInLineOfSight(who); } bool SmartAI::CanAIAttack(const Unit* /*who*/) const { - if (me->GetReactState() == REACT_PASSIVE) - return false; - return true; + return !(me->HasReactState(REACT_PASSIVE)); } bool SmartAI::AssistPlayerInCombat(Unit* who) diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 7fe3628f893..c8a258ad386 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -138,7 +138,7 @@ bool ForcedDespawnDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) Creature::Creature(bool isWorldObject): Unit(isWorldObject), MapObject(), m_groupLootTimer(0), lootingGroupLowGUID(0), m_PlayerDamageReq(0), m_lootRecipient(), m_lootRecipientGroup(0), _skinner(), _pickpocketLootRestore(0), m_corpseRemoveTime(0), m_respawnTime(0), -m_respawnDelay(300), m_corpseDelay(60), m_respawnradius(0.0f), m_reactState(REACT_AGGRESSIVE), +m_respawnDelay(300), m_corpseDelay(60), m_respawnradius(0.0f), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE), m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_regenHealth(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(NULL), m_creatureData(NULL), m_waypointID(0), m_path_id(0), m_formation(NULL) @@ -539,10 +539,44 @@ void Creature::Update(uint32 diff) LastCharmerGUID.Clear(); } + // if periodic combat pulse is enabled and we are both in combat and in a dungeon, do this now + if (m_combatPulseDelay > 0 && IsInCombat() && GetMap()->IsDungeon()) + { + if (diff > m_combatPulseTime) + m_combatPulseTime = 0; + else + m_combatPulseTime -= diff; + + if (m_combatPulseTime == 0) + { + Map::PlayerList const &players = GetMap()->GetPlayers(); + if (!players.isEmpty()) + for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it) + { + if (Player* player = it->GetSource()) + { + if (player->IsGameMaster()) + continue; + + if (player->IsAlive() && this->IsHostileTo(player)) + { + if (CanHaveThreatList()) + AddThreat(player, 0.0f); + this->SetInCombatWith(player); + player->SetInCombatWith(this); + } + } + } + + m_combatPulseTime = m_combatPulseDelay * IN_MILLISECONDS; + } + } + if (!IsInEvadeMode() && IsAIEnabled) { // do not allow the AI to be changed during update m_AI_locked = true; + i_AI->UpdateAI(diff); m_AI_locked = false; } diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 3e381063772..79832b70f52 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -601,6 +601,14 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject float GetRespawnRadius() const { return m_respawnradius; } void SetRespawnRadius(float dist) { m_respawnradius = dist; } + 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) + { + m_combatPulseDelay = delay; + if (m_combatPulseTime == 0 || m_combatPulseTime > delay) + m_combatPulseTime = delay; + } + uint32 m_groupLootTimer; // (msecs)timer used for group loot uint32 lootingGroupLowGUID; // used to find group which is looting corpse @@ -686,6 +694,8 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject uint32 m_respawnDelay; // (secs) delay between corpse disappearance and respawning uint32 m_corpseDelay; // (secs) delay between death and corpse disappearance float m_respawnradius; + uint32 m_combatPulseTime; // (msecs) remaining time for next zone-in-combat pulse + uint32 m_combatPulseDelay; // (secs) how often the creature puts the entire zone in combat (only works in dungeons) ReactStates m_reactState; // for AI, not charmInfo void RegenerateMana(); diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index f27f9220aaa..55d0ad0b4dc 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -3105,11 +3105,13 @@ void SpellMgr::LoadSpellInfoCorrections() case 28796: // Poison Bolt Volly - Faerlina spellInfo->MaxAffectedTargets = 5; break; + case 54835: // Curse of the Plaguebringer - Noth (H) + spellInfo->MaxAffectedTargets = 8; + break; case 40827: // Sinful Beam case 40859: // Sinister Beam case 40860: // Vile Beam case 40861: // Wicked Beam - case 54835: // Curse of the Plaguebringer - Noth (H) case 54098: // Poison Bolt Volly - Faerlina (H) spellInfo->MaxAffectedTargets = 10; break; diff --git a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp index 3d7c128c8dd..5248c48029c 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp @@ -21,36 +21,42 @@ #include "naxxramas.h" #include "Player.h" -enum Heigan +enum Spells { - SPELL_DECREPIT_FEVER = 29998, // 25-man: 55011 - SPELL_SPELL_DISRUPTION = 29310, - SPELL_PLAGUE_CLOUD = 29350, - - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_TAUNT = 2, - SAY_DEATH = 3 + SPELL_DECREPIT_FEVER = 29998, // 25-man: 55011 + SPELL_SPELL_DISRUPTION = 29310, + SPELL_PLAGUE_CLOUD = 29350, + SPELL_TELEPORT_SELF = 30211, +}; + +enum Yells +{ + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_TAUNT = 2, + SAY_DEATH = 3, + + EMOTE_DANCE = 4, + EMOTE_DANCE_END = 5, }; enum Events { - EVENT_NONE, - EVENT_DISRUPT, + EVENT_DISRUPT = 1, EVENT_FEVER, EVENT_ERUPT, - EVENT_PHASE, + EVENT_DANCE, + EVENT_DANCE_END }; enum Phases { PHASE_FIGHT = 1, - PHASE_DANCE, + PHASE_DANCE }; enum Misc { - ACTION_SAFETY_DANCE_FAIL = 1, DATA_SAFETY_DANCE = 19962139 }; @@ -66,39 +72,25 @@ public: struct boss_heiganAI : public BossAI { - boss_heiganAI(Creature* creature) : BossAI(creature, BOSS_HEIGAN) + boss_heiganAI(Creature* creature) : BossAI(creature, BOSS_HEIGAN), eruptSection(0), eruptDirection(false), safetyDance(false) { } + + void Reset() override { - eruptSection = 0; - eruptDirection = false; - safetyDance = false; - phase = PHASE_FIGHT; + me->SetReactState(REACT_AGGRESSIVE); + _Reset(); } - uint32 eruptSection; - bool eruptDirection; - bool safetyDance; - Phases phase; - void KilledUnit(Unit* who) override { - if (!(rand32() % 5)) - Talk(SAY_SLAY); + Talk(SAY_SLAY); + if (who->GetTypeId() == TYPEID_PLAYER) safetyDance = false; } - void SetData(uint32 id, uint32 data) override - { - if (id == DATA_SAFETY_DANCE) - safetyDance = data ? true : false; - } - uint32 GetData(uint32 type) const override { - if (type == DATA_SAFETY_DANCE) - return safetyDance ? 1 : 0; - - return 0; + return (type == DATA_SAFETY_DANCE && safetyDance) ? 1u : 0u; } void JustDied(Unit* /*killer*/) override @@ -111,35 +103,14 @@ public: { _EnterCombat(); Talk(SAY_AGGRO); - EnterPhase(PHASE_FIGHT); - safetyDance = true; - } - - void EnterPhase(Phases newPhase) - { - phase = newPhase; - events.Reset(); + eruptSection = 3; - if (phase == PHASE_FIGHT) - { - events.ScheduleEvent(EVENT_DISRUPT, urand(10000, 25000)); - events.ScheduleEvent(EVENT_FEVER, urand(15000, 20000)); - events.ScheduleEvent(EVENT_PHASE, 90000); - events.ScheduleEvent(EVENT_ERUPT, 15000); - me->GetMotionMaster()->MoveChase(me->GetVictim()); - } - else - { - float x, y, z, o; - me->GetHomePosition(x, y, z, o); - me->NearTeleportTo(x, y, z, o - (float(M_PI) / 2)); - me->GetMotionMaster()->Clear(); - me->GetMotionMaster()->MoveIdle(); - me->SetTarget(ObjectGuid::Empty); - DoCastAOE(SPELL_PLAGUE_CLOUD); - events.ScheduleEvent(EVENT_PHASE, 45000); - events.ScheduleEvent(EVENT_ERUPT, 8000); - } + events.ScheduleEvent(EVENT_DISRUPT, urand(15 * IN_MILLISECONDS, 20 * IN_MILLISECONDS), 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_FEVER, urand(10 * IN_MILLISECONDS, 20 * IN_MILLISECONDS), 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_DANCE, 90 * IN_MILLISECONDS, 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_ERUPT, 15 * IN_MILLISECONDS, 0, PHASE_FIGHT); + + safetyDance = true; } void UpdateAI(uint32 diff) override @@ -155,15 +126,36 @@ public: { case EVENT_DISRUPT: DoCastAOE(SPELL_SPELL_DISRUPTION); - events.ScheduleEvent(EVENT_DISRUPT, urand(5000, 10000)); + events.ScheduleEvent(EVENT_DISRUPT, 11 * IN_MILLISECONDS); break; case EVENT_FEVER: DoCastAOE(SPELL_DECREPIT_FEVER); - events.ScheduleEvent(EVENT_FEVER, urand(20000, 25000)); + events.ScheduleEvent(EVENT_FEVER, urand(20 * IN_MILLISECONDS, 25 * IN_MILLISECONDS)); + break; + case EVENT_DANCE: + events.SetPhase(PHASE_DANCE); + Talk(SAY_TAUNT); + Talk(EMOTE_DANCE); + eruptSection = 3; + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + me->StopMoving(); + DoCast(SPELL_TELEPORT_SELF); + DoCastAOE(SPELL_PLAGUE_CLOUD); + events.ScheduleEvent(EVENT_DANCE_END, 45 * IN_MILLISECONDS, 0, PHASE_DANCE); + events.ScheduleEvent(EVENT_ERUPT, 10 * IN_MILLISECONDS); break; - case EVENT_PHASE: - /// @todo Add missing texts for both phase switches - EnterPhase(phase == PHASE_FIGHT ? PHASE_DANCE : PHASE_FIGHT); + case EVENT_DANCE_END: + events.SetPhase(PHASE_FIGHT); + Talk(EMOTE_DANCE_END); + eruptSection = 3; + events.ScheduleEvent(EVENT_DISRUPT, urand(10, 25) * IN_MILLISECONDS, 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_FEVER, urand(15, 20) * IN_MILLISECONDS, 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_DANCE, 90 * IN_MILLISECONDS, 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_ERUPT, 15 * IN_MILLISECONDS, 0, PHASE_FIGHT); + me->CastStop(); + me->SetReactState(REACT_AGGRESSIVE); + DoZoneInCombat(); break; case EVENT_ERUPT: instance->SetData(DATA_HEIGAN_ERUPT, eruptSection); @@ -176,13 +168,22 @@ public: eruptDirection ? ++eruptSection : --eruptSection; - events.ScheduleEvent(EVENT_ERUPT, phase == PHASE_FIGHT ? 10000 : 3000); + if (events.IsInPhase(PHASE_DANCE)) + events.ScheduleEvent(EVENT_ERUPT, 3 * IN_MILLISECONDS, 0, PHASE_DANCE); + else + events.ScheduleEvent(EVENT_ERUPT, 10 * IN_MILLISECONDS, 0, PHASE_FIGHT); break; } } DoMeleeAttackIfReady(); } + + private: + uint32 eruptSection; + bool eruptDirection; + + bool safetyDance; // is achievement still possible? (= no player deaths yet) }; }; @@ -205,7 +206,7 @@ class spell_heigan_eruption : public SpellScriptLoader if (GetHitDamage() >= int32(GetHitPlayer()->GetHealth())) if (InstanceScript* instance = caster->GetInstanceScript()) if (Creature* Heigan = ObjectAccessor::GetCreature(*caster, instance->GetGuidData(DATA_HEIGAN))) - Heigan->AI()->SetData(DATA_SAFETY_DANCE, 0); + Heigan->AI()->KilledUnit(GetHitPlayer()); } void Register() override @@ -223,9 +224,7 @@ class spell_heigan_eruption : public SpellScriptLoader class achievement_safety_dance : public AchievementCriteriaScript { public: - achievement_safety_dance() : AchievementCriteriaScript("achievement_safety_dance") - { - } + achievement_safety_dance() : AchievementCriteriaScript("achievement_safety_dance") { } bool OnCheck(Player* /*player*/, Unit* target) override { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp b/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp index 33fb43b6bbc..db6a7a77588 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp @@ -27,7 +27,10 @@ enum Spells SPELL_WARN_NECROTIC_AURA = 59481, SPELL_SUMMON_SPORE = 29234, SPELL_DEATHBLOOM = 29865, - SPELL_INEVITABLE_DOOM = 29204 + SPELL_INEVITABLE_DOOM = 29204, + SPELL_FUNGAL_CREEP = 29232, + + SPELL_DEATHBLOOM_FINAL_DAMAGE = 55594, }; enum Texts @@ -72,29 +75,35 @@ class boss_loatheb : public CreatureScript void Reset() override { _Reset(); + instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_FUNGAL_CREEP); Initialize(); } void EnterCombat(Unit* /*who*/) override { _EnterCombat(); - events.ScheduleEvent(EVENT_NECROTIC_AURA, 17000); - events.ScheduleEvent(EVENT_DEATHBLOOM, 5000); - events.ScheduleEvent(EVENT_SPORE, IsHeroic() ? 18000 : 36000); - events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 120000); + events.ScheduleEvent(EVENT_NECROTIC_AURA, 17 * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_DEATHBLOOM, 5 * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_SPORE, RAID_MODE(36,18) * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 2 * MINUTE * IN_MILLISECONDS); } - void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override { _sporeLoserData = false; + summon->CastSpell(summon,SPELL_FUNGAL_CREEP,true); } - uint32 GetData(uint32 id) const override + void SummonedCreatureDespawn(Creature* summon) override { - if (id != DATA_ACHIEVEMENT_SPORE_LOSER) - return 0; + summons.Despawn(summon); + if (summon->IsAlive()) + summon->CastSpell(summon,SPELL_FUNGAL_CREEP,true); + } - return uint32(_sporeLoserData); + uint32 GetData(uint32 id) const override + { + return (_sporeLoserData && id == DATA_ACHIEVEMENT_SPORE_LOSER) ? 1u : 0u; } void UpdateAI(uint32 diff) override @@ -111,21 +120,29 @@ class boss_loatheb : public CreatureScript case EVENT_NECROTIC_AURA: DoCastAOE(SPELL_NECROTIC_AURA); DoCast(me, SPELL_WARN_NECROTIC_AURA); - events.ScheduleEvent(EVENT_NECROTIC_AURA, 20000); - events.ScheduleEvent(EVENT_NECROTIC_AURA_FADING, 14000); + events.ScheduleEvent(EVENT_NECROTIC_AURA, 20 * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_NECROTIC_AURA_FADING, 14 * IN_MILLISECONDS); break; case EVENT_DEATHBLOOM: DoCastAOE(SPELL_DEATHBLOOM); - events.ScheduleEvent(EVENT_DEATHBLOOM, 30000); + events.ScheduleEvent(EVENT_DEATHBLOOM, 30 * IN_MILLISECONDS); break; case EVENT_INEVITABLE_DOOM: _doomCounter++; DoCastAOE(SPELL_INEVITABLE_DOOM); - events.ScheduleEvent(EVENT_INEVITABLE_DOOM, std::max(120000 - _doomCounter * 15000, 15000)); // needs to be confirmed + if (_doomCounter > 6) + { + if (_doomCounter & 1) // odd + events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 14 * IN_MILLISECONDS); + else + events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 17 * IN_MILLISECONDS); + } + else + events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 30 * IN_MILLISECONDS); break; case EVENT_SPORE: DoCast(me, SPELL_SUMMON_SPORE, false); - events.ScheduleEvent(EVENT_SPORE, IsHeroic() ? 18000 : 36000); + events.ScheduleEvent(EVENT_SPORE, RAID_MODE(36,18) * IN_MILLISECONDS); break; case EVENT_NECROTIC_AURA_FADING: Talk(SAY_NECROTIC_AURA_FADING); @@ -203,9 +220,46 @@ class spell_loatheb_necrotic_aura_warning : public SpellScriptLoader } }; +class spell_loatheb_deathbloom : public SpellScriptLoader +{ + public: + spell_loatheb_deathbloom() : SpellScriptLoader("spell_loatheb_deathbloom") { } + + class spell_loatheb_deathbloom_AuraScript : public AuraScript + { + PrepareAuraScript(spell_loatheb_deathbloom_AuraScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_DEATHBLOOM_FINAL_DAMAGE)) + return false; + return true; + } + + void AfterRemove(AuraEffect const* eff, AuraEffectHandleModes /*mode*/) + { + if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_EXPIRE) + return; + + GetTarget()->CastSpell(nullptr, SPELL_DEATHBLOOM_FINAL_DAMAGE, true, nullptr, eff, GetCasterGUID()); + } + + void Register() override + { + AfterEffectRemove += AuraEffectRemoveFn(spell_loatheb_deathbloom_AuraScript::AfterRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_loatheb_deathbloom_AuraScript(); + } +}; + void AddSC_boss_loatheb() { new boss_loatheb(); new achievement_spore_loser(); new spell_loatheb_necrotic_aura_warning(); + new spell_loatheb_deathbloom(); } diff --git a/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp b/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp index f5e5b287571..810d446111a 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp @@ -19,51 +19,58 @@ #include "ScriptedCreature.h" #include "naxxramas.h" -enum Noth +enum Phases { - SAY_AGGRO = 0, - SAY_SUMMON = 1, - SAY_SLAY = 2, - SAY_DEATH = 3, - - SOUND_DEATH = 8848, - - SPELL_CURSE_PLAGUEBRINGER = 29213, // 25-man: 54835 - SPELL_CRIPPLE = 29212, // 25-man: 54814 - SPELL_TELEPORT = 29216, - - NPC_WARRIOR = 16984, - NPC_CHAMPION = 16983, - NPC_GUARDIAN = 16981 + PHASE_NONE, + PHASE_GROUND, + PHASE_BALCONY }; -#define SPELL_BLINK RAND(29208, 29209, 29210, 29211) +enum Events +{ + EVENT_CURSE = 1, // curse of the plaguebringer + EVENT_BLINK, // blink (25m only) + EVENT_WARRIOR, // summon warriors during ground phase + EVENT_BALCONY, // become untargetable and begin balcony phase + EVENT_BALCONY_TELEPORT, // actually teleport to balcony, this is slightly delayed + EVENT_WAVE, // spawn wave during balcony phase + EVENT_GROUND, // end balcony phase and teleport to ground + EVENT_GROUND_ATTACKABLE // become attackable and aggressive again at start of ground phase, once again slightly delayed to prevent motionmaster weirdness +}; -// Teleport position of Noth on his balcony -Position const Teleport = { 2631.370f, -3529.680f, 274.040f, 6.277f }; +enum Talk +{ + SAY_AGGRO = 0, + SAY_SUMMON = 1, + SAY_SLAY = 2, + SAY_DEATH = 3, -#define MAX_SUMMON_POS 5 + EMOTE_SUMMON = 4, // ground phase + EMOTE_SUMMON_WAVE = 5, // balcony phase + EMOTE_TELEPORT_1 = 6, // ground to balcony + EMOTE_TELEPORT_2 = 7 // balcony to ground +}; -Position const SummonPos[MAX_SUMMON_POS] = +enum Spells { - { 2728.12f, -3544.43f, 261.91f, 6.04f }, - { 2729.05f, -3544.47f, 261.91f, 5.58f }, - { 2728.24f, -3465.08f, 264.20f, 3.56f }, - { 2704.11f, -3456.81f, 265.53f, 4.51f }, - { 2663.56f, -3464.43f, 262.66f, 5.20f } + SPELL_CURSE = 29213, // 25-man: 54835 + SPELL_CRIPPLE = 29212, // 25-man: 54814 + + SPELL_TELEPORT = 29216, // ground to balcony + SPELL_TELEPORT_BACK = 29231 // balcony to ground }; -enum Events +enum Adds { - EVENT_NONE, - EVENT_BERSERK, - EVENT_CURSE, - EVENT_BLINK, - EVENT_WARRIOR, - EVENT_BALCONY, - EVENT_WAVE, - EVENT_GROUND + N_WARRIOR_SPELLS = 3, + N_CHAMPION_SPELLS = 6, + N_GUARDIAN_SPELLS = 3 }; +const uint32 SummonWarriorSpells[N_WARRIOR_SPELLS] = { 29247, 29248, 29249 }; +const uint32 SummonChampionSpells[N_CHAMPION_SPELLS] = { 29238, 29255, 29257, 29258, 29262, 29267 }; +const uint32 SummonGuardianSpells[N_GUARDIAN_SPELLS] = { 29239, 29256, 29268 }; + +#define SPELL_BLINK RAND(29208, 29209, 29210, 29211) class boss_noth : public CreatureScript { @@ -72,16 +79,38 @@ public: struct boss_nothAI : public BossAI { - boss_nothAI(Creature* creature) : BossAI(creature, BOSS_NOTH) + boss_nothAI(Creature* creature) : BossAI(creature, BOSS_NOTH), balconyCount(0), justBlinked(false) { - balconyCount = 0; - waveCount = 0; + std::copy(SummonWarriorSpells, SummonWarriorSpells + N_WARRIOR_SPELLS, _SummonWarriorSpells); + std::copy(SummonChampionSpells, SummonChampionSpells + N_CHAMPION_SPELLS, _SummonChampionSpells); + std::copy(SummonGuardianSpells, SummonGuardianSpells + N_GUARDIAN_SPELLS, _SummonGuardianSpells); + + events.SetPhase(PHASE_NONE); + } + + void EnterEvadeMode() override + { + Reset(); // teleport back first + _EnterEvadeMode(); } void Reset() override { - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + if (!me->IsAlive()) + return; + + // in case we reset during balcony phase + if (events.IsInPhase(PHASE_BALCONY)) + { + DoCastAOE(SPELL_TELEPORT_BACK); + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE); + } + + balconyCount = 0; + events.SetPhase(PHASE_NONE); + justBlinked = false; + _Reset(); } @@ -89,31 +118,44 @@ public: { _EnterCombat(); Talk(SAY_AGGRO); - balconyCount = 0; EnterPhaseGround(); } void EnterPhaseGround() { - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + events.SetPhase(PHASE_GROUND); + DoZoneInCombat(); if (me->getThreatManager().isThreatListEmpty()) - EnterEvadeMode(); + Reset(); else { - events.ScheduleEvent(EVENT_BALCONY, 110000); - events.ScheduleEvent(EVENT_CURSE, 10000 + rand32() % 15000); - events.ScheduleEvent(EVENT_WARRIOR, 30000); + uint8 secondsGround; + switch (balconyCount) + { + case 0: + secondsGround = 90; + break; + case 1: + secondsGround = 110; + break; + case 2: + default: + secondsGround = 180; + } + events.ScheduleEvent(EVENT_GROUND_ATTACKABLE, 2 * IN_MILLISECONDS, 0, PHASE_GROUND); + events.ScheduleEvent(EVENT_BALCONY, secondsGround * IN_MILLISECONDS, 0, PHASE_GROUND); + events.ScheduleEvent(EVENT_CURSE, urand(10,25) * IN_MILLISECONDS, 0, PHASE_GROUND); + events.ScheduleEvent(EVENT_WARRIOR, urand(20,30) * IN_MILLISECONDS, 0, PHASE_GROUND); if (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL) - events.ScheduleEvent(EVENT_BLINK, urand(20000, 40000)); + events.ScheduleEvent(EVENT_BLINK, urand(20,30) * IN_MILLISECONDS, 0, PHASE_GROUND); } } - void KilledUnit(Unit* /*victim*/) override + void KilledUnit(Unit* victim) override { - if (!(rand32() % 5)) + if(victim->GetTypeId() == TYPEID_PLAYER) Talk(SAY_SLAY); } @@ -121,7 +163,7 @@ public: { summons.Summon(summon); summon->setActive(true); - summon->AI()->DoZoneInCombat(); + summon->AI()->DoZoneInCombat(nullptr, 250.0f); // specify range to cover entire room - default 50yd is not enough } void JustDied(Unit* /*killer*/) override @@ -130,10 +172,35 @@ public: Talk(SAY_DEATH); } - void SummonUndead(uint32 entry, uint32 num) + void DamageTaken(Unit* /*who*/, uint32& damage) // prevent noth from somehow dying in the balcony phase { - for (uint32 i = 0; i < num; ++i) - me->SummonCreature(entry, SummonPos[rand32() % MAX_SUMMON_POS], TEMPSUMMON_CORPSE_DESPAWN, 60000); + if (!events.IsInPhase(PHASE_BALCONY)) + return; + if (damage < me->GetHealth()) + return; + + me->SetHealth(1u); + damage = 0u; + } + + void HandleSummon(uint32* spellsList, const uint8 nSpells, uint8 num) + { // this ensures we do not spawn two mobs using the same spell (<=> in the same position) if we can help it + while (num) + for (uint8 it = 0; it < nSpells && num; ++it) + { + num--; + uint8 selected = urand(it, nSpells - 1); + DoCastAOE(spellsList[selected]); + if (selected != it) // shuffle the selected into the part of the array that is no longer being searched + std::swap(spellsList[selected], spellsList[it]); + } + } + + void CastSummon(uint8 nWarrior, uint8 nChampion, uint8 nGuardian) + { + HandleSummon(_SummonWarriorSpells, N_WARRIOR_SPELLS, nWarrior); + HandleSummon(_SummonChampionSpells, N_CHAMPION_SPELLS, nChampion); + HandleSummon(_SummonGuardianSpells, N_GUARDIAN_SPELLS, nGuardian); } void UpdateAI(uint32 diff) override @@ -151,72 +218,115 @@ public: switch (eventId) { case EVENT_CURSE: - DoCastAOE(SPELL_CURSE_PLAGUEBRINGER); - events.ScheduleEvent(EVENT_CURSE, urand(50000, 60000)); - return; + { + DoCastAOE(SPELL_CURSE); + events.ScheduleEvent(EVENT_CURSE, urand(50, 70) * IN_MILLISECONDS, 0, PHASE_GROUND); + break; + } case EVENT_WARRIOR: Talk(SAY_SUMMON); - SummonUndead(NPC_WARRIOR, RAID_MODE(2, 3)); - events.ScheduleEvent(EVENT_WARRIOR, 30000); - return; + Talk(EMOTE_SUMMON); + + CastSummon(RAID_MODE(2, 3), 0, 0); + + events.ScheduleEvent(EVENT_WARRIOR, 40 * IN_MILLISECONDS, 0, PHASE_GROUND); + break; case EVENT_BLINK: DoCastAOE(SPELL_CRIPPLE, true); DoCastAOE(SPELL_BLINK); DoResetThreat(); - events.ScheduleEvent(EVENT_BLINK, 40000); - return; + justBlinked = true; + + events.ScheduleEvent(EVENT_BLINK, 40000, 0, PHASE_GROUND); + break; case EVENT_BALCONY: + events.SetPhase(PHASE_BALCONY); me->SetReactState(REACT_PASSIVE); - me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE); me->AttackStop(); + me->StopMoving(); me->RemoveAllAuras(); - me->NearTeleportTo(Teleport.GetPositionX(), Teleport.GetPositionY(), Teleport.GetPositionZ(), Teleport.GetOrientation()); - events.Reset(); - events.ScheduleEvent(EVENT_WAVE, urand(2000, 5000)); - waveCount = 0; - return; + + events.ScheduleEvent(EVENT_BALCONY_TELEPORT, 3 * IN_MILLISECONDS, 0, PHASE_BALCONY); + events.ScheduleEvent(EVENT_WAVE, urand(5 * IN_MILLISECONDS, 8 * IN_MILLISECONDS), 0, PHASE_BALCONY); + + uint8 secondsBalcony; + switch (balconyCount) + { + case 0: + secondsBalcony = 70; + break; + case 1: + secondsBalcony = 97; + break; + case 2: + default: + secondsBalcony = 120; + break; + } + events.ScheduleEvent(EVENT_GROUND, secondsBalcony * IN_MILLISECONDS, 0, PHASE_BALCONY); + break; + case EVENT_BALCONY_TELEPORT: + Talk(EMOTE_TELEPORT_1); + DoCastAOE(SPELL_TELEPORT); + break; case EVENT_WAVE: - Talk(SAY_SUMMON); + Talk(EMOTE_SUMMON_WAVE); switch (balconyCount) { case 0: - SummonUndead(NPC_CHAMPION, RAID_MODE(2, 4)); + CastSummon(0, RAID_MODE(2, 4), 0); break; case 1: - SummonUndead(NPC_CHAMPION, RAID_MODE(1, 2)); - SummonUndead(NPC_GUARDIAN, RAID_MODE(1, 2)); + CastSummon(0, RAID_MODE(1, 2), RAID_MODE(1, 2)); break; case 2: - SummonUndead(NPC_GUARDIAN, RAID_MODE(2, 4)); + CastSummon(0, 0, RAID_MODE(2, 4)); break; default: - SummonUndead(NPC_CHAMPION, RAID_MODE(5, 10)); - SummonUndead(NPC_GUARDIAN, RAID_MODE(5, 10)); + CastSummon(0, RAID_MODE(5, 10), RAID_MODE(5, 10)); break; } - ++waveCount; - events.ScheduleEvent(waveCount < 2 ? EVENT_WAVE : EVENT_GROUND, urand(30000, 45000)); - return; + events.ScheduleEvent(EVENT_WAVE, urand(30, 45) * IN_MILLISECONDS, 0, PHASE_BALCONY); + break; case EVENT_GROUND: - { ++balconyCount; - float x, y, z, o; - me->GetHomePosition(x, y, z, o); - me->NearTeleportTo(x, y, z, o); - events.ScheduleEvent(EVENT_BALCONY, 110000); + + DoCastAOE(SPELL_TELEPORT_BACK); + Talk(EMOTE_TELEPORT_2); + EnterPhaseGround(); - return; - } + break; + case EVENT_GROUND_ATTACKABLE: + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE); + me->SetReactState(REACT_AGGRESSIVE); + break; } } - if (me->HasReactState(REACT_AGGRESSIVE)) - DoMeleeAttackIfReady(); + if (events.IsInPhase(PHASE_GROUND)) + { + /* workaround for movechase breaking after blinking + without this noth would just stand there unless his current target moves */ + if (justBlinked && me->GetVictim() && !me->IsWithinMeleeRange(me->EnsureVictim())) + { + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveChase(me->EnsureVictim()); + justBlinked = false; + } + else + DoMeleeAttackIfReady(); + } } private: - uint32 waveCount; uint32 balconyCount; + + bool justBlinked; + + uint32 _SummonWarriorSpells[N_WARRIOR_SPELLS]; + uint32 _SummonChampionSpells[N_CHAMPION_SPELLS]; + uint32 _SummonGuardianSpells[N_GUARDIAN_SPELLS]; }; CreatureAI* GetAI(Creature* creature) const override diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp index 3a431ab3ac0..692e6d8b589 100644 --- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp +++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp @@ -78,12 +78,13 @@ ObjectData const objectData[] = { 0, 0, } }; -float const HeiganPos[2] = { 2796.0f, -3707.0f }; +// from P2 teleport spell stored target +float const HeiganPos[2] = { 2793.86f, -3707.38f }; float const HeiganEruptionSlope[3] = { - (-3685.0f - HeiganPos[1]) / (2724.0f - HeiganPos[0]), - (-3647.0f - HeiganPos[1]) / (2749.0f - HeiganPos[0]), - (-3637.0f - HeiganPos[1]) / (2771.0f - HeiganPos[0]) + (-3703.303223f - HeiganPos[1]) / (2777.494141f - HeiganPos[0]), // between right center and far right + (-3696.948242f - HeiganPos[1]) / (2785.624268f - HeiganPos[0]), // between left and right halves + (-3691.880615f - HeiganPos[1]) / (2790.280029f - HeiganPos[0]) // between far left and left center }; // 0 H x @@ -246,7 +247,6 @@ class instance_naxxramas : public InstanceMapScript if (go->GetGOInfo()->displayId == 6785 || go->GetGOInfo()->displayId == 1287) { uint32 section = GetEruptionSection(go->GetPositionX(), go->GetPositionY()); - HeiganEruptionGUID[section].erase(go->GetGUID()); return; } @@ -552,7 +552,7 @@ class instance_naxxramas : public InstanceMapScript // This Function is called in CheckAchievementCriteriaMeet and CheckAchievementCriteriaMeet is called before SetBossState(bossId, DONE), // so to check if all bosses are done the checker must exclude 1 boss, the last done, if there is at most 1 encouter in progress when is // called this function then all bosses are done. The one boss that check is the boss that calls this function, so it is dead. - bool AreAllEncoutersDone() + bool AreAllEncountersDone() { uint32 numBossAlive = 0; for (uint32 i = 0; i < EncounterCount; ++i) @@ -589,7 +589,7 @@ class instance_naxxramas : public InstanceMapScript case 13239: // Loatheb case 13240: // Thaddius case 7617: // Kel'Thuzad - if (AreAllEncoutersDone() && !playerDied) + if (AreAllEncountersDone() && !playerDied) return true; return false; } diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index 0ab7c2fcb54..ca8c9a9ef93 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -3424,7 +3424,7 @@ class spell_gen_turkey_marker : public SpellScriptLoader void Register() override { - AfterEffectApply += AuraEffectApplyFn(spell_gen_turkey_marker_AuraScript::OnApply, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL); + AfterEffectApply += AuraEffectApplyFn(spell_gen_turkey_marker_AuraScript::OnApply, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); OnEffectPeriodic += AuraEffectPeriodicFn(spell_gen_turkey_marker_AuraScript::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); } |