refactor(Scripts/HoR): modernize Halls of Reflection scripts (#25176)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andrew
2026-03-22 12:51:35 -03:00
committed by GitHub
parent 11d4d01e7f
commit 94faf55a86
7 changed files with 2363 additions and 2632 deletions

View File

@@ -134,6 +134,11 @@ void InstanceScript::HandleGameObject(ObjectGuid GUID, bool open, GameObject* go
}
}
void InstanceScript::HandleGameObject(uint32 type, bool open)
{
HandleGameObject(ObjectGuid::Empty, open, GetGameObject(type));
}
bool InstanceScript::IsEncounterInProgress() const
{
for (std::vector<BossInfo>::const_iterator itr = bosses.begin(); itr != bosses.end(); ++itr)

View File

@@ -193,11 +193,21 @@ public:
//Called when a player enters/leaves water bodies.
virtual void OnPlayerInWaterStateUpdate(Player* /*player*/, bool /*inWater*/) {}
//Handle open / close objects
//use HandleGameObject(ObjectGuid::Empty, boolen, GO); in OnObjectCreate in instance scripts
//use HandleGameObject(GUID, boolen, nullptr); in any other script
/**
* @brief Open or close a GameObject by GUID.
* @param guid The GUID of the GameObject. Pass ObjectGuid::Empty when providing the go pointer directly.
* @param open true to open (GO_STATE_ACTIVE), false to close (GO_STATE_READY).
* @param go Optional pointer to the GameObject. If nullptr, the object is looked up by GUID.
*/
void HandleGameObject(ObjectGuid guid, bool open, GameObject* go = nullptr);
/**
* @brief Open or close a GameObject registered via LoadObjectData.
* @param type The ObjectData type constant (e.g. GO_FRONT_DOOR) used in the gameObjectData array.
* @param open true to open (GO_STATE_ACTIVE), false to close (GO_STATE_READY).
*/
void HandleGameObject(uint32 type, bool open);
//change active state of doors or buttons
void DoUseDoorOrButton(ObjectGuid guid, uint32 withRestoreTime = 0, bool useAlternativeState = false);

View File

@@ -16,173 +16,151 @@
*/
#include "CreatureScript.h"
#include "halls_of_reflection.h"
#include "ScriptedCreature.h"
#include "SpellMgr.h"
#include "halls_of_reflection.h"
enum Yells
{
SAY_AGGRO = 0,
SAY_SLAY = 1,
SAY_DEATH = 2,
SAY_IMPENDING_DESPAIR = 3,
SAY_DEFILING_HORROR = 4,
SAY_AGGRO = 0,
SAY_SLAY = 1,
SAY_DEATH = 2,
SAY_IMPENDING_DESPAIR = 3,
SAY_DEFILING_HORROR = 4,
};
enum Spells
{
SPELL_QUIVERING_STRIKE = 72422,
SPELL_IMPENDING_DESPAIR = 72426,
SPELL_DEFILING_HORROR = 72435,
SPELL_QUIVERING_STRIKE = 72422,
SPELL_IMPENDING_DESPAIR = 72426,
SPELL_DEFILING_HORROR = 72435,
};
enum Events
{
EVENT_NONE,
EVENT_QUIVERING_STRIKE,
EVENT_QUIVERING_STRIKE = 1,
EVENT_IMPENDING_DESPAIR,
EVENT_DEFILING_HORROR,
EVENT_UNROOT,
};
const uint32 hopelessnessId[3] = { 72395, 72396, 72397 };
constexpr uint32 hopelessnessId[3] = { 72395, 72396, 72397 };
class boss_falric : public CreatureScript
struct boss_falric : public BossAI
{
public:
boss_falric() : CreatureScript("boss_falric") { }
boss_falric(Creature* creature) : BossAI(creature, DATA_FALRIC) { }
struct boss_falricAI : public ScriptedAI
void Reset() override
{
boss_falricAI(Creature* creature) : ScriptedAI(creature)
{
pInstance = creature->GetInstanceScript();
}
InstanceScript* pInstance;
EventMap events;
uint8 uiHopelessnessCount;
uint16 startFightTimer;
void Reset() override
{
startFightTimer = 0;
uiHopelessnessCount = 0;
me->SetImmuneToAll(true);
me->SetControlled(false, UNIT_STATE_ROOT);
events.Reset();
if (pInstance)
pInstance->SetData(DATA_FALRIC, NOT_STARTED);
}
void JustEngagedWith(Unit* /*who*/) override
{
me->SetImmuneToAll(false);
events.ScheduleEvent(EVENT_QUIVERING_STRIKE, 5s);
events.ScheduleEvent(EVENT_IMPENDING_DESPAIR, 11s);
events.ScheduleEvent(EVENT_DEFILING_HORROR, 20s);
}
void DoAction(int32 a) override
{
if (a == 1)
{
Talk(SAY_AGGRO);
startFightTimer = 8000;
}
}
void UpdateAI(uint32 diff) override
{
if (startFightTimer)
{
if (startFightTimer <= diff)
{
startFightTimer = 0;
me->SetInCombatWithZone();
}
else
startFightTimer -= diff;
}
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
switch (events.ExecuteEvent())
{
case EVENT_QUIVERING_STRIKE:
me->CastSpell(me->GetVictim(), SPELL_QUIVERING_STRIKE, false);
events.ScheduleEvent(EVENT_QUIVERING_STRIKE, 5s);
break;
case EVENT_IMPENDING_DESPAIR:
if (Unit* target = SelectTargetFromPlayerList(45.0f, 0, true))
{
Talk(SAY_IMPENDING_DESPAIR);
me->CastSpell(target, SPELL_IMPENDING_DESPAIR, false);
}
events.ScheduleEvent(EVENT_IMPENDING_DESPAIR, 12s);
break;
case EVENT_DEFILING_HORROR:
Talk(SAY_DEFILING_HORROR);
me->CastSpell((Unit*)nullptr, SPELL_DEFILING_HORROR, false);
me->SetControlled(true, UNIT_STATE_ROOT);
events.DelayEventsToMax(5s, 0);
events.ScheduleEvent(EVENT_UNROOT, 4s);
events.ScheduleEvent(EVENT_DEFILING_HORROR, 20s);
break;
case EVENT_UNROOT:
me->SetControlled(false, UNIT_STATE_ROOT);
break;
}
if ((uiHopelessnessCount == 0 && HealthBelowPct(67)) || (uiHopelessnessCount == 1 && HealthBelowPct(34)) || (uiHopelessnessCount == 2 && HealthBelowPct(11)))
{
if (uiHopelessnessCount)
me->RemoveOwnedAura(sSpellMgr->GetSpellIdForDifficulty(hopelessnessId[uiHopelessnessCount - 1], me));
me->CastSpell((Unit*)nullptr, hopelessnessId[uiHopelessnessCount], true);
++uiHopelessnessCount;
}
if (!me->HasUnitState(UNIT_STATE_ROOT))
DoMeleeAttackIfReady();
}
void JustDied(Unit* /*killer*/) override
{
Talk(SAY_DEATH);
if (pInstance)
pInstance->SetData(DATA_FALRIC, DONE);
}
void KilledUnit(Unit* who) override
{
if (who->IsPlayer())
Talk(SAY_SLAY);
}
void EnterEvadeMode(EvadeReason why) override
{
me->SetControlled(false, UNIT_STATE_ROOT);
ScriptedAI::EnterEvadeMode(why);
if (startFightTimer)
Reset();
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return GetHallsOfReflectionAI<boss_falricAI>(creature);
BossAI::Reset();
_hopelessnessCount = 0;
_startingFight = false;
me->SetImmuneToAll(true);
me->SetControlled(false, UNIT_STATE_ROOT);
}
void JustEngagedWith(Unit* who) override
{
BossAI::JustEngagedWith(who);
me->SetImmuneToAll(false);
events.ScheduleEvent(EVENT_QUIVERING_STRIKE, 5s);
events.ScheduleEvent(EVENT_IMPENDING_DESPAIR, 11s);
events.ScheduleEvent(EVENT_DEFILING_HORROR, 20s);
}
void DoAction(int32 action) override
{
if (action == 1)
{
Talk(SAY_AGGRO);
_startingFight = true;
me->m_Events.AddEventAtOffset([this]()
{
_startingFight = false;
me->SetInCombatWithZone();
}, 8s);
}
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
switch (events.ExecuteEvent())
{
case EVENT_QUIVERING_STRIKE:
DoCastVictim(SPELL_QUIVERING_STRIKE);
events.Repeat(5s);
break;
case EVENT_IMPENDING_DESPAIR:
if (Unit* target = SelectTargetFromPlayerList(45.0f, 0, true))
{
Talk(SAY_IMPENDING_DESPAIR);
DoCast(target, SPELL_IMPENDING_DESPAIR);
}
events.Repeat(12s);
break;
case EVENT_DEFILING_HORROR:
Talk(SAY_DEFILING_HORROR);
DoCastAOE(SPELL_DEFILING_HORROR);
me->SetControlled(true, UNIT_STATE_ROOT);
events.DelayEventsToMax(5s, 0);
events.ScheduleEvent(EVENT_UNROOT, 4s);
events.Repeat(20s);
break;
case EVENT_UNROOT:
me->SetControlled(false, UNIT_STATE_ROOT);
break;
}
if ((_hopelessnessCount == 0 && HealthBelowPct(67))
|| (_hopelessnessCount == 1 && HealthBelowPct(34))
|| (_hopelessnessCount == 2 && HealthBelowPct(11)))
{
if (_hopelessnessCount)
me->RemoveOwnedAura(sSpellMgr->GetSpellIdForDifficulty(hopelessnessId[_hopelessnessCount - 1], me));
DoCastAOE(hopelessnessId[_hopelessnessCount], true);
++_hopelessnessCount;
}
if (!me->HasUnitState(UNIT_STATE_ROOT))
DoMeleeAttackIfReady();
}
void JustDied(Unit* killer) override
{
BossAI::JustDied(killer);
Talk(SAY_DEATH);
}
void KilledUnit(Unit* who) override
{
if (who->IsPlayer())
Talk(SAY_SLAY);
}
void EnterEvadeMode(EvadeReason why) override
{
me->SetControlled(false, UNIT_STATE_ROOT);
BossAI::EnterEvadeMode(why);
if (_startingFight)
Reset();
}
private:
uint8 _hopelessnessCount{};
bool _startingFight{};
};
void AddSC_boss_falric()
{
new boss_falric();
RegisterHallsOfReflectionCreatureAI(boss_falric);
}

View File

@@ -16,160 +16,136 @@
*/
#include "CreatureScript.h"
#include "SpellScriptLoader.h"
#include "halls_of_reflection.h"
#include "ScriptedCreature.h"
#include "SpellAuraEffects.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "halls_of_reflection.h"
enum Yells
{
SAY_AGGRO = 0,
SAY_SLAY = 1,
SAY_DEATH = 2,
SAY_CORRUPTED_FLESH = 3,
SAY_CORRUPTED_WELL = 4,
SAY_AGGRO = 0,
SAY_SLAY = 1,
SAY_DEATH = 2,
SAY_CORRUPTED_FLESH = 3,
SAY_CORRUPTED_WELL = 4,
};
enum Spells
{
SPELL_OBLITERATE = 72360,
SPELL_WELL_OF_CORRUPTION = 72362,
SPELL_CORRUPTED_FLESH = 72363,
SPELL_SHARED_SUFFERING = 72368,
SPELL_OBLITERATE = 72360,
SPELL_WELL_OF_CORRUPTION = 72362,
SPELL_CORRUPTED_FLESH = 72363,
SPELL_SHARED_SUFFERING = 72368,
};
enum Events
{
EVENT_NONE,
EVENT_OBLITERATE,
EVENT_OBLITERATE = 1,
EVENT_WELL_OF_CORRUPTION,
EVENT_CORRUPTED_FLESH,
EVENT_SHARED_SUFFERING,
};
class boss_marwyn : public CreatureScript
struct boss_marwyn : public BossAI
{
public:
boss_marwyn() : CreatureScript("boss_marwyn") { }
boss_marwyn(Creature* creature) : BossAI(creature, DATA_MARWYN) { }
struct boss_marwynAI : public ScriptedAI
void Reset() override
{
boss_marwynAI(Creature* creature) : ScriptedAI(creature)
BossAI::Reset();
_startingFight = false;
me->SetImmuneToAll(true);
}
void JustEngagedWith(Unit* who) override
{
BossAI::JustEngagedWith(who);
me->SetImmuneToAll(false);
events.ScheduleEvent(EVENT_OBLITERATE, 15s);
events.ScheduleEvent(EVENT_WELL_OF_CORRUPTION, 13s);
events.ScheduleEvent(EVENT_CORRUPTED_FLESH, 20s);
events.ScheduleEvent(EVENT_SHARED_SUFFERING, 5s);
}
void DoAction(int32 action) override
{
if (action == 1)
{
pInstance = creature->GetInstanceScript();
}
InstanceScript* pInstance;
EventMap events;
uint16 startFightTimer;
void Reset() override
{
startFightTimer = 0;
me->SetImmuneToAll(true);
events.Reset();
if (pInstance)
pInstance->SetData(DATA_MARWYN, NOT_STARTED);
}
void JustEngagedWith(Unit* /*who*/) override
{
me->SetImmuneToAll(false);
events.ScheduleEvent(EVENT_OBLITERATE, 15s);
events.ScheduleEvent(EVENT_WELL_OF_CORRUPTION, 13s);
events.ScheduleEvent(EVENT_CORRUPTED_FLESH, 20s);
events.ScheduleEvent(EVENT_SHARED_SUFFERING, 5s);
}
void DoAction(int32 a) override
{
if (a == 1)
Talk(SAY_AGGRO);
_startingFight = true;
me->m_Events.AddEventAtOffset([this]()
{
Talk(SAY_AGGRO);
startFightTimer = 8000;
}
_startingFight = false;
me->SetInCombatWithZone();
}, 8s);
}
}
void UpdateAI(uint32 diff) override
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
switch (events.ExecuteEvent())
{
if (startFightTimer)
{
if (startFightTimer <= diff)
case EVENT_OBLITERATE:
if (me->IsWithinMeleeRange(me->GetVictim()))
{
startFightTimer = 0;
me->SetInCombatWithZone();
DoCastVictim(SPELL_OBLITERATE);
events.Repeat(15s);
}
else
startFightTimer -= diff;
}
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
switch (events.ExecuteEvent())
{
case EVENT_OBLITERATE:
if (me->IsWithinMeleeRange(me->GetVictim()))
{
me->CastSpell(me->GetVictim(), SPELL_OBLITERATE, false);
events.ScheduleEvent(EVENT_OBLITERATE, 15s);
}
else
events.ScheduleEvent(EVENT_OBLITERATE, 3s);
break;
case EVENT_WELL_OF_CORRUPTION:
Talk(SAY_CORRUPTED_WELL);
if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true))
me->CastSpell(target, SPELL_WELL_OF_CORRUPTION, false);
events.ScheduleEvent(EVENT_WELL_OF_CORRUPTION, 13s);
break;
case EVENT_CORRUPTED_FLESH:
Talk(SAY_CORRUPTED_FLESH);
me->CastSpell((Unit*)nullptr, SPELL_CORRUPTED_FLESH, false);
events.ScheduleEvent(EVENT_CORRUPTED_FLESH, 20s);
break;
case EVENT_SHARED_SUFFERING:
if (Unit* target = SelectTargetFromPlayerList(200.0f, 0, true))
me->CastSpell(target, SPELL_SHARED_SUFFERING, true);
events.ScheduleEvent(EVENT_SHARED_SUFFERING, 15s);
break;
}
DoMeleeAttackIfReady();
events.Repeat(3s);
break;
case EVENT_WELL_OF_CORRUPTION:
Talk(SAY_CORRUPTED_WELL);
if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true))
DoCast(target, SPELL_WELL_OF_CORRUPTION);
events.Repeat(13s);
break;
case EVENT_CORRUPTED_FLESH:
Talk(SAY_CORRUPTED_FLESH);
DoCastAOE(SPELL_CORRUPTED_FLESH);
events.Repeat(20s);
break;
case EVENT_SHARED_SUFFERING:
if (Unit* target = SelectTargetFromPlayerList(200.0f, 0, true))
DoCast(target, SPELL_SHARED_SUFFERING, true);
events.Repeat(15s);
break;
}
void JustDied(Unit* /*killer*/) override
{
Talk(SAY_DEATH);
if (pInstance)
pInstance->SetData(DATA_MARWYN, DONE);
}
void KilledUnit(Unit* who) override
{
if (who->IsPlayer())
Talk(SAY_SLAY);
}
void EnterEvadeMode(EvadeReason why) override
{
ScriptedAI::EnterEvadeMode(why);
if (startFightTimer)
Reset();
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return GetHallsOfReflectionAI<boss_marwynAI>(creature);
DoMeleeAttackIfReady();
}
void JustDied(Unit* killer) override
{
BossAI::JustDied(killer);
Talk(SAY_DEATH);
}
void KilledUnit(Unit* who) override
{
if (who->IsPlayer())
Talk(SAY_SLAY);
}
void EnterEvadeMode(EvadeReason why) override
{
BossAI::EnterEvadeMode(why);
if (_startingFight)
Reset();
}
private:
bool _startingFight{};
};
enum SharedSufferingAura
@@ -186,25 +162,37 @@ class spell_hor_shared_suffering_aura : public AuraScript
return ValidateSpellInfo({ SPELL_SHARED_SUFFERING_DAMAGE });
}
void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
{
if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_ENEMY_SPELL) // dispelled
if (Unit* caster = GetCaster())
if (Map* map = caster->FindMap())
if (Aura* a = aurEff->GetBase())
{
uint32 count = 0;
uint32 ticks = 0;
uint32 dmgPerTick = a->GetSpellInfo()->Effects[0].BasePoints;
Map::PlayerList const& pl = map->GetPlayers();
for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
if (Player* p = itr->GetSource())
if (p->IsAlive())
++count;
ticks = (a->GetDuration() / int32(a->GetSpellInfo()->Effects[0].Amplitude)) + 1;
int32 dmg = (ticks * dmgPerTick) / count;
caster->CastCustomSpell(GetTarget(), SPELL_SHARED_SUFFERING_DAMAGE, nullptr, &dmg, nullptr, true);
}
if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_ENEMY_SPELL)
return;
Unit* caster = GetCaster();
if (!caster)
return;
Map* map = caster->FindMap();
if (!map)
return;
Aura* aura = aurEff->GetBase();
if (!aura)
return;
uint32 count = 0;
uint32 dmgPerTick = aura->GetSpellInfo()->Effects[0].BasePoints;
Map::PlayerList const& pl = map->GetPlayers();
for (auto itr = pl.begin(); itr != pl.end(); ++itr)
if (Player* p = itr->GetSource())
if (p->IsAlive())
++count;
if (!count)
return;
uint32 ticks = (aura->GetDuration() / int32(aura->GetSpellInfo()->Effects[0].Amplitude)) + 1;
int32 dmg = (ticks * dmgPerTick) / count;
caster->CastCustomSpell(GetTarget(), SPELL_SHARED_SUFFERING_DAMAGE, nullptr, &dmg, nullptr, true);
}
void Register() override
@@ -215,6 +203,6 @@ class spell_hor_shared_suffering_aura : public AuraScript
void AddSC_boss_marwyn()
{
new boss_marwyn();
RegisterHallsOfReflectionCreatureAI(boss_marwyn);
RegisterSpellScript(spell_hor_shared_suffering_aura);
}

