/* * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it * and/or modify it under version 3 of the License, or (at your option), any later version. */ #include "RaidSSCMultipliers.h" #include "RaidSSCActions.h" #include "RaidSSCHelpers.h" #include "ChooseTargetActions.h" #include "DestroyItemAction.h" #include "DKActions.h" #include "DruidActions.h" #include "DruidBearActions.h" #include "DruidCatActions.h" #include "DruidShapeshiftActions.h" #include "FollowActions.h" #include "GenericSpellActions.h" #include "HunterActions.h" #include "LootAction.h" #include "MageActions.h" #include "PaladinActions.h" #include "Playerbots.h" #include "ReachTargetActions.h" #include "RogueActions.h" #include "ShamanActions.h" #include "WarlockActions.h" #include "WarriorActions.h" #include "WipeAction.h" using namespace SerpentShrineCavernHelpers; // Trash float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action) { if (bot->HasAura(SPELL_TOXIC_POOL) && dynamic_cast(action) && !dynamic_cast(action)) return 0.0f; return 1.0f; } // Hydross the Unstable float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) { if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0, true)) return 1.0f; Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); if (!hydross) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action)) return 0.0f; if ((botAI->IsMainTank(bot) && !hydross->HasAura(SPELL_CORRUPTION)) || (botAI->IsAssistTankOfIndex(bot, 0, true) && hydross->HasAura(SPELL_CORRUPTION))) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action) && !dynamic_cast(action))) return 0.0f; return 1.0f; } float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) { Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); if (!hydross) return 1.0f; Unit* waterElemental = AI_VALUE2(Unit*, "find target", "pure spawn of hydross"); Unit* natureElemental = AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); if (botAI->IsAssistTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0, true) && (waterElemental || natureElemental)) return 1.0f; if (dynamic_cast(action)) return 1.0f; const uint32 instanceId = hydross->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); constexpr uint8 phaseChangeWaitSeconds = 1; constexpr uint8 dpsWaitSeconds = 5; if (!hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsMainTank(bot)) { auto itDps = hydrossFrostDpsWaitTimer.find(instanceId); auto itPhase = hydrossChangeToFrostPhaseTimer.find(instanceId); bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() || (now - itDps->second) < dpsWaitSeconds); bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && (now - itPhase->second) > phaseChangeWaitSeconds); if (!justChanged && !aboutToChange) return 1.0f; if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) return 0.0f; } if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true)) { auto itDps = hydrossNatureDpsWaitTimer.find(instanceId); auto itPhase = hydrossChangeToNaturePhaseTimer.find(instanceId); bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() || (now - itDps->second) < dpsWaitSeconds); bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && (now - itPhase->second) > phaseChangeWaitSeconds); if (!justChanged && !aboutToChange) return 1.0f; if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) return 0.0f; } return 1.0f; } float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action) { if (bot->getClass() != CLASS_HUNTER) return 1.0f; if (AI_VALUE2(Unit*, "find target", "hydross the unstable") && dynamic_cast(action)) return 0.0f; return 1.0f; } // The Lurker Below float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action) { Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker) return 1.0f; const time_t now = std::time(nullptr); auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId()); if (it != lurkerSpoutTimer.end() && it->second > now) { if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; if (dynamic_cast(action) && !dynamic_cast(action) && !dynamic_cast(action)) return 0.0f; } return 1.0f; } float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action) { if (!botAI->IsRanged(bot)) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "the lurker below")) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } // Disable tank assist during Submerge only if there are 3 or more tanks in the raid float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) { if (!botAI->IsTank(bot)) return 1.0f; if (bot->GetVictim() == nullptr) return 1.0f; Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED) return 1.0f; Group* group = bot->GetGroup(); if (!group) return 1.0f; uint8 tankCount = 0; for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); if (!member || !member->IsAlive()) continue; if (botAI->IsTank(member)) ++tankCount; } if (tankCount < 3) return 1.0f; if (dynamic_cast(action)) return 0.0f; return 1.0f; } // Leotheras the Blind float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) { if (botAI->IsTank(bot)) return 1.0f; if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return 1.0f; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); if (!leotheras || (!leotheras->HasAura(SPELL_WHIRLWIND) && !leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL))) return 1.0f; if (dynamic_cast(action)) return 0.0f; if (dynamic_cast(action) && !dynamic_cast(action) && !dynamic_cast(action)) return 0.0f; return 1.0f; } float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) { if (!botAI->IsTank(bot) || bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return 1.0f; if (GetPhase2LeotherasDemon(bot) && dynamic_cast(action)) return 0.0f; if (!GetPhase3LeotherasDemon(bot) && dynamic_cast(action)) return 0.0f; return 1.0f; } float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action) { if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* action) { if (botAI->IsRanged(bot) || botAI->IsTank(bot)) return 1.0f; if (!GetPhase2LeotherasDemon(bot)) return 1.0f; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); if (!chaosBlast || chaosBlast->GetStackAmount() < 5) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) { Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); if (!leotheras) return 1.0f; if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return 1.0f; if (dynamic_cast(action)) return 1.0f; const uint32 instanceId = leotheras->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); constexpr uint8 dpsWaitSecondsPhase1 = 5; Unit* leotherasHuman = GetLeotherasHuman(bot); Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(bot); if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && !leotherasPhase3Demon) { if (botAI->IsTank(bot)) return 1.0f; auto it = leotherasHumanFormDpsWaitTimer.find(instanceId); if (it == leotherasHumanFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase1) { if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) return 0.0f; } } constexpr uint8 dpsWaitSecondsPhase2 = 12; Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(bot); Player* demonFormTank = GetLeotherasDemonFormTank(bot); if (leotherasPhase2Demon) { if (demonFormTank && demonFormTank == bot) return 1.0f; if (!demonFormTank && botAI->IsTank(bot)) return 1.0f; auto it = leotherasDemonFormDpsWaitTimer.find(instanceId); if (it == leotherasDemonFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase2) { if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) return 0.0f; } } constexpr uint8 dpsWaitSecondsPhase3 = 8; if (leotherasPhase3Demon) { if ((demonFormTank && demonFormTank == bot) || botAI->IsTank(bot)) return 1.0f; auto it = leotherasFinalPhaseDpsWaitTimer.find(instanceId); if (it == leotherasFinalPhaseDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase3) { if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) return 0.0f; } } return 1.0f; } // Don't use Bloodlust/Heroism during the Channeler phase float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) { if (bot->getClass() != CLASS_SHAMAN) return 1.0f; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); if (!leotheras || !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } // Fathom-Lord Karathress float FathomLordKarathressDisableTankActionsMultiplier::GetValue(Action* action) { if (!botAI->IsTank(bot)) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return 1.0f; if (bot->GetVictim() != nullptr && dynamic_cast(action)) return 0.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action) { if (!botAI->IsDps(bot)) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return 1.0f; auto castSpellAction = dynamic_cast(action); if (castSpellAction && castSpellAction->getThreatType() == Action::ActionThreatType::Aoe) return 0.0f; return 1.0f; } float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action) { if (bot->getClass() != CLASS_HUNTER) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return 1.0f; if (dynamic_cast(action)) return 0.0f; return 1.0f; } float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) { if (botAI->IsTank(bot)) return 1.0f; Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); if (!karathress) return 1.0f; if (dynamic_cast(action)) return 1.0f; const time_t now = std::time(nullptr); constexpr uint8 dpsWaitSeconds = 12; auto it = karathressDpsWaitTimer.find(karathress->GetMap()->GetInstanceId()); if (it == karathressDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) { if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) return 0.0f; } return 1.0f; } float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue(Action* action) { if (!botAI->IsAssistHealOfIndex(bot, 0, true)) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } // Morogrim Tidewalker // Use Bloodlust/Heroism after the first Murloc spawn float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) { if (bot->getClass() != CLASS_SHAMAN) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) return 1.0f; if (AI_VALUE2(Unit*, "find target", "tidewalker lurker")) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action) { if (!botAI->IsMainTank(bot)) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) return 1.0f; if (dynamic_cast(action)) return 0.0f; return 1.0f; } float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* action) { if (!botAI->IsRanged(bot)) return 1.0f; Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); if (!tidewalker || tidewalker->GetHealthPct() > 25.0f) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } // Lady Vashj // Wait until phase 3 to use Bloodlust/Heroism // Don't use other major cooldowns in Phase 1, either float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action) { if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return 1.0f; if (bot->getClass() == CLASS_SHAMAN && !IsLadyVashjInPhase3(botAI) && (dynamic_cast(action) || dynamic_cast(action))) return 0.0f; if (!botAI->IsDps(bot) || !IsLadyVashjInPhase1(botAI)) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } float LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier::GetValue(Action* action) { if (bot->getClass() != CLASS_SHAMAN) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return 1.0f; if (!IsMainTankInSameSubgroup(botAI, bot)) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } float LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action) { if (!botAI->IsRanged(bot)) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase1(botAI)) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) { if (botAI->IsMainTank(bot) || !bot->HasAura(SPELL_STATIC_CHARGE)) return 1.0f; if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; return 1.0f; } // Bots should not loot the core with normal looting logic float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) { if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return 1.0f; if (dynamic_cast(action)) return 0.0f; return 1.0f; } float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action) { if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI)) return 1.0f; if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 1.0f; auto coreHandlers = GetCoreHandlers(botAI, bot); bool isCoreHandler = false; for (int i = 0; i < static_cast(coreHandlers.size()); ++i) { if (coreHandlers[i] && coreHandlers[i] == bot) { isCoreHandler = true; } } if (!isCoreHandler) return 1.0f; auto hasCore = [](Player* player) { return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false); }; // If the bot actually has the core, only allow core handling if (hasCore(bot) && !dynamic_cast(action)) return 0.0f; // First and second passers block movement when the looter teleports to the elemental Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f && (bot == coreHandlers[1] || bot == coreHandlers[2]) && (dynamic_cast(action) && !dynamic_cast(action))) return 0.0f; // If any prior handler (including self) recently had the core, block other movement if (AnyRecentCoreInInventory(botAI, bot) && dynamic_cast(action) && !dynamic_cast(action)) return 0.0f; return 1.0f; } // All of phases 2 and 3 require a custom movement and targeting system // So the standard target selection system must be disabled float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) return 1.0f; if (dynamic_cast(action)) return 0.0f; if (IsLadyVashjInPhase2(botAI)) { if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; if (bot->GetExactDist2d(vashj) < 60.0f && dynamic_cast(action)) return 0.0f; if (!botAI->IsHeal(bot) && dynamic_cast(action)) return 0.0f; Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); if (enchanted && AI_VALUE(Unit*, "current target") == enchanted && dynamic_cast(action)) return 0.0f; } if (IsLadyVashjInPhase3(botAI)) { if (dynamic_cast(action) || dynamic_cast(action)) return 0.0f; Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite"); if (enchanted || strider || elite) { if (dynamic_cast(action) || dynamic_cast(action)) return 0.0f; if (enchanted && AI_VALUE(Unit*, "current target") == enchanted && dynamic_cast(action)) return 0.0f; } else if (dynamic_cast(action)) return 0.0f; } return 1.0f; }