From dca0be2932b42a53a43b68f1829799342eed522e Mon Sep 17 00:00:00 2001 From: Crow Date: Fri, 13 Mar 2026 16:21:37 -0500 Subject: [PATCH] Updates to SSC Strategies (#2138) # Pull Request Most of the changes are not functional but are to modify style based on comments received to the TK PR (e.g., eliminate nesting of if statements) and leverage general boss helpers. There is some reordering of returns and other changes to try to consolidate or clean-up the code (such as removing unnecessary parameters). The strategies themselves have only minor changes. - Main tank no longer uses tangential movement for Lurker Spout, unlike other bots. The MT will just call moves directly to a position behind Lurker. This is because I found tangential movement was taking too long for the MT to get in place since it starts right in front of Lurker. - Vashj MT group Shaman will now use Grounding Totem without actually switching to the Grounding Totem strategy. I have now eliminated all strategy swaps, which I dislike because they persist after the encounter, and it's better not to mess with player strategies since presumably people are generally using Windfury or Wrath of Air for the Air Totem. - I made a ton of changes to Vashj core passing as I noticed the existing logic is nonfunctional in several ways. It generally works fine ingame, but the changes should make things much smoother. For example, the storing of the nearest trigger NPC for generators in the existing strategy is useless because it relies on insert_or_assign for an unordered map that will continue to run during the course of the core passing logic, and a similar issue exists with respect to the map to store the last time a bot held a core. The result is that there is slight movement of bots when the core is flying through the air and not held by any bot because the trigger for core passing does not fire during that period. In practice, the time is brief enough that the sequence works OK, but it looks stupid because the bots should not be moving at all. So that should be fixed. There is a known issue re: core passing that would take extreme effort to fix and I am not going to do it because it is a fringe situation. There are a couple of spots where the Tainted Elemental can rarely spawn that can result in a straight-line passing sequence to the nearest generator that is blocked by LoS. Fixing this would be extremely difficult and niche, so what you will need to do if this happens is to just command your bots to destroy the core and try again with the next spawn. --- ## Design Philosophy We prioritize **stability, performance, and predictability** over behavioral realism. Complex player-mimicking logic is intentionally limited due to its negative impact on scalability, maintainability, and long-term robustness. Excessive processing overhead can lead to server hiccups, increased CPU usage, and degraded performance for all participants. Because every action and decision tree is executed **per bot and per trigger**, even small increases in logic complexity can scale poorly and negatively affect both players and world (random) bots. Bots are not expected to behave perfectly, and perfect simulation of human decision-making is not a project goal. Increased behavioral realism often introduces disproportionate cost, reduced predictability, and significantly higher maintenance overhead. Every additional branch of logic increases long-term responsibility. All decision paths must be tested, validated, and maintained continuously as the system evolves. If advanced or AI-intensive behavior is introduced, the **default configuration must remain the lightweight decision model**. More complex behavior should only be available as an **explicit opt-in option**, clearly documented as having a measurable performance cost. Principles: - **Stability before intelligence** A stable system is always preferred over a smarter one. - **Performance is a shared resource** Any increase in bot cost affects all players and all bots. - **Simple logic scales better than smart logic** Predictable behavior under load is more valuable than perfect decisions. - **Complexity must justify itself** If a feature cannot clearly explain its cost, it should not exist. - **Defaults must be cheap** Expensive behavior must always be optional and clearly communicated. - **Bots should look reasonable, not perfect** The goal is believable behavior, not human simulation. Before submitting, confirm that this change aligns with those principles. --- ## Feature Evaluation Please answer the following: - Describe the **minimum logic** required to achieve the intended behavior? - Describe the **cheapest implementation** that produces an acceptable result? - Describe the **runtime cost** when this logic executes across many bots? I have attempted to streamline methods and even remove some. The strategy is admittedly somewhat performance heavy due to the need for function calls such as iterating over inventory items. However, the new version should be less performance intensive than the merged strategy--for example, there were places where all members of the raid would have their inventory checked, but I've now limited the check to only the 5 core handler bots. I've run the instance with pmon on, and there are no methods that stand out as particularly resource intensive when not in a boss encounter, and I view that as the most important thing (though I make effort to reduce performance impact during encounters also). Expensive checks that are unavoidable for the strategy to work such as grid searches are gated behind cheaper checks. --- ## How to Test the Changes - Step-by-step instructions to test the change - Any required setup (e.g. multiple players, bots, specific configuration) - Expected behavior and how to verify it Enter SSC with a raid group and run the instance, including all bosses. Every boss should be killable, and every major mechanic should be addressed by bots. I will work with Dreathean to get the Wiki up soon so that should be a reference for testing strategies. ## Complexity & Impact Does this change add new decision branches? - - [ ] No - - [x] Yes (**explain below**) Only within the context of strategies, which are basically all new decision branches, and there are some tweaks here to what is currently merged. Does this change increase per-bot or per-tick processing? - - [x] No - - [ ] Yes (**describe and justify impact**) Could this logic scale poorly under load? - - [ ] No - - [x] Yes (**explain why**) I'm sure if you have a large server, with multiple raid groups running the instance at the same time, the performance impact could be significant. But I have done my best to limit it, and I think some notable performance impact is unavoidable with the current framework for raid strategies. --- ## Defaults & Configuration Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Only for strategies in the instance. If this introduces more advanced or AI-heavy logic: - - [ ] Lightweight mode remains the default - - [x] More complex behavior is optional and thereby configurable There should be no impact if co +ssc and nc +ssc are not added to bots. Because raid strategies are currently not removed after leaving an instance, players should manually remove them (or reset botAI). This is a general issue that needs to be addressed with the module. --- ## AI Assistance Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change? - - [ ] No - - [x] Yes (**explain below**) If yes, please specify: - AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.) - Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation) - Which parts of the change were influenced or generated - Whether the result was manually reviewed and adapted AI assistance is allowed, but all submitted code must be fully understood, reviewed, and owned by the contributor. Any AI-influenced changes must be verified against existing CORE and PB logic. We expect contributors to be honest about what they do and do not understand. GPT-4 because I don't like to use up my premium requests in CoPilot and I generally like it better than GPT-5 =P I use LLMs to draft code snippets but do review everything and have become less-and-less reliant over time. I don't use agent mode, only ask. For this PR, I had it do the updated version of AnyRecentCoreInInventory(), which is more complicated than before and uses indexing for each bot to consider status of only prior bots in the passing chain. Everything else either I wrote or could have written but had the AI help and just edited afterward to save time. --- ## Final Checklist - - [x] Stability is not compromised - - [x] Performance impact is understood, tested, and acceptable - - [x] Added logic complexity is justified and explained - - [x] Documentation updated if needed --- ## Notes for Reviewers Anything that significantly improves realism at the cost of stability or performance should be carefully discussed before merging. --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- .../Action/RaidSSCActions.cpp | 1488 +++++++---------- .../Action/RaidSSCActions.h | 26 +- .../Multiplier/RaidSSCMultipliers.cpp | 485 +++--- .../Multiplier/RaidSSCMultipliers.h | 13 + .../RaidSSCActionContext.h | 11 +- .../RaidSSCTriggerContext.h | 11 +- .../Strategy/RaidSSCStrategy.cpp | 9 +- .../Strategy/RaidSSCStrategy.h | 5 + .../Trigger/RaidSSCTriggers.cpp | 360 ++-- .../Trigger/RaidSSCTriggers.h | 13 +- .../Util/RaidSSCHelpers.cpp | 307 ++-- .../SerpentshrineCavern/Util/RaidSSCHelpers.h | 40 +- 12 files changed, 1221 insertions(+), 1547 deletions(-) diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp index 7aa3eda3..0b31a1c1 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp @@ -1,3 +1,8 @@ +/* + * 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 "RaidSSCActions.h" #include "RaidSSCHelpers.h" #include "AiFactory.h" @@ -19,42 +24,40 @@ bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event /*event*/) const ObjectGuid guid = bot->GetGUID(); bool erased = false; - if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) + + if (!AI_VALUE2(Unit*, "find target", "hydross the unstable") && + (hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0 || + hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0 || + hydrossNatureDpsWaitTimer.erase(instanceId) > 0 || + hydrossFrostDpsWaitTimer.erase(instanceId) > 0)) { - if (hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0) - erased = true; - if (hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0) - erased = true; - if (hydrossNatureDpsWaitTimer.erase(instanceId) > 0) - erased = true; - if (hydrossFrostDpsWaitTimer.erase(instanceId) > 0) - erased = true; + erased = true; } - if (!AI_VALUE2(Unit*, "find target", "the lurker below")) + + if (!AI_VALUE2(Unit*, "find target", "the lurker below") && + (lurkerRangedPositions.erase(guid) > 0 || + lurkerSpoutTimer.erase(instanceId) > 0)) { - if (lurkerRangedPositions.erase(guid) > 0) - erased = true; - if (lurkerSpoutTimer.erase(instanceId) > 0) - erased = true; + erased = true; } - if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) + + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && + karathressDpsWaitTimer.erase(instanceId) > 0) { - if (karathressDpsWaitTimer.erase(instanceId) > 0) - erased = true; + erased = true; } - if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) + + if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker") && + (tidewalkerTankStep.erase(guid) > 0 || + tidewalkerRangedStep.erase(guid) > 0)) { - if (tidewalkerTankStep.erase(guid) > 0) - erased = true; - if (tidewalkerRangedStep.erase(guid) > 0) - erased = true; + erased = true; } - if (!AI_VALUE2(Unit*, "find target", "lady vashj")) + + if (!AI_VALUE2(Unit*, "find target", "lady vashj") && + hasReachedVashjRangedPosition.erase(guid) > 0) { - if (vashjRangedPositions.erase(guid) > 0) - erased = true; - if (hasReachedVashjRangedPosition.erase(guid) > 0) - erased = true; + erased = true; } return erased; @@ -74,21 +77,18 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event /*event*/) return false; float radius = dynObj->GetRadius(); - if (radius <= 0.0f) + const SpellInfo* sInfo = sSpellMgr->GetSpellInfo(dynObj->GetSpellId()); + if (radius <= 0.0f && sInfo) { - const SpellInfo* sInfo = sSpellMgr->GetSpellInfo(dynObj->GetSpellId()); - if (sInfo) + for (int e = 0; e < MAX_SPELL_EFFECTS; ++e) { - for (int e = 0; e < MAX_SPELL_EFFECTS; ++e) + auto const& eff = sInfo->Effects[e]; + if (eff.Effect == SPELL_EFFECT_SCHOOL_DAMAGE || + (eff.Effect == SPELL_EFFECT_APPLY_AURA && + eff.ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)) { - auto const& eff = sInfo->Effects[e]; - if (eff.Effect == SPELL_EFFECT_SCHOOL_DAMAGE || - (eff.Effect == SPELL_EFFECT_APPLY_AURA && - eff.ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)) - { - radius = eff.CalcRadius(); - break; - } + radius = eff.CalcRadius(); + break; } } } @@ -207,15 +207,13 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event /*event*/) } } - if (hydross->HasAura(SPELL_CORRUPTION)) + const Position& position = HYDROSS_FROST_TANK_POSITION; + if (hydross->HasAura(SPELL_CORRUPTION) && + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { - const Position& position = HYDROSS_FROST_TANK_POSITION; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) - { - return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), - position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); } return false; @@ -289,15 +287,13 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event /*event*/) } } - if (!hydross->HasAura(SPELL_CORRUPTION)) + const Position& position = HYDROSS_NATURE_TANK_POSITION; + if (!hydross->HasAura(SPELL_CORRUPTION) && + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { - const Position& position = HYDROSS_NATURE_TANK_POSITION; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) - { - return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), - position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); } return false; @@ -305,8 +301,7 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event /*event*/) bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event /*event*/) { - Unit* waterElemental = GetFirstAliveUnitByEntry(botAI, NPC_PURE_SPAWN_OF_HYDROSS); - if (waterElemental) + if (Unit* waterElemental = GetFirstAliveUnitByEntry(botAI, NPC_PURE_SPAWN_OF_HYDROSS)) { if (IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr)) MarkTargetWithSkull(bot, waterElemental); @@ -336,20 +331,10 @@ bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event /*event*/) if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) return false; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || member == bot || !member->IsAlive()) - continue; - - constexpr float safeDistance = 6.0f; - constexpr uint32 minInterval = 1000; - if (bot->GetExactDist2d(member) < safeDistance) - return FleePosition(member->GetPosition(), safeDistance, minInterval); - } - } + constexpr float safeDistance = 6.0f; + constexpr uint32 minInterval = 1000; + if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistance)) + return FleePosition(nearestPlayer->GetPosition(), safeDistance, minInterval); return false; } @@ -360,33 +345,17 @@ bool HydrossTheUnstableMisdirectBossToTankAction::Execute(Event /*event*/) if (!hydross) return false; - if (Group* group = bot->GetGroup()) - { - if (TryMisdirectToFrostTank(hydross, group)) - return true; - - if (TryMisdirectToNatureTank(hydross, group)) - return true; - } - - return false; + return TryMisdirectToFrostTank(hydross) || TryMisdirectToNatureTank(hydross); } bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToFrostTank( - Unit* hydross, Group* group) + Unit* hydross) { - Player* frostTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - frostTank = member; - break; - } - } + Player* frostTank = GetGroupMainTank(botAI, bot); + if (!frostTank) + return false; - if (HasNoMarkOfHydross(bot) && !hydross->HasAura(SPELL_CORRUPTION) && frostTank) + if (HasNoMarkOfHydross(bot) && !hydross->HasAura(SPELL_CORRUPTION)) { if (botAI->CanCastSpell("misdirection", frostTank)) return botAI->CastSpell("misdirection", frostTank); @@ -399,20 +368,13 @@ bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToFrostTank( } bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToNatureTank( - Unit* hydross, Group* group) + Unit* hydross) { - Player* natureTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && botAI->IsAssistTankOfIndex(member, 0, true)) - { - natureTank = member; - break; - } - } + Player* natureTank = GetGroupAssistTank(botAI, bot, 0); + if (!natureTank) + return false; - if (HasNoMarkOfCorruption(bot) && hydross->HasAura(SPELL_CORRUPTION) && natureTank) + if (HasNoMarkOfCorruption(bot) && hydross->HasAura(SPELL_CORRUPTION)) { if (botAI->CanCastSpell("misdirection", natureTank)) return botAI->CastSpell("misdirection", natureTank); @@ -480,33 +442,28 @@ bool HydrossTheUnstableManageTimersAction::Execute(Event /*event*/) const time_t now = std::time(nullptr); bool changed = false; + if (!hydross->HasAura(SPELL_CORRUPTION)) { - if (hydrossFrostDpsWaitTimer.try_emplace(instanceId, now).second) + if (hydrossFrostDpsWaitTimer.try_emplace(instanceId, now).second || + hydrossNatureDpsWaitTimer.erase(instanceId) > 0 || + hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0) changed = true; - if (hydrossNatureDpsWaitTimer.erase(instanceId) > 0) + + if (HasMarkOfHydrossAt100Percent(bot) && + hydrossChangeToNaturePhaseTimer.try_emplace(instanceId, now).second) changed = true; - if (hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0) - changed = true; - if (HasMarkOfHydrossAt100Percent(bot)) - { - if (hydrossChangeToNaturePhaseTimer.try_emplace(instanceId, now).second) - changed = true; - } } else { - if (hydrossNatureDpsWaitTimer.try_emplace(instanceId, now).second) + if (hydrossNatureDpsWaitTimer.try_emplace(instanceId, now).second || + hydrossFrostDpsWaitTimer.erase(instanceId) > 0 || + hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0) changed = true; - if (hydrossFrostDpsWaitTimer.erase(instanceId) > 0) + + if (HasMarkOfCorruptionAt100Percent(bot) && + hydrossChangeToFrostPhaseTimer.try_emplace(instanceId, now).second) changed = true; - if (hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0) - changed = true; - if (HasMarkOfCorruptionAt100Percent(bot)) - { - if (hydrossChangeToFrostPhaseTimer.try_emplace(instanceId, now).second) - changed = true; - } } return changed; @@ -521,17 +478,19 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event /*event*/) if (!lurker) return false; - float radius = frand(20.0f, 21.0f); + float radius = frand(19.0f, 20.0f); float botAngle = std::atan2( bot->GetPositionY() - lurker->GetPositionY(), bot->GetPositionX() - lurker->GetPositionX()); float relativeAngle = Position::NormalizeOrientation(botAngle - lurker->GetOrientation()); constexpr float safeArc = M_PI / 2.0f; - if (std::fabs(Position::NormalizeOrientation(relativeAngle - M_PI)) > safeArc / 2.0f) + if (!botAI->IsMainTank(bot) && + std::fabs(Position::NormalizeOrientation(relativeAngle - M_PI)) > safeArc / 2.0f) { float tangentAngle = botAngle + (relativeAngle > M_PI ? -0.1f : 0.1f); float moveX = lurker->GetPositionX() + radius * std::cos(tangentAngle); float moveY = lurker->GetPositionY() + radius * std::sin(tangentAngle); + botAI->Reset(); return MoveTo(SSC_MAP_ID, moveX, moveY, lurker->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); @@ -541,6 +500,7 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event /*event*/) float behindAngle = lurker->GetOrientation() + M_PI + frand(-0.5f, 0.5f) * safeArc; float targetX = lurker->GetPositionX() + radius * std::cos(behindAngle); float targetY = lurker->GetPositionY() + radius * std::sin(behindAngle); + if (bot->GetExactDist2d(targetX, targetY) > 2.0f) { botAI->Reset(); @@ -611,7 +571,7 @@ bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event /*event*/) float angle = (count == 1) ? arcCenter : (arcStart + arcSpan * static_cast(botIndex) / static_cast(count - 1)); - float radius = 28.0f; + constexpr float radius = 27.0f; float targetX = lurker->GetPositionX() + radius * std::sin(angle); float targetY = lurker->GetPositionY() + radius * std::cos(angle); @@ -638,38 +598,24 @@ bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event /*event*/) // the first 3 will each pick up 1 Guardian bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event /*event*/) { - Player* mainTank = nullptr; - Player* firstAssistTank = nullptr; - Player* secondAssistTank = nullptr; - - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; - - if (!mainTank && botAI->IsMainTank(member)) - mainTank = member; - else if (!firstAssistTank && botAI->IsAssistTankOfIndex(member, 0, true)) - firstAssistTank = member; - else if (!secondAssistTank && botAI->IsAssistTankOfIndex(member, 1, true)) - secondAssistTank = member; - } - } - + Player* mainTank = GetGroupMainTank(botAI, bot); + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); + Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1); if (!mainTank || !firstAssistTank || !secondAssistTank) return false; std::vector guardians; - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto guid : npcs) + auto const& attackers = + botAI->GetAiObjectContext()->GetValue("possible targets no los")->Get(); + + for (auto guid : attackers) { Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_COILFANG_GUARDIAN) + if (unit && unit->IsAlive() && + unit->GetEntry() == NPC_COILFANG_GUARDIAN) + { guardians.push_back(unit); + } } if (guardians.size() < 3) @@ -692,7 +638,8 @@ bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event /*event*/) { MarkTargetWithIcon(bot, guardian, rtiIndices[i]); SetRtiTarget(botAI, rtiNames[i], guardian); - if (bot->GetTarget() != guardian->GetGUID()) + + if (bot->GetVictim() != guardian) return Attack(guardian); } } @@ -709,18 +656,24 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event /*event*/) const uint32 instanceId = lurker->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); + bool changed = false; + auto it = lurkerSpoutTimer.find(instanceId); if (it != lurkerSpoutTimer.end() && it->second <= now) { lurkerSpoutTimer.erase(it); + changed = true; it = lurkerSpoutTimer.end(); } const time_t spoutCastTime = 20; if (IsLurkerCastingSpout(lurker) && it == lurkerSpoutTimer.end()) + { lurkerSpoutTimer.try_emplace(instanceId, now + spoutCastTime); + changed = true; + } - return false; + return changed; } // Leotheras the Blind @@ -737,15 +690,16 @@ bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event /*event*/) // Use tank strategy for Demon Form and DPS strategy for Human Form bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event /*event*/) { + auto const& attackers = + botAI->GetAiObjectContext()->GetValue("possible targets no los")->Get(); + Unit* innerDemon = nullptr; - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) + for (auto guid : attackers) { Unit* unit = botAI->GetUnit(guid); Creature* creature = unit ? unit->ToCreature() : nullptr; - if (creature && creature->GetEntry() == NPC_INNER_DEMON - && creature->GetSummonerGUID() == bot->GetGUID()) + if (creature && creature->GetEntry() == NPC_INNER_DEMON && + creature->GetSummonerGUID() == bot->GetGUID()) { innerDemon = creature; break; @@ -755,7 +709,7 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event /*event*/) if (innerDemon) return false; - if (Unit* leotherasDemon = GetActiveLeotherasDemon(botAI)) + if (Unit* leotherasDemon = GetActiveLeotherasDemon(bot)) { MarkTargetWithSquare(bot, leotherasDemon); SetRtiTarget(botAI, "square", leotherasDemon); @@ -781,7 +735,7 @@ bool LeotherasTheBlindMeleeTanksDontAttackDemonFormAction::Execute(Event /*event bool LeotherasTheBlindPositionRangedAction::Execute(Event /*event*/) { constexpr float safeDistFromBoss = 15.0f; - Unit* leotherasHuman = GetLeotherasHuman(botAI); + Unit* leotherasHuman = GetLeotherasHuman(bot); if (leotherasHuman && bot->GetExactDist2d(leotherasHuman) < safeDistFromBoss && leotherasHuman->GetVictim() != bot) { @@ -789,11 +743,10 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event /*event*/) return FleePosition(leotherasHuman->GetPosition(), safeDistFromBoss, minInterval); } - Group* group = bot->GetGroup(); - if (!group) + if (!GetActiveLeotherasDemon(bot)) return false; - if (GetActiveLeotherasDemon(botAI)) + if (Group* group = bot->GetGroup()) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -804,9 +757,9 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event /*event*/) constexpr uint32 minInterval = 0; if (GetLeotherasDemonFormTank(bot) == member) { - constexpr float safeDistFromTank = 10.0f; - if (bot->GetExactDist2d(member) < safeDistFromTank) - return FleePosition(member->GetPosition(), safeDistFromTank, minInterval); + constexpr float safeDistFromMember = 10.0f; + if (bot->GetExactDist2d(member) < safeDistFromMember) + return FleePosition(member->GetPosition(), safeDistFromMember, minInterval); } else { @@ -822,7 +775,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event /*event*/) bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event /*event*/) { - if (Unit* leotherasHuman = GetLeotherasHuman(botAI)) + if (Unit* leotherasHuman = GetLeotherasHuman(bot)) { float currentDistance = bot->GetExactDist2d(leotherasHuman); constexpr float safeDistance = 25.0f; @@ -843,7 +796,7 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event /*event*/) if (botAI->CanCastSpell("cloak of shadows", bot)) return botAI->CastSpell("cloak of shadows", bot); - Unit* leotheras = GetPhase2LeotherasDemon(botAI); + Unit* leotheras = GetPhase2LeotherasDemon(bot); if (!leotheras) return false; @@ -853,12 +806,8 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event /*event*/) float currentDistance = bot->GetExactDist2d(demonVictim); constexpr float safeDistance = 10.0f; - if (currentDistance < safeDistance) - { - botAI->Reset(); - if (demonVictim != bot) - return MoveAway(demonVictim, safeDistance - currentDistance); - } + if (currentDistance < safeDistance && demonVictim != bot) + return MoveAway(demonVictim, safeDistance - currentDistance); return false; } @@ -866,15 +815,16 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event /*event*/) // Hardcoded actions for healers and bear tanks to kill Inner Demons bool LeotherasTheBlindDestroyInnerDemonAction::Execute(Event /*event*/) { + auto const& attackers = + botAI->GetAiObjectContext()->GetValue("possible targets no los")->Get(); + Unit* innerDemon = nullptr; - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) + for (auto guid : attackers) { Unit* unit = botAI->GetUnit(guid); Creature* creature = unit ? unit->ToCreature() : nullptr; - if (creature && creature->GetEntry() == NPC_INNER_DEMON - && creature->GetSummonerGUID() == bot->GetGUID()) + if (creature && creature->GetEntry() == NPC_INNER_DEMON && + creature->GetSummonerGUID() == bot->GetGUID()) { innerDemon = creature; break; @@ -910,37 +860,34 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleFeralTankStrategy(Unit* inn bot->RemoveAura(SPELL_BEAR_FORM); bool casted = false; - if (!bot->HasAura(SPELL_CAT_FORM) && botAI->CanCastSpell("cat form", bot)) - { - if (botAI->CastSpell("cat form", bot)) - casted = true; - } - if (botAI->CanCastSpell("berserk", bot)) - { - if (botAI->CastSpell("berserk", bot)) - casted = true; - } - if (bot->GetPower(POWER_ENERGY) < 30 && botAI->CanCastSpell("tiger's fury", bot)) - { - if (botAI->CastSpell("tiger's fury", bot)) - casted = true; - } - if (bot->GetComboPoints() >= 4 && botAI->CanCastSpell("ferocious bite", innerDemon)) - { - if (botAI->CastSpell("ferocious bite", innerDemon)) - casted = true; - } + + if (!bot->HasAura(SPELL_CAT_FORM) && + botAI->CanCastSpell("cat form", bot) && + botAI->CastSpell("cat form", bot)) + casted = true; + + if (botAI->CanCastSpell("berserk", bot) && + botAI->CastSpell("berserk", bot)) + casted = true; + + if (bot->GetPower(POWER_ENERGY) < 30 && + botAI->CanCastSpell("tiger's fury", bot) && + botAI->CastSpell("tiger's fury", bot)) + casted = true; + + if (bot->GetComboPoints() >= 4 && + botAI->CanCastSpell("ferocious bite", innerDemon) && + botAI->CastSpell("ferocious bite", innerDemon)) + casted = true; + if (bot->GetComboPoints() == 0 && innerDemon->GetHealthPct() > 25.0f && - botAI->CanCastSpell("rake", innerDemon)) - { - if (botAI->CastSpell("rake", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("mangle (cat)", innerDemon)) - { - if (botAI->CastSpell("mangle (cat)", innerDemon)) - casted = true; - } + botAI->CanCastSpell("rake", innerDemon) && + botAI->CastSpell("rake", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("mangle (cat)", innerDemon) && + botAI->CastSpell("mangle (cat)", innerDemon)) + casted = true; return casted; } @@ -953,52 +900,44 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerD bot->RemoveAura(SPELL_TREE_OF_LIFE); bool casted = false; - if (botAI->CanCastSpell("barkskin", bot)) - { - if (botAI->CastSpell("barkskin", bot)) - casted = true; - } - if (botAI->CanCastSpell("wrath", innerDemon)) - { - if (botAI->CastSpell("wrath", innerDemon)) - casted = true; - } + + if (botAI->CanCastSpell("barkskin", bot) && + botAI->CastSpell("barkskin", bot)) + casted = true; + + if (botAI->CanCastSpell("wrath", innerDemon) && + botAI->CastSpell("wrath", innerDemon)) + casted = true; return casted; } else if (bot->getClass() == CLASS_PALADIN) { bool casted = false; - if (botAI->CanCastSpell("avenging wrath", bot)) - { - if (botAI->CastSpell("avenging wrath", bot)) - casted = true; - } - if (botAI->CanCastSpell("consecration", bot)) - { - if (botAI->CastSpell("consecration", bot)) - casted = true; - } - if (botAI->CanCastSpell("exorcism", innerDemon)) - { - if (botAI->CastSpell("exorcism", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("hammer of wrath", innerDemon)) - { - if (botAI->CastSpell("hammer of wrath", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("holy shock", innerDemon)) - { - if (botAI->CastSpell("holy shock", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("judgment of light", innerDemon)) - { - if (botAI->CastSpell("judgment of light", innerDemon)) - casted = true; - } + + if (botAI->CanCastSpell("avenging wrath", bot) && + botAI->CastSpell("avenging wrath", bot)) + casted = true; + + if (botAI->CanCastSpell("consecration", bot) && + botAI->CastSpell("consecration", bot)) + casted = true; + + if (botAI->CanCastSpell("exorcism", innerDemon) && + botAI->CastSpell("exorcism", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("hammer of wrath", innerDemon) && + botAI->CastSpell("hammer of wrath", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("holy shock", innerDemon) && + botAI->CastSpell("holy shock", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("judgement of light", innerDemon) && + botAI->CastSpell("judgement of light", innerDemon)) + casted = true; return casted; } @@ -1010,21 +949,18 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerD else if (bot->getClass() == CLASS_SHAMAN) { bool casted = false; - if (botAI->CanCastSpell("earth shock", innerDemon)) - { - if (botAI->CastSpell("earth shock", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("chain lightning", innerDemon)) - { - if (botAI->CastSpell("chain lightning", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("lightning bolt", innerDemon)) - { - if (botAI->CastSpell("lightning bolt", innerDemon)) - casted = true; - } + + if (botAI->CanCastSpell("earth shock", innerDemon) && + botAI->CastSpell("earth shock", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("chain lightning", innerDemon) && + botAI->CastSpell("chain lightning", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("lightning bolt", innerDemon) && + botAI->CastSpell("lightning bolt", innerDemon)) + casted = true; return casted; } @@ -1035,7 +971,7 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerD // Everybody except the Warlock tank should focus on Leotheras in Phase 3 bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event /*event*/) { - Unit* leotherasHuman = GetLeotherasHuman(botAI); + Unit* leotherasHuman = GetLeotherasHuman(bot); if (!leotherasHuman) return false; @@ -1045,23 +981,27 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event /*event*/ if (bot->GetTarget() != leotherasHuman->GetGUID()) return Attack(leotherasHuman); - Unit* leotherasDemon = GetPhase3LeotherasDemon(botAI); - if (leotherasDemon) - { - if (leotherasHuman->GetVictim() == bot) - { - Unit* demonTarget = leotherasDemon->GetVictim(); - if (demonTarget && leotherasHuman->GetExactDist2d(demonTarget) < 20.0f) - { - float angle = atan2(bot->GetPositionY() - demonTarget->GetPositionY(), - bot->GetPositionX() - demonTarget->GetPositionX()); - float targetX = bot->GetPositionX() + 25.0f * std::cos(angle); - float targetY = bot->GetPositionY() + 25.0f * std::sin(angle); + Unit* leotherasDemon = GetPhase3LeotherasDemon(bot); + if (!leotherasDemon) + return false; - return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, - false, false, MovementPriority::MOVEMENT_FORCED, true, false); - } - } + if (leotherasHuman->GetVictim() != bot) + return false; + + Unit* demonTarget = leotherasDemon->GetVictim(); + if (!demonTarget) + return false; + + constexpr float safeDistanceFromDemon = 20.0f; + if (leotherasHuman->GetExactDist2d(demonTarget) < safeDistanceFromDemon) + { + float angle = atan2(bot->GetPositionY() - demonTarget->GetPositionY(), + bot->GetPositionX() - demonTarget->GetPositionX()); + float targetX = bot->GetPositionX() + safeDistanceFromDemon * std::cos(angle); + float targetY = bot->GetPositionY() + safeDistanceFromDemon * std::sin(angle); + + return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false); } return false; @@ -1070,28 +1010,13 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event /*event*/ // Misdirect to Warlock tank or to main tank if there is no Warlock tank bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event /*event*/) { - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + Unit* leotherasDemon = GetActiveLeotherasDemon(bot); if (!leotherasDemon) return false; - Player* demonFormTank = GetLeotherasDemonFormTank(bot); - Player* targetTank = demonFormTank; - + Player* targetTank = GetLeotherasDemonFormTank(bot); if (!targetTank) - { - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - targetTank = member; - break; - } - } - } - } + targetTank = GetGroupMainTank(botAI, bot); if (!targetTank) return false; @@ -1117,43 +1042,37 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event /*event*/) bool changed = false; // Encounter start/reset: clear all timers - if (leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) + if (leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) && + (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0 || + leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0 || + leotherasFinalPhaseDpsWaitTimer.erase(instanceId) > 0)) { - if (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; - if (leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; - if (leotherasFinalPhaseDpsWaitTimer.erase(instanceId) > 0) - changed = true; + changed = true; } // Human Phase - Unit* leotherasHuman = GetLeotherasHuman(botAI); - Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); - if (leotherasHuman && !leotherasPhase3Demon) + Unit* leotherasHuman = GetLeotherasHuman(bot); + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(bot); + if (leotherasHuman && !leotherasPhase3Demon && + (leotherasHumanFormDpsWaitTimer.try_emplace(instanceId, now).second || + leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0)) { - if (leotherasHumanFormDpsWaitTimer.try_emplace(instanceId, now).second) - changed = true; - if (leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; + changed = true; } // Demon Phase - else if (Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI)) + else if (GetPhase2LeotherasDemon(bot) && + (leotherasDemonFormDpsWaitTimer.try_emplace(instanceId, now).second || + leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0)) { - if (leotherasDemonFormDpsWaitTimer.try_emplace(instanceId, now).second) - changed = true; - if (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; + changed = true; } // Final Phase (<15% HP) - else if (leotherasHuman && leotherasPhase3Demon) + else if (leotherasHuman && leotherasPhase3Demon && + (leotherasFinalPhaseDpsWaitTimer.try_emplace(instanceId, now).second || + leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0 || + leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0)) { - if (leotherasFinalPhaseDpsWaitTimer.try_emplace(instanceId, now).second) - changed = true; - if (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; - if (leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; + changed = true; } return changed; @@ -1367,41 +1286,17 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event /*event*/) if (hunterIndex == 0) { bossTarget = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0, false)) - { - tankTarget = member; - break; - } - } + tankTarget = GetGroupAssistTank(botAI, bot, 0); } else if (hunterIndex == 1) { bossTarget = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 2, false)) - { - tankTarget = member; - break; - } - } + tankTarget = GetGroupAssistTank(botAI, bot, 2); } else if (hunterIndex == 2) { bossTarget = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 1, false)) - { - tankTarget = member; - break; - } - } + tankTarget = GetGroupAssistTank(botAI, bot, 1); } if (!bossTarget || !tankTarget) @@ -1529,11 +1424,9 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) bool FathomLordKarathressManageDpsTimerAction::Execute(Event /*event*/) { Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (!karathress) - return false; - - karathressDpsWaitTimer.try_emplace( - karathress->GetMap()->GetInstanceId(), std::time(nullptr)); + if (karathress && karathressDpsWaitTimer.try_emplace( + karathress->GetMap()->GetInstanceId(), std::time(nullptr)).second) + return true; return false; } @@ -1546,20 +1439,7 @@ bool MorogrimTidewalkerMisdirectBossToMainTankAction::Execute(Event /*event*/) if (!tidewalker) return false; - Player* mainTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - mainTank = member; - break; - } - } - } - + Player* mainTank = GetGroupMainTank(botAI, bot); if (!mainTank) return false; @@ -1757,8 +1637,7 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event /*event*/) // Phase 3: No fixed position, but move Vashj away from Enchanted Elementals else if (IsLadyVashjInPhase3(botAI)) { - Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); - if (enchanted) + if (Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental")) { float currentDistance = bot->GetExactDist2d(enchanted); constexpr float safeDistance = 10.0f; @@ -1775,70 +1654,52 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event /*event*/) bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event /*event*/) { std::vector spreadMembers; - if (Group* group = bot->GetGroup()) + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)) - { - if (botAI->IsRanged(member)) - spreadMembers.push_back(member); - } - } + Player* member = ref->GetSource(); + if (member && GET_PLAYERBOT_AI(member) && botAI->IsRanged(member)) + spreadMembers.push_back(member); } const ObjectGuid guid = bot->GetGUID(); - - auto itPos = vashjRangedPositions.find(guid); auto itReached = hasReachedVashjRangedPosition.find(guid); - if (itPos == vashjRangedPositions.end()) - { - auto it = std::find(spreadMembers.begin(), spreadMembers.end(), bot); - size_t botIndex = (it != spreadMembers.end()) ? - std::distance(spreadMembers.begin(), it) : 0; - size_t count = spreadMembers.size(); - if (count == 0) - return false; - const Position& center = VASHJ_PLATFORM_CENTER_POSITION; - constexpr float minRadius = 20.0f; - constexpr float maxRadius = 30.0f; - - constexpr float arcCenter = M_PI / 2.0f; // North - constexpr float arcSpan = M_PI; // 180° - constexpr float arcStart = arcCenter - arcSpan / 2.0f; - - float angle; - if (count == 1) - angle = arcCenter; - else - angle = arcStart + (static_cast(botIndex) / (count - 1)) * arcSpan; - - float radius = frand(minRadius, maxRadius); - float targetX = center.GetPositionX() + radius * std::cos(angle); - float targetY = center.GetPositionY() + radius * std::sin(angle); - - auto res = vashjRangedPositions.try_emplace(guid, Position(targetX, targetY, center.GetPositionZ())); - itPos = res.first; - hasReachedVashjRangedPosition.try_emplace(guid, false); - itReached = hasReachedVashjRangedPosition.find(guid); - } - - if (itPos == vashjRangedPositions.end()) + auto it = std::find(spreadMembers.begin(), spreadMembers.end(), bot); + size_t botIndex = (it != spreadMembers.end()) ? + std::distance(spreadMembers.begin(), it) : 0; + size_t count = spreadMembers.size(); + if (count == 0) return false; - Position position = itPos->second; + constexpr float arcCenter = M_PI / 2.0f; // North + constexpr float arcSpan = M_PI; // 180° + constexpr float arcStart = arcCenter - arcSpan / 2.0f; + + float angle; + if (count == 1) + angle = arcCenter; + else + angle = arcStart + (static_cast(botIndex) / (count - 1)) * arcSpan; + + const Position& center = VASHJ_PLATFORM_CENTER_POSITION; + float radius = 25.0f; + float targetX = center.GetPositionX() + radius * std::cos(angle); + float targetY = center.GetPositionY() + radius * std::sin(angle); + float targetZ = center.GetPositionZ(); + if (itReached == hasReachedVashjRangedPosition.end() || !(itReached->second)) { - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + if (bot->GetExactDist2d(targetX, targetY) > 2.0f) { - return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), - position.GetPositionZ(), false, false, false, false, + hasReachedVashjRangedPosition.try_emplace(guid, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } - if (itReached != hasReachedVashjRangedPosition.end()) - itReached->second = true; + hasReachedVashjRangedPosition[guid] = true; } return false; @@ -1847,37 +1708,19 @@ bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event /*event*/) // For absorbing Shock Burst bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event /*event*/) { - Player* mainTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - mainTank = member; - break; - } - } - } - + Player* mainTank = GetGroupMainTank(botAI, bot); if (!mainTank) return false; - if (bot->GetExactDist2d(mainTank) > 25.0f) - { - return MoveInside(SSC_MAP_ID, mainTank->GetPositionX(), mainTank->GetPositionY(), - mainTank->GetPositionZ(), 20.0f, MovementPriority::MOVEMENT_COMBAT); - } + if (mainTank->HasAura(SPELL_GROUNDING_TOTEM_EFFECT)) + return false; - if (!botAI->HasStrategy("grounding", BotState::BOT_STATE_COMBAT)) - botAI->ChangeStrategy("+grounding", BotState::BOT_STATE_COMBAT); + constexpr float distFromTank = 25.0f; + if (bot->GetDistance(mainTank) > distFromTank) + return MoveTo(mainTank, distFromTank, MovementPriority::MOVEMENT_COMBAT); - if (!bot->HasAura(SPELL_GROUNDING_TOTEM_EFFECT) && - botAI->CanCastSpell("grounding totem", bot)) - return botAI->CastSpell("grounding totem", bot); - - return false; + return botAI->CanCastSpell("grounding totem", bot) && + botAI->CastSpell("grounding totem", bot); } bool LadyVashjMisdirectBossToMainTankAction::Execute(Event /*event*/) @@ -1886,20 +1729,7 @@ bool LadyVashjMisdirectBossToMainTankAction::Execute(Event /*event*/) if (!vashj) return false; - Player* mainTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - mainTank = member; - break; - } - } - } - + Player* mainTank = GetGroupMainTank(botAI, bot); if (!mainTank) return false; @@ -1919,19 +1749,8 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event /*event*/) return false; // If the main tank has Static Charge, other group members should move away - Player* mainTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member) && - member->HasAura(SPELL_STATIC_CHARGE)) - { - mainTank = member; - break; - } - } - - if (mainTank && bot != mainTank) + Player* mainTank = GetGroupMainTank(botAI, bot); + if (mainTank && bot != mainTank && mainTank->HasAura(SPELL_STATIC_CHARGE)) { float currentDistance = bot->GetExactDist2d(mainTank); constexpr float safeDistance = 11.0f; @@ -1979,7 +1798,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) } auto const& attackers = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + botAI->GetAiObjectContext()->GetValue("possible targets no los")->Get(); Unit* target = nullptr; Unit* enchanted = nullptr; Unit* elite = nullptr; @@ -2019,7 +1838,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) break; case NPC_TOXIC_SPOREBAT: - if (!sporebat || bot->GetExactDist2d(unit) < bot->GetExactDist2d(sporebat)) + if (!sporebat || bot->GetDistance(unit) < bot->GetDistance(sporebat)) sporebat = unit; break; @@ -2067,10 +1886,10 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) SetRtiTarget(botAI, "diamond", vashj); targets = { vashj }; } - else if (botAI->IsAssistTankOfIndex(bot, 0, true)) + else if (botAI->HasCheat(BotCheatMask::raid) && + botAI->IsAssistTankOfIndex(bot, 0, true)) { - if (botAI->HasCheat(BotCheatMask::raid)) - targets = { strider, elite, enchanted, vashj }; + targets = { strider, elite, enchanted, vashj }; } else targets = { elite, strider, enchanted, vashj }; @@ -2114,22 +1933,8 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) return Attack(target); // If bots have wandered too far from the center, move them back - if (bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY()) > 55.0f) - { - Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(bot->GetGroup(), botAI); - // A bot will not move back to the middle if (1) there is a Tainted Elemental, and - // (2) the bot is either the designated looter or the first core passer - if (Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental")) - { - if ((designatedLooter && designatedLooter == bot) || - (firstCorePasser && firstCorePasser == bot)) - return false; - } - - return MoveInside(SSC_MAP_ID, center.GetPositionX(), center.GetPositionY(), - center.GetPositionZ(), 40.0f, MovementPriority::MOVEMENT_COMBAT); - } + if (bot->GetExactDist2d(vashj) > maxPursueRange) + return MoveTo(vashj, maxPursueRange - 10.0f, MovementPriority::MOVEMENT_FORCED); return false; } @@ -2144,24 +1949,11 @@ bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event /*event*/) if (bot->getClass() != CLASS_HUNTER) return false; - Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); + Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); if (!strider) return false; - Player* firstAssistTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && botAI->IsAssistTankOfIndex(member, 0, true)) - { - firstAssistTank = member; - break; - } - } - } - + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); if (!firstAssistTank || strider->GetVictim() == firstAssistTank) return false; @@ -2180,7 +1972,7 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event /*event*/) if (!vashj) return false; - Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); + Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); if (!strider) return false; @@ -2192,53 +1984,41 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event /*event*/) if (!bot->HasAura(SPELL_FEAR_WARD)) bot->AddAura(SPELL_FEAR_WARD, bot); - if (botAI->IsAssistTankOfIndex(bot, 0, true) && - bot->GetTarget() != strider->GetGUID()) + if (botAI->IsAssistTankOfIndex(bot, 0, true) && bot->GetTarget() != strider->GetGUID()) return Attack(strider); - if (strider->GetVictim() == bot) - { - float currentDistance = bot->GetExactDist2d(vashj); - constexpr float safeDistance = 28.0f; - - if (currentDistance < safeDistance) - return MoveAway(vashj, safeDistance - currentDistance); - } + float currentDistance = bot->GetExactDist2d(vashj); + constexpr float safeDistance = 28.0f; + if (strider->GetVictim() == bot && currentDistance < safeDistance) + return MoveAway(vashj, safeDistance - currentDistance); return false; } // Don't move away if raid cheats are enabled, or in any case if the bot is a tank - if (!botAI->HasCheat(BotCheatMask::raid) || !botAI->IsTank(bot)) + if (!botAI->HasCheat(BotCheatMask::raid)) { float currentDistance = bot->GetExactDist2d(strider); constexpr float safeDistance = 20.0f; - if (currentDistance < safeDistance) + if (!botAI->IsTank(bot) && currentDistance < safeDistance) return MoveAway(strider, safeDistance - currentDistance); - } - - // Try to root/slow the Strider if it is not tankable (poor man's kiting strategy) - if (!botAI->HasCheat(BotCheatMask::raid)) - { - if (!strider->HasAura(SPELL_HEAVY_NETHERWEAVE_NET)) - { - Item* net = bot->GetItemByEntry(ITEM_HEAVY_NETHERWEAVE_NET); - if (net && botAI->HasItemInInventory(ITEM_HEAVY_NETHERWEAVE_NET) && - botAI->CanCastSpell("heavy netherweave net", strider)) - return botAI->CastSpell("heavy netherweave net", strider); - } + // Try to root/slow the Strider if it is not tankable (poor man's kiting strategy) if (!botAI->HasAura("frost shock", strider) && bot->getClass() == CLASS_SHAMAN && botAI->CanCastSpell("frost shock", strider)) + { return botAI->CastSpell("frost shock", strider); - - if (!strider->HasAura(SPELL_CURSE_OF_EXHAUSTION) && bot->getClass() == CLASS_WARLOCK && - botAI->CanCastSpell("curse of exhaustion", strider)) + } + else if (!strider->HasAura(SPELL_CURSE_OF_EXHAUSTION) && bot->getClass() == CLASS_WARLOCK && + botAI->CanCastSpell("curse of exhaustion", strider)) + { return botAI->CastSpell("curse of exhaustion", strider); - - if (!strider->HasAura(SPELL_SLOW) && bot->getClass() == CLASS_MAGE && - botAI->CanCastSpell("slow", strider)) + } + else if (!strider->HasAura(SPELL_SLOW) && bot->getClass() == CLASS_MAGE && + botAI->CanCastSpell("slow", strider)) + { return botAI->CastSpell("slow", strider); + } } return false; @@ -2252,7 +2032,7 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event /*event*/) if (!tainted) return false; - if (bot->GetExactDist2d(tainted) >= 10.0f) + if (bot->GetExactDist2d(tainted) > 10.0f) { bot->AttackStop(); bot->InterruptNonMeleeSpells(true); @@ -2276,87 +2056,7 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event /*event*/) return false; } -bool LadyVashjLootTaintedCoreAction::Execute(Event) -{ - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) - return false; - - auto const& corpses = context->GetValue("nearest corpses")->Get(); - const float maxLootRange = sPlayerbotAIConfig.lootDistance; - - for (auto const& guid : corpses) - { - LootObject loot(bot, guid); - if (!loot.IsLootPossible(bot)) - continue; - - WorldObject* object = loot.GetWorldObject(bot); - if (!object) - continue; - - Creature* creature = object->ToCreature(); - if (!creature || creature->GetEntry() != NPC_TAINTED_ELEMENTAL || creature->IsAlive()) - continue; - - context->GetValue("loot target")->Set(loot); - - float dist = bot->GetDistance(object); - if (dist > maxLootRange) - return MoveTo(object, 2.0f, MovementPriority::MOVEMENT_FORCED); - - OpenLootAction open(botAI); - if (!open.Execute(Event())) - return false; - - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - return true; - } - } - - const ObjectGuid botGuid = bot->GetGUID(); - const ObjectGuid corpseGuid = guid; - constexpr uint8 coreIndex = 0; - - botAI->AddTimedEvent([botGuid, corpseGuid, coreIndex, vashj]() - { - Player* receiver = botGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(botGuid); - if (!receiver) - return; - - if (Group* group = receiver->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - return; - } - } - - receiver->SetLootGUID(corpseGuid); - - WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); - *packet << coreIndex; - receiver->GetSession()->QueuePacket(packet); - - const uint32 instanceId = vashj->GetMap()->GetInstanceId(); - const time_t now = std::time(nullptr); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); - }, 600); - - return true; - } - - return false; -} - -bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) +bool LadyVashjLootTaintedCoreAction::Execute(Event /*event*/) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) @@ -2366,36 +2066,78 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) if (!group) return false; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - if (!designatedLooter) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + return false; + } + + constexpr float searchRadius = 150.0f; + Creature* elemental = bot->FindNearestCreature(NPC_TAINTED_ELEMENTAL, searchRadius, false); + + if (!elemental || elemental->IsAlive()) return false; - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); - Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); - Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); + LootObject loot(bot, elemental->GetGUID()); + if (!loot.IsLootPossible(bot)) + return false; + + context->GetValue("loot target")->Set(loot); + + const float maxLootRange = sPlayerbotAIConfig.lootDistance; + constexpr float distFromObject = 2.0f; + + if (bot->GetDistance(elemental) > maxLootRange) + return MoveTo(elemental, distFromObject, MovementPriority::MOVEMENT_FORCED); + + OpenLootAction open(botAI); + if (!open.Execute(Event())) + return false; + + bot->SetLootGUID(elemental->GetGUID()); + constexpr uint8 coreIndex = 0; + WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); + *packet << coreIndex; + bot->GetSession()->QueuePacket(packet); + + const time_t now = std::time(nullptr); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); + + return true; +} + +bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); + Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); + Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot); + Player* thirdCorePasser = GetThirdTaintedCorePasser(botAI, bot); + Player* fourthCorePasser = GetFourthTaintedCorePasser(botAI, bot); + const uint32 instanceId = vashj->GetMap()->GetInstanceId(); Unit* closestTrigger = nullptr; - if (Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental")) + if (Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); + (closestTrigger = GetNearestActiveShieldGeneratorTriggerByEntry(tainted))) { - closestTrigger = GetNearestActiveShieldGeneratorTriggerByEntry(tainted); - if (closestTrigger) - nearestTriggerGuid.insert_or_assign(instanceId, closestTrigger->GetGUID()); + nearestTriggerGuid.try_emplace(instanceId, closestTrigger->GetGUID()); } auto itSnap = nearestTriggerGuid.find(instanceId); if (itSnap != nearestTriggerGuid.end() && !itSnap->second.IsEmpty()) { - Unit* snapUnit = botAI->GetUnit(itSnap->second); - if (snapUnit) + if (Unit* snapUnit = botAI->GetUnit(itSnap->second)) closestTrigger = snapUnit; else nearestTriggerGuid.erase(instanceId); } - if (!firstCorePasser || !secondCorePasser || !thirdCorePasser || - !fourthCorePasser || !closestTrigger) + if (!closestTrigger) return false; // Not gated behind CheatMask because the auto application of Fear Ward is necessary @@ -2408,63 +2150,57 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) if (!item || !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) { // Passer order: HealAssistantOfIndex 0, 1, 2, then RangedDpsAssistantOfIndex 0 - if (bot == firstCorePasser) + if (bot == firstCorePasser && + LineUpFirstCorePasser(designatedLooter, closestTrigger)) { - if (LineUpFirstCorePasser(designatedLooter, closestTrigger)) - return true; + return true; } - else if (bot == secondCorePasser) + else if (bot == secondCorePasser && + LineUpSecondCorePasser(firstCorePasser, closestTrigger)) { - if (LineUpSecondCorePasser(firstCorePasser, closestTrigger)) - return true; + return true; } - else if (bot == thirdCorePasser) + else if (bot == thirdCorePasser && LineUpThirdCorePasser( + designatedLooter, firstCorePasser, secondCorePasser, closestTrigger)) { - if (LineUpThirdCorePasser(designatedLooter, firstCorePasser, - secondCorePasser, closestTrigger)) - return true; + return true; } - else if (bot == fourthCorePasser) + else if (bot == fourthCorePasser && LineUpFourthCorePasser( + firstCorePasser, secondCorePasser, thirdCorePasser, closestTrigger)) { - if (LineUpFourthCorePasser(firstCorePasser, secondCorePasser, - thirdCorePasser, closestTrigger)) - return true; + return true; } } else if (item && botAI->HasItemInInventory(ITEM_TAINTED_CORE)) { // Designated core looter logic // Applicable only if cheat mode is on and thus looter is a bot - if (bot == designatedLooter) - { - if (IsFirstCorePasserInIntendedPosition( - designatedLooter, firstCorePasser, closestTrigger)) - { - const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(instanceId); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) - { - lastImbueAttempt.insert_or_assign(instanceId, now); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); - botAI->ImbueItem(item, firstCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleTransferCoreAfterImbue(botAI, bot, firstCorePasser); - return true; - } - } - } - // First core passer: receive core from looter at the top of the stairs, - // pass to second core passer - else if (bot == firstCorePasser) + if (bot == designatedLooter && + IsFirstCorePasserInPosition(designatedLooter, firstCorePasser, closestTrigger)) + { + const time_t now = std::time(nullptr); + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + { + lastImbueAttempt.insert_or_assign(instanceId, now); + botAI->ImbueItem(item, firstCorePasser); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); + ScheduleTransferCoreAfterImbue(botAI, bot, firstCorePasser); + return true; + } + } + // First core passer: receive core from looter at the top of the stairs, + // pass to second core passer + else if (bot == firstCorePasser && + IsSecondCorePasserInPosition(firstCorePasser, secondCorePasser, closestTrigger)) { const time_t now = std::time(nullptr); auto it = lastImbueAttempt.find(instanceId); if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { lastImbueAttempt.insert_or_assign(instanceId, now); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); botAI->ImbueItem(item, secondCorePasser); - intendedLineup.erase(bot->GetGUID()); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); ScheduleTransferCoreAfterImbue(botAI, bot, secondCorePasser); return true; } @@ -2472,55 +2208,41 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) // Second core passer: if closest usable generator is within passing distance // of the first passer, move to the generator; otherwise, move as close as // possible to the generator while staying in passing range - else if (bot == secondCorePasser) + else if (bot == secondCorePasser && !UseCoreOnNearestGenerator(instanceId) && + IsThirdCorePasserInPosition(secondCorePasser, thirdCorePasser, closestTrigger)) { - if (!UseCoreOnNearestGenerator(instanceId)) + const time_t now = std::time(nullptr); + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { - if (IsThirdCorePasserInIntendedPosition( - secondCorePasser, thirdCorePasser, closestTrigger)) - { - const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(instanceId); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) - { - lastImbueAttempt.insert_or_assign(instanceId, now); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); - botAI->ImbueItem(item, thirdCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleTransferCoreAfterImbue(botAI, bot, thirdCorePasser); - return true; - } - } + lastImbueAttempt.insert_or_assign(instanceId, now); + botAI->ImbueItem(item, thirdCorePasser); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); + ScheduleTransferCoreAfterImbue(botAI, bot, thirdCorePasser); + return true; } } // Third core passer: if closest usable generator is within passing distance // of the second passer, move to the generator; otherwise, move as close as // possible to the generator while staying in passing range - else if (bot == thirdCorePasser) + else if (bot == thirdCorePasser && !UseCoreOnNearestGenerator(instanceId) && + IsFourthCorePasserInPosition(thirdCorePasser, fourthCorePasser, closestTrigger)) { - if (!UseCoreOnNearestGenerator(instanceId)) + const time_t now = std::time(nullptr); + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { - if (IsFourthCorePasserInIntendedPosition( - thirdCorePasser, fourthCorePasser, closestTrigger)) - { - const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(instanceId); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) - { - lastImbueAttempt.insert_or_assign(instanceId, now); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); - botAI->ImbueItem(item, fourthCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleTransferCoreAfterImbue(botAI, bot, fourthCorePasser); - return true; - } - } + lastImbueAttempt.insert_or_assign(instanceId, now); + botAI->ImbueItem(item, fourthCorePasser); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); + ScheduleTransferCoreAfterImbue(botAI, bot, fourthCorePasser); + return true; } } // Fourth core passer: the fourth passer is rarely needed and no more than // four ever should be, so it should use the Core on the nearest generator - else if (bot == fourthCorePasser) - UseCoreOnNearestGenerator(instanceId); + else if (bot == fourthCorePasser && UseCoreOnNearestGenerator(instanceId)) + return true; } return false; @@ -2533,15 +2255,25 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser( const float centerY = VASHJ_PLATFORM_CENTER_POSITION.GetPositionY(); constexpr float radius = 57.5f; - float mx = designatedLooter->GetPositionX(); - float my = designatedLooter->GetPositionY(); - float angle = atan2(my - centerY, mx - centerX); + auto it = intendedLineup.find(bot->GetGUID()); + if (it == intendedLineup.end()) + { + float mx = designatedLooter->GetPositionX(); + float my = designatedLooter->GetPositionY(); + float angle = atan2(my - centerY, mx - centerX); - float targetX = centerX + radius * std::cos(angle); - float targetY = centerY + radius * std::sin(angle); - constexpr float targetZ = 41.097f; + float targetX = centerX + radius * std::cos(angle); + float targetY = centerY + radius * std::sin(angle); + constexpr float targetZ = VASHJ_PLATFORM_CENTER_Z; - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); + intendedLineup.try_emplace(bot->GetGUID(), Position(targetX, targetY, targetZ)); + it = intendedLineup.find(bot->GetGUID()); + } + + const Position& pos = it->second; + float targetX = pos.GetPositionX(); + float targetY = pos.GetPositionY(); + float targetZ = pos.GetPositionZ(); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); @@ -2552,143 +2284,183 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser( bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser( Player* firstCorePasser, Unit* closestTrigger) { - float fx = firstCorePasser->GetPositionX(); - float fy = firstCorePasser->GetPositionY(); - - float dx = closestTrigger->GetPositionX() - fx; - float dy = closestTrigger->GetPositionY() - fy; - float distToTrigger = firstCorePasser->GetExactDist2d(closestTrigger); - - if (distToTrigger == 0.0f) + auto itFirst = intendedLineup.find(firstCorePasser->GetGUID()); + if (itFirst == intendedLineup.end()) return false; - dx /= distToTrigger; dy /= distToTrigger; + const Position& firstLineup = itFirst->second; - // Target is on a line between firstCorePasser and closestTrigger - float targetX, targetY, targetZ; - // If firstCorePasser is within thresholdDist of closestTrigger, - // go to nearTriggerDist short of closestTrigger - constexpr float thresholdDist = 40.0f; - constexpr float nearTriggerDist = 1.5f; - // If firstCorePasser is not thresholdDist yards from closestTrigger, - // go to farDistance from firstCorePasser - constexpr float farDistance = 38.0f; + auto itSecond = intendedLineup.find(bot->GetGUID()); + if (itSecond == intendedLineup.end()) + { + float fx = itFirst->second.GetPositionX(); + float fy = itFirst->second.GetPositionY(); - if (distToTrigger <= thresholdDist) - { - float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); - targetX = fx + dx * moveDist; - targetY = fy + dy * moveDist; - } - else - { - targetX = fx + dx * farDistance; - targetY = fy + dy * farDistance; + float dx = closestTrigger->GetPositionX() - fx; + float dy = closestTrigger->GetPositionY() - fy; + float distToTrigger = std::sqrt(dx * dx + dy * dy); + + if (distToTrigger == 0.0f) + return false; + + dx /= distToTrigger; dy /= distToTrigger; + + float targetX, targetY; + constexpr float targetZ = VASHJ_PLATFORM_CENTER_Z; + constexpr float thresholdDist = 40.0f; + constexpr float nearTriggerDist = 1.5f; + constexpr float farDistance = 38.0f; + + if (distToTrigger <= thresholdDist) + { + float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); + targetX = fx + dx * moveDist; + targetY = fy + dy * moveDist; + } + else + { + targetX = fx + dx * farDistance; + targetY = fy + dy * farDistance; + } + + intendedLineup.try_emplace(bot->GetGUID(), Position(targetX, targetY, targetZ)); + itSecond = intendedLineup.find(bot->GetGUID()); } - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, VASHJ_PLATFORM_Z)); + const Position& pos = itSecond->second; + float targetX = pos.GetPositionX(); + float targetY = pos.GetPositionY(); + float targetZ = pos.GetPositionZ(); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, VASHJ_PLATFORM_Z, false, false, false, true, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser( - Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger) + Player* designatedLooter, Player* firstCorePasser, + Player* secondCorePasser, Unit* closestTrigger) { - // Wait to move until it is clear that a third passer is needed - bool needThird = - (IsFirstCorePasserInIntendedPosition(designatedLooter, firstCorePasser, closestTrigger) && + bool needThirdPasser = + (IsFirstCorePasserInPosition(designatedLooter, firstCorePasser, closestTrigger) && firstCorePasser->GetExactDist2d(closestTrigger) > 42.0f) || - (IsSecondCorePasserInIntendedPosition(firstCorePasser, secondCorePasser, closestTrigger) && + (IsSecondCorePasserInPosition(firstCorePasser, secondCorePasser, closestTrigger) && secondCorePasser->GetExactDist2d(closestTrigger) > 4.0f); - if (!needThird) + if (!needThirdPasser) return false; - float sx = secondCorePasser->GetPositionX(); - float sy = secondCorePasser->GetPositionY(); - - float dx = closestTrigger->GetPositionX() - sx; - float dy = closestTrigger->GetPositionY() - sy; - float distToTrigger = secondCorePasser->GetExactDist2d(closestTrigger); - - if (distToTrigger == 0.0f) + auto itSecond = intendedLineup.find(secondCorePasser->GetGUID()); + if (itSecond == intendedLineup.end()) return false; - dx /= distToTrigger; dy /= distToTrigger; - - float targetX, targetY, targetZ; - constexpr float thresholdDist = 40.0f; - constexpr float nearTriggerDist = 1.5f; - constexpr float farDistance = 38.0f; - - if (distToTrigger <= thresholdDist) + auto itThird = intendedLineup.find(bot->GetGUID()); + if (itThird == intendedLineup.end()) { - float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); - targetX = sx + dx * moveDist; - targetY = sy + dy * moveDist; - } - else - { - targetX = sx + dx * farDistance; - targetY = sy + dy * farDistance; + float sx = itSecond->second.GetPositionX(); + float sy = itSecond->second.GetPositionY(); + + float dx = closestTrigger->GetPositionX() - sx; + float dy = closestTrigger->GetPositionY() - sy; + float distToTrigger = std::sqrt(dx * dx + dy * dy); + + if (distToTrigger == 0.0f) + return false; + + dx /= distToTrigger; dy /= distToTrigger; + + float targetX, targetY; + constexpr float targetZ = VASHJ_PLATFORM_CENTER_Z; + constexpr float thresholdDist = 40.0f; + constexpr float nearTriggerDist = 1.5f; + constexpr float farDistance = 38.0f; + + if (distToTrigger <= thresholdDist) + { + float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); + targetX = sx + dx * moveDist; + targetY = sy + dy * moveDist; + } + else + { + targetX = sx + dx * farDistance; + targetY = sy + dy * farDistance; + } + + intendedLineup.try_emplace(bot->GetGUID(), Position(targetX, targetY, targetZ)); + itThird = intendedLineup.find(bot->GetGUID()); } - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, VASHJ_PLATFORM_Z)); + const Position& pos = itThird->second; + float targetX = pos.GetPositionX(); + float targetY = pos.GetPositionY(); + float targetZ = pos.GetPositionZ(); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, VASHJ_PLATFORM_Z, false, false, false, true, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); - - return false; } bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser( - Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger) + Player* firstCorePasser, Player* secondCorePasser, + Player* thirdCorePasser, Unit* closestTrigger) { - // Wait to move until it is clear that a fourth passer is needed - bool needFourth = - (IsSecondCorePasserInIntendedPosition(firstCorePasser, secondCorePasser, closestTrigger) && + bool needFourthPasser = + (IsSecondCorePasserInPosition(firstCorePasser, secondCorePasser, closestTrigger) && secondCorePasser->GetExactDist2d(closestTrigger) > 42.0f) || - (IsThirdCorePasserInIntendedPosition(secondCorePasser, thirdCorePasser, closestTrigger) && + (IsThirdCorePasserInPosition(secondCorePasser, thirdCorePasser, closestTrigger) && thirdCorePasser->GetExactDist2d(closestTrigger) > 4.0f); - if (!needFourth) + if (!needFourthPasser) return false; - float sx = thirdCorePasser->GetPositionX(); - float sy = thirdCorePasser->GetPositionY(); - - float tx = closestTrigger->GetPositionX(); - float ty = closestTrigger->GetPositionY(); - - float dx = tx - sx; - float dy = ty - sy; - float distToTrigger = thirdCorePasser->GetExactDist2d(closestTrigger); - - if (distToTrigger == 0.0f) + auto itThird = intendedLineup.find(thirdCorePasser->GetGUID()); + if (itThird == intendedLineup.end()) return false; - dx /= distToTrigger; dy /= distToTrigger; + auto itFourth = intendedLineup.find(bot->GetGUID()); + if (itFourth == intendedLineup.end()) + { + float sx = itThird->second.GetPositionX(); + float sy = itThird->second.GetPositionY(); - constexpr float nearTriggerDist = 1.5f; - float targetX = tx - dx * nearTriggerDist; - float targetY = ty - dy * nearTriggerDist; + float tx = closestTrigger->GetPositionX(); + float ty = closestTrigger->GetPositionY(); - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, VASHJ_PLATFORM_Z)); + float dx = tx - sx; + float dy = ty - sy; + float distToTrigger = std::sqrt(dx * dx + dy * dy); + + if (distToTrigger == 0.0f) + return false; + + dx /= distToTrigger; dy /= distToTrigger; + + constexpr float nearTriggerDist = 1.5f; + float targetX = tx - dx * nearTriggerDist; + float targetY = ty - dy * nearTriggerDist; + constexpr float targetZ = VASHJ_PLATFORM_CENTER_Z; + + intendedLineup.try_emplace(bot->GetGUID(), Position(targetX, targetY, targetZ)); + itFourth = intendedLineup.find(bot->GetGUID()); + } + + const Position& pos = itFourth->second; + float targetX = pos.GetPositionX(); + float targetY = pos.GetPositionY(); + float targetZ = pos.GetPositionZ(); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, VASHJ_PLATFORM_Z, false, false, false, true, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } // The next four functions check if the respective passer is <= 2 yards of their intended // position and are used to determine when the prior bot in the chain can pass the core -bool LadyVashjPassTheTaintedCoreAction::IsFirstCorePasserInIntendedPosition( +bool LadyVashjPassTheTaintedCoreAction::IsFirstCorePasserInPosition( Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger) { auto itSnap = intendedLineup.find(firstCorePasser->GetGUID()); @@ -2702,7 +2474,7 @@ bool LadyVashjPassTheTaintedCoreAction::IsFirstCorePasserInIntendedPosition( return false; } -bool LadyVashjPassTheTaintedCoreAction::IsSecondCorePasserInIntendedPosition( +bool LadyVashjPassTheTaintedCoreAction::IsSecondCorePasserInPosition( Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger) { auto itSnap = intendedLineup.find(secondCorePasser->GetGUID()); @@ -2716,7 +2488,7 @@ bool LadyVashjPassTheTaintedCoreAction::IsSecondCorePasserInIntendedPosition( return false; } -bool LadyVashjPassTheTaintedCoreAction::IsThirdCorePasserInIntendedPosition( +bool LadyVashjPassTheTaintedCoreAction::IsThirdCorePasserInPosition( Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger) { auto itSnap = intendedLineup.find(thirdCorePasser->GetGUID()); @@ -2730,7 +2502,7 @@ bool LadyVashjPassTheTaintedCoreAction::IsThirdCorePasserInIntendedPosition( return false; } -bool LadyVashjPassTheTaintedCoreAction::IsFourthCorePasserInIntendedPosition( +bool LadyVashjPassTheTaintedCoreAction::IsFourthCorePasserInPosition( Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger) { auto itSnap = intendedLineup.find(fourthCorePasser->GetGUID()); @@ -2799,17 +2571,11 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator(const uint32 i return false; GameObject* generator = botAI->GetGameObject(nearestGen->guid); - if (!generator) - return false; - - if (bot->GetExactDist2d(generator) > 4.5f) + if (!generator || bot->GetExactDist2d(generator) > 4.5f) return false; Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE); - if (!core) - return false; - - if (bot->CanUseItem(core) != EQUIP_ERR_OK) + if (!core || bot->CanUseItem(core) != EQUIP_ERR_OK) return false; if (bot->IsNonMeleeSpellCast(false)) @@ -2845,9 +2611,18 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator(const uint32 i packet << generator->GetGUID().WriteAsPacked(); bot->GetSession()->HandleUseItemOpcode(packet); - nearestTriggerGuid.erase(instanceId); + lastImbueAttempt.erase(instanceId); - lastCoreInInventoryTime.erase(instanceId); + auto coreHandlers = GetCoreHandlers(botAI, bot); + for (Player* handler : coreHandlers) + { + if (handler) + { + intendedLineup.erase(handler->GetGUID()); + lastCoreInInventoryTime.erase(handler->GetGUID()); + } + } + return true; } @@ -2864,35 +2639,12 @@ bool LadyVashjDestroyTaintedCoreAction::Execute(Event /*event*/) return false; } -// This needs to be separate from the general map erasing logic because -// Bots may end up out of combat during the Vashj encounter -bool LadyVashjEraseCorePassingTrackersAction::Execute(Event /*event*/) -{ - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) - return false; - - const uint32 instanceId = vashj->GetMap()->GetInstanceId(); - - bool erased = false; - if (nearestTriggerGuid.erase(instanceId) > 0) - erased = true; - if (lastImbueAttempt.erase(instanceId) > 0) - erased = true; - if (lastCoreInInventoryTime.erase(instanceId) > 0) - erased = true; - if (intendedLineup.erase(bot->GetGUID()) > 0) - erased = true; - - return erased; -} - // The standard "avoid aoe" strategy does work for Toxic Spores, but this method // provides more buffer distance and limits the area in which bots can move // so that they do not go down the stairs bool LadyVashjAvoidToxicSporesAction::Execute(Event /*event*/) { - auto const& spores = GetAllSporeDropTriggers(botAI, bot); + auto const& spores = GetAllSporeDropTriggers(bot); if (spores.empty()) return false; @@ -2910,16 +2662,21 @@ bool LadyVashjAvoidToxicSporesAction::Execute(Event /*event*/) if (!inDanger) return false; + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + const Position& vashjCenter = VASHJ_PLATFORM_CENTER_POSITION; constexpr float maxRadius = 60.0f; Position safestPos = FindSafestNearbyPosition(spores, vashjCenter, maxRadius, hazardRadius); + bool backwards = vashj->GetVictim() == bot; + MovementPriority priority = backwards ? + MovementPriority::MOVEMENT_FORCED : MovementPriority::MOVEMENT_COMBAT; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - bool backwards = (vashj && vashj->GetVictim() == bot); return MoveTo(SSC_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), safestPos.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, backwards); + priority, true, backwards); } Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition( @@ -3015,19 +2772,18 @@ bool LadyVashjAvoidToxicSporesAction::IsPathSafeFromSpores(const Position& start // When Toxic Sporebats spit poison, they summon "Spore Drop Trigger" NPCs // that create the toxic pools -std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers( - PlayerbotAI* botAI, Player* bot) +std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(Player* bot) { std::vector sporeDropTriggers; - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); - for (auto const& npcGuid : npcs) + std::list creatureList; + constexpr float searchRadius = 50.0f; + + bot->GetCreatureListWithEntryInGrid(creatureList, NPC_SPORE_DROP_TRIGGER, searchRadius); + + for (Creature* creature : creatureList) { - constexpr float maxSearchRadius = 40.0f; - Unit* unit = botAI->GetUnit(npcGuid); - if (unit && unit->GetEntry() == NPC_SPORE_DROP_TRIGGER && - bot->GetExactDist2d(unit) < maxSearchRadius) - sporeDropTriggers.push_back(unit); + if (creature && creature->IsAlive()) + sporeDropTriggers.push_back(creature); } return sporeDropTriggers; @@ -3040,7 +2796,7 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event /*event*/) return false; auto const& spores = - LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(botAI, bot); + LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(bot); constexpr float toxicSporeRadius = 6.0f; // If Rogues are Entangled and either have Static Charge or @@ -3109,19 +2865,13 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event /*event*/) { // Priority 1: Entangled in Toxic Spores (prefer main tank) Player* toxicTarget = mainTankToxic ? mainTankToxic : anyToxic; - if (toxicTarget) - { - if (botAI->CanCastSpell("hand of freedom", toxicTarget)) - return botAI->CastSpell("hand of freedom", toxicTarget); - } + if (toxicTarget && botAI->CanCastSpell("hand of freedom", toxicTarget)) + return botAI->CastSpell("hand of freedom", toxicTarget); // Priority 2: Entangled with Static Charge (prefer main tank) Player* staticTarget = mainTankStatic ? mainTankStatic : anyStatic; - if (staticTarget) - { - if (botAI->CanCastSpell("hand of freedom", staticTarget)) - return botAI->CastSpell("hand of freedom", staticTarget); - } + if (staticTarget && botAI->CanCastSpell("hand of freedom", staticTarget)) + return botAI->CastSpell("hand of freedom", staticTarget); } return false; diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h index cbd23740..08ad4c48 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h @@ -1,3 +1,8 @@ +/* + * 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. + */ + #ifndef _PLAYERBOT_RAIDSSCACTIONS_H #define _PLAYERBOT_RAIDSSCACTIONS_H @@ -75,8 +80,8 @@ public: bool Execute(Event event) override; private: - bool TryMisdirectToFrostTank(Unit* hydross, Group* group); - bool TryMisdirectToNatureTank(Unit* hydross, Group* group); + bool TryMisdirectToFrostTank(Unit* hydross); + bool TryMisdirectToNatureTank(Unit* hydross); }; class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action @@ -413,10 +418,10 @@ private: bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger); bool LineUpThirdCorePasser(Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); bool LineUpFourthCorePasser(Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); - bool IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger); - bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); - bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); - bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger); + bool IsFirstCorePasserInPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger); + bool IsSecondCorePasserInPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); + bool IsThirdCorePasserInPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); + bool IsFourthCorePasserInPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger); void ScheduleTransferCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver); bool UseCoreOnNearestGenerator(const uint32 instanceId); }; @@ -428,19 +433,12 @@ public: bool Execute(Event event) override; }; -class LadyVashjEraseCorePassingTrackersAction : public Action -{ -public: - LadyVashjEraseCorePassingTrackersAction(PlayerbotAI* botAI, std::string const name = "lady vashj erase core passing trackers") : Action(botAI, name) {} - bool Execute(Event event) override; -}; - class LadyVashjAvoidToxicSporesAction : public MovementAction { public: LadyVashjAvoidToxicSporesAction(PlayerbotAI* botAI, std::string const name = "lady vashj avoid toxic spores") : MovementAction(botAI, name) {} bool Execute(Event event) override; - static std::vector GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot); + static std::vector GetAllSporeDropTriggers(Player* bot); private: Position FindSafestNearbyPosition(const std::vector& spores, const Position& position, float maxRadius, float hazardRadius); diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index c99cafa3..c841c7dc 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -1,3 +1,8 @@ +/* + * 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" @@ -28,12 +33,10 @@ using namespace SerpentShrineCavernHelpers; float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action) { - if (bot->HasAura(SPELL_TOXIC_POOL)) - { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } + if (bot->HasAura(SPELL_TOXIC_POOL) && + dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -53,16 +56,16 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* 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))) - { - if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) || - (botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION))) - return 0.0f; - } + return 0.0f; return 1.0f; } @@ -97,13 +100,13 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && (now - itPhase->second) > phaseChangeWaitSeconds); - if (justChanged || aboutToChange) - { - if (dynamic_cast(action) || - (dynamic_cast(action) && - !dynamic_cast(action))) - return 0.0f; - } + 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)) @@ -116,13 +119,13 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && (now - itPhase->second) > phaseChangeWaitSeconds); - if (justChanged || aboutToChange) - { - if (dynamic_cast(action) || - (dynamic_cast(action) && - !dynamic_cast(action))) - return 0.0f; - } + if (!justChanged && !aboutToChange) + return 1.0f; + + if (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; } return 1.0f; @@ -133,11 +136,9 @@ float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action) if (bot->getClass() != CLASS_HUNTER) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "hydross the unstable")) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (AI_VALUE2(Unit*, "find target", "hydross the unstable") && + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -175,14 +176,14 @@ float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action) if (!botAI->IsRanged(bot)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "the lurker below")) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.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; } @@ -215,11 +216,11 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) ++tankCount; } - if (tankCount >= 3) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (tankCount < 3) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -234,22 +235,18 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return 1.0f; - Unit* leotherasHuman = GetLeotherasHuman(botAI); - if (!leotherasHuman) + 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 (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && - (leotherasHuman->HasAura(SPELL_WHIRLWIND) || - leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL))) - { - if (dynamic_cast(action)) - return 0.0f; + if (dynamic_cast(action)) + return 0.0f; - if (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; } @@ -262,10 +259,10 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return 1.0f; - if (GetPhase2LeotherasDemon(botAI) && dynamic_cast(action)) + if (GetPhase2LeotherasDemon(bot) && dynamic_cast(action)) return 0.0f; - if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast(action)) + if (!GetPhase3LeotherasDemon(bot) && dynamic_cast(action)) return 0.0f; return 1.0f; @@ -273,21 +270,21 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action) { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - { - 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; - } + 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; } @@ -297,19 +294,19 @@ float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* actio if (botAI->IsRanged(bot) || botAI->IsTank(bot)) return 1.0f; - if (!GetPhase2LeotherasDemon(botAI)) + if (!GetPhase2LeotherasDemon(bot)) return 1.0f; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); - if (chaosBlast && chaosBlast->GetStackAmount() >= 5) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + 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; } @@ -330,8 +327,8 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) const time_t now = std::time(nullptr); constexpr uint8 dpsWaitSecondsPhase1 = 5; - Unit* leotherasHuman = GetLeotherasHuman(botAI); - Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); + Unit* leotherasHuman = GetLeotherasHuman(bot); + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(bot); if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && !leotherasPhase3Demon) { @@ -345,12 +342,12 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } } constexpr uint8 dpsWaitSecondsPhase2 = 12; - Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); + Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(bot); Player* demonFormTank = GetLeotherasDemonFormTank(bot); if (leotherasPhase2Demon) { @@ -367,7 +364,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } } @@ -384,7 +381,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } } @@ -398,12 +395,12 @@ float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* acti return 1.0f; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - if (leotheras && leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) - { - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (!leotheras || !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -447,14 +444,12 @@ float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action) if (!botAI->IsDps(bot)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) - { - if (auto castSpellAction = dynamic_cast(action)) - { - if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe) - return 0.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; } @@ -464,11 +459,11 @@ float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action if (bot->getClass() != CLASS_HUNTER) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -494,7 +489,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } return 1.0f; @@ -505,12 +500,12 @@ float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue if (!botAI->IsAssistHealOfIndex(bot, 0, true)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) - { - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.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; } @@ -526,12 +521,12 @@ float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* act if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) return 1.0f; - if (!AI_VALUE2(Unit*, "find target", "tidewalker lurker")) - { - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.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; } @@ -541,11 +536,11 @@ float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action) if (!botAI->IsMainTank(bot)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -556,17 +551,14 @@ float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* actio return 1.0f; Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); - if (!tidewalker) + if (!tidewalker || tidewalker->GetHealthPct() > 25.0f) return 1.0f; - if (tidewalker->GetHealthPct() < 25.0f) - { - 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) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -580,41 +572,59 @@ float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action) if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return 1.0f; - if (bot->getClass() == CLASS_SHAMAN) - { - if (IsLadyVashjInPhase3(botAI)) - return 1.0f; + if (bot->getClass() == CLASS_SHAMAN && + !IsLadyVashjInPhase3(botAI) && + (dynamic_cast(action) || + dynamic_cast(action))) + return 0.0f; - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (!botAI->IsDps(bot) || !IsLadyVashjInPhase1(botAI)) + return 1.0f; - if (botAI->IsDps(bot) && IsLadyVashjInPhase1(botAI)) - { - 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; - } + 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; } @@ -624,15 +634,15 @@ float LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action) if (!botAI->IsRanged(bot)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "lady vashj") && - IsLadyVashjInPhase1(botAI)) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.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; } @@ -658,19 +668,18 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) // Bots should not loot the core with normal looting logic float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) { - if (AI_VALUE2(Unit*, "find target", "lady vashj")) - { - if (dynamic_cast(action)) - return 0.0f; - } + 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)) + if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI)) return 1.0f; if (dynamic_cast(action) || @@ -678,65 +687,43 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti dynamic_cast(action)) return 1.0f; - Group* group = bot->GetGroup(); - if (!group) - return 1.0f; + auto coreHandlers = GetCoreHandlers(botAI, bot); - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); - Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); - Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); + bool isCoreHandler = false; + int myIndex = -1; + for (int i = 0; i < static_cast(coreHandlers.size()); ++i) + { + if (coreHandlers[i] && coreHandlers[i] == bot) + { + isCoreHandler = true; + myIndex = i; + } + } + if (!isCoreHandler) + return 1.0f; auto hasCore = [](Player* player) { return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false); }; - if (hasCore(bot)) - { - if (!dynamic_cast(action)) - return 0.0f; - } + // If the bot actually has the core, only allow core handling + if (hasCore(bot) && !dynamic_cast(action)) + return 0.0f; - if (bot == designatedLooter) - { - if (!hasCore(bot)) - return 1.0f; - } - else if (bot == firstCorePasser) - { - if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) || - hasCore(fourthCorePasser)) - return 1.0f; - } - else if (bot == secondCorePasser) - { - if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) - return 1.0f; - } - else if (bot == thirdCorePasser) - { - if (hasCore(fourthCorePasser)) - return 1.0f; - } - else if (bot != fourthCorePasser) - return 1.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 (AI_VALUE2(Unit*, "find target", "tainted elemental") && - (bot == firstCorePasser || bot == secondCorePasser)) - { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } - - if (AnyRecentCoreInInventory(group, botAI)) - { - if (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; } @@ -745,7 +732,8 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti // So the standard target selection system must be disabled float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action) { - if (!AI_VALUE2(Unit*, "find target", "lady vashj")) + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) return 1.0f; if (dynamic_cast(action)) @@ -755,24 +743,26 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac { if (dynamic_cast(action) || 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 && bot->GetVictim() == enchanted) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (enchanted && bot->GetVictim() == enchanted && + dynamic_cast(action)) + return 0.0f; } if (IsLadyVashjInPhase3(botAI)) { - if (dynamic_cast(action)) + if (dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); @@ -780,16 +770,13 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite"); if (enchanted || strider || elite) { - if (dynamic_cast(action) || - dynamic_cast(action) || + if (dynamic_cast(action) || dynamic_cast(action)) return 0.0f; - if (enchanted && bot->GetVictim() == enchanted) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (enchanted && bot->GetVictim() == enchanted && + dynamic_cast(action)) + return 0.0f; } else if (dynamic_cast(action)) return 0.0f; diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h index 6630dc20..19ba7352 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h @@ -1,3 +1,8 @@ +/* + * 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. + */ + #ifndef _PLAYERBOT_RAIDSSCMULTIPLIERS_H #define _PLAYERBOT_RAIDSSCMULTIPLIERS_H @@ -193,6 +198,14 @@ public: virtual float GetValue(Action* action); }; +class LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier : public Multiplier +{ +public: + LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj main tank group shaman use grounding totem") {} + virtual float GetValue(Action* action); +}; + class LadyVashjMaintainPhase1RangedSpreadMultiplier : public Multiplier { public: diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h b/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h index e6dce169..1e2c44b4 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h @@ -1,3 +1,8 @@ +/* + * 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. + */ + #ifndef _PLAYERBOT_RAIDSSCACTIONCONTEXT_H #define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H @@ -161,9 +166,6 @@ public: creators["lady vashj destroy tainted core"] = &RaidSSCActionContext::lady_vashj_destroy_tainted_core; - creators["lady vashj erase core passing trackers"] = - &RaidSSCActionContext::lady_vashj_erase_core_passing_trackers; - creators["lady vashj avoid toxic spores"] = &RaidSSCActionContext::lady_vashj_avoid_toxic_spores; @@ -324,9 +326,6 @@ private: static Action* lady_vashj_destroy_tainted_core( PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); } - static Action* lady_vashj_erase_core_passing_trackers( - PlayerbotAI* botAI) { return new LadyVashjEraseCorePassingTrackersAction(botAI); } - static Action* lady_vashj_avoid_toxic_spores( PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); } diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h b/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h index 13135bf3..737fd3a3 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h @@ -1,3 +1,8 @@ +/* + * 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. + */ + #ifndef _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H @@ -155,9 +160,6 @@ public: creators["lady vashj tainted core is unusable"] = &RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable; - creators["lady vashj need to reset core passing trackers"] = - &RaidSSCTriggerContext::lady_vashj_need_to_reset_core_passing_trackers; - creators["lady vashj toxic sporebats are spewing poison clouds"] = &RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds; @@ -312,9 +314,6 @@ private: static Trigger* lady_vashj_tainted_core_is_unusable( PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); } - static Trigger* lady_vashj_need_to_reset_core_passing_trackers( - PlayerbotAI* botAI) { return new LadyVashjNeedToResetCorePassingTrackersTrigger(botAI); } - static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds( PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); } diff --git a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp index 139667dc..624049c8 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp @@ -1,3 +1,8 @@ +/* + * 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 "RaidSSCStrategy.h" #include "RaidSSCMultipliers.h" @@ -144,9 +149,6 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", { NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) })); - triggers.push_back(new TriggerNode("lady vashj need to reset core passing trackers", { - NextAction("lady vashj erase core passing trackers", ACTION_EMERGENCY + 10) })); - triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", { NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1) })); @@ -198,6 +200,7 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) // Lady Vashj multipliers.push_back(new LadyVashjDelayCooldownsMultiplier(botAI)); + multipliers.push_back(new LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier(botAI)); multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI)); multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI)); multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI)); diff --git a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h index 3c2c05f5..a994600b 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h +++ b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h @@ -1,3 +1,8 @@ +/* + * 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. + */ + #ifndef _PLAYERBOT_RAIDSSCSTRATEGY_H_ #define _PLAYERBOT_RAIDSSCSTRATEGY_H_ diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp index e77e6364..c3e75322 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp @@ -1,3 +1,8 @@ +/* + * 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 "RaidSSCTriggers.h" #include "RaidSSCHelpers.h" #include "RaidSSCActions.h" @@ -26,35 +31,37 @@ bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive() bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive() { return botAI->IsDps(bot) && - GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); + AI_VALUE2(Unit*, "find target", "greyheart tidecaller"); } // Hydross the Unstable bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "hydross the unstable") && - botAI->IsMainTank(bot); + return botAI->IsMainTank(bot) && + AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "hydross the unstable") && - botAI->IsAssistTankOfIndex(bot, 0, true); + return botAI->IsAssistTankOfIndex(bot, 0, true) && + AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive() { + if (botAI->IsHeal(bot)) + return false; + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - if (hydross && hydross->GetHealthPct() < 10.0f) + if (!hydross || hydross->GetHealthPct() < 10.0f) return false; - if (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") && - !AI_VALUE2(Unit*, "find target", "tainted spawn of hydross")) + if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0, true)) return false; - return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) && - !botAI->IsAssistTankOfIndex(bot, 0, true); + return AI_VALUE2(Unit*, "find target", "pure spawn of hydross") || + AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); } bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive() @@ -71,19 +78,19 @@ bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive() bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() { - if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) + if (bot->getClass() == CLASS_HUNTER || + botAI->IsHeal(bot) || + botAI->IsMainTank(bot) || + botAI->IsAssistTankOfIndex(bot, 0, true)) return false; - return bot->getClass() != CLASS_HUNTER && - !botAI->IsHeal(bot) && - !botAI->IsMainTank(bot) && - !botAI->IsAssistTankOfIndex(bot, 0, true); + return AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "hydross the unstable") && - IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "hydross the unstable"); } // The Lurker Below @@ -102,11 +109,11 @@ bool TheLurkerBelowSpoutIsActiveTrigger::IsActive() bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive() { - Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); - if (!lurker) + if (!botAI->IsMainTank(bot)) return false; - if (!botAI->IsMainTank(bot)) + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker) return false; const time_t now = std::time(nullptr); @@ -135,35 +142,16 @@ bool TheLurkerBelowBossCastsGeyserTrigger::IsActive() // Trigger will be active only if there are at least 3 tanks in the raid bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() { + if (!botAI->IsTank(bot)) + return false; + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED) return false; - Player* mainTank = nullptr; - Player* firstAssistTank = nullptr; - Player* secondAssistTank = nullptr; - - Group* group = bot->GetGroup(); - if (!group) - return false; - - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; - - PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (!mainTank && memberAI->IsMainTank(member)) - mainTank = member; - else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0, true)) - firstAssistTank = member; - else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1, true)) - secondAssistTank = member; - } + Player* mainTank = GetGroupMainTank(botAI, bot); + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); + Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1); if (!mainTank || !firstAssistTank || !secondAssistTank) return false; @@ -173,51 +161,55 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "the lurker below") && - IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "the lurker below"); } // Leotheras the Blind bool LeotherasTheBlindBossIsInactiveTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "greyheart spellbinder"); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "greyheart spellbinder"); } bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() { + if (bot->getClass() != CLASS_WARLOCK) + return false; + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return false; if (GetLeotherasDemonFormTank(bot) != bot) return false; - return GetActiveLeotherasDemon(botAI); + return GetActiveLeotherasDemon(bot); } bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive() { - if (botAI->IsRanged(bot) || !botAI->IsTank(bot)) + if (!botAI->IsTank(bot)) + return false; + + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return false; - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - if (!GetLeotherasDemonFormTank(bot)) return false; - return GetPhase2LeotherasDemon(botAI); + return GetPhase2LeotherasDemon(bot); } bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + if (!botAI->IsRanged(bot)) return false; - if (!botAI->IsRanged(bot)) + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); @@ -231,14 +223,14 @@ bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - - if (botAI->IsTank(bot) && botAI->IsMelee(bot)) + if (botAI->IsTank(bot)) return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - if (!leotheras || leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) + if (!leotheras) + return false; + + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; return leotheras->HasAura(SPELL_WHIRLWIND) || @@ -247,10 +239,13 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + if (botAI->IsRanged(bot)) return false; - if (botAI->IsRanged(bot)) + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) + return false; + + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); @@ -260,7 +255,7 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot)) return false; - return GetPhase2LeotherasDemon(botAI); + return GetPhase2LeotherasDemon(bot); } bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() @@ -271,89 +266,68 @@ bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - if (botAI->IsHeal(bot)) return false; - if (GetLeotherasDemonFormTank(bot) == bot) + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return false; - return GetPhase3LeotherasDemon(botAI) && - GetLeotherasHuman(botAI); + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return false; + + if (bot->getClass() == CLASS_WARLOCK && GetLeotherasDemonFormTank(bot) == bot) + return false; + + return GetPhase3LeotherasDemon(bot); } bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - if (bot->getClass() != CLASS_HUNTER) return false; - return AI_VALUE2(Unit*, "find target", "leotheras the blind"); + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) + return false; + + return !bot->HasAura(SPELL_INSIDIOUS_WHISPER); } bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "leotheras the blind") && - IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "leotheras the blind"); } // Fathom-Lord Karathress bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && - botAI->IsMainTank(bot); + return botAI->IsMainTank(bot) && + AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); } bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") && - botAI->IsAssistTankOfIndex(bot, 0, false); + return botAI->IsAssistTankOfIndex(bot, 0, false) && + AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); } bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") && - botAI->IsAssistTankOfIndex(bot, 1, false); + return botAI->IsAssistTankOfIndex(bot, 1, false) && + AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); } bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") && - botAI->IsAssistTankOfIndex(bot, 2, false); + return botAI->IsAssistTankOfIndex(bot, 2, false) && + AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); } bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() { - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - if (!caribdis) - return false; - - if (!botAI->IsAssistHealOfIndex(bot, 0, true)) - return false; - - Player* firstAssistTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; - - if (botAI->IsAssistTankOfIndex(member, 0, false)) - { - firstAssistTank = member; - break; - } - } - } - - return firstAssistTank; + return botAI->IsAssistHealOfIndex(bot, 0, true) && + AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); } bool FathomLordKarathressPullingBossesTrigger::IsActive() @@ -367,10 +341,10 @@ bool FathomLordKarathressPullingBossesTrigger::IsActive() bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() { - if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) + if (botAI->IsHeal(bot)) return false; - if (botAI->IsHeal(bot)) + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return false; if (botAI->IsDps(bot)) @@ -387,8 +361,8 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && - IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); } // Morogrim Tidewalker @@ -404,8 +378,8 @@ bool MorogrimTidewalkerPullingBossTrigger::IsActive() bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") && - botAI->IsMainTank(bot); + return botAI->IsMainTank(bot) && + AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); } bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() @@ -421,8 +395,11 @@ bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() bool LadyVashjBossEngagedByMainTankTrigger::IsActive() { + if (!botAI->IsMainTank(bot)) + return false; + return AI_VALUE2(Unit*, "find target", "lady vashj") && - !IsLadyVashjInPhase2(botAI) && botAI->IsMainTank(bot); + !IsLadyVashjInPhase2(botAI); } bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() @@ -439,10 +416,7 @@ bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive() IsLadyVashjInPhase2(botAI)) return false; - if (!IsMainTankInSameSubgroup(bot)) - return false; - - return true; + return IsMainTankInSameSubgroup(botAI, bot); } bool LadyVashjBotHasStaticChargeTrigger::IsActive() @@ -450,14 +424,15 @@ bool LadyVashjBotHasStaticChargeTrigger::IsActive() if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return false; - if (Group* group = bot->GetGroup()) + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->HasAura(SPELL_STATIC_CHARGE)) - return true; - } + Player* member = ref->GetSource(); + if (member && member->HasAura(SPELL_STATIC_CHARGE)) + return true; } return false; @@ -500,9 +475,10 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() return false; bool taintedPresent = false; - Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental"); - if (taintedUnit) + if (AI_VALUE2(Unit*, "find target", "tainted elemental")) + { taintedPresent = true; + } else { GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses"); @@ -513,13 +489,11 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() if (!object) continue; - if (Creature* creature = object->ToCreature()) + if (Creature* creature = object->ToCreature(); + creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive()) { - if (creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive()) - { - taintedPresent = true; - break; - } + taintedPresent = true; + break; } } } @@ -527,12 +501,8 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() if (!taintedPresent) return false; - Group* group = bot->GetGroup(); - if (!group) - return false; - - return (GetDesignatedCoreLooter(group, botAI) == bot && - !bot->HasItemCount(ITEM_TAINTED_CORE, 1, false)); + return GetDesignatedCoreLooter(botAI, bot) == bot && + !bot->HasItemCount(ITEM_TAINTED_CORE, 1, false); } bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() @@ -540,54 +510,24 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI)) return false; - Group* group = bot->GetGroup(); - if (!group) + auto coreHandlers = GetCoreHandlers(botAI, bot); + + bool isCoreHandler = false; + for (Player* handler : coreHandlers) + if (handler == bot) + isCoreHandler = true; + + if (!isCoreHandler) return false; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); - Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); - Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); - - auto hasCore = [](Player* player) -> bool - { - return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false); - }; - - if (bot == designatedLooter) - { - if (!hasCore(bot)) - return false; - } - else if (bot == firstCorePasser) - { - if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) || - hasCore(fourthCorePasser)) - return false; - } - else if (bot == secondCorePasser) - { - if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) - return false; - } - else if (bot == thirdCorePasser) - { - if (hasCore(fourthCorePasser)) - return false; - } - else if (bot != fourthCorePasser) - return false; - - if (AnyRecentCoreInInventory(group, botAI)) - return true; - // First and second passers move to positions as soon as the elemental appears - if (AI_VALUE2(Unit*, "find target", "tainted elemental") && - (bot == firstCorePasser || bot == secondCorePasser)) + Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); + if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f && + (bot == coreHandlers[1] || bot == coreHandlers[2])) return true; - return false; + // Main logic: run if core is in play for this bot or a prior handler + return AnyRecentCoreInInventory(botAI, bot); } bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() @@ -599,18 +539,7 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() if (!IsLadyVashjInPhase2(botAI)) return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false); - Group* group = bot->GetGroup(); - if (!group) - return false; - - Player* coreHandlers[] = - { - GetDesignatedCoreLooter(group, botAI), - GetFirstTaintedCorePasser(group, botAI), - GetSecondTaintedCorePasser(group, botAI), - GetThirdTaintedCorePasser(group, botAI), - GetFourthTaintedCorePasser(group, botAI) - }; + auto coreHandlers = GetCoreHandlers(botAI, bot); if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false)) { @@ -625,24 +554,6 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() return false; } -bool LadyVashjNeedToResetCorePassingTrackersTrigger::IsActive() -{ - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj || IsLadyVashjInPhase2(botAI)) - return false; - - Group* group = bot->GetGroup(); - if (!group) - return false; - - return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr) || - GetDesignatedCoreLooter(group, botAI) == bot || - GetFirstTaintedCorePasser(group, botAI) == bot || - GetSecondTaintedCorePasser(group, botAI) == bot || - GetThirdTaintedCorePasser(group, botAI) == bot || - GetFourthTaintedCorePasser(group, botAI) == bot; -} - bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive() { return IsLadyVashjInPhase3(botAI); @@ -653,17 +564,18 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive() if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return false; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->HasAura(SPELL_ENTANGLE)) - continue; + Group* group = bot->GetGroup(); + if (!group) + return false; - if (botAI->IsMelee(member)) - return true; - } + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->HasAura(SPELL_ENTANGLE)) + continue; + + if (botAI->IsMelee(member)) + return true; } return false; diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h index e106b58f..4bf9e741 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h @@ -1,3 +1,8 @@ +/* + * 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. + */ + #ifndef _PLAYERBOT_RAIDSSCTRIGGERS_H #define _PLAYERBOT_RAIDSSCTRIGGERS_H @@ -387,14 +392,6 @@ public: bool IsActive() override; }; -class LadyVashjNeedToResetCorePassingTrackersTrigger : public Trigger -{ -public: - LadyVashjNeedToResetCorePassingTrackersTrigger( - PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to reset core passing trackers") {} - bool IsActive() override; -}; - class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger { public: diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp index 7bda085b..4ebeb6f8 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp @@ -1,3 +1,8 @@ +/* + * 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 "RaidSSCHelpers.h" #include "AiFactory.h" #include "Creature.h" @@ -79,61 +84,54 @@ namespace SerpentShrineCavernHelpers std::unordered_map leotherasDemonFormDpsWaitTimer; std::unordered_map leotherasFinalPhaseDpsWaitTimer; - Unit* GetLeotherasHuman(PlayerbotAI* botAI) + Unit* GetLeotherasHuman(Player* bot) { - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && - unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS)) - return unit; - } + constexpr float searchRadius = 100.0f; + Creature* leotheras = + bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true); + + if (leotheras && leotheras->IsInCombat() && + !leotheras->HasAura(SPELL_METAMORPHOSIS)) + return leotheras; + return nullptr; } - Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI) + Unit* GetPhase2LeotherasDemon(Player* bot) { - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && - unit->HasAura(SPELL_METAMORPHOSIS)) - return unit; - } + constexpr float searchRadius = 100.0f; + Creature* leotheras = + bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true); + + if (leotheras && leotheras->HasAura(SPELL_METAMORPHOSIS)) + return leotheras; + return nullptr; } - Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI) + Unit* GetPhase3LeotherasDemon(Player* bot) { - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS) - return unit; - } - return nullptr; + constexpr float searchRadius = 100.0f; + return bot->FindNearestCreature(NPC_SHADOW_OF_LEOTHERAS, searchRadius, true); } - Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI) + Unit* GetActiveLeotherasDemon(Player* bot) { - Unit* phase2 = GetPhase2LeotherasDemon(botAI); - Unit* phase3 = GetPhase3LeotherasDemon(botAI); + Unit* phase2 = GetPhase2LeotherasDemon(bot); + Unit* phase3 = GetPhase3LeotherasDemon(bot); return phase2 ? phase2 : phase3; } + // (1) First priority is an assistant Warlock (real player or bot) + // (2) If no assistant Warlock, then look for any Warlock bot Player* GetLeotherasDemonFormTank(Player* bot) { Group* group = bot->GetGroup(); if (!group) return nullptr; - // (1) First loop: Return the first assistant Warlock (real player or bot) + Player* fallbackWarlock = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); @@ -142,21 +140,12 @@ namespace SerpentShrineCavernHelpers if (group->IsAssistant(member->GetGUID())) return member; + + if (!fallbackWarlock && GET_PLAYERBOT_AI(member)) + fallbackWarlock = member; } - // (2) Fall back to first found bot Warlock - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - member->getClass() != CLASS_WARLOCK) - continue; - - return member; - } - - // (3) Return nullptr if none found - return nullptr; + return fallbackWarlock; } // Fathom-Lord Karathress @@ -182,16 +171,15 @@ namespace SerpentShrineCavernHelpers // Lady Vashj - const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f }; + const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.902f }; - std::unordered_map vashjRangedPositions; std::unordered_map hasReachedVashjRangedPosition; std::unordered_map nearestTriggerGuid; std::unordered_map intendedLineup; std::unordered_map lastImbueAttempt; - std::unordered_map lastCoreInInventoryTime; + std::unordered_map lastCoreInInventoryTime; - bool IsMainTankInSameSubgroup(Player* bot) + bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot) { Group* group = bot->GetGroup(); if (!group || !group->isRaidGroup()) @@ -210,11 +198,8 @@ namespace SerpentShrineCavernHelpers if (group->GetMemberGroup(member->GetGUID()) != botSubGroup) continue; - if (PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member)) - { - if (memberAI->IsMainTank(member)) - return true; - } + if (botAI->IsMainTank(member)) + return true; } return false; @@ -277,38 +262,9 @@ namespace SerpentShrineCavernHelpers return false; } - bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds) - { - Unit* vashj = - botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); - if (!vashj) - return false; - - if (group) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - return true; - } - } - - const uint32 instanceId = vashj->GetMap()->GetInstanceId(); - const time_t now = std::time(nullptr); - - auto it = lastCoreInInventoryTime.find(instanceId); - if (it != lastCoreInInventoryTime.end()) - { - if ((now - it->second) <= static_cast(graceSeconds)) - return true; - } - - return false; - } - - Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI) + Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; @@ -317,10 +273,15 @@ namespace SerpentShrineCavernHelpers if (!leaderGuid.IsEmpty()) leader = ObjectAccessor::FindPlayer(leaderGuid); + // If cheats are disabled, the group leader will be the designated looter if (!botAI->HasCheat(BotCheatMask::raid)) return leader; - Player* fallback = leader; + // Priority: (1) assistant melee DPS, (2) other melee DPS, (3) any ranged DPS + Player* meleeDpsAssistant = nullptr; + Player* meleeDps = nullptr; + Player* rangedDps = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); @@ -331,22 +292,36 @@ namespace SerpentShrineCavernHelpers if (!memberAI) continue; - if (memberAI->IsMelee(member) && memberAI->IsDps(member)) - return member; + if (!meleeDpsAssistant && memberAI->IsMelee(member) && + memberAI->IsDps(member) && group->IsAssistant(member->GetGUID())) + { + meleeDpsAssistant = member; + break; + } - if (!fallback && memberAI->IsRangedDps(member)) - fallback = member; + if (!meleeDps && memberAI->IsMelee(member) && memberAI->IsDps(member)) + meleeDps = member; + + if (!rangedDps && memberAI->IsRangedDps(member)) + rangedDps = member; } - return fallback ? fallback : leader; + if (meleeDpsAssistant) + return meleeDpsAssistant; + if (meleeDps) + return meleeDps; + if (rangedDps) + return rangedDps; + return leader; } - Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI) + Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -355,32 +330,29 @@ namespace SerpentShrineCavernHelpers continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (memberAI->IsAssistHealOfIndex(member, 0, true)) + if (memberAI && memberAI->IsAssistHealOfIndex(member, 0, true)) return member; } for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - botAI->IsTank(member) || member == designatedLooter) - continue; - return member; + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && + !botAI->IsTank(member) && member != designatedLooter) + return member; } return nullptr; } - Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI) + Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); + Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -390,34 +362,31 @@ namespace SerpentShrineCavernHelpers continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (memberAI->IsAssistHealOfIndex(member, 1, true)) + if (memberAI && memberAI->IsAssistHealOfIndex(member, 1, true)) return member; } for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - botAI->IsTank(member) || member == designatedLooter || - member == firstCorePasser) - continue; - return member; + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && + !botAI->IsTank(member) && member != designatedLooter && + member != firstCorePasser) + return member; } return nullptr; } - Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI) + Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); + Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); + Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -427,35 +396,32 @@ namespace SerpentShrineCavernHelpers continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (memberAI->IsAssistHealOfIndex(member, 2, true)) + if (memberAI && memberAI->IsAssistHealOfIndex(member, 2, true)) return member; } for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - botAI->IsTank(member) || member == designatedLooter || - member == firstCorePasser || member == secondCorePasser) - continue; - return member; + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && + !botAI->IsTank(member) && member != designatedLooter && + member != firstCorePasser && member != secondCorePasser) + return member; } return nullptr; } - Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI) + Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); - Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); + Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); + Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot); + Player* thirdCorePasser = GetThirdTaintedCorePasser(botAI, bot); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -466,27 +432,75 @@ namespace SerpentShrineCavernHelpers continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (memberAI->IsAssistRangedDpsOfIndex(member, 0, true)) + if (memberAI && memberAI->IsAssistRangedDpsOfIndex(member, 0, true)) return member; } for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - botAI->IsTank(member) || member == designatedLooter || - member == firstCorePasser || member == secondCorePasser || - member == thirdCorePasser) - continue; - return member; + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && + !botAI->IsTank(member) && member != designatedLooter && + member != firstCorePasser && member != secondCorePasser && + member != thirdCorePasser) + return member; } return nullptr; } + std::array GetCoreHandlers(PlayerbotAI* botAI, Player* bot) + { + return + { + GetDesignatedCoreLooter(botAI, bot), + GetFirstTaintedCorePasser(botAI, bot), + GetSecondTaintedCorePasser(botAI, bot), + GetThirdTaintedCorePasser(botAI, bot), + GetFourthTaintedCorePasser(botAI, bot) + }; + } + + // Checks if any bot from earlier in the passing sequence has the Tainted Core or + // had it within the prior 3 seconds so the chain is not broken when the Core is in transit + bool AnyRecentCoreInInventory(PlayerbotAI* botAI, Player* bot) + { + Unit* vashj = + botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); + if (!vashj) + return false; + + auto coreHandlers = GetCoreHandlers(botAI, bot); + + int8 myIndex = -1; + for (int8 i = 0; i < 5; ++i) + if (coreHandlers[i] && coreHandlers[i] == bot) + myIndex = i; + + if (myIndex == -1) + return false; + + const time_t now = std::time(nullptr); + constexpr uint8 lookbackSeconds = 3; + + for (int8 i = 0; i <= myIndex; ++i) + { + Player* handler = coreHandlers[i]; + if (!handler) + continue; + + if (handler->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + return true; + + auto it = lastCoreInInventoryTime.find(handler->GetGUID()); + if (it != lastCoreInInventoryTime.end() && + (now - it->second) <= static_cast(lookbackSeconds)) + return true; + } + + return false; + } + const std::vector SHIELD_GENERATOR_DB_GUIDS = { 47482, // NW @@ -510,10 +524,7 @@ namespace SerpentShrineCavernHelpers continue; GameObject* go = bounds.first->second; - if (!go) - continue; - - if (go->GetGoState() != GO_STATE_READY) + if (!go || go->GetGoState() != GO_STATE_READY) continue; GeneratorInfo info; @@ -529,7 +540,7 @@ namespace SerpentShrineCavernHelpers // Returns the nearest active Shield Generator to the bot // Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures, - // which depawn after use + // which despawn after use Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference) { if (!reference) diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h index a725e28f..72ee52c8 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h @@ -1,3 +1,8 @@ +/* + * 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. + */ + #ifndef _PLAYERBOT_RAIDSSCHELPERS_H_ #define _PLAYERBOT_RAIDSSCHELPERS_H_ @@ -64,9 +69,6 @@ namespace SerpentShrineCavernHelpers // Warlock SPELL_CURSE_OF_EXHAUSTION = 18223, - - // Item - SPELL_HEAVY_NETHERWEAVE_NET = 31368, }; enum SerpentShrineCavernNPCs @@ -105,9 +107,6 @@ namespace SerpentShrineCavernHelpers { // Lady Vashj ITEM_TAINTED_CORE = 31088, - - // Tailoring - ITEM_HEAVY_NETHERWEAVE_NET = 24269, }; constexpr uint32 SSC_MAP_ID = 548; @@ -134,10 +133,10 @@ namespace SerpentShrineCavernHelpers extern std::unordered_map leotherasHumanFormDpsWaitTimer; extern std::unordered_map leotherasDemonFormDpsWaitTimer; extern std::unordered_map leotherasFinalPhaseDpsWaitTimer; - Unit* GetLeotherasHuman(PlayerbotAI* botAI); - Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI); - Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI); - Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI); + Unit* GetLeotherasHuman(Player* bot); + Unit* GetPhase2LeotherasDemon(Player* bot); + Unit* GetPhase3LeotherasDemon(Player* bot); + Unit* GetActiveLeotherasDemon(Player* bot); Player* GetLeotherasDemonFormTank(Player* bot); // Fathom-Lord Karathress @@ -158,25 +157,26 @@ namespace SerpentShrineCavernHelpers extern std::unordered_map tidewalkerRangedStep; // Lady Vashj - constexpr float VASHJ_PLATFORM_Z = 42.985f; + constexpr float VASHJ_PLATFORM_CENTER_Z = 42.902f; + constexpr float VASHJ_PLATFORM_EDGE_Z = 41.097f; extern const Position VASHJ_PLATFORM_CENTER_POSITION; - extern std::unordered_map vashjRangedPositions; extern std::unordered_map hasReachedVashjRangedPosition; extern std::unordered_map nearestTriggerGuid; extern std::unordered_map intendedLineup; extern std::unordered_map lastImbueAttempt; - extern std::unordered_map lastCoreInInventoryTime; - bool IsMainTankInSameSubgroup(Player* bot); + extern std::unordered_map lastCoreInInventoryTime; + bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot); bool IsLadyVashjInPhase1(PlayerbotAI* botAI); bool IsLadyVashjInPhase2(PlayerbotAI* botAI); bool IsLadyVashjInPhase3(PlayerbotAI* botAI); bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI); - bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds = 3); - Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI); - Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI); - Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI); - Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI); - Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI); + Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot); + Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot); + Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot); + Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot); + Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot); + std::array GetCoreHandlers(PlayerbotAI* botAI, Player* bot); + bool AnyRecentCoreInInventory(PlayerbotAI* botAI, Player* bot); struct GeneratorInfo { ObjectGuid guid; float x, y, z; }; extern const std::vector SHIELD_GENERATOR_DB_GUIDS; std::vector GetAllGeneratorInfosByDbGuids(