diff --git a/data/sql/updates/pending_db_world/rev_1660074715862707400.sql b/data/sql/updates/pending_db_world/rev_1660074715862707400.sql
new file mode 100644
index 000000000..ac19529df
--- /dev/null
+++ b/data/sql/updates/pending_db_world/rev_1660074715862707400.sql
@@ -0,0 +1,4 @@
+--
+UPDATE `creature_template` SET `unit_flags` = `unit_flags` | 33554432, `ScriptName` = 'npc_dirt_mound' WHERE `entry` = 15712;
+
+UPDATE `gameobject_template_addon` SET `flags` = `flags` | 16 WHERE `entry` = 180795;
diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp
index 761a01ac0..1304a6cfe 100644
--- a/src/server/game/AI/CoreAI/UnitAI.cpp
+++ b/src/server/game/AI/CoreAI/UnitAI.cpp
@@ -90,6 +90,24 @@ bool UnitAI::DoSpellAttackIfReady(uint32 spell)
return false;
}
+void UnitAI::DoSpellAttackToRandomTargetIfReady(uint32 spell, uint32 threatTablePosition /*= 0*/, float dist /*= 0.f*/, bool playerOnly /*= true*/)
+{
+ if (me->HasUnitState(UNIT_STATE_CASTING) || !me->isAttackReady())
+ return;
+
+ if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell))
+ {
+ if (Unit* target = SelectTarget(SelectTargetMethod::Random, threatTablePosition, dist, playerOnly))
+ {
+ if (me->IsWithinCombatRange(target, spellInfo->GetMaxRange(false)))
+ {
+ me->CastSpell(target, spell, false);
+ me->resetAttackTimer();
+ }
+ }
+ }
+}
+
Unit* UnitAI::SelectTarget(SelectTargetMethod targetType, uint32 position, float dist, bool playerOnly, int32 aura)
{
return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, aura));
diff --git a/src/server/game/AI/CoreAI/UnitAI.h b/src/server/game/AI/CoreAI/UnitAI.h
index 4f8b00d41..a423997b9 100644
--- a/src/server/game/AI/CoreAI/UnitAI.h
+++ b/src/server/game/AI/CoreAI/UnitAI.h
@@ -330,6 +330,7 @@ public:
void DoMeleeAttackIfReady();
bool DoSpellAttackIfReady(uint32 spell);
+ void DoSpellAttackToRandomTargetIfReady(uint32 spell, uint32 threatTablePosition = 0, float dist = 0.f, bool playerOnly = true);
static AISpellInfoType* AISpellInfo;
static void FillAISpellInfo();
diff --git a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_ouro.cpp b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_ouro.cpp
index 59f655c92..974620cb8 100644
--- a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_ouro.cpp
+++ b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_ouro.cpp
@@ -15,25 +15,47 @@
* with this program. If not, see .
*/
-/* ScriptData
-SDName: Boss_Ouro
-SD%Complete: 85
-SDComment: No model for submerging. Currently just invisible.
-SDCategory: Temple of Ahn'Qiraj
-EndScriptData */
-
+#include "Cell.h"
+#include "CellImpl.h"
+#include "GridNotifiers.h"
+#include "GridNotifiersImpl.h"
+#include "Player.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
+#include "TaskScheduler.h"
#include "temple_of_ahnqiraj.h"
enum Spells
{
+ // Ouro
SPELL_SWEEP = 26103,
- SPELL_SANDBLAST = 26102,
+ SPELL_SAND_BLAST = 26102,
SPELL_GROUND_RUPTURE = 26100,
- SPELL_BIRTH = 26262, // The Birth Animation
+ SPELL_BERSERK = 26615,
+ SPELL_BOULDER = 26616,
+ SPELL_OURO_SUBMERGE_VISUAL = 26063,
+ SPELL_SUMMON_SANDWORM_BASE = 26133,
+
+ // Misc - Mounds, Ouro Spawner
+ SPELL_BIRTH = 26586,
SPELL_DIRTMOUND_PASSIVE = 26092,
- SPELL_SUMMON_OURO = 26061
+ SPELL_SUMMON_OURO = 26061,
+ SPELL_SUMMON_OURO_MOUNDS = 26058,
+ SPELL_QUAKE = 26093,
+ SPELL_SUMMON_SCARABS = 26060,
+ SPELL_SUMMON_OURO_AURA = 26642,
+ SPELL_DREAM_FOG = 24780
+};
+
+enum Misc
+{
+ GROUP_EMERGED = 0,
+ GROUP_PHASE_TRANSITION = 1,
+
+ NPC_DIRT_MOUND = 15712,
+ GO_SANDWORM_BASE = 180795,
+
+ DATA_OURO_HEALTH = 0
};
struct npc_ouro_spawner : public ScriptedAI
@@ -48,15 +70,15 @@ struct npc_ouro_spawner : public ScriptedAI
void Reset() override
{
hasSummoned = false;
- DoCast(me, SPELL_DIRTMOUND_PASSIVE);
+ DoCastSelf(SPELL_DIRTMOUND_PASSIVE);
}
void MoveInLineOfSight(Unit* who) override
{
// Spawn Ouro on LoS check
- if (!hasSummoned && who->GetTypeId() == TYPEID_PLAYER && me->IsWithinDistInMap(who, 40.0f))
+ if (!hasSummoned && who->GetTypeId() == TYPEID_PLAYER && me->IsWithinDistInMap(who, 40.0f) && !who->ToPlayer()->IsGameMaster())
{
- DoCast(me, SPELL_SUMMON_OURO);
+ DoCastSelf(SPELL_SUMMON_OURO);
hasSummoned = true;
}
@@ -69,43 +91,170 @@ struct npc_ouro_spawner : public ScriptedAI
if (creature->GetEntry() == NPC_OURO)
{
creature->SetInCombatWithZone();
- creature->CastSpell(creature, SPELL_BIRTH, false);
me->DespawnOrUnsummon();
}
}
-
};
-struct boss_ouro : public ScriptedAI
+struct boss_ouro : public BossAI
{
- boss_ouro(Creature* creature) : ScriptedAI(creature) { }
+ boss_ouro(Creature* creature) : BossAI(creature, DATA_OURO)
+ {
+ SetCombatMovement(false);
+ me->SetControlled(true, UNIT_STATE_ROOT);
+ _scheduler.SetValidator([this] { return !me->HasUnitState(UNIT_STATE_CASTING); });
+ }
- uint32 Sweep_Timer;
- uint32 SandBlast_Timer;
- uint32 Submerge_Timer;
- uint32 Back_Timer;
- uint32 ChangeTarget_Timer;
- uint32 Spawn_Timer;
+ void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType, SpellSchoolMask) override
+ {
+ if (me->HealthBelowPctDamaged(20, damage) && !_enraged)
+ {
+ DoCastSelf(SPELL_BERSERK, true);
+ _enraged = true;
+ _scheduler.CancelGroup(GROUP_PHASE_TRANSITION);
+ _scheduler.Schedule(1s, [this](TaskContext context)
+ {
+ if (!IsPlayerWithinMeleeRange())
+ DoSpellAttackToRandomTargetIfReady(SPELL_BOULDER);
- bool Enrage;
- bool Submerged;
+ context.Repeat();
+ })
+ .Schedule(20s, [this](TaskContext context)
+ {
+ DoCastSelf(SPELL_SUMMON_OURO_MOUNDS, true);
+ context.Repeat();
+ });
+ }
+ }
+
+ void Submerge()
+ {
+ me->AttackStop();
+ me->SetReactState(REACT_PASSIVE);
+ me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE);
+ _submergeMelee = 0;
+ _submerged = true;
+ DoCastSelf(SPELL_OURO_SUBMERGE_VISUAL);
+ _scheduler.CancelGroup(GROUP_EMERGED);
+ _scheduler.CancelGroup(GROUP_PHASE_TRANSITION);
+
+ if (GameObject* base = me->FindNearestGameObject(GO_SANDWORM_BASE, 10.f))
+ {
+ base->Use(me);
+ base->DespawnOrUnsummon(6s);
+ }
+
+ DoCastSelf(SPELL_SUMMON_OURO_MOUNDS, true);
+ // According to sniffs, Ouro uses his mounds to respawn. The health management could be a little scuffed.
+ std::list ouroMounds;
+ me->GetCreatureListWithEntryInGrid(ouroMounds, NPC_DIRT_MOUND, 200.f);
+ if (!ouroMounds.empty()) // This can't be possible, but just to be sure.
+ {
+ if (Creature* mound = Acore::Containers::SelectRandomContainerElement(ouroMounds))
+ {
+ mound->AddAura(SPELL_SUMMON_OURO_AURA, mound);
+ mound->AI()->SetData(DATA_OURO_HEALTH, me->GetHealth());
+ }
+ }
+
+ me->DespawnOrUnsummon(1000);
+ }
+
+ void CastGroundRupture()
+ {
+ std::list targets;
+ Acore::AllWorldObjectsInRange checker(me, 10.0f);
+ Acore::WorldObjectListSearcher searcher(me, targets, checker);
+ Cell::VisitAllObjects(me, searcher, 10.0f);
+
+ for (WorldObject* target : targets)
+ {
+ if (Unit* unitTarget = target->ToUnit())
+ {
+ if (unitTarget->IsHostileTo(me))
+ DoCast(unitTarget, SPELL_GROUND_RUPTURE, true);
+ }
+ }
+ }
+
+ void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override
+ {
+ if (spellInfo->Id == SPELL_SAND_BLAST && target)
+ me->GetThreatMgr().modifyThreatPercent(target, 100);
+ }
+
+ void Emerge()
+ {
+ DoCastSelf(SPELL_BIRTH);
+ DoCastSelf(SPELL_SUMMON_SANDWORM_BASE, true);
+ me->SetReactState(REACT_AGGRESSIVE);
+ CastGroundRupture();
+ _scheduler
+ .Schedule(20s, GROUP_EMERGED, [this](TaskContext context)
+ {
+ DoCastVictim(SPELL_SAND_BLAST);
+ context.Repeat();
+ })
+ .Schedule(22s, GROUP_EMERGED, [this](TaskContext context)
+ {
+ DoCastVictim(SPELL_SWEEP);
+ context.Repeat();
+ })
+ .Schedule(90s, GROUP_PHASE_TRANSITION, [this](TaskContext /*context*/)
+ {
+ Submerge();
+ })
+ .Schedule(3s, GROUP_PHASE_TRANSITION, [this](TaskContext context)
+ {
+ if (!IsPlayerWithinMeleeRange() && !_submerged)
+ {
+ if (_submergeMelee < 10)
+ {
+ _submergeMelee++;
+ }
+ else
+ {
+ if (!_enraged)
+ Submerge();
+ _submergeMelee = 0;
+ }
+ }
+ else
+ {
+ _submergeMelee = 0;
+ }
+
+ if (!_submerged)
+ context.Repeat(1s);
+ });
+ }
void Reset() override
{
- Sweep_Timer = urand(5000, 10000);
- SandBlast_Timer = urand(20000, 35000);
- Submerge_Timer = urand(90000, 150000);
- Back_Timer = urand(30000, 45000);
- ChangeTarget_Timer = urand(5000, 8000);
- Spawn_Timer = urand(10000, 20000);
-
- Enrage = false;
- Submerged = false;
+ instance->SetBossState(DATA_OURO, NOT_STARTED);
+ _scheduler.CancelAll();
+ _submergeMelee = 0;
+ _submerged = false;
+ _enraged = false;
}
- void EnterCombat(Unit* /*who*/) override
+ void EnterEvadeMode(EvadeReason /*why*/) override
{
- DoCastVictim(SPELL_BIRTH);
+ DoCastSelf(SPELL_OURO_SUBMERGE_VISUAL);
+ me->DespawnOrUnsummon(1000);
+ // Remove after the header file is sorted with the bosses first.
+ if (Creature* ouroSpawner = instance->GetCreature(DATA_OURO_SPAWNER))
+ ouroSpawner->Respawn();
+ instance->SetBossState(DATA_OURO, FAIL);
+ if (GameObject* base = me->FindNearestGameObject(GO_SANDWORM_BASE, 200.f))
+ base->DespawnOrUnsummon();
+ }
+
+ void EnterCombat(Unit* who) override
+ {
+ Emerge();
+
+ BossAI::EnterCombat(who);
}
void UpdateAI(uint32 diff) override
@@ -114,67 +263,112 @@ struct boss_ouro : public ScriptedAI
if (!UpdateVictim())
return;
- //Sweep_Timer
- if (!Submerged && Sweep_Timer <= diff)
- {
- DoCastVictim(SPELL_SWEEP);
- Sweep_Timer = urand(15000, 30000);
- }
- else Sweep_Timer -= diff;
-
- //SandBlast_Timer
- if (!Submerged && SandBlast_Timer <= diff)
- {
- DoCastVictim(SPELL_SANDBLAST);
- SandBlast_Timer = urand(20000, 35000);
- }
- else SandBlast_Timer -= diff;
-
- //Submerge_Timer
- if (!Submerged && Submerge_Timer <= diff)
- {
- //Cast
- me->HandleEmoteCommand(EMOTE_ONESHOT_SUBMERGE);
- me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
- me->SetFaction(FACTION_FRIENDLY);
- DoCast(me, SPELL_DIRTMOUND_PASSIVE);
-
- Submerged = true;
- Back_Timer = urand(30000, 45000);
- }
- else Submerge_Timer -= diff;
-
- //ChangeTarget_Timer
- if (Submerged && ChangeTarget_Timer <= diff)
- {
- Unit* target = SelectTarget(SelectTargetMethod::Random, 0);
-
- if (target)
- me->NearTeleportTo(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), me->GetOrientation());
-
- ChangeTarget_Timer = urand(10000, 20000);
- }
- else ChangeTarget_Timer -= diff;
-
- //Back_Timer
- if (Submerged && Back_Timer <= diff)
- {
- me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
- me->SetFaction(FACTION_MONSTER);
-
- DoCastVictim(SPELL_GROUND_RUPTURE);
-
- Submerged = false;
- Submerge_Timer = urand(60000, 120000);
- }
- else Back_Timer -= diff;
-
- DoMeleeAttackIfReady();
+ _scheduler.Update(diff, [this]
+ {
+ DoMeleeAttackIfReady();
+ });
}
+
+protected:
+ TaskScheduler _scheduler;
+ bool _enraged;
+ uint8 _submergeMelee;
+ bool _submerged;
+
+ bool IsPlayerWithinMeleeRange() const
+ {
+ return me->IsWithinMeleeRange(me->GetVictim());
+ }
+};
+
+struct npc_dirt_mound : ScriptedAI
+{
+ npc_dirt_mound(Creature* creature) : ScriptedAI(creature)
+ {
+ _instance = creature->GetInstanceScript();
+ }
+
+ void JustSummoned(Creature* creature) override
+ {
+ if (creature->GetEntry() == NPC_OURO)
+ {
+ creature->SetInCombatWithZone();
+ creature->SetHealth(_ouroHealth);
+ }
+ }
+
+ void SetData(uint32 type, uint32 data) override
+ {
+ if (type == DATA_OURO_HEALTH)
+ _ouroHealth = data;
+ }
+
+ void EnterCombat(Unit* /*who*/) override
+ {
+ DoZoneInCombat();
+ _scheduler.Schedule(30s, [this](TaskContext /*context*/)
+ {
+ DoCastSelf(SPELL_SUMMON_SCARABS, true);
+ me->DespawnOrUnsummon(1000);
+ })
+ .Schedule(100ms, [this](TaskContext context)
+ {
+ ChaseNewTarget();
+ context.Repeat(5s, 10s);
+ });
+ }
+
+ void ChaseNewTarget()
+ {
+ DoResetThreat();
+ if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 200.f, true))
+ {
+ me->AddThreat(target, 1000000.f);
+ AttackStart(target);
+ }
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ if (!UpdateVictim())
+ return;
+
+ _scheduler.Update(diff);
+ }
+
+ void Reset() override
+ {
+ DoCastSelf(SPELL_DIRTMOUND_PASSIVE, true);
+ DoCastSelf(SPELL_DREAM_FOG, true);
+ DoCastSelf(SPELL_QUAKE, true);
+ }
+
+ void EnterEvadeMode(EvadeReason /*why*/) override
+ {
+ if (_instance)
+ {
+ _instance->SetBossState(DATA_OURO, FAIL);
+
+ // Remove after the header file is sorted with the bosses first.
+ if (Creature* ouroSpawner = _instance->GetCreature(DATA_OURO_SPAWNER))
+ ouroSpawner->Respawn();
+ }
+
+ if (GameObject* base = me->FindNearestGameObject(GO_SANDWORM_BASE, 200.f))
+ base->DespawnOrUnsummon();
+
+ me->DespawnOrUnsummon();
+ }
+
+protected:
+ TaskScheduler _scheduler;
+ uint32 _ouroHealth;
+ InstanceScript* _instance;
};
void AddSC_boss_ouro()
{
RegisterTempleOfAhnQirajCreatureAI(npc_ouro_spawner);
RegisterTempleOfAhnQirajCreatureAI(boss_ouro);
+ RegisterTempleOfAhnQirajCreatureAI(npc_dirt_mound);
}
diff --git a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/instance_temple_of_ahnqiraj.cpp b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/instance_temple_of_ahnqiraj.cpp
index 3056a305f..215e4c790 100644
--- a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/instance_temple_of_ahnqiraj.cpp
+++ b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/instance_temple_of_ahnqiraj.cpp
@@ -30,7 +30,8 @@ EndScriptData */
ObjectData const creatureData[] =
{
{ NPC_SARTURA, DATA_SARTURA },
- { NPC_EYE_OF_CTHUN, DATA_EYE_OF_CTHUN }
+ { NPC_EYE_OF_CTHUN, DATA_EYE_OF_CTHUN },
+ { NPC_OURO_SPAWNER, DATA_OURO_SPAWNER }
};
class instance_temple_of_ahnqiraj : public InstanceMapScript
@@ -102,6 +103,10 @@ public:
case NPC_VISCIDUS:
ViscidusGUID = creature->GetGUID();
break;
+ case NPC_OURO_SPAWNER:
+ if (GetBossState(DATA_OURO) != DONE)
+ creature->Respawn();
+ break;
}
InstanceScript::OnCreatureCreate(creature);
@@ -177,6 +182,27 @@ public:
break;
}
}
+
+ bool SetBossState(uint32 type, EncounterState state) override
+ {
+ if (!InstanceScript::SetBossState(type, state))
+ return false;
+
+ switch (type)
+ {
+ case DATA_OURO:
+ if (state == FAIL)
+ {
+ if (Creature* ouroSpawner = GetCreature(DATA_OURO))
+ ouroSpawner->Respawn();
+ }
+ break;
+ default:
+ break;
+ }
+
+ return true;
+ }
};
};
diff --git a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.h b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.h
index 63727f51b..9301c9503 100644
--- a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.h
+++ b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.h
@@ -36,7 +36,9 @@ enum DataTypes
DATA_VEKNILASHISDEAD = 10,
DATA_VEKNILASH_DEATH = 11,
DATA_FANKRISS = 12,
- DATA_BUG_TRIO_DEATH = 14,
+ DATA_OURO = 13,
+ DATA_OURO_SPAWNER = 14,
+ DATA_BUG_TRIO_DEATH = 15,
DATA_CTHUN_PHASE = 20,
DATA_VISCIDUS = 21,
DATA_SARTURA = 22,
@@ -67,6 +69,7 @@ enum Creatures
NPC_VEKLOR = 15276,
NPC_VEKNILASH = 15275,
NPC_OURO = 15517,
+ NPC_OURO_SPAWNER = 15957,
NPC_SARTURA = 15516
};