mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-13 09:17:18 +00:00
fix(Core/TempleOfAhnQiraj): Ouro rewrite (#12683)
This commit is contained in:
@@ -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;
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -15,25 +15,47 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* 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<Creature*> 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<WorldObject*> targets;
|
||||
Acore::AllWorldObjectsInRange checker(me, 10.0f);
|
||||
Acore::WorldObjectListSearcher<Acore::AllWorldObjectsInRange> 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user