fix(Scripts/EoE): Implement Malygos Vortex (#24726)

Co-authored-by: Manuel Carrasco (maanuel) <noreply@trinitycore.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Manuel Carrasco <manuel@carrasco.dev>
Co-authored-by: Trista <Trista@users.noreply.github.com>
This commit is contained in:
Andrew
2026-02-21 19:50:14 -03:00
committed by GitHub
parent 052c36224a
commit 0994714bd1
5 changed files with 191 additions and 165 deletions

View File

@@ -19,8 +19,6 @@
#include "CreatureScript.h"
#include "GameObjectAI.h"
#include "GameObjectScript.h"
#include "MoveSplineInit.h"
#include "Opcodes.h"
#include "PassiveAI.h"
#include "Player.h"
#include "ScriptMgr.h"
@@ -61,9 +59,11 @@ enum MalygosSpells
SPELL_ARCANE_BREATH = 56272,
SPELL_ARCANE_STORM = 61693,
SPELL_VORTEX_1 = 56237,
SPELL_VORTEX_VISUAL = 55873,
SPELL_VORTEX_DUMMY = 56105,
SPELL_VORTEX_TELEPORT = 73040,
SPELL_VORTEX_CONTROL_VEHICLE = 56263,
SPELL_FREEZE_ANIM = 55883,
SPELL_ARCANE_OVERLOAD = 56430,
SPELL_ARCANE_OVERLOAD_SUMMON = 56429,
@@ -195,6 +195,7 @@ struct boss_malygos : public BossAI
uint32 timer1, timer2;
uint8 IntroCounter;
bool bLockHealthCheck;
bool _executingVortex;
void InitializeAI() override
{
@@ -210,6 +211,7 @@ struct boss_malygos : public BossAI
timer2 = INTRO_MOVEMENT_INTERVAL;
IntroCounter = 0;
bLockHealthCheck = false;
_executingVortex = false;
SetInvincibility(true);
me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE);
@@ -223,7 +225,7 @@ struct boss_malygos : public BossAI
void MovementInform(uint32 type, uint32 id) override
{
if (type != POINT_MOTION_TYPE)
if (type != POINT_MOTION_TYPE && type != EFFECT_MOTION_TYPE)
return;
switch (id)
@@ -245,9 +247,8 @@ struct boss_malygos : public BossAI
events.RescheduleEvent(EVENT_INTRO_LAND, 0ms, 1);
break;
case MI_POINT_VORTEX_CENTER:
if (Creature* trigger = me->SummonCreature(NPC_WORLD_TRIGGER_LAOI, CenterPos, TEMPSUMMON_TIMED_DESPAWN, 15000))
trigger->CastSpell(trigger, SPELL_VORTEX_VISUAL, true);
events.RescheduleEvent(EVENT_START_VORTEX_REAL, 1s, 1);
me->GetMotionMaster()->MoveIdle();
events.RescheduleEvent(EVENT_START_VORTEX_REAL, 0ms, 1);
break;
case MI_POINT_CENTER_GROUND_PH_2:
events.RescheduleEvent(EVENT_START_PHASE_2_FLY_UP, 0ms, 1);
@@ -384,10 +385,20 @@ struct boss_malygos : public BossAI
break;
}
case EVENT_SPELL_ARCANE_BREATH:
if (_executingVortex)
{
events.Repeat(12s, 15s);
break;
}
me->CastSpell(me->GetVictim(), SPELL_ARCANE_BREATH, false);
events.Repeat(12s, 15s);
break;
case EVENT_SPELL_ARCANE_STORM:
if (_executingVortex)
{
events.Repeat(10s, 15s);
break;
}
me->CastCustomSpell(SPELL_ARCANE_STORM, SPELLVALUE_MAX_TARGETS, DUNGEON_MODE(5, 12), me, true);
events.Repeat(10s, 15s);
break;
@@ -407,6 +418,7 @@ struct boss_malygos : public BossAI
break;
case EVENT_START_VORTEX_0:
{
_executingVortex = true;
bLockHealthCheck = true;
Talk(SAY_MAGIC_BLAST);
EntryCheckPredicate pred(NPC_POWER_SPARK);
@@ -425,7 +437,9 @@ struct boss_malygos : public BossAI
}
break;
case EVENT_VORTEX_FLY_TO_CENTER:
me->GetMotionMaster()->MovePoint(MI_POINT_VORTEX_CENTER, CenterPos.GetPositionX(), CenterPos.GetPositionY(), CenterPos.GetPositionZ() + 20.0f);
me->SetReactState(REACT_PASSIVE);
me->AttackStop();
me->GetMotionMaster()->MovePoint(MI_POINT_VORTEX_CENTER, VortexPos);
break;
case EVENT_START_VORTEX_REAL:
{
@@ -433,63 +447,9 @@ struct boss_malygos : public BossAI
me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_CUSTOM_SPELL_01);
me->HandleEmoteCommand(EMOTE_STATE_CUSTOM_SPELL_01);
Position pos;
float angle = (me->GetOrientation() >= M_PI / 4 ? me->GetOrientation() - M_PI / 4 : 7 * M_PI / 4 + me->GetOrientation());
pos.m_positionX = CenterPos.GetPositionX() + cos(angle) * 40.0f;
pos.m_positionY = CenterPos.GetPositionY() + std::sin(angle) * 40.0f;
pos.m_positionZ = CenterPos.GetPositionZ() + 20.0f;
pos.SetOrientation(pos.GetAngle(&CenterPos));
if (Creature* vp = me->SummonCreature(NPC_WORLD_TRIGGER_LAOI, pos, TEMPSUMMON_TIMED_DESPAWN, 14000))
{
vp->SetDisableGravity(true);
me->GetMap()->DoForAllPlayers([&](Player* player)
{
if (!player->IsAlive() || player->IsGameMaster())
return;
Position plrpos;
float playerAngle = CenterPos.GetAngle(player);
plrpos.m_positionX = CenterPos.GetPositionX() + cos(playerAngle) * 5.0f;
plrpos.m_positionY = CenterPos.GetPositionY() + std::sin(playerAngle) * 5.0f;
plrpos.m_positionZ = CenterPos.GetPositionZ() + 18.0f;
plrpos.SetOrientation(plrpos.GetAngle(&CenterPos));
if (Creature* vortex = me->SummonCreature(NPC_VORTEX, plrpos, TEMPSUMMON_TIMED_DESPAWN, 15000))
{
player->CastSpell(player, SPELL_FREEZE_ANIM, true);
player->CastSpell(vortex, SPELL_VORTEX_CONTROL_VEHICLE, true);
if (!player->GetVehicle()) // didn't work somehow, try again with a different way, if fails - break
{
player->EnterVehicle(vortex, 0);
if (!player->GetVehicle())
return;
}
//player->ClearUnitState(UNIT_STATE_ONVEHICLE);
Movement::MoveSplineInit init(player); // TODO: has to be removed and handled with vehicle exit and vehicle enter code
init.MoveTo(CenterPos.GetPositionX(), CenterPos.GetPositionY(), CenterPos.GetPositionZ());
init.SetFacing(player->GetOrientation());
init.SetTransportExit();
init.Launch();
player->SetUnitMovementFlags(MOVEMENTFLAG_NONE);
player->SetDisableGravity(true);
WorldPacket data(SMSG_SPLINE_MOVE_UNROOT, 8);
data << player->GetPackGUID();
player->SendMessageToSet(&data, true);
sScriptMgr->AnticheatSetUnderACKmount(player);
player->SetGuidValue(PLAYER_FARSIGHT, vp->GetGUID());
vortex->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
}
});
}
events.RescheduleEvent(EVENT_VORTEX_LAND_0, 11s, 1);
me->CastSpell(me, SPELL_VORTEX_1, true);
me->CastSpell(me, SPELL_VORTEX_VISUAL, true);
me->CastSpell(me, SPELL_VORTEX_DUMMY, true);
break;
}
case EVENT_VORTEX_LAND_0:
@@ -497,10 +457,12 @@ struct boss_malygos : public BossAI
break;
case EVENT_VORTEX_LAND_1:
{
_executingVortex = false;
bLockHealthCheck = false;
EntryCheckPredicate pred(NPC_POWER_SPARK);
summons.DoAction(ACTION_POWER_SPARK_FOLLOW, pred);
me->RemoveUnitFlag(UNIT_FLAG_PACIFIED);
me->SetReactState(REACT_AGGRESSIVE);
me->ResumeChasingVictim();
events.RescheduleEvent(EVENT_START_VORTEX_0, 60s, 1);
break;
@@ -779,98 +741,6 @@ struct boss_malygos : public BossAI
}
};
#define VORTEX_DEFAULT_DIFF 250
#define VORTEX_TRAVEL_TIME 3000
struct npc_vortex_ride : public VehicleAI
{
npc_vortex_ride(Creature* creature) : VehicleAI(creature)
{
vortexRadius = urand(22, 28);
float h = urand(15, 30);
float angle = CenterPos.GetAngle(me);
Position pos;
pos.m_positionX = CenterPos.GetPositionX() + vortexRadius * cos(angle);
pos.m_positionY = CenterPos.GetPositionY() + vortexRadius * std::sin(angle);
pos.m_positionZ = CenterPos.GetPositionZ() + h;
pos.SetOrientation(pos.GetAngle(&CenterPos));
me->SetPosition(pos);
timer = 0;
despawnTimer = 9500;
bUpdatedFlying = false;
}
uint32 timer;
uint32 despawnTimer;
bool bUpdatedFlying;
float vortexRadius;
void PassengerBoarded(Unit* pass, int8 /*seat*/, bool apply) override
{
if (!pass || apply || !pass->IsPlayer())
return;
Player* player = pass->ToPlayer();
float speed = player->GetDistance(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()) / (1.0f * 0.001f);
player->SetDisableGravity(false); // packet only would lead to issues elsewhere
player->GetMotionMaster()->MoveCharge(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), speed);
player->RemoveAura(SPELL_FREEZE_ANIM);
player->SetGuidValue(PLAYER_FARSIGHT, ObjectGuid::Empty);
sScriptMgr->AnticheatSetCanFlybyServer(player, false);
sScriptMgr->AnticheatSetUnderACKmount(player);
}
void UpdateAI(uint32 diff) override
{
if (despawnTimer <= diff)
{
despawnTimer = 0;
me->UpdatePosition(CenterPos.GetPositionX(), CenterPos.GetPositionY(), CenterPos.GetPositionZ() + 18.0f, 0.0f, true);
me->StopMovingOnCurrentPos();
if (Vehicle* vehicle = me->GetVehicleKit())
vehicle->RemoveAllPassengers();
me->DespawnOrUnsummon();
return;
}
else
despawnTimer -= diff;
if (timer <= diff)
{
float angle = CenterPos.GetAngle(me);
float newangle = angle + 2 * M_PI / ((float)VORTEX_TRAVEL_TIME / VORTEX_DEFAULT_DIFF);
if (newangle >= 2 * M_PI)
newangle -= 2 * M_PI;
float newx = CenterPos.GetPositionX() + vortexRadius * cos(newangle);
float newy = CenterPos.GetPositionY() + vortexRadius * std::sin(newangle);
float arcangle = me->GetAngle(newx, newy);
float dist = 2 * me->GetDistance2d(newx, newy);
if (me->GetVehicleKit())
if (Unit* pass = me->GetVehicleKit()->GetPassenger(0))
if (Player* player = pass->ToPlayer())
{
if (!bUpdatedFlying && timer)
{
bUpdatedFlying = true;
player->SetDisableGravity(true);
}
player->SendMonsterMove(me->GetPositionX() + dist * cos(arcangle), me->GetPositionY() + dist * std::sin(arcangle), me->GetPositionZ(), VORTEX_DEFAULT_DIFF * 2, SPLINEFLAG_FLYING);
me->Relocate(newx, newy);
}
timer = (diff - timer <= VORTEX_DEFAULT_DIFF) ? VORTEX_DEFAULT_DIFF - (diff - timer) : 0;
}
else
timer -= diff;
}
void AttackStart(Unit* /*who*/) override {}
void MoveInLineOfSight(Unit* /*who*/) override {}
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override { damage = 0; }
};
struct npc_power_spark : public NullCreatureAI
{
npc_power_spark(Creature* creature) : NullCreatureAI(creature)
@@ -948,7 +818,15 @@ struct npc_power_spark : public NullCreatureAI
{
if (_instance)
if (Creature* malygos = _instance->GetCreature(DATA_MALYGOS))
{
if (malygos->HasAura(SPELL_VORTEX_1))
{
me->GetMotionMaster()->MoveIdle();
_moveTimer = 2000;
return;
}
me->GetMotionMaster()->MovePoint(0, *malygos);
}
_moveTimer = 2000;
}
else
@@ -1273,6 +1151,80 @@ struct go_the_focusing_iris : public GameObjectAI
}
};
// 56105 - Vortex
class spell_malygos_vortex_dummy : public SpellScript
{
PrepareSpellScript(spell_malygos_vortex_dummy);
bool Load() override
{
return GetCaster()->IsCreature();
}
void HandleScript(SpellEffIndex /*effIndex*/)
{
Creature* caster = GetCaster()->ToCreature();
if (!caster)
return;
if (InstanceScript* instance = caster->GetInstanceScript())
instance->SetData(DATA_VORTEX_HANDLING, 0);
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_malygos_vortex_dummy::HandleScript, EFFECT_0, SPELL_EFFECT_DUMMY);
}
};
// 55873 - Vortex
class spell_malygos_vortex_visual : public AuraScript
{
PrepareAuraScript(spell_malygos_vortex_visual);
bool Load() override
{
return GetCaster()->IsCreature();
}
bool Validate(SpellInfo const* /*spell*/) override
{
return ValidateSpellInfo({ SPELL_VORTEX_1, SPELL_VORTEX_TELEPORT });
}
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
Creature* caster = GetCaster()->ToCreature();
if (!caster)
return;
for (auto const* ref : caster->GetThreatMgr().GetUnsortedThreatList())
{
if (Player* player = ref->GetVictim()->ToPlayer())
{
if (player->IsGameMaster())
continue;
if (InstanceScript* instance =caster->GetInstanceScript())
{
if (Creature* trigger =ObjectAccessor::GetCreature(*caster, instance->GetGuidData(DATA_VORTEX_TRIGGER)))
trigger->CastSpell(player, SPELL_VORTEX_TELEPORT, true);
}
}
}
caster->GetMotionMaster()->MoveLand(MI_POINT_VORTEX_LAND, VortexLandPos);
caster->RemoveAura(SPELL_VORTEX_1);
}
void Register() override
{
AfterEffectRemove += AuraEffectRemoveFn(
spell_malygos_vortex_visual::OnRemove,
EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
};
class spell_eoe_ph3_surge_of_power : public SpellScript
{
PrepareSpellScript(spell_eoe_ph3_surge_of_power);
@@ -1363,7 +1315,6 @@ void AddSC_boss_malygos()
{
RegisterEoECreatureAI(boss_malygos);
RegisterEoECreatureAI(npc_power_spark);
RegisterEoECreatureAI(npc_vortex_ride);
RegisterEoECreatureAI(npc_alexstrasza);
RegisterGameObjectAI(go_the_focusing_iris);
RegisterEoECreatureAI(npc_nexus_lord);
@@ -1374,4 +1325,6 @@ void AddSC_boss_malygos()
RegisterSpellScript(spell_wyrmrest_skytalon_ride_red_dragon_buddy_trigger);
RegisterSpellScript(spell_eoe_ph3_surge_of_power);
RegisterSpellScript(spell_malygos_vortex_dummy);
RegisterSpellScript(spell_malygos_vortex_visual);
}

View File

@@ -60,6 +60,8 @@ enum Data
DATA_IRIS_ACTIVATED,
DATA_SET_IRIS_INACTIVE,
DATA_HIDE_IRIS_AND_PORTAL,
DATA_VORTEX_HANDLING,
DATA_VORTEX_TRIGGER,
};
enum eSpells
@@ -78,7 +80,9 @@ enum eSpells
SPELL_ALEXSTRASZA_GIFT = 61028,
SPELL_SUMMON_RED_DRAGON_BUDDY = 56070,
SPELL_RIDE_RED_DRAGON = 56072
SPELL_RIDE_RED_DRAGON = 56072,
SPELL_VORTEX_4 = 55853,
};
enum eAchiev
@@ -116,6 +120,8 @@ enum AlexstraszaEvents
/*** POSITIONS/WAYPOINTS BELOW ***/
const Position CenterPos = {754.395f, 1301.27f, 266.10f, 0.0f};
const Position VortexPos = {754.393f, 1301.27f, 292.91f, 0.0f};
const Position VortexLandPos = {754.362f, 1301.61f, 266.17f, 0.0f};
const Position FourSidesPos[] =
{

View File

@@ -48,6 +48,52 @@ struct instance_eye_of_eternity : public InstanceScript
}
bool _pokeAchievementValid = false;
GuidVector _vortexTriggers;
void OnCreatureCreate(Creature* creature) override
{
InstanceScript::OnCreatureCreate(creature);
if (creature->GetEntry() == NPC_VORTEX)
_vortexTriggers.push_back(creature->GetGUID());
}
ObjectGuid GetGuidData(uint32 data) const override
{
if (data == DATA_VORTEX_TRIGGER)
return !_vortexTriggers.empty() ? _vortexTriggers.front() : ObjectGuid::Empty;
return InstanceScript::GetGuidData(data);
}
void VortexHandling()
{
Creature* malygos = GetCreature(DATA_MALYGOS);
if (!malygos)
return;
for (ObjectGuid const& guid : _vortexTriggers)
{
uint8 counter = 0;
if (Creature* trigger = instance->GetCreature(guid))
{
for (auto* ref : malygos->GetThreatMgr().GetUnsortedThreatList())
{
if (counter >= 5)
break;
if (Player* player = ref->GetVictim()->ToPlayer())
{
if (player->IsGameMaster() || player->HasAura(SPELL_VORTEX_4))
continue;
player->CastSpell(trigger, SPELL_VORTEX_4, true);
counter++;
}
}
}
}
}
void OnPlayerEnter(Player* player) override
{
@@ -108,6 +154,9 @@ struct instance_eye_of_eternity : public InstanceScript
if (GameObject* portal = GetGameObject(DATA_EXIT_PORTAL))
portal->SetPhaseMask(2, true);
break;
case DATA_VORTEX_HANDLING:
VortexHandling();
break;
}
}