View File

@@ -44,6 +44,7 @@ enum Data
ACTION_DELETE_ICE_WALL,
DATA_WAVE_NUMBER,
DATA_LK_BATTLE,// in progress
DATA_SHIP_CAPTAIN,
};
enum Creatures
@@ -108,6 +109,17 @@ enum BatteredHiltStatusFlags
BHSF_FINISHED = 4,
};
enum HoRPersistentData
{
PERSISTENT_DATA_INTRO,
PERSISTENT_DATA_FROSTSWORN_GENERAL,
PERSISTENT_DATA_LK_INTRO,
PERSISTENT_DATA_BATTERED_HILT,
PERSISTENT_DATA_COUNT
};
constexpr uint8 MAX_SPIRITUAL_REFLECTIONS = 5;
constexpr uint8 MAX_ICE_WALL_TARGETS = 4;
#define NUM_OF_TRASH 34
#define MAX_DIST_FROM_CENTER_IN_COMBAT 70.5f
#define MAX_DIST_FROM_CENTER_TO_START 40.0f
@@ -291,6 +303,11 @@ enum hMisc
// Battered Hilt - Summon Quel'Delar
SPELL_SUMMON_EVIL_QUEL = 69966,
SPELL_QUEL_DELAR_HATRED = 70300,
SPELL_FROSTMOURNE_ALTAR_GLOW = 70720,
SPELL_UTHER_HOLY_LIGHT_VISUAL = 73036,
SPELL_QUELDELAR_COMPULSION = 70013,
SPELL_QUELDELAR_WILL = 70698,
};
const uint32 allowedCompositions[8][5] =
@@ -390,4 +407,7 @@ inline AI* GetHallsOfReflectionAI(T* obj)
return GetInstanceAI<AI>(obj, HallsOfReflectionScriptName);
}
#define RegisterHallsOfReflectionCreatureAI(ai_name) \
RegisterCreatureAIWithFactory(ai_name, GetHallsOfReflectionAI)
#endif