fix(Core/TempleOfAhnQiraj): Ouro rewrite (#12683)

This commit is contained in:
Angelo Venturini
2022-08-15 17:51:20 -03:00
committed by GitHub
parent c7a0fa1aff
commit cb887bbc22
6 changed files with 340 additions and 94 deletions

View File

@@ -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;

View File

@@ -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));

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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;
}
};
};

View File

@@ -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
};