refactor(Scripts/FoS): modernize Forge of Souls bosses to use BossAI (#25174)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andrew 2026-03-22 13:13:53 -03:00 committed by GitHub
parent b5da3363f1
commit f63a93276e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 431 additions and 528 deletions

View file

@ -32,7 +32,7 @@ enum Yells
SAY_CORRUPT_SOUL = 4,
};
enum eSpells
enum Spells
{
SPELL_SOULSTORM_CHANNEL_OOC = 69008,
@ -51,7 +51,7 @@ enum eSpells
SPELL_SOULSTORM = 68872,
};
enum eEvents
enum Events
{
EVENT_SPELL_SHADOW_BOLT = 1,
EVENT_SPELL_FEAR,
@ -60,207 +60,171 @@ enum eEvents
EVENT_START_SOULSTORM,
};
class boss_bronjahm : public CreatureScript
struct boss_bronjahm : public BossAI
{
public:
boss_bronjahm() : CreatureScript("boss_bronjahm") { }
boss_bronjahm(Creature* creature) : BossAI(creature, DATA_BRONJAHM) { }
struct boss_bronjahmAI : public ScriptedAI
void JustReachedHome() override
{
boss_bronjahmAI(Creature* creature) : ScriptedAI(creature), summons(me)
{
pInstance = creature->GetInstanceScript();
}
BossAI::JustReachedHome();
DoCastSelf(SPELL_SOULSTORM_CHANNEL_OOC, true);
}
InstanceScript* pInstance;
EventMap events;
SummonList summons;
void JustReachedHome() override
{
me->CastSpell(me, SPELL_SOULSTORM_CHANNEL_OOC, true);
}
void Reset() override
{
me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE);
me->CastSpell(me, SPELL_SOULSTORM_CHANNEL_OOC, true);
events.Reset();
summons.DespawnAll();
if (pInstance)
pInstance->SetData(DATA_BRONJAHM, NOT_STARTED);
}
void JustEngagedWith(Unit* /*who*/) override
{
Talk(SAY_AGGRO);
me->RemoveAurasDueToSpell(SPELL_SOULSTORM_CHANNEL_OOC);
DoZoneInCombat();
events.Reset();
events.RescheduleEvent(EVENT_SPELL_SHADOW_BOLT, 2s);
events.RescheduleEvent(EVENT_SPELL_MAGICS_BANE, 5s, 10s);
events.RescheduleEvent(EVENT_SPELL_CORRUPT_SOUL, 14s, 20s);
if (pInstance)
pInstance->SetData(DATA_BRONJAHM, IN_PROGRESS);
}
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
{
if (!me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE) && me->HealthBelowPctDamaged(35, damage))
{
me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE);
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MoveIdle();
me->CastSpell(me, SPELL_TELEPORT, false);
events.CancelEvent(EVENT_SPELL_CORRUPT_SOUL);
events.DelayEvents(6s);
events.RescheduleEvent(EVENT_SPELL_FEAR, 8s, 14s);
}
}
void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override
{
if (spell->Id == SPELL_TELEPORT)
{
me->CastSpell(me, SPELL_TELEPORT_VISUAL, true);
events.RescheduleEvent(EVENT_START_SOULSTORM, 1ms);
}
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
if (me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE))
if (me->isAttackReady())
me->SetFacingToObject(me->GetVictim());
switch (events.ExecuteEvent())
{
case 0:
break;
case EVENT_SPELL_SHADOW_BOLT:
if (!me->IsWithinMeleeRange(me->GetVictim()))
me->CastSpell(me->GetVictim(), SPELL_SHADOW_BOLT, false);
events.Repeat(2s);
break;
case EVENT_SPELL_FEAR:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 10.0f, true))
me->CastCustomSpell(SPELL_FEAR, SPELLVALUE_MAX_TARGETS, 1, target, false);
events.Repeat(8s, 12s);
break;
case EVENT_SPELL_MAGICS_BANE:
me->CastSpell(me->GetVictim(), SPELL_MAGICS_BANE, false);
events.Repeat(10s, 15s);
break;
case EVENT_SPELL_CORRUPT_SOUL:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
{
Talk(SAY_CORRUPT_SOUL);
me->CastSpell(target, SPELL_CORRUPT_SOUL, false);
}
events.Repeat(20s, 25s);
break;
case EVENT_START_SOULSTORM:
Talk(SAY_SOUL_STORM);
me->CastSpell(me, SPELL_SOULSTORM, false);
me->CastSpell(me, SPELL_TELEPORT_VISUAL, true);
me->CastSpell(me, SPELL_SOULSTORM_VISUAL, true);
break;
}
DoMeleeAttackIfReady();
}
void JustDied(Unit* /*killer*/) override
{
Talk(SAY_DEATH);
if (pInstance)
pInstance->SetData(DATA_BRONJAHM, DONE);
}
void KilledUnit(Unit* who) override
{
if (who->IsPlayer())
Talk(SAY_SLAY);
}
void JustSummoned(Creature* summon) override
{
summons.Summon(summon);
summon->SetReactState(REACT_PASSIVE);
}
void EnterEvadeMode(EvadeReason why) override
{
me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE);
ScriptedAI::EnterEvadeMode(why);
}
};
CreatureAI* GetAI(Creature* creature) const override
void Reset() override
{
return GetForgeOfSoulsAI<boss_bronjahmAI>(creature);
BossAI::Reset();
me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE);
DoCastSelf(SPELL_SOULSTORM_CHANNEL_OOC, true);
}
void JustEngagedWith(Unit* who) override
{
BossAI::JustEngagedWith(who);
Talk(SAY_AGGRO);
me->RemoveAurasDueToSpell(SPELL_SOULSTORM_CHANNEL_OOC);
events.RescheduleEvent(EVENT_SPELL_SHADOW_BOLT, 2s);
events.RescheduleEvent(EVENT_SPELL_MAGICS_BANE, 5s, 10s);
events.RescheduleEvent(EVENT_SPELL_CORRUPT_SOUL, 14s, 20s);
}
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
{
if (!me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE) && me->HealthBelowPctDamaged(35, damage))
{
me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE);
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MoveIdle();
DoCastSelf(SPELL_TELEPORT);
events.CancelEvent(EVENT_SPELL_CORRUPT_SOUL);
events.DelayEvents(6s);
events.RescheduleEvent(EVENT_SPELL_FEAR, 8s, 14s);
}
}
void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override
{
if (spell->Id == SPELL_TELEPORT)
{
DoCastSelf(SPELL_TELEPORT_VISUAL, true);
events.RescheduleEvent(EVENT_START_SOULSTORM, 1ms);
}
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
if (me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE))
if (me->isAttackReady())
me->SetFacingToObject(me->GetVictim());
switch (events.ExecuteEvent())
{
case EVENT_SPELL_SHADOW_BOLT:
if (!me->IsWithinMeleeRange(me->GetVictim()))
DoCastVictim(SPELL_SHADOW_BOLT);
events.Repeat(2s);
break;
case EVENT_SPELL_FEAR:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 10.0f, true))
me->CastCustomSpell(SPELL_FEAR, SPELLVALUE_MAX_TARGETS, 1, target);
events.Repeat(8s, 12s);
break;
case EVENT_SPELL_MAGICS_BANE:
DoCastVictim(SPELL_MAGICS_BANE);
events.Repeat(10s, 15s);
break;
case EVENT_SPELL_CORRUPT_SOUL:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
{
Talk(SAY_CORRUPT_SOUL);
DoCast(target, SPELL_CORRUPT_SOUL);
}
events.Repeat(20s, 25s);
break;
case EVENT_START_SOULSTORM:
Talk(SAY_SOUL_STORM);
DoCastSelf(SPELL_SOULSTORM);
DoCastSelf(SPELL_TELEPORT_VISUAL, true);
DoCastSelf(SPELL_SOULSTORM_VISUAL, true);
break;
}
DoMeleeAttackIfReady();
}
void JustDied(Unit* killer) override
{
BossAI::JustDied(killer);
Talk(SAY_DEATH);
}
void KilledUnit(Unit* who) override
{
if (who->IsPlayer())
Talk(SAY_SLAY);
}
void JustSummoned(Creature* summon) override
{
BossAI::JustSummoned(summon);
summon->SetReactState(REACT_PASSIVE);
}
void EnterEvadeMode(EvadeReason why) override
{
me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE);
BossAI::EnterEvadeMode(why);
}
};
class npc_fos_corrupted_soul_fragment : public CreatureScript
struct npc_fos_corrupted_soul_fragment : public NullCreatureAI
{
public:
npc_fos_corrupted_soul_fragment() : CreatureScript("npc_fos_corrupted_soul_fragment") { }
struct npc_fos_corrupted_soul_fragmentAI : public NullCreatureAI
npc_fos_corrupted_soul_fragment(Creature* creature) : NullCreatureAI(creature)
{
npc_fos_corrupted_soul_fragmentAI(Creature* creature) : NullCreatureAI(creature)
{
pInstance = me->GetInstanceScript();
}
Instance = me->GetInstanceScript();
}
uint32 timer;
InstanceScript* pInstance;
uint32 Timer = 0;
InstanceScript* Instance = nullptr;
void Reset() override
{
timer = 0;
}
void UpdateAI(uint32 diff) override
{
if (pInstance)
if (Creature* b = pInstance->instance->GetCreature(pInstance->GetGuidData(DATA_BRONJAHM)))
{
if (me->GetExactDist2d(b) <= 2.0f)
{
me->GetMotionMaster()->MoveIdle();
me->CastSpell(b, SPELL_CONSUME_SOUL, true);
me->DespawnOrUnsummon(1ms);
return;
}
if (timer <= diff)
{
if (!me->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED))
me->GetMotionMaster()->MovePoint(0, *b);
timer = 1000;
}
else
timer -= diff;
}
}
};
CreatureAI* GetAI(Creature* creature) const override
void Reset() override
{
return GetForgeOfSoulsAI<npc_fos_corrupted_soul_fragmentAI>(creature);
Timer = 0;
}
void UpdateAI(uint32 diff) override
{
if (!Instance)
return;
Creature* bronjahm = Instance->GetCreature(DATA_BRONJAHM);
if (!bronjahm)
return;
if (me->GetExactDist2d(bronjahm) <= 2.0f)
{
me->GetMotionMaster()->MoveIdle();
me->CastSpell(bronjahm, SPELL_CONSUME_SOUL, true);
me->DespawnOrUnsummon(1ms);
return;
}
if (Timer <= diff)
{
if (!me->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED))
me->GetMotionMaster()->MovePoint(0, *bronjahm);
Timer = 1000;
}
else
Timer -= diff;
}
};
@ -275,7 +239,7 @@ class spell_bronjahm_magic_bane : public SpellScript
if (Unit* caster = GetCaster())
{
const int32 maxDamage = caster->GetMap()->GetSpawnMode() == 1 ? 15000 : 10000;
int32 const maxDamage = caster->GetMap()->GetSpawnMode() == 1 ? 15000 : 10000;
int32 newDamage = GetHitDamage();
newDamage += GetHitUnit()->GetMaxPower(POWER_MANA) / 2;
newDamage = std::min<int32>(maxDamage, newDamage);
@ -365,9 +329,8 @@ class spell_bronjahm_soulstorm_targeting : public SpellScript
void AddSC_boss_bronjahm()
{
new boss_bronjahm();
new npc_fos_corrupted_soul_fragment();
RegisterForgeOfSoulsCreatureAI(boss_bronjahm);
RegisterForgeOfSoulsCreatureAI(npc_fos_corrupted_soul_fragment);
RegisterSpellScript(spell_bronjahm_magic_bane);
RegisterSpellScript(spell_bronjahm_soulstorm_channel_ooc_aura);
RegisterSpellScript(spell_bronjahm_soulstorm_visual_aura);

View file

@ -24,7 +24,7 @@
#include "SpellScriptLoader.h"
#include "forge_of_souls.h"
enum eTexts
enum Texts
{
SAY_FACE_AGGRO = 0,
SAY_FACE_ANGER_SLAY = 1,
@ -38,7 +38,7 @@ enum eTexts
SAY_FACE_WAILING_SOUL = 9,
};
enum eSpells
enum Spells
{
SPELL_PHANTOM_BLAST = 68982,
SPELL_PHANTOM_BLAST_H = 70322,
@ -57,7 +57,7 @@ enum eSpells
SPELL_WAILING_SOULS_DMG_H = 70324, // 100yd, 104.0
};
enum eEvents
enum Events
{
EVENT_SPELL_PHANTOM_BLAST = 1,
EVENT_SPELL_MIRRORED_SOUL,
@ -66,245 +66,214 @@ enum eEvents
EVENT_SPELL_WAILING_SOULS,
};
enum eDisplayIds
enum ModelIds
{
DISPLAY_ANGER = 30148,
DISPLAY_SORROW = 30149,
DISPLAY_DESIRE = 30150,
MODEL_ANGER = 30148,
MODEL_SORROW = 30149,
MODEL_DESIRE = 30150,
};
enum eMisc
enum Misc
{
NPC_CRUCIBLE_OF_SOULS = 37094,
NPC_UNLEASHED_SOUL = 36595,
QUEST_TEMPERING_THE_BLADE_A = 24476,
QUEST_TEMPERING_THE_BLADE_H = 24560,
};
class boss_devourer_of_souls : public CreatureScript
struct boss_devourer_of_souls : public BossAI
{
public:
boss_devourer_of_souls() : CreatureScript("boss_devourer_of_souls") { }
boss_devourer_of_souls(Creature* creature) : BossAI(creature, DATA_DEVOURER) { }
struct boss_devourer_of_soulsAI : public ScriptedAI
bool AchievementCompleted = true;
void Reset() override
{
boss_devourer_of_soulsAI(Creature* creature) : ScriptedAI(creature), summons(me)
{
pInstance = creature->GetInstanceScript();
}
BossAI::Reset();
AchievementCompleted = true;
me->SetControlled(false, UNIT_STATE_ROOT);
me->DisableRotate(false);
me->SetReactState(REACT_AGGRESSIVE);
}
InstanceScript* pInstance;
EventMap events;
SummonList summons;
bool bAchiev;
void Reset() override
{
bAchiev = true;
me->SetControlled(false, UNIT_STATE_ROOT);
me->DisableRotate(false);
me->SetReactState(REACT_AGGRESSIVE);
events.Reset();
summons.DespawnAll();
if (pInstance)
pInstance->SetData(DATA_DEVOURER, NOT_STARTED);
}
uint32 GetData(uint32 id) const override
{
if (id == 1)
return bAchiev;
return 0;
}
void JustEngagedWith(Unit* /*who*/) override
{
Talk(SAY_FACE_AGGRO);
DoZoneInCombat();
events.Reset();
events.RescheduleEvent(EVENT_SPELL_PHANTOM_BLAST, 5s);
events.RescheduleEvent(EVENT_SPELL_MIRRORED_SOUL, 9s);
events.RescheduleEvent(EVENT_SPELL_WELL_OF_SOULS, 6s, 8s);
events.RescheduleEvent(EVENT_SPELL_UNLEASHED_SOULS, 18s, 20s);
events.RescheduleEvent(EVENT_SPELL_WAILING_SOULS, 65s);
if (pInstance)
pInstance->SetData(DATA_DEVOURER, IN_PROGRESS);
// Suport for Quest Tempering the Blade
Map::PlayerList const& pList = me->GetMap()->GetPlayers();
for(Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr)
{
Player* player = itr->GetSource();
if ((player->GetTeamId() == TEAM_ALLIANCE && player->GetQuestStatus(QUEST_TEMPERING_THE_BLADE_A) == QUEST_STATUS_INCOMPLETE) ||
(player->GetTeamId() == TEAM_HORDE && player->GetQuestStatus(QUEST_TEMPERING_THE_BLADE_H) == QUEST_STATUS_INCOMPLETE))
{
if (!me->FindNearestCreature(NPC_CRUCIBLE_OF_SOULS, 100.0f))
me->SummonCreature(NPC_CRUCIBLE_OF_SOULS, 5672.29f, 2520.69f, 713.44f, 0.96f);
}
}
}
void SpellHitTarget(Unit* target, SpellInfo const* spell) override
{
if (spell->Id == SPELL_PHANTOM_BLAST_H)
bAchiev = false;
else if (spell->Id == SPELL_WAILING_SOULS_TARGETING)
{
me->SetOrientation(me->GetAngle(target));
me->SetControlled(true, UNIT_STATE_ROOT);
me->DisableRotate(true);
me->SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
me->SetReactState(REACT_PASSIVE);
me->GetMotionMaster()->Clear(false);
me->GetMotionMaster()->MoveIdle();
me->StopMovingOnCurrentPos();
me->SetFacingToObject(target);
me->SendMovementFlagUpdate();
me->CastSpell(me, SPELL_WAILING_SOULS, false);
}
}
bool CanAIAttack(Unit const* target) const override { return target->GetPositionZ() > 706.5f; }
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
events.Update(diff);
if (Spell* s = me->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
if (s->m_spellInfo->Id == SPELL_MIRRORED_SOUL)
{
switch (events.ExecuteEvent())
{
case 0:
break;
case EVENT_SPELL_PHANTOM_BLAST:
me->CastSpell(me->GetVictim(), SPELL_PHANTOM_BLAST, false);
events.Repeat(5s);
break;
default:
events.Repeat(1s);
break;
}
if (!me->GetCurrentSpell(CURRENT_GENERIC_SPELL))
{
me->ClearUnitState(UNIT_STATE_CASTING);
DoMeleeAttackIfReady();
me->AddUnitState(UNIT_STATE_CASTING);
}
return;
}
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
switch (events.ExecuteEvent())
{
case 0:
break;
case EVENT_SPELL_PHANTOM_BLAST:
me->CastSpell(me->GetVictim(), SPELL_PHANTOM_BLAST, false);
events.Repeat(5s);
break;
case EVENT_SPELL_MIRRORED_SOUL:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 90.0f, true))
{
me->CastSpell(target, SPELL_MIRRORED_SOUL, false);
me->setAttackTimer(BASE_ATTACK, 2500);
Talk(EMOTE_MIRRORED_SOUL);
}
events.Repeat(20s, 30s);
break;
case EVENT_SPELL_WELL_OF_SOULS:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 40.0f, true))
me->CastSpell(target, SPELL_WELL_OF_SOULS, false);
events.Repeat(25s, 30s);
events.DelayEventsToMax(4s, 0);
break;
case EVENT_SPELL_UNLEASHED_SOULS:
me->CastSpell(me, SPELL_UNLEASHED_SOULS, false);
Talk(SAY_FACE_UNLEASH_SOUL);
Talk(EMOTE_UNLEASH_SOUL);
events.Repeat(30s, 40s);
events.DelayEventsToMax(5s, 0);
me->setAttackTimer(BASE_ATTACK, 5500);
break;
case EVENT_SPELL_WAILING_SOULS:
Talk(SAY_FACE_WAILING_SOUL);
Talk(EMOTE_WAILING_SOUL);
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true))
me->CastCustomSpell(SPELL_WAILING_SOULS_TARGETING, SPELLVALUE_MAX_TARGETS, 1, target, false);
events.Repeat(80s);
events.DelayEventsToMax(20s, 0);
break;
}
DoMeleeAttackIfReady();
}
void JustDied(Unit* /*killer*/) override
{
Talk(SAY_FACE_DEATH);
summons.DespawnAll();
if (pInstance)
pInstance->SetData(DATA_DEVOURER, DONE);
}
void KilledUnit(Unit* victim) override
{
if (!victim->IsPlayer())
return;
int32 textId = 0;
switch (me->GetDisplayId())
{
case DISPLAY_ANGER:
textId = SAY_FACE_ANGER_SLAY;
break;
case DISPLAY_SORROW:
textId = SAY_FACE_SORROW_SLAY;
break;
case DISPLAY_DESIRE:
textId = SAY_FACE_DESIRE_SLAY;
break;
default:
break;
}
if (textId)
Talk(textId);
}
void JustSummoned(Creature* summon) override
{
if (summon->GetEntry() != NPC_CRUCIBLE_OF_SOULS)
summons.Summon(summon);
if (summon->GetEntry() == 36595)
if (Player* plr = summon->SelectNearestPlayer(100.0f))
{
summon->AddThreat(plr, 100000.0f);
summon->AI()->AttackStart(plr);
}
}
void EnterEvadeMode(EvadeReason why) override
{
me->SetControlled(false, UNIT_STATE_ROOT);
me->DisableRotate(false);
ScriptedAI::EnterEvadeMode(why);
}
};
CreatureAI* GetAI(Creature* creature) const override
uint32 GetData(uint32 id) const override
{
return GetForgeOfSoulsAI<boss_devourer_of_soulsAI>(creature);
if (id == 1)
return AchievementCompleted;
return 0;
}
void JustEngagedWith(Unit* who) override
{
BossAI::JustEngagedWith(who);
Talk(SAY_FACE_AGGRO);
events.RescheduleEvent(EVENT_SPELL_PHANTOM_BLAST, 5s);
events.RescheduleEvent(EVENT_SPELL_MIRRORED_SOUL, 9s);
events.RescheduleEvent(EVENT_SPELL_WELL_OF_SOULS, 6s, 8s);
events.RescheduleEvent(EVENT_SPELL_UNLEASHED_SOULS, 18s, 20s);
events.RescheduleEvent(EVENT_SPELL_WAILING_SOULS, 65s);
// Support for Quest Tempering the Blade
me->GetMap()->DoForAllPlayers([this](Player* player)
{
if (me->FindNearestCreature(NPC_CRUCIBLE_OF_SOULS, 100.0f))
return;
uint32 questId = player->GetTeamId() == TEAM_ALLIANCE ? QUEST_TEMPERING_THE_BLADE_A : QUEST_TEMPERING_THE_BLADE_H;
if (player->GetQuestStatus(questId) == QUEST_STATUS_INCOMPLETE)
me->SummonCreature(NPC_CRUCIBLE_OF_SOULS, 5672.29f, 2520.69f, 713.44f, 0.96f);
});
}
void SpellHitTarget(Unit* target, SpellInfo const* spell) override
{
if (spell->Id == SPELL_PHANTOM_BLAST_H)
AchievementCompleted = false;
else if (spell->Id == SPELL_WAILING_SOULS_TARGETING)
{
me->SetOrientation(me->GetAngle(target));
me->SetControlled(true, UNIT_STATE_ROOT);
me->DisableRotate(true);
me->SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
me->SetReactState(REACT_PASSIVE);
me->GetMotionMaster()->Clear(false);
me->GetMotionMaster()->MoveIdle();
me->StopMovingOnCurrentPos();
me->SetFacingToObject(target);
me->SendMovementFlagUpdate();
DoCastSelf(SPELL_WAILING_SOULS);
}
}
bool CanAIAttack(Unit const* target) const override { return target->GetPositionZ() > 706.5f; }
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
events.Update(diff);
if (Spell* s = me->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
if (s->m_spellInfo->Id == SPELL_MIRRORED_SOUL)
{
switch (events.ExecuteEvent())
{
case EVENT_SPELL_PHANTOM_BLAST:
DoCastVictim(SPELL_PHANTOM_BLAST);
events.Repeat(5s);
break;
default:
events.Repeat(1s);
break;
}
if (!me->GetCurrentSpell(CURRENT_GENERIC_SPELL))
{
me->ClearUnitState(UNIT_STATE_CASTING);
DoMeleeAttackIfReady();
me->AddUnitState(UNIT_STATE_CASTING);
}
return;
}
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
switch (events.ExecuteEvent())
{
case EVENT_SPELL_PHANTOM_BLAST:
DoCastVictim(SPELL_PHANTOM_BLAST);
events.Repeat(5s);
break;
case EVENT_SPELL_MIRRORED_SOUL:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 90.0f, true))
{
DoCast(target, SPELL_MIRRORED_SOUL);
me->setAttackTimer(BASE_ATTACK, 2500);
Talk(EMOTE_MIRRORED_SOUL);
}
events.Repeat(20s, 30s);
break;
case EVENT_SPELL_WELL_OF_SOULS:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 40.0f, true))
DoCast(target, SPELL_WELL_OF_SOULS);
events.Repeat(25s, 30s);
events.DelayEventsToMax(4s, 0);
break;
case EVENT_SPELL_UNLEASHED_SOULS:
DoCastSelf(SPELL_UNLEASHED_SOULS);
Talk(SAY_FACE_UNLEASH_SOUL);
Talk(EMOTE_UNLEASH_SOUL);
events.Repeat(30s, 40s);
events.DelayEventsToMax(5s, 0);
me->setAttackTimer(BASE_ATTACK, 5500);
break;
case EVENT_SPELL_WAILING_SOULS:
Talk(SAY_FACE_WAILING_SOUL);
Talk(EMOTE_WAILING_SOUL);
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true))
me->CastCustomSpell(SPELL_WAILING_SOULS_TARGETING, SPELLVALUE_MAX_TARGETS, 1, target);
events.Repeat(80s);
events.DelayEventsToMax(20s, 0);
break;
}
DoMeleeAttackIfReady();
}
void JustDied(Unit* killer) override
{
BossAI::JustDied(killer);
Talk(SAY_FACE_DEATH);
}
void KilledUnit(Unit* victim) override
{
if (!victim->IsPlayer())
return;
uint8 textId = 0;
switch (me->GetDisplayId())
{
case MODEL_ANGER:
textId = SAY_FACE_ANGER_SLAY;
break;
case MODEL_SORROW:
textId = SAY_FACE_SORROW_SLAY;
break;
case MODEL_DESIRE:
textId = SAY_FACE_DESIRE_SLAY;
break;
default:
break;
}
if (textId)
Talk(textId);
}
void JustSummoned(Creature* summon) override
{
if (summon->GetEntry() != NPC_CRUCIBLE_OF_SOULS)
BossAI::JustSummoned(summon);
if (summon->GetEntry() == NPC_UNLEASHED_SOUL)
if (Player* player = summon->SelectNearestPlayer(100.0f))
{
summon->AddThreat(player, 100000.0f);
summon->AI()->AttackStart(player);
}
}
void EnterEvadeMode(EvadeReason why) override
{
me->SetControlled(false, UNIT_STATE_ROOT);
me->DisableRotate(false);
BossAI::EnterEvadeMode(why);
}
};
@ -317,7 +286,7 @@ class spell_wailing_souls_periodic_aura : public AuraScript
return ValidateSpellInfo({ SPELL_WAILING_SOULS_DMG_N });
}
int8 dir;
int8 dir = 0;
bool Load() override
{
@ -328,31 +297,30 @@ class spell_wailing_souls_periodic_aura : public AuraScript
void HandlePeriodicTick(AuraEffect const* aurEff)
{
PreventDefaultAction();
if (Unit* t = GetTarget())
if (Unit* target = GetTarget())
{
if (aurEff->GetTickNumber() < 30)
{
// spinning, casting, etc.
float diff = (2 * M_PI) / (4 * 30);
float new_o = t->GetOrientation() + diff * dir;
if (new_o >= 2 * M_PI)
new_o -= 2 * M_PI;
else if (new_o < 0)
new_o += 2 * M_PI;
t->UpdateOrientation(new_o);
t->SetFacingTo(new_o);
t->CastSpell(t, SPELL_WAILING_SOULS_DMG_N, true);
float newOrientation = target->GetOrientation() + diff * dir;
if (newOrientation >= 2 * M_PI)
newOrientation -= 2 * M_PI;
else if (newOrientation < 0)
newOrientation += 2 * M_PI;
target->UpdateOrientation(newOrientation);
target->SetFacingTo(newOrientation);
target->CastSpell(target, SPELL_WAILING_SOULS_DMG_N, true);
}
else if (aurEff->GetTickNumber() == 33)
{
t->SetControlled(false, UNIT_STATE_ROOT);
t->DisableRotate(false);
if (t->IsCreature())
t->ToCreature()->SetReactState(REACT_AGGRESSIVE);
if (t->GetVictim())
target->SetControlled(false, UNIT_STATE_ROOT);
target->DisableRotate(false);
if (target->IsCreature())
target->ToCreature()->SetReactState(REACT_AGGRESSIVE);
if (target->GetVictim())
{
t->SetGuidValue(UNIT_FIELD_TARGET, t->GetVictim()->GetGUID());
t->GetMotionMaster()->MoveChase(t->GetVictim());
target->SetGuidValue(UNIT_FIELD_TARGET, target->GetVictim()->GetGUID());
target->GetMotionMaster()->MoveChase(target->GetVictim());
}
}
else if (aurEff->GetTickNumber() >= 34)
@ -368,6 +336,6 @@ class spell_wailing_souls_periodic_aura : public AuraScript
void AddSC_boss_devourer_of_souls()
{
new boss_devourer_of_souls();
RegisterForgeOfSoulsCreatureAI(boss_devourer_of_souls);
RegisterSpellScript(spell_wailing_souls_periodic_aura);
}

View file

@ -30,6 +30,11 @@ enum Data
DATA_BRONJAHM,
DATA_DEVOURER,
MAX_ENCOUNTER,
DATA_LEADER_FIRST,
DATA_LEADER_SECOND,
DATA_GUARD_FIRST,
DATA_GUARD_SECOND,
};
enum Creatures
@ -99,4 +104,6 @@ inline AI* GetForgeOfSoulsAI(T* obj)
return GetInstanceAI<AI>(obj, ForgeOfSoulsScriptName);
}
#define RegisterForgeOfSoulsCreatureAI(ai_name) RegisterCreatureAIWithFactory(ai_name, GetForgeOfSoulsAI)
#endif

View file

@ -21,12 +21,29 @@
#include "forge_of_souls.h"
#include "Group.h"
enum Misc
{
NPC_CORRUPTED_SOUL_FRAGMENT = 36535,
ACHIEV_CRITERIA_SOUL_POWER = 12752,
ACHIEV_CRITERIA_THREE_FACED = 12976,
SOUL_POWER_FRAGMENT_COUNT = 4,
};
BossBoundaryData const boundaries =
{
{ DATA_BRONJAHM, new CircleBoundary(Position(5297.3f, 2506.45f), 100.96) },
{ DATA_DEVOURER, new ParallelogramBoundary(Position(5663.56f, 2570.53f), Position(5724.39f, 2520.45f), Position(5570.36f, 2461.42f)) }
};
static ObjectData const creatureData[] =
{
{ NPC_BRONJAHM, DATA_BRONJAHM },
{ NPC_DEVOURER, DATA_DEVOURER },
{ 0, 0 }
};
class instance_forge_of_souls : public InstanceMapScript
{
public:
@ -42,38 +59,22 @@ public:
instance_forge_of_souls_InstanceScript(Map* map) : InstanceScript(map)
{
SetHeaders(DataHeader);
SetBossNumber(MAX_ENCOUNTER);
LoadObjectData(creatureData, nullptr);
LoadBossBoundaries(boundaries);
}
uint32 m_auiEncounter[MAX_ENCOUNTER];
std::string str_data;
ObjectGuid NPC_BronjahmGUID;
ObjectGuid NPC_DevourerGUID;
ObjectGuid NPC_LeaderFirstGUID;
ObjectGuid NPC_LeaderSecondGUID;
ObjectGuid NPC_GuardFirstGUID;
ObjectGuid NPC_GuardSecondGUID;
void Initialize() override
{
memset(&m_auiEncounter, 0, sizeof(m_auiEncounter));
}
bool IsEncounterInProgress() const override
{
for (uint8 i = 0; i < MAX_ENCOUNTER; ++i)
if (m_auiEncounter[i] == IN_PROGRESS) return true;
return false;
}
ObjectGuid LeaderFirstGUID;
ObjectGuid LeaderSecondGUID;
ObjectGuid GuardFirstGUID;
ObjectGuid GuardSecondGUID;
void OnPlayerEnter(Player* player) override
{
InstanceScript::OnPlayerEnter(player);
// this will happen only after crash and loading the instance from db
if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE && (!NPC_LeaderSecondGUID || !instance->GetCreature(NPC_LeaderSecondGUID)))
if (AllBossesDone() && (!LeaderSecondGUID || !instance->GetCreature(LeaderSecondGUID)))
{
Position pos = {5658.15f, 2502.564f, 708.83f, 0.885207f};
instance->SummonCreature(NPC_SYLVANAS_PART2, pos);
@ -82,44 +83,40 @@ public:
void OnCreatureCreate(Creature* creature) override
{
InstanceScript::OnCreatureCreate(creature);
switch (creature->GetEntry())
{
case NPC_BRONJAHM:
NPC_BronjahmGUID = creature->GetGUID();
break;
case NPC_DEVOURER:
NPC_DevourerGUID = creature->GetGUID();
break;
case NPC_SYLVANAS_PART1:
if (GetTeamIdInInstance() == TEAM_ALLIANCE)
creature->UpdateEntry(NPC_JAINA_PART1);
NPC_LeaderFirstGUID = creature->GetGUID();
LeaderFirstGUID = creature->GetGUID();
if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE)
if (AllBossesDone())
creature->SetVisible(false);
break;
case NPC_SYLVANAS_PART2:
if (GetTeamIdInInstance() == TEAM_ALLIANCE)
creature->UpdateEntry(NPC_JAINA_PART2);
NPC_LeaderSecondGUID = creature->GetGUID();
LeaderSecondGUID = creature->GetGUID();
break;
case NPC_LORALEN:
if (GetTeamIdInInstance() == TEAM_ALLIANCE)
creature->UpdateEntry(NPC_ELANDRA);
if (!NPC_GuardFirstGUID)
if (!GuardFirstGUID)
{
NPC_GuardFirstGUID = creature->GetGUID();
if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE)
GuardFirstGUID = creature->GetGUID();
if (AllBossesDone())
creature->SetVisible(false);
}
break;
case NPC_KALIRA:
if (GetTeamIdInInstance() == TEAM_ALLIANCE)
creature->UpdateEntry(NPC_KORELN);
if (!NPC_GuardSecondGUID)
if (!GuardSecondGUID)
{
NPC_GuardSecondGUID = creature->GetGUID();
if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE)
GuardSecondGUID = creature->GetGUID();
if (AllBossesDone())
creature->SetVisible(false);
}
break;
@ -128,9 +125,9 @@ public:
void HandleOutro()
{
if (!NPC_LeaderSecondGUID || !instance->GetCreature(NPC_LeaderSecondGUID))
if (!LeaderSecondGUID || !instance->GetCreature(LeaderSecondGUID))
if (Creature* leader = instance->SummonCreature(NPC_SYLVANAS_PART2, outroSpawnPoint))
if (Creature* boss = instance->GetCreature(NPC_DevourerGUID))
if (Creature* boss = GetCreature(DATA_DEVOURER))
{
float angle = boss->GetAngle(leader);
leader->GetMotionMaster()->MovePoint(1, boss->GetPositionX() + 10.0f * cos(angle), boss->GetPositionY() + 10.0f * std::sin(angle), boss->GetPositionZ());
@ -144,69 +141,37 @@ public:
}
}
void SetData(uint32 type, uint32 data) override
bool SetBossState(uint32 type, EncounterState state) override
{
switch (type)
{
case DATA_BRONJAHM:
m_auiEncounter[type] = data;
break;
case DATA_DEVOURER:
m_auiEncounter[type] = data;
if (m_auiEncounter[0] == DONE && m_auiEncounter[1] == DONE)
HandleOutro();
break;
}
if (!InstanceScript::SetBossState(type, state))
return false;
if (data == DONE)
SaveToDB();
}
if (state == DONE && AllBossesDone())
HandleOutro();
ObjectGuid GetGuidData(uint32 type) const override
{
switch (type)
{
case DATA_BRONJAHM:
return NPC_BronjahmGUID;
}
return ObjectGuid::Empty;
return true;
}
bool CheckAchievementCriteriaMeet(uint32 criteria_id, Player const* /*source*/, Unit const* /*target*/, uint32 /*miscvalue1*/) override
{
switch (criteria_id)
{
case 12752: // Soul Power
if (Creature* c = instance->GetCreature(NPC_BronjahmGUID))
case ACHIEV_CRITERIA_SOUL_POWER:
if (Creature* bronjahm = GetCreature(DATA_BRONJAHM))
{
std::list<Creature*> L;
uint8 count = 0;
c->GetCreaturesWithEntryInRange(L, 200.0f, 36535); // find all Corrupted Soul Fragment (36535)
for( std::list<Creature*>::const_iterator itr = L.begin(); itr != L.end(); ++itr )
if ((*itr)->IsAlive())
++count;
return (count >= 4);
std::list<Creature*> fragments;
bronjahm->GetCreaturesWithEntryInRange(fragments, 200.0f, NPC_CORRUPTED_SOUL_FRAGMENT);
return std::count_if(fragments.begin(), fragments.end(),
[](Creature const* fragment) { return fragment->IsAlive(); }) >= SOUL_POWER_FRAGMENT_COUNT;
}
break;
case 12976:
if (Creature* c = instance->GetCreature(NPC_DevourerGUID))
return (bool)c->AI()->GetData(1);
case ACHIEV_CRITERIA_THREE_FACED:
if (Creature* devourer = GetCreature(DATA_DEVOURER))
return devourer->AI()->GetData(1) != 0;
break;
}
return false;
}
void ReadSaveDataMore(std::istringstream& data) override
{
data >> m_auiEncounter[0];
data >> m_auiEncounter[1];
}
void WriteSaveDataMore(std::ostringstream& data) override
{
data << m_auiEncounter[0] << ' ' << m_auiEncounter[1];
}
};
};