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
@@ -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 bool InstanceScript::IsEncounterInProgress() const
{ {
for (std::vector<BossInfo>::const_iterator itr = bosses.begin(); itr != bosses.end(); ++itr) for (std::vector<BossInfo>::const_iterator itr = bosses.begin(); itr != bosses.end(); ++itr)
+13 -3
View File
@@ -193,11 +193,21 @@ public:
//Called when a player enters/leaves water bodies. //Called when a player enters/leaves water bodies.
virtual void OnPlayerInWaterStateUpdate(Player* /*player*/, bool /*inWater*/) {} virtual void OnPlayerInWaterStateUpdate(Player* /*player*/, bool /*inWater*/) {}
//Handle open / close objects /**
//use HandleGameObject(ObjectGuid::Empty, boolen, GO); in OnObjectCreate in instance scripts * @brief Open or close a GameObject by GUID.
//use HandleGameObject(GUID, boolen, nullptr); in any other script * @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); 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 //change active state of doors or buttons
void DoUseDoorOrButton(ObjectGuid guid, uint32 withRestoreTime = 0, bool useAlternativeState = false); void DoUseDoorOrButton(ObjectGuid guid, uint32 withRestoreTime = 0, bool useAlternativeState = false);
@@ -16,173 +16,151 @@
*/ */
#include "CreatureScript.h" #include "CreatureScript.h"
#include "halls_of_reflection.h"
#include "ScriptedCreature.h" #include "ScriptedCreature.h"
#include "SpellMgr.h" #include "SpellMgr.h"
#include "halls_of_reflection.h"
enum Yells enum Yells
{ {
SAY_AGGRO = 0, SAY_AGGRO = 0,
SAY_SLAY = 1, SAY_SLAY = 1,
SAY_DEATH = 2, SAY_DEATH = 2,
SAY_IMPENDING_DESPAIR = 3, SAY_IMPENDING_DESPAIR = 3,
SAY_DEFILING_HORROR = 4, SAY_DEFILING_HORROR = 4,
}; };
enum Spells enum Spells
{ {
SPELL_QUIVERING_STRIKE = 72422, SPELL_QUIVERING_STRIKE = 72422,
SPELL_IMPENDING_DESPAIR = 72426, SPELL_IMPENDING_DESPAIR = 72426,
SPELL_DEFILING_HORROR = 72435, SPELL_DEFILING_HORROR = 72435,
}; };
enum Events enum Events
{ {
EVENT_NONE, EVENT_QUIVERING_STRIKE = 1,
EVENT_QUIVERING_STRIKE,
EVENT_IMPENDING_DESPAIR, EVENT_IMPENDING_DESPAIR,
EVENT_DEFILING_HORROR, EVENT_DEFILING_HORROR,
EVENT_UNROOT, 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(Creature* creature) : BossAI(creature, DATA_FALRIC) { }
boss_falric() : CreatureScript("boss_falric") { }
struct boss_falricAI : public ScriptedAI void Reset() override
{ {
boss_falricAI(Creature* creature) : ScriptedAI(creature) BossAI::Reset();
{ _hopelessnessCount = 0;
pInstance = creature->GetInstanceScript(); _startingFight = false;
} me->SetImmuneToAll(true);
me->SetControlled(false, UNIT_STATE_ROOT);
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);
} }
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() void AddSC_boss_falric()
{ {
new boss_falric(); RegisterHallsOfReflectionCreatureAI(boss_falric);
} }
@@ -16,160 +16,136 @@
*/ */
#include "CreatureScript.h" #include "CreatureScript.h"
#include "SpellScriptLoader.h"
#include "halls_of_reflection.h"
#include "ScriptedCreature.h" #include "ScriptedCreature.h"
#include "SpellAuraEffects.h" #include "SpellAuraEffects.h"
#include "SpellScript.h" #include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "halls_of_reflection.h"
enum Yells enum Yells
{ {
SAY_AGGRO = 0, SAY_AGGRO = 0,
SAY_SLAY = 1, SAY_SLAY = 1,
SAY_DEATH = 2, SAY_DEATH = 2,
SAY_CORRUPTED_FLESH = 3, SAY_CORRUPTED_FLESH = 3,
SAY_CORRUPTED_WELL = 4, SAY_CORRUPTED_WELL = 4,
}; };
enum Spells enum Spells
{ {
SPELL_OBLITERATE = 72360, SPELL_OBLITERATE = 72360,
SPELL_WELL_OF_CORRUPTION = 72362, SPELL_WELL_OF_CORRUPTION = 72362,
SPELL_CORRUPTED_FLESH = 72363, SPELL_CORRUPTED_FLESH = 72363,
SPELL_SHARED_SUFFERING = 72368, SPELL_SHARED_SUFFERING = 72368,
}; };
enum Events enum Events
{ {
EVENT_NONE, EVENT_OBLITERATE = 1,
EVENT_OBLITERATE,
EVENT_WELL_OF_CORRUPTION, EVENT_WELL_OF_CORRUPTION,
EVENT_CORRUPTED_FLESH, EVENT_CORRUPTED_FLESH,
EVENT_SHARED_SUFFERING, EVENT_SHARED_SUFFERING,
}; };
class boss_marwyn : public CreatureScript struct boss_marwyn : public BossAI
{ {
public: boss_marwyn(Creature* creature) : BossAI(creature, DATA_MARWYN) { }
boss_marwyn() : CreatureScript("boss_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(); Talk(SAY_AGGRO);
} _startingFight = true;
me->m_Events.AddEventAtOffset([this]()
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 = false;
startFightTimer = 8000; 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) case EVENT_OBLITERATE:
{ if (me->IsWithinMeleeRange(me->GetVictim()))
if (startFightTimer <= diff)
{ {
startFightTimer = 0; DoCastVictim(SPELL_OBLITERATE);
me->SetInCombatWithZone(); events.Repeat(15s);
} }
else else
startFightTimer -= diff; events.Repeat(3s);
} break;
case EVENT_WELL_OF_CORRUPTION:
if (!UpdateVictim()) Talk(SAY_CORRUPTED_WELL);
return; if (Unit* target = SelectTargetFromPlayerList(40.0f, 0, true))
DoCast(target, SPELL_WELL_OF_CORRUPTION);
events.Update(diff); events.Repeat(13s);
break;
if (me->HasUnitState(UNIT_STATE_CASTING)) case EVENT_CORRUPTED_FLESH:
return; Talk(SAY_CORRUPTED_FLESH);
DoCastAOE(SPELL_CORRUPTED_FLESH);
switch (events.ExecuteEvent()) events.Repeat(20s);
{ break;
case EVENT_OBLITERATE: case EVENT_SHARED_SUFFERING:
if (me->IsWithinMeleeRange(me->GetVictim())) if (Unit* target = SelectTargetFromPlayerList(200.0f, 0, true))
{ DoCast(target, SPELL_SHARED_SUFFERING, true);
me->CastSpell(me->GetVictim(), SPELL_OBLITERATE, false); events.Repeat(15s);
events.ScheduleEvent(EVENT_OBLITERATE, 15s); break;
}
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();
} }
void JustDied(Unit* /*killer*/) override DoMeleeAttackIfReady();
{
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);
} }
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 enum SharedSufferingAura
@@ -186,25 +162,37 @@ class spell_hor_shared_suffering_aura : public AuraScript
return ValidateSpellInfo({ SPELL_SHARED_SUFFERING_DAMAGE }); 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 (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_ENEMY_SPELL)
if (Unit* caster = GetCaster()) return;
if (Map* map = caster->FindMap())
if (Aura* a = aurEff->GetBase()) Unit* caster = GetCaster();
{ if (!caster)
uint32 count = 0; return;
uint32 ticks = 0;
uint32 dmgPerTick = a->GetSpellInfo()->Effects[0].BasePoints; Map* map = caster->FindMap();
Map::PlayerList const& pl = map->GetPlayers(); if (!map)
for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) return;
if (Player* p = itr->GetSource())
if (p->IsAlive()) Aura* aura = aurEff->GetBase();
++count; if (!aura)
ticks = (a->GetDuration() / int32(a->GetSpellInfo()->Effects[0].Amplitude)) + 1; return;
int32 dmg = (ticks * dmgPerTick) / count;
caster->CastCustomSpell(GetTarget(), SPELL_SHARED_SUFFERING_DAMAGE, nullptr, &dmg, nullptr, true); 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 void Register() override
@@ -215,6 +203,6 @@ class spell_hor_shared_suffering_aura : public AuraScript
void AddSC_boss_marwyn() void AddSC_boss_marwyn()
{ {
new boss_marwyn(); RegisterHallsOfReflectionCreatureAI(boss_marwyn);
RegisterSpellScript(spell_hor_shared_suffering_aura); RegisterSpellScript(spell_hor_shared_suffering_aura);
} }
File diff suppressed because it is too large Load Diff
@@ -44,6 +44,7 @@ enum Data
ACTION_DELETE_ICE_WALL, ACTION_DELETE_ICE_WALL,
DATA_WAVE_NUMBER, DATA_WAVE_NUMBER,
DATA_LK_BATTLE,// in progress DATA_LK_BATTLE,// in progress
DATA_SHIP_CAPTAIN,
}; };
enum Creatures enum Creatures
@@ -108,6 +109,17 @@ enum BatteredHiltStatusFlags
BHSF_FINISHED = 4, 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 NUM_OF_TRASH 34
#define MAX_DIST_FROM_CENTER_IN_COMBAT 70.5f #define MAX_DIST_FROM_CENTER_IN_COMBAT 70.5f
#define MAX_DIST_FROM_CENTER_TO_START 40.0f #define MAX_DIST_FROM_CENTER_TO_START 40.0f
@@ -291,6 +303,11 @@ enum hMisc
// Battered Hilt - Summon Quel'Delar // Battered Hilt - Summon Quel'Delar
SPELL_SUMMON_EVIL_QUEL = 69966, 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] = const uint32 allowedCompositions[8][5] =
@@ -390,4 +407,7 @@ inline AI* GetHallsOfReflectionAI(T* obj)
return GetInstanceAI<AI>(obj, HallsOfReflectionScriptName); return GetInstanceAI<AI>(obj, HallsOfReflectionScriptName);
} }
#define RegisterHallsOfReflectionCreatureAI(ai_name) \
RegisterCreatureAIWithFactory(ai_name, GetHallsOfReflectionAI)
#endif #endif