diff --git a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp index 72b7f558019..bd87431f0c5 100644 --- a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp +++ b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp @@ -171,6 +171,16 @@ bool RageWinterchillSpreadRangedInCircleAction::Execute(Event /*event*/) return false; } +bool RageWinterchillMoveAwayFromDeathAndDecayAction::Execute(Event /*event*/) +{ + Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill"); + if (!winterchill || + !IsBotInsideActiveWinterchillDeathAndDecay(bot, WINTERCHILL_DEATH_AND_DECAY_ACTION_RADIUS)) + return false; + + return MoveAway(winterchill, 5.0f); +} + // Anetheron bool AnetheronMisdirectBossAndInfernalsToTanksAction::Execute(Event /*event*/) diff --git a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h index 6eab69c238f..06323fb1cae 100644 --- a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h +++ b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h @@ -49,6 +49,14 @@ class RageWinterchillSpreadRangedInCircleAction : public HyjalSummitMovementActi bool Execute(Event event) override; }; +class RageWinterchillMoveAwayFromDeathAndDecayAction : public HyjalSummitAttackAction +{ +public: + RageWinterchillMoveAwayFromDeathAndDecayAction( + PlayerbotAI* botAI) : HyjalSummitAttackAction(botAI, "rage winterchill move away from death and decay") {} + bool Execute(Event event) override; +}; + // Anetheron class AnetheronMisdirectBossAndInfernalsToTanksAction : public HyjalSummitAttackAction diff --git a/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp b/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp index b559e74bf7e..73b9d380826 100644 --- a/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp +++ b/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp @@ -81,6 +81,27 @@ float RageWinterchillDisableCombatFormationMoveMultiplier::GetValue(Action* acti return 1.0f; } +float RageWinterchillControlDeathAndDecayAvoidanceMultiplier::GetValue(Action* action) +{ + if (botAI->IsTank(bot) || !AI_VALUE2(Unit*, "find target", "rage winterchill")) + return 1.0f; + + if (IsBotInsideActiveWinterchillDeathAndDecay(bot, WINTERCHILL_DEATH_AND_DECAY_CONTROL_RADIUS)) + { + if (dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + // Anetheron float AnetheronDisableTankActionsMultiplier::GetValue(Action* action) diff --git a/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h b/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h index 10447ae2198..bc903c6c3e1 100644 --- a/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h +++ b/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h @@ -34,6 +34,14 @@ class RageWinterchillDisableCombatFormationMoveMultiplier : public Multiplier virtual float GetValue(Action* action); }; +class RageWinterchillControlDeathAndDecayAvoidanceMultiplier : public Multiplier +{ +public: + RageWinterchillControlDeathAndDecayAvoidanceMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "rage winterchill control death and decay avoidance multiplier") {} + virtual float GetValue(Action* action); +}; + // Anetheron class AnetheronDisableTankActionsMultiplier : public Multiplier diff --git a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h b/src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h index 27ef6fe9d3f..ae098d4a036 100644 --- a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h +++ b/src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h @@ -28,6 +28,9 @@ class RaidHyjalSummitActionContext : public NamedObjectContext creators["rage winterchill spread ranged in circle"] = &RaidHyjalSummitActionContext::rage_winterchill_spread_ranged_in_circle; + creators["rage winterchill move away from death and decay"] = + &RaidHyjalSummitActionContext::rage_winterchill_move_away_from_death_and_decay; + // Anetheron creators["anetheron misdirect boss and infernals to tanks"] = &RaidHyjalSummitActionContext::anetheron_misdirect_boss_and_infernals_to_tanks; @@ -126,6 +129,9 @@ class RaidHyjalSummitActionContext : public NamedObjectContext static Action* rage_winterchill_spread_ranged_in_circle( PlayerbotAI* botAI) { return new RageWinterchillSpreadRangedInCircleAction(botAI); } + static Action* rage_winterchill_move_away_from_death_and_decay( + PlayerbotAI* botAI) { return new RageWinterchillMoveAwayFromDeathAndDecayAction(botAI); } + // Anetheron static Action* anetheron_misdirect_boss_and_infernals_to_tanks( PlayerbotAI* botAI) { return new AnetheronMisdirectBossAndInfernalsToTanksAction(botAI); } diff --git a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h b/src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h index e47595625ea..5738c7fac9d 100644 --- a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h +++ b/src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h @@ -28,6 +28,9 @@ class RaidHyjalSummitTriggerContext : public NamedObjectContext creators["rage winterchill boss casts death and decay"] = &RaidHyjalSummitTriggerContext::rage_winterchill_boss_casts_death_and_decay; + creators["rage winterchill bot is standing in death and decay"] = + &RaidHyjalSummitTriggerContext::rage_winterchill_bot_is_standing_in_death_and_decay; + // Anetheron creators["anetheron pulling boss or infernal"] = &RaidHyjalSummitTriggerContext::anetheron_pulling_boss_or_infernal; @@ -126,6 +129,9 @@ class RaidHyjalSummitTriggerContext : public NamedObjectContext static Trigger* rage_winterchill_boss_casts_death_and_decay( PlayerbotAI* botAI) { return new RageWinterchillBossCastsDeathAndDecayTrigger(botAI); } + static Trigger* rage_winterchill_bot_is_standing_in_death_and_decay( + PlayerbotAI* botAI) { return new RageWinterchillBotIsStandingInDeathAndDecayTrigger(botAI); } + // Anetheron static Trigger* anetheron_pulling_boss_or_infernal( PlayerbotAI* botAI) { return new AnetheronPullingBossOrInfernalTrigger(botAI); } diff --git a/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp b/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp index 21b7e0ba05e..c2af6d68652 100644 --- a/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp +++ b/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp @@ -22,6 +22,9 @@ void RaidHyjalSummitStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("rage winterchill boss casts death and decay", { NextAction("rage winterchill spread ranged in circle", ACTION_RAID + 1) })); + triggers.push_back(new TriggerNode("rage winterchill bot is standing in death and decay", { + NextAction("rage winterchill move away from death and decay", ACTION_EMERGENCY + 2) })); + // Anetheron triggers.push_back(new TriggerNode("anetheron pulling boss or infernal", { NextAction("anetheron misdirect boss and infernals to tanks", ACTION_RAID + 3) })); @@ -113,6 +116,7 @@ void RaidHyjalSummitStrategy::InitMultipliers(std::vector& multipli // Rage Winterchill multipliers.push_back(new RageWinterchillDisableMainTankAvoidAoeMultiplier(botAI)); multipliers.push_back(new RageWinterchillDisableCombatFormationMoveMultiplier(botAI)); + multipliers.push_back(new RageWinterchillControlDeathAndDecayAvoidanceMultiplier(botAI)); // Anetheron multipliers.push_back(new AnetheronDisableTankActionsMultiplier(botAI)); diff --git a/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp b/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp index a1c2d66c81b..32fde01e9a3 100644 --- a/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp +++ b/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp @@ -42,6 +42,17 @@ bool RageWinterchillBossCastsDeathAndDecayTrigger::IsActive() AI_VALUE2(Unit*, "find target", "rage winterchill"); } +bool RageWinterchillBotIsStandingInDeathAndDecayTrigger::IsActive() +{ + if (botAI->IsTank(bot)) + return false; + + if (!AI_VALUE2(Unit*, "find target", "rage winterchill")) + return false; + + return IsBotInsideActiveWinterchillDeathAndDecay(bot, WINTERCHILL_DEATH_AND_DECAY_TRIGGER_RADIUS); +} + // Anetheron bool AnetheronPullingBossOrInfernalTrigger::IsActive() diff --git a/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h b/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h index 8327865ba22..4eddaab3267 100644 --- a/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h +++ b/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h @@ -44,6 +44,14 @@ class RageWinterchillBossCastsDeathAndDecayTrigger : public Trigger bool IsActive() override; }; +class RageWinterchillBotIsStandingInDeathAndDecayTrigger : public Trigger +{ +public: + RageWinterchillBotIsStandingInDeathAndDecayTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill bot is standing in death and decay") {} + bool IsActive() override; +}; + // Anetheron class AnetheronPullingBossOrInfernalTrigger : public Trigger diff --git a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp index 4220f950f04..061ea7d5c85 100644 --- a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp +++ b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp @@ -13,6 +13,22 @@ namespace HyjalSummitHelpers { + DeathAndDecayData* GetActiveWinterchillDeathAndDecay(uint32 instanceId) + { + auto instanceIt = deathAndDecayPosition.find(instanceId); + if (instanceIt == deathAndDecayPosition.end()) + return nullptr; + + uint32 now = getMSTime(); + if (getMSTimeDiff(instanceIt->second.spawnTime, now) >= WINTERCHILL_DEATH_AND_DECAY_DURATION) + { + deathAndDecayPosition.erase(instanceIt); + return nullptr; + } + + return &instanceIt->second; + } + std::unordered_map* GetActiveAzgalorRainOfFire(uint32 instanceId) { auto instanceIt = rainOfFirePosition.find(instanceId); @@ -76,6 +92,19 @@ namespace HyjalSummitHelpers const Position WINTERCHILL_TANK_POSITION = { 5031.061f, -1784.521f, 1321.626f }; std::unordered_map hasReachedWinterchillPosition; + std::unordered_map deathAndDecayPosition; + + bool IsBotInsideActiveWinterchillDeathAndDecay(Player* bot, float radius) + { + if (!bot || !bot->GetMap()) + return false; + + DeathAndDecayData* data = GetActiveWinterchillDeathAndDecay(bot->GetMap()->GetInstanceId()); + if (!data) + return false; + + return bot->GetExactDist2d(data->position) < radius; + } // Anetheron diff --git a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h index bea3fbbb7a8..4cb57bf9867 100644 --- a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h +++ b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h @@ -18,6 +18,9 @@ namespace HyjalSummitHelpers { enum class HyjalSummitSpells : uint32 { + // Rage Winterchill + SPELL_DEATH_AND_DECAY = 31258, + // Anetheron SPELL_INFERNO = 31299, @@ -69,6 +72,20 @@ namespace HyjalSummitHelpers // Rage Winterchill extern const Position WINTERCHILL_TANK_POSITION; extern std::unordered_map hasReachedWinterchillPosition; + constexpr uint32 WINTERCHILL_DEATH_AND_DECAY_DURATION = 20000; + // Match Azgalor-style escape thresholds: detect inside the effect, keep moving + // until slightly clear, and hold competing movement a bit longer to avoid churn. + constexpr float WINTERCHILL_DEATH_AND_DECAY_TRIGGER_RADIUS = 16.0f; + constexpr float WINTERCHILL_DEATH_AND_DECAY_ACTION_RADIUS = 17.0f; + constexpr float WINTERCHILL_DEATH_AND_DECAY_CONTROL_RADIUS = 23.0f; + struct DeathAndDecayData + { + Position position; + uint32 spawnTime; + }; + extern std::unordered_map deathAndDecayPosition; + DeathAndDecayData* GetActiveWinterchillDeathAndDecay(uint32 instanceId); + bool IsBotInsideActiveWinterchillDeathAndDecay(Player* bot, float radius); // Anetheron extern const Position ANETHERON_TANK_POSITION; diff --git a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp index 444f9e3c698..a370e9bf42b 100644 --- a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp +++ b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp @@ -100,8 +100,26 @@ class AzgalorRainOfFireScript : public DynamicObjectScript } }; +// Records the active Death and Decay dynamic object so bots can step away from +// Winterchill using encounter-specific movement instead of generic aoe avoidance +class RageWinterchillDeathAndDecayScript : public DynamicObjectScript +{ +public: + RageWinterchillDeathAndDecayScript() : DynamicObjectScript("RageWinterchillDeathAndDecayScript") {} + + void OnUpdate(DynamicObject* dynobj, uint32 /*diff*/) override + { + if (dynobj->GetSpellId() != static_cast(HyjalSummitSpells::SPELL_DEATH_AND_DECAY)) + return; + + deathAndDecayPosition[dynobj->GetMap()->GetInstanceId()] = + DeathAndDecayData{ dynobj->GetPosition(), getMSTime() }; + } +}; + void AddSC_HyjalSummitBotScripts() { new ArchimondeDoomfireTrailScript(); new AzgalorRainOfFireScript(); + new RageWinterchillDeathAndDecayScript(); }