4 Commits
Author SHA1 Message Date
luis d9fa1aa73a Missing followers 2026-09-17 20:01:21 -03:00
luis 6f93adbb2e typo fix 2026-09-17 19:14:22 -03:00
luis 3f5e997afd move 2026-09-17 19:13:47 -03:00
luis f19ee3937b Core/ Script HOI 2026-09-17 18:16:46 -03:00
9 changed files with 660 additions and 190 deletions
+2 -2
View File
@@ -942,12 +942,12 @@ void Map::Update(uint32 t_diff)
if (!m_mapRefManager.empty() || !m_activeNonPlayers.empty())
ProcessRelocationNotifies(t_diff);
sScriptMgr->OnMapUpdate(this, t_diff);
//WowCommunity
UpdateWorldScenarios(t_diff);
//WowCommunity
sScriptMgr->OnMapUpdate(this, t_diff);
TC_METRIC_VALUE("map_creatures", uint64(GetObjectsStore().Size<Creature>()),
TC_METRIC_TAG("map_id", std::to_string(GetId())),
TC_METRIC_TAG("map_instanceid", std::to_string(GetInstanceId())));
@@ -105,13 +105,6 @@ namespace WorldPackets::Garrison
return data;
}
// Sniff-verified for 12.0.1.66102 ? observed in the 8316-byte SMSG_GET_GARRISON_INFO_RESULT
// body (SNIFF_AUDIT_12.0.1.66102.md -5.3). int32 ItemFileDataID is always present
// (sometimes zero, sometimes a real DBD ID like 1599042); the trailing OptionalInit bit
// gates an optional ItemInstance blob.
// The brief's Deserialize_JamGarrisonMissionReward @ 0x7FF75C1754C0 (Byte-gated VarUInt32)
// decodes the JAM-mirror code path, NOT this dedicated SMSG. Both formats coexist for
// different sub-collection sync paths.
ByteBuffer& operator<<(ByteBuffer& data, GarrisonMissionReward const& missionRewardItem)
{
data << int32(missionRewardItem.ItemID);
@@ -121,9 +114,7 @@ namespace WorldPackets::Garrison
data << uint32(missionRewardItem.FollowerXP);
data << uint32(missionRewardItem.GarrMssnBonusAbilityID);
data << int32(missionRewardItem.ItemFileDataID);
// itemInstance = OptionalInit bit + FlushBits. This is the SNIFF-CONFIRMED encoding (ADD_MISSION_RESULT
// decoded byte-exact / 0 trailing bytes). The talent socketData is the SAME reflection kind and must match
// this (bit), NOT a uint32 count - see operator<<(GarrisonTalent).
data << OptionalInit(missionRewardItem.ItemInstance);
data.FlushBits();
@@ -133,13 +124,6 @@ namespace WorldPackets::Garrison
return data;
}
// Sniff-verified for 12.0.1.66102 (SNIFF_AUDIT_12.0.1.66102.md -3.15, embedded in
// the 188-byte SMSG_GET_GARRISON_INFO_RESULT). MissionScalar(float) sits between
// Flags and ContentTuningID followed by 3 size fields. Note: SMSG_GARRISON_START_MISSION_RESULT
// embeds a Mission whose exact byte layout has a residual ~2-byte misalignment vs this
// writer (audit -4.1) ? needs IDA pseudocode of Build_GarrisonStartMissionResult to fully
// resolve. The brief's Deserialize_JamGarrisonMission @ 0x7FF75C1755E0 with Currency/
// BonusActions/AutoMissionData tail is the JAM-mirror path, NOT this dedicated SMSG.
ByteBuffer& operator<<(ByteBuffer& data, GarrisonMission const& mission)
{
data << uint64(mission.DbID);
@@ -188,43 +172,16 @@ namespace WorldPackets::Garrison
ByteBuffer& operator<<(ByteBuffer& data, GarrisonTalent const& talent)
{
// Wire order (client JamGarrisonTalent, Ghidra-confirmed via ctor @client 0x729093890): garrTalentID(i32),
// garrTalentRank(i32), researchStartTime(i64), flags(i32), socketData(OptionalInit bit + FlushBits).
//
// The client reads the wire `flags` field with its OWN semantics, which differ from TC's internal
// Garrison::Talent::Flags. Bits were pinned by live client testing (the builder that computes these is
// control-flow-flattened, so it isn't offline-decompilable):
// client bit 0x4 = "this talent is being researched" ? gates whether the Order Advancement UI reads
// researchStartTime and renders the research timer. Verified live: flags=0 kept the client's startTime in
// memory (proven by a server-side diag log + a sentinel injection) but drew the talent idle; setting 0x4
// makes C_Garrison.GetTalentInfo(id).isBeingResearched=true with the real DB2 researchDuration.
// client bit 0x2 = a duration override that forces researchDuration to a fixed 24h; MUST NEVER be set.
// client bit 0x1 = "researched / talent taken". Recovered by static RE of the 12.0.7 client, which also
// proves it is PURELY server-supplied: the complete set of writers to the client-side talent record's
// flags word (+0x40) is three sites - RVA 0x22992A5 and 0x2299395, both verbatim copies of the wire
// struct inside Garrison::SetTalent (RVA 0x2298FF0), and RVA 0x2299448 in Garrison::RemoveTalent,
// which only zeroes it. There is no or/bts against that field anywhere in the image and no
// research-timer expiry that recomputes it, so the client can never derive "researched" on its own.
// Readers gated on the bit: ModifierTree 201/202/317 (evaluator RVA 0x20664B0 - the covenant/taxi
// PlayerCondition path), C_Garrison.GetTalentInfo's `researched` (builder RVA 0x22B95F0 via
// 0x228E2F0), GetTalentAvailability (RVA 0x2171390, bit set => availability 2) and
// CanResearchAtThisTier (RVA 0x2298DF0, which early-outs "already taken" only when the bit is set).
// Leaving it clear is why a fully researched sanctum/order-hall talent rendered as un-researched and
// why talent-gated unlocks (e.g. the covenant transport-network taxi nodes) never opened.
// TC's internal flags (only GARRISON_TALENT_FLAG_TEMPORARY=0x1 today, an unrelated meaning) are not
// wire-compatible, so derive the wire value purely from research state.
int32 wireFlags = 0;
if (talent.Rank >= 1)
wireFlags |= 0x1; // client "researched / taken" bit; every client test pairs it with a rank check
wireFlags |= 0x1;
if (talent.ResearchStartTime != 0)
wireFlags |= 0x4; // client "being researched" bit (never 0x2 = duration override)
wireFlags |= 0x4;
data << int32(talent.GarrTalentID);
data << int32(talent.Rank);
data << talent.ResearchStartTime;
data << int32(wireFlags);
// socketData is the SAME reflection kind (0x80003) as the reward itemInstance, which is sniff-confirmed as
// an OptionalInit bit (NOT a uint32 count). The prior uint32 "fix" made each talent 3 bytes too long,
// shifting the mission-reward data written right after the Talents block (rewards showed value x 2^16).
data << OptionalInit(talent.Socket);
data.FlushBits();
if (talent.Socket)
@@ -420,10 +377,7 @@ namespace WorldPackets::Garrison
{
_worldPacket >> GarrTalentID;
uint32 count = _worldPacket.read<uint32>();
// Sanity cap the client-supplied socket count before resize(): a soulbind/conduit tree has at most a few dozen
// sockets, so this is well above any legitimate value while stopping a crafted count (e.g. 0xFFFFFFFF) from
// requesting a multi-gigabyte allocation. resize() throws std::bad_alloc, which the opcode dispatcher does not
// catch (only ByteBufferException), so an unbounded count would crash the world thread instead of disconnecting.
count = std::min<uint32>(count, 64);
Sockets.resize(count);
for (GarrisonTalentSocketData& socket : Sockets)
@@ -559,7 +513,6 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// Conservative shape: u32 Result, u32 GarrSpecID, u32 GarrPlotInstanceID.
WorldPacket const* GarrisonLearnSpecializationResult::Write()
{
_worldPacket << uint32(Result);
@@ -568,7 +521,6 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// Conservative shape: u32 Result, u32 GarrPlotInstanceID, u32 GarrSpecID.
WorldPacket const* GarrisonBuildingSetActiveSpecializationResult::Write()
{
_worldPacket << uint32(Result);
@@ -579,8 +531,6 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// u32 GarrPlotInstanceID, u64 TimeBuilt, u32 Result - see the header for the disassembly that inverts the
// order the old comment claimed. Result LAST is the field the client's `cmp [rcx+0x30], 0` gate reads.
WorldPacket const* GarrisonCompleteBuildingConstructionResult::Write()
{
_worldPacket << uint32(GarrPlotInstanceID);
@@ -590,7 +540,6 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980791 (-8.45): PackedGuid + sub-call + varU32 size + varU32[size].
WorldPacket const* GarrisonOpenCrafter::Write()
{
_worldPacket << NpcGUID;
@@ -602,8 +551,6 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980817 (-8.51): generic byte-block helper. Conservative: u32 NewMinLevel.
// 8 bytes, not 4 - the client's handler (0x22A0BA0) reads two u32s out of the opaque tail. See the header.
WorldPacket const* GarrisonAutoTroopMinLevelUpdateResult::Write()
{
_worldPacket << uint32(UnkLookupKey);
@@ -612,8 +559,6 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980772 (-8.30): u8 GarrTypeID + GarrisonSmallStruct.
// Conservative inner shape: {u32 MissionRecID, u32 BonusAbilityID}.
WorldPacket const* GarrisonActivateMissionBonusAbility::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -667,7 +612,7 @@ namespace WorldPackets::Garrison
void GarrisonGetMissionReward::Read()
{
_worldPacket >> DbID; // RAW uint64, not a PackedGuid ? see GarrisonPackets.h
_worldPacket >> DbID;
_worldPacket >> MissionRecID;
}
@@ -747,8 +692,6 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT -10.2. 3 CONFIRMED + 2 HIGH
// fields. FollowerInfo struct shape matches COMPLETE_MISSION_RESULT (-10.1.3).
WorldPacket const* GarrisonMissionBonusRollResult::Write()
{
_worldPacket << Mission;
@@ -31,6 +31,26 @@ enum HOINFUSIONCreatureIds
BOSS_GULPING_GOLIATH = 189722,
BOSS_KHAJIN_THE_UNYIELDING = 189727,
BOSS_PRIMAL_TSUNAMI = 189729,
NPC_CONTAINMENT_APPARATUS = 190342,
NPC_PRIMALIST_GEOMANCER = 190345,
NPC_PRIMALIST_RAVAGER = 190348,
NPC_SKULKING_ZEALOT = 190359,
NPC_DAZZLING_DRAGONFLY = 190362,
NPC_CURIOUS_SWOGLET = 190366,
NPC_FLAMECALLER_AYMI = 190368,
NPC_SQUALLBRINGER_CYRAZ = 190370,
NPC_PRIMALIST_EARTHSHAKER = 190371,
NPC_PRIMALIST_GALESINGER = 190373,
NPC_PRIMALIST_ICECALLER = 190377,
NPC_GLACIAL_PROTO_DRAGON = 190403,
NPC_INFUSER_SARIYA = 190405,
NPC_AQUA_RAGER = 190407,
NPC_REFTI_DEFENDER = 190340,
NPC_CURIOUS_SWOGLET_ALT = 195399,
NPC_PRIMALIST_INFUSER = 196043,
NPC_PRIMALIST_SHOCKTROOPER = 199037,
NPC_INFUSED_GLOBULE = 196008,
};
template <class AI, class T>
@@ -5,23 +5,535 @@
* and read our Terms
*/
#include "AreaTrigger.h"
#include "AreaTriggerAI.h"
#include "InstanceScript.h"
#include "Map.h"
#include "SpellAuras.h"
#include "SpellScript.h"
#include "ScriptMgr.h"
#include "Unit.h"
#include "HallsOfInfusion.h"
#include "InstanceScript.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "SpellHistory.h"
#include "Spell.h"
#include "Player.h"
#include "MotionMaster.h"
#include "TemporarySummon.h"
//here to script trash and gobs
// Halls of Infusion trash (retail 12.1 sniff 69814)
enum NpcsSpells
enum TrashSpells
{
//Refti Defender 190340
SPELL_DEMORALIZING_SHOUT = 374339,
SPELL_SPEAR_FLURRY = 393432,
SPELL_GUSHING_WOUND = 393444,
//Primalist Geomancer 190345
SPELL_SEISMIC_SLAM = 374073,
SPELL_TAILWIND_GEOMANCER = 374154,
//Primalist Ravager 190348
SPELL_TAILWIND_RAVAGER = 374149,
//Skulking Zealot 190359
SPELL_STEALTH = 374612,
SPELL_CHEAP_SHOT = 374614,
SPELL_RISING_SQUALL = 374616,
//Dazzling Dragonfly 190362
SPELL_DAZZLE = 374563,
SPELL_EVASIVE_FLUTTER = 374578,
//Flamecaller Aymi 190368
SPELL_CAUTERIZE = 374699,
SPELL_PYRETIC_BURST = 374706,
SPELL_MOLTEN_SUBDUCTION = 374718,
SPELL_MAGMA_CRUSH = 374735,
//Squallbringer Cyraz 190370
SPELL_ZEPHYRS_CALL = 374823,
SPELL_GALE_FORCE_CHARGE = 374863,
SPELL_WHIRLING_FURY = 375079,
//Primalist Earthshaker 190371
SPELL_RUMBLING_EARTH = 375384,
//Primalist Galesinger 190373
SPELL_THUNDERSTRIKE = 437717,
//Primalist Icecaller 190377
SPELL_ICE_SHARD = 375950,
SPELL_REFRESHING_TIDES = 376171,
//Glacial Proto-Dragon 190403
SPELL_OCEANIC_BREATH = 375351,
SPELL_DEEP_CHILL = 391634,
//Infuser Sariya 190405
SPELL_AQUEOUS_BARRIER = 377402,
SPELL_INUNDATE = 388882,
SPELL_FLASH_FLOOD = 390290,
SPELL_WATER_MISSILE = 396783,
//Aqua Rager 190407
SPELL_TIDAL_DIVERGENCE = 377341,
SPELL_BOILING_RAGE = 377384,
//Primalist Infuser 196043
SPELL_INFUSE = 387618,
//Primalist Shocktrooper 199037
SPELL_LIGHTNING_BLAST = 395690,
SPELL_INFUSED_LIGHTNING = 395691,
SPELL_ELEMENTAL_FOCUS = 395694,
//Containment Apparatus 190342
SPELL_INVIS_DETECT = 18950,
SPELL_CONTAINMENT_BEAM = 374020,
SPELL_EXPULSE = 374045,
};
enum TrashEvents
{
EVENT_TH_A = 1,
EVENT_TH_B,
EVENT_TH_C,
};
// ---------- generic two-spell caster trash ----------
template<uint32 SpellA, uint32 CooldownA, uint32 SpellB, uint32 CooldownB>
struct npc_hoi_two_caster : public ScriptedAI
{
npc_hoi_two_caster(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_TH_A, 2s);
_events.ScheduleEvent(EVENT_TH_B, 6s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_TH_A:
DoCastVictim(SpellA);
_events.ScheduleEvent(EVENT_TH_A, Milliseconds(CooldownA));
break;
case EVENT_TH_B:
DoCastVictim(SpellB);
_events.ScheduleEvent(EVENT_TH_B, Milliseconds(CooldownB));
break;
default:
break;
}
}
me->DoMeleeAttackIfReady();
}
private:
EventMap _events;
};
// ---------- generic single-spell trash ----------
template<uint32 SpellA, uint32 CooldownA>
struct npc_hoi_one_spell : public ScriptedAI
{
npc_hoi_one_spell(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_TH_A, 2s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
if (eventId == EVENT_TH_A)
{
DoCastVictim(SpellA);
_events.ScheduleEvent(EVENT_TH_A, Milliseconds(CooldownA));
}
}
me->DoMeleeAttackIfReady();
}
private:
EventMap _events;
};
// ---------- named trash AI ----------
//Refti Defender 190340
struct npc_hoi_refti_defender : public ScriptedAI
{
npc_hoi_refti_defender(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_TH_A, 4s);
_events.ScheduleEvent(EVENT_TH_B, 8s);
_events.ScheduleEvent(EVENT_TH_C, 5s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_TH_A:
DoCastAOE(SPELL_DEMORALIZING_SHOUT);
_events.ScheduleEvent(EVENT_TH_A, 15s);
break;
case EVENT_TH_B:
DoCastAOE(SPELL_SPEAR_FLURRY);
_events.ScheduleEvent(EVENT_TH_B, 12s);
break;
case EVENT_TH_C:
DoCastVictim(SPELL_GUSHING_WOUND);
_events.ScheduleEvent(EVENT_TH_C, 6s);
break;
default:
break;
}
}
me->DoMeleeAttackIfReady();
}
private:
EventMap _events;
};
//Skulking Zealot 190359 - stealth opener
struct npc_hoi_skulking_zealot : public ScriptedAI
{
npc_hoi_skulking_zealot(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
DoCastSelf(SPELL_STEALTH);
_events.ScheduleEvent(EVENT_TH_A, 5s);
_events.ScheduleEvent(EVENT_TH_B, 12s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_TH_A:
DoCastVictim(SPELL_CHEAP_SHOT);
_events.ScheduleEvent(EVENT_TH_A, 8s);
break;
case EVENT_TH_B:
DoCastAOE(SPELL_RISING_SQUALL);
_events.ScheduleEvent(EVENT_TH_B, 15s);
break;
default:
break;
}
}
me->DoMeleeAttackIfReady();
}
private:
EventMap _events;
};
//Flamecaller Aymi 190368
struct npc_hoi_flamecaller_aymi : public ScriptedAI
{
npc_hoi_flamecaller_aymi(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_TH_A, 5s);
_events.ScheduleEvent(EVENT_TH_B, 10s);
_events.ScheduleEvent(EVENT_TH_C, 8s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_TH_A:
DoCastAOE(SPELL_PYRETIC_BURST);
_events.ScheduleEvent(EVENT_TH_A, 12s);
break;
case EVENT_TH_B:
DoCastAOE(SPELL_MOLTEN_SUBDUCTION);
_events.ScheduleEvent(EVENT_TH_B, 18s);
break;
case EVENT_TH_C:
if (HealthBelowPct(70))
DoCastSelf(SPELL_CAUTERIZE);
else
DoCastVictim(SPELL_MAGMA_CRUSH);
_events.ScheduleEvent(EVENT_TH_C, 14s);
break;
default:
break;
}
}
me->DoMeleeAttackIfReady();
}
private:
EventMap _events;
};
//Squallbringer Cyraz 190370
struct npc_hoi_squallbringer_cyraz : public ScriptedAI
{
npc_hoi_squallbringer_cyraz(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_TH_A, 6s);
_events.ScheduleEvent(EVENT_TH_B, 12s);
_events.ScheduleEvent(EVENT_TH_C, 20s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_TH_A:
DoCastAOE(SPELL_ZEPHYRS_CALL);
_events.ScheduleEvent(EVENT_TH_A, 15s);
break;
case EVENT_TH_B:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 40.0f, true))
DoCast(target, SPELL_GALE_FORCE_CHARGE);
_events.ScheduleEvent(EVENT_TH_B, 20s);
break;
case EVENT_TH_C:
DoCastSelf(SPELL_WHIRLING_FURY);
_events.ScheduleEvent(EVENT_TH_C, 25s);
break;
default:
break;
}
}
me->DoMeleeAttackIfReady();
}
private:
EventMap _events;
};
//Infuser Sariya 190405
struct npc_hoi_infuser_sariya : public ScriptedAI
{
npc_hoi_infuser_sariya(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
DoCastSelf(SPELL_AQUEOUS_BARRIER);
_events.ScheduleEvent(EVENT_TH_A, 3s);
_events.ScheduleEvent(EVENT_TH_B, 12s);
_events.ScheduleEvent(EVENT_TH_C, 25s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_TH_A:
DoCastVictim(SPELL_WATER_MISSILE);
_events.ScheduleEvent(EVENT_TH_A, 4s);
break;
case EVENT_TH_B:
DoCastAOE(SPELL_INUNDATE);
_events.ScheduleEvent(EVENT_TH_B, 15s);
break;
case EVENT_TH_C:
DoCastAOE(SPELL_FLASH_FLOOD);
_events.ScheduleEvent(EVENT_TH_C, 30s);
break;
default:
break;
}
}
me->DoMeleeAttackIfReady();
}
private:
EventMap _events;
};
//Containment Apparatus 190342
struct npc_hoi_containment_apparatus : public ScriptedAI
{
npc_hoi_containment_apparatus(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_TH_A, 5s);
_events.ScheduleEvent(EVENT_TH_B, 20s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_TH_A:
DoCastAOE(SPELL_CONTAINMENT_BEAM);
_events.ScheduleEvent(EVENT_TH_A, 8s);
break;
case EVENT_TH_B:
DoCastSelf(SPELL_EXPULSE);
_events.ScheduleEvent(EVENT_TH_B, 20s);
break;
default:
break;
}
}
me->DoMeleeAttackIfReady();
}
private:
EventMap _events;
};
//Primalist Infuser 196043 - buffs nearby allies
struct npc_hoi_primalist_infuser : public ScriptedAI
{
npc_hoi_primalist_infuser(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_TH_A, 4s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
if (eventId == EVENT_TH_A)
{
DoCastSelf(SPELL_INFUSE);
_events.ScheduleEvent(EVENT_TH_A, 15s);
}
}
me->DoMeleeAttackIfReady();
}
private:
EventMap _events;
};
// ---------- bindings (AI name = ScriptName) ----------
typedef npc_hoi_one_spell<SPELL_TAILWIND_RAVAGER, 8000> npc_hoi_primalist_ravager;
typedef npc_hoi_one_spell<SPELL_ICE_SHARD, 4000> npc_hoi_primalist_icecaller;
typedef npc_hoi_one_spell<SPELL_THUNDERSTRIKE, 6000> npc_hoi_primalist_galesinger;
typedef npc_hoi_one_spell<SPELL_RUMBLING_EARTH, 9000> npc_hoi_primalist_earthshaker;
typedef npc_hoi_two_caster<SPELL_SEISMIC_SLAM, 10000, SPELL_TAILWIND_GEOMANCER, 15000> npc_hoi_primalist_geomancer;
typedef npc_hoi_two_caster<SPELL_DAZZLE, 8000, SPELL_EVASIVE_FLUTTER, 20000> npc_hoi_dazzling_dragonfly;
typedef npc_hoi_two_caster<SPELL_OCEANIC_BREATH, 12000, SPELL_DEEP_CHILL, 8000> npc_hoi_glacial_proto_dragon;
typedef npc_hoi_two_caster<SPELL_BOILING_RAGE, 8000, SPELL_TIDAL_DIVERGENCE, 12000> npc_hoi_aqua_rager;
typedef npc_hoi_two_caster<SPELL_LIGHTNING_BLAST, 6000, SPELL_INFUSED_LIGHTNING, 9000> npc_hoi_primalist_shocktrooper;
void AddSC_HallsOfInfusion()
{
}
RegisterCreatureAI(npc_hoi_refti_defender);
RegisterCreatureAI(npc_hoi_skulking_zealot);
RegisterCreatureAI(npc_hoi_flamecaller_aymi);
RegisterCreatureAI(npc_hoi_squallbringer_cyraz);
RegisterCreatureAI(npc_hoi_infuser_sariya);
RegisterCreatureAI(npc_hoi_containment_apparatus);
RegisterCreatureAI(npc_hoi_primalist_infuser);
RegisterCreatureAI(npc_hoi_primalist_ravager);
RegisterCreatureAI(npc_hoi_primalist_icecaller);
RegisterCreatureAI(npc_hoi_primalist_galesinger);
RegisterCreatureAI(npc_hoi_primalist_earthshaker);
RegisterCreatureAI(npc_hoi_primalist_geomancer);
RegisterCreatureAI(npc_hoi_dazzling_dragonfly);
RegisterCreatureAI(npc_hoi_glacial_proto_dragon);
RegisterCreatureAI(npc_hoi_aqua_rager);
RegisterCreatureAI(npc_hoi_primalist_shocktrooper);
}
@@ -24,9 +24,8 @@
enum GulpinggoliathSpells
{
SPELL_BELLY_SLAM = 385531,
SPELL_GULP = 385551, //385555,
SPELL_GULP_SWOG_TOXIN = 374389,
SPELL_HANGRY = 385743,
SPELL_BELLY_SLAM_FOLLOWUP = 385691,
SPELL_GULP = 385551,
SPELL_OVERPOWERING_CROAK = 385181,
SPELL_TOXIC_EFFLUVIA = 385442,
SPELL_PERIODIC_ENERGY_GAIN = 299956,
@@ -35,9 +34,8 @@ enum GulpinggoliathSpells
enum Gulpinggoliathevents
{
EVENT_BELLY_SLAM = 1,
EVENT_BELLY_SLAM_FOLLOWUP,
EVENT_GULP,
EVENT_GULP_SWOG_TOXIN,
EVENT_HANGRY,
EVENT_OVERPOWERING_CROAK,
EVENT_TOXIC_EFFLUVIA,
};
@@ -62,12 +60,10 @@ public:
BossAI::JustEngagedWith(who);
instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 1);
DoCast(SPELL_PERIODIC_ENERGY_GAIN);
events.ScheduleEvent(EVENT_BELLY_SLAM, 24s);
events.ScheduleEvent(EVENT_GULP, 8s);
events.ScheduleEvent(EVENT_GULP_SWOG_TOXIN, 12s);
events.ScheduleEvent(EVENT_HANGRY, 14s);
events.ScheduleEvent(EVENT_OVERPOWERING_CROAK, 5s);
events.ScheduleEvent(EVENT_TOXIC_EFFLUVIA, 19s);
events.ScheduleEvent(EVENT_OVERPOWERING_CROAK, 4s);
events.ScheduleEvent(EVENT_GULP, 10s);
events.ScheduleEvent(EVENT_TOXIC_EFFLUVIA, 22s);
events.ScheduleEvent(EVENT_BELLY_SLAM, 30s);
}
void EnterEvadeMode(EvadeReason /*why*/) override
@@ -108,19 +104,15 @@ public:
{
case EVENT_BELLY_SLAM:
DoCastVictim(SPELL_BELLY_SLAM);
events.ScheduleEvent(EVENT_BELLY_SLAM, 20s);
events.ScheduleEvent(EVENT_BELLY_SLAM_FOLLOWUP, 4s);
events.ScheduleEvent(EVENT_BELLY_SLAM, 30s);
break;
case EVENT_BELLY_SLAM_FOLLOWUP:
DoCastSelf(SPELL_BELLY_SLAM_FOLLOWUP);
break;
case EVENT_GULP:
DoCast(SPELL_GULP);
events.ScheduleEvent(EVENT_GULP, 15s);
break;
case EVENT_GULP_SWOG_TOXIN:
DoCastVictim(SPELL_GULP_SWOG_TOXIN);
events.ScheduleEvent(EVENT_GULP_SWOG_TOXIN, 25s);
break;
case EVENT_HANGRY:
DoCastSelf(SPELL_HANGRY);
events.ScheduleEvent(EVENT_HANGRY, 33s);
events.ScheduleEvent(EVENT_GULP, 47s);
break;
case EVENT_OVERPOWERING_CROAK:
DoCastSelf(SPELL_OVERPOWERING_CROAK);
@@ -23,19 +23,19 @@
enum KhajintheunyieldingSpells
{
SPELL_FROST_CYCLONE = 390111,
SPELL_FROST_SHOCK = 385963,
SPELL_GLACIAL_SURGE = 386559,
SPELL_GLACIAL_SURGE_FOLLOW = 386562,
SPELL_HAILSTORM = 386757,
SPELL_POLAR_WINDS = 386743,
SPELL_PERIODIC_ENERGY_GAIN = 299956,
SPELL_POLAR_WINDS = 386741,
SPELL_POLAR_WINDS_FOLLOW = 386743,
};
enum Khajintheunyieldingevents
{
EVENT_FROST_CYCLONE = 1,
EVENT_FROST_SHOCK,
EVENT_FROST_SHOCK = 1,
EVENT_GLACIAL_SURGE,
EVENT_GLACIAL_SURGE_FOLLOW,
EVENT_HAILSTORM,
EVENT_POLAR_WINDS,
};
@@ -49,9 +49,6 @@ public:
void Reset() override
{
BossAI::Reset();
me->RemoveAura(SPELL_PERIODIC_ENERGY_GAIN);
me->SetPowerType(POWER_ENERGY);
me->SetPower(POWER_ENERGY, 0);
me->RemoveAllAreaTriggers();
}
@@ -59,12 +56,11 @@ public:
{
BossAI::JustEngagedWith(who);
instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 1);
DoCast(SPELL_PERIODIC_ENERGY_GAIN);
events.ScheduleEvent(EVENT_FROST_CYCLONE, 24s);
events.ScheduleEvent(EVENT_FROST_SHOCK, 8s);
events.ScheduleEvent(EVENT_GLACIAL_SURGE, 12s);
events.ScheduleEvent(EVENT_HAILSTORM, 14s);
events.ScheduleEvent(EVENT_POLAR_WINDS, 17s);
DoCastSelf(SPELL_POLAR_WINDS);
events.ScheduleEvent(EVENT_POLAR_WINDS, 500ms);
events.ScheduleEvent(EVENT_FROST_SHOCK, 6s);
events.ScheduleEvent(EVENT_HAILSTORM, 10s);
events.ScheduleEvent(EVENT_GLACIAL_SURGE, 22s);
}
void EnterEvadeMode(EvadeReason /*why*/) override
@@ -91,37 +87,30 @@ public:
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
if (me->GetPowerPct(POWER_ENERGY) == 100)
{
me->ModifyPower(POWER_ENERGY, -100);
events.ScheduleEvent(EVENT_POLAR_WINDS, 2s);
}
events.Update(diff);
while (uint32 eventId = events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_FROST_CYCLONE:
DoCast(SPELL_FROST_CYCLONE);
events.ScheduleEvent(EVENT_FROST_CYCLONE, 20s);
case EVENT_POLAR_WINDS:
DoCastSelf(SPELL_POLAR_WINDS_FOLLOW);
break;
case EVENT_FROST_SHOCK:
DoCastVictim(SPELL_FROST_SHOCK);
events.ScheduleEvent(EVENT_FROST_SHOCK, 15s);
events.ScheduleEvent(EVENT_FROST_SHOCK, 12s);
break;
case EVENT_GLACIAL_SURGE:
DoCast(SPELL_GLACIAL_SURGE);
events.ScheduleEvent(EVENT_GLACIAL_SURGE, 12s);
DoCastSelf(SPELL_GLACIAL_SURGE);
events.ScheduleEvent(EVENT_GLACIAL_SURGE_FOLLOW, 4s);
events.ScheduleEvent(EVENT_GLACIAL_SURGE, 22s);
break;
case EVENT_GLACIAL_SURGE_FOLLOW:
DoCast(SPELL_GLACIAL_SURGE_FOLLOW);
break;
case EVENT_HAILSTORM:
DoCast(SPELL_HAILSTORM);
events.ScheduleEvent(EVENT_HAILSTORM, 33s);
break;
case EVENT_POLAR_WINDS:
DoCast(SPELL_POLAR_WINDS);
events.ScheduleEvent(EVENT_POLAR_WINDS, 37s);
events.ScheduleEvent(EVENT_HAILSTORM, 28s);
break;
default:
break;
@@ -23,58 +23,56 @@
enum PrimaltsunamiSpells
{
SPELL_FOCUSED_DELUGE = 387571,
SPELL_INFUSE = 387619,
SPELL_INFUSED_GLOBULES = 387559,
SPELL_ROGUE_WAVES = 388760,
SPELL_ROGUE_WAVES = 388788,
SPELL_SQUALL_BUFFET = 387504,
SPELL_SUBMERGE = 387585,
SPELL_TEMPESTS_FURY = 388424,
SPELL_UNDERTOW = 389872,
SPELL_UNDERTOW_DAMAGE = 389875,
SPELL_PERIODIC_ENERGY_GAIN = 299956,
SPELL_CRASHING_TSUNAMI = 388712,
SPELL_TELEPORT_CORPSES = 396893,
SPELL_PASSIVE_ENERGY_GAIN = 387593,
};
enum Primaltsunamievents
{
EVENT_FOCUSED_DELUGE = 1,
EVENT_INFUSE,
EVENT_INFUSED_GLOBULES,
EVENT_INFUSED_GLOBULES = 1,
EVENT_ROGUE_WAVES,
EVENT_SQUALL_BUFFET,
EVENT_SUBMERGE,
EVENT_TEMPESTS_FURY,
EVENT_UNDERTOW,
EVENT_CRASHING_TSUNAMI,
EVENT_RE_EMERGE,
};
//189729 Primal Tsunami
class boss_primal_tsunami : public BossAI
{
public:
boss_primal_tsunami(Creature* creature) : BossAI(creature, DATA_PRIMAL_TSUNAMI) {}
boss_primal_tsunami(Creature* creature) : BossAI(creature, DATA_PRIMAL_TSUNAMI), _submerged(false) {}
void Reset() override
{
BossAI::Reset();
me->RemoveAura(SPELL_PERIODIC_ENERGY_GAIN);
me->RemoveAura(SPELL_PASSIVE_ENERGY_GAIN);
me->SetPowerType(POWER_ENERGY);
me->SetPower(POWER_ENERGY, 0);
me->RemoveAllAreaTriggers();
me->SetReactState(REACT_AGGRESSIVE);
_submerged = false;
}
void JustEngagedWith(Unit* who) override
{
BossAI::JustEngagedWith(who);
instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 1);
DoCast(SPELL_PERIODIC_ENERGY_GAIN);
events.ScheduleEvent(EVENT_FOCUSED_DELUGE, 24s);
events.ScheduleEvent(EVENT_INFUSE, 8s);
events.ScheduleEvent(EVENT_INFUSED_GLOBULES, 12s);
events.ScheduleEvent(EVENT_ROGUE_WAVES, 14s);
events.ScheduleEvent(EVENT_SQUALL_BUFFET, 22s);
events.ScheduleEvent(EVENT_SUBMERGE, 17s);
DoCastSelf(SPELL_PASSIVE_ENERGY_GAIN);
DoCastSelf(SPELL_UNDERTOW);
events.ScheduleEvent(EVENT_ROGUE_WAVES, 1s);
events.ScheduleEvent(EVENT_TEMPESTS_FURY, 5s);
events.ScheduleEvent(EVENT_UNDERTOW, 29s);
events.ScheduleEvent(EVENT_INFUSED_GLOBULES, 9s);
events.ScheduleEvent(EVENT_SQUALL_BUFFET, 17s);
}
void EnterEvadeMode(EvadeReason /*why*/) override
@@ -98,10 +96,7 @@ public:
if (!UpdateVictim())
return;
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
if (me->GetPowerPct(POWER_ENERGY) == 100)
if (!_submerged && me->GetPowerPct(POWER_ENERGY) == 100)
{
me->ModifyPower(POWER_ENERGY, -100);
events.ScheduleEvent(EVENT_SUBMERGE, 2s);
@@ -113,37 +108,47 @@ public:
{
switch (eventId)
{
case EVENT_FOCUSED_DELUGE:
DoCastVictim(SPELL_FOCUSED_DELUGE);
events.ScheduleEvent(EVENT_FOCUSED_DELUGE, 20s);
break;
case EVENT_INFUSE:
DoCastSelf(SPELL_INFUSE);
me->RemoveAurasDueToSpell(SPELL_SUBMERGE);
events.ScheduleEvent(EVENT_INFUSE, 15s);
break;
case EVENT_INFUSED_GLOBULES:
DoCast(SPELL_INFUSED_GLOBULES);
events.ScheduleEvent(EVENT_INFUSED_GLOBULES, 12s);
break;
case EVENT_ROGUE_WAVES:
DoCast(SPELL_ROGUE_WAVES);
events.ScheduleEvent(EVENT_ROGUE_WAVES, 91s);
break;
case EVENT_SQUALL_BUFFET:
DoCastVictim(SPELL_SQUALL_BUFFET);
events.ScheduleEvent(EVENT_SQUALL_BUFFET, 37s);
break;
case EVENT_SUBMERGE:
DoCastSelf(SPELL_SUBMERGE);
me->SetReactState(REACT_PASSIVE);
me->AttackStop();
_submerged = true;
events.ScheduleEvent(EVENT_CRASHING_TSUNAMI, 3s);
events.ScheduleEvent(EVENT_RE_EMERGE, 30s);
break;
case EVENT_CRASHING_TSUNAMI:
DoCastSelf(SPELL_CRASHING_TSUNAMI);
if (_submerged)
events.ScheduleEvent(EVENT_CRASHING_TSUNAMI, 7s);
break;
case EVENT_RE_EMERGE:
me->SetReactState(REACT_AGGRESSIVE);
DoCastSelf(SPELL_TELEPORT_CORPSES);
_submerged = false;
me->SetPower(POWER_ENERGY, 0);
events.ScheduleEvent(EVENT_TEMPESTS_FURY, 5s);
events.ScheduleEvent(EVENT_INFUSED_GLOBULES, 9s);
events.ScheduleEvent(EVENT_SQUALL_BUFFET, 17s);
events.ScheduleEvent(EVENT_ROGUE_WAVES, 25s);
break;
case EVENT_INFUSED_GLOBULES:
DoCastSelf(SPELL_INFUSED_GLOBULES);
events.ScheduleEvent(EVENT_INFUSED_GLOBULES, 17s);
break;
case EVENT_ROGUE_WAVES:
DoCastSelf(SPELL_ROGUE_WAVES);
events.ScheduleEvent(EVENT_ROGUE_WAVES, 20s);
break;
case EVENT_SQUALL_BUFFET:
DoCast(SPELL_SQUALL_BUFFET);
events.ScheduleEvent(EVENT_SQUALL_BUFFET, 37s);
break;
case EVENT_TEMPESTS_FURY:
DoCast(SPELL_TEMPESTS_FURY);
DoCastSelf(SPELL_TEMPESTS_FURY);
events.ScheduleEvent(EVENT_TEMPESTS_FURY, 44s);
break;
case EVENT_UNDERTOW:
DoCast(SPELL_UNDERTOW);
DoCast(SPELL_UNDERTOW_DAMAGE);
DoCastSelf(SPELL_UNDERTOW);
events.ScheduleEvent(EVENT_UNDERTOW, 25s);
break;
default:
@@ -151,7 +156,15 @@ public:
}
}
if (!_submerged && me->HasUnitState(UNIT_STATE_CASTING))
return;
if (!_submerged && me->GetVictim())
me->DoMeleeAttackIfReady();
}
private:
bool _submerged;
};
void AddSC_boss_primal_tsunami()
@@ -26,8 +26,8 @@ enum watcherirideusSpells
SPELL_ABLATIVE_BARRIER = 383840,
SPELL_POWER_FIELD = 389181,
SPELL_POWER_OVERLOAD = 389179,
SPELL_SIPHON_POWER = 389056, //389486,
SPELL_SPARK_VOLLEY = 383935, //384351,
SPELL_SIPHON_POWER = 389056,
SPELL_SPARK_VOLLEY = 384351,
SPELL_STATIC_SURGE = 384014,
SPELL_TITANIC_FIST = 384524,
SPELL_PERIODIC_ENERGY_GAIN = 299956,
@@ -65,13 +65,11 @@ public:
instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 1);
Talk(0);
DoCast(SPELL_PERIODIC_ENERGY_GAIN);
events.ScheduleEvent(EVENT_ABLATIVE_BARRIER, 24s);
events.ScheduleEvent(EVENT_POWER_FIELD, 8s);
events.ScheduleEvent(EVENT_POWER_OVERLOAD, 12s);
events.ScheduleEvent(EVENT_SIPHON_POWER, 14s);
events.ScheduleEvent(EVENT_SPARK_VOLLEY, 3s);
events.ScheduleEvent(EVENT_STATIC_SURGE, 17s);
events.ScheduleEvent(EVENT_TITANIC_FIST, 5s);
events.ScheduleEvent(EVENT_STATIC_SURGE, 10s);
events.ScheduleEvent(EVENT_POWER_OVERLOAD, 22s);
events.ScheduleEvent(EVENT_POWER_FIELD, 24s);
events.ScheduleEvent(EVENT_SPARK_VOLLEY, 26s);
}
void EnterEvadeMode(EvadeReason /*why*/) override
@@ -113,10 +111,11 @@ public:
{
case EVENT_ABLATIVE_BARRIER:
DoCastSelf(SPELL_ABLATIVE_BARRIER);
events.ScheduleEvent(EVENT_SIPHON_POWER, 2s);
break;
case EVENT_POWER_FIELD:
DoCastVictim(SPELL_POWER_FIELD);
events.ScheduleEvent(EVENT_POWER_FIELD, 20s);
DoCast(SPELL_POWER_FIELD);
events.ScheduleEvent(EVENT_POWER_FIELD, 25s);
break;
case EVENT_POWER_OVERLOAD:
DoCastSelf(SPELL_POWER_OVERLOAD);
@@ -124,19 +123,18 @@ public:
break;
case EVENT_SIPHON_POWER:
DoCastSelf(SPELL_SIPHON_POWER);
events.ScheduleEvent(EVENT_SIPHON_POWER, 12s);
break;
case EVENT_SPARK_VOLLEY:
DoCastVictim(SPELL_SPARK_VOLLEY);
events.ScheduleEvent(EVENT_SPARK_VOLLEY, 39s);
DoCastSelf(SPELL_SPARK_VOLLEY);
events.ScheduleEvent(EVENT_SPARK_VOLLEY, 30s);
break;
case EVENT_STATIC_SURGE:
DoCastSelf(SPELL_STATIC_SURGE);
events.ScheduleEvent(EVENT_STATIC_SURGE, 15s);
break;
case EVENT_TITANIC_FIST:
DoCast(SPELL_TITANIC_FIST);
events.ScheduleEvent(EVENT_TITANIC_FIST, 34s);
DoCastSelf(SPELL_TITANIC_FIST);
events.ScheduleEvent(EVENT_TITANIC_FIST, 30s);
break;
default:
break;
@@ -52,12 +52,15 @@ public:
if (!player->GetGroup() || !player->GetGroup()->GetLeaderGUID())
return;
player->GetGroup()->AddFollowerModeBots(player);
}
void OnPlayerExit(Player* player)
{
if (!player->GetGroup())
return;
player->GetGroup()->DespawnFollowerModeBots(player);
}
};