diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index d9ef2cedb..825717f83 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -43,21 +43,21 @@ any impact on performance, you may skip these question. If necessary, a maintain ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) + - - [ ] No, not at all + - - [ ] Minimal impact (**explain below**) + - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [ ] Yes (**explain why**) + - - [ ] No + - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [ ] Yes (**explain below**) + - - [ ] No + - - [ ] Yes (**explain below**) @@ -68,8 +68,8 @@ the message is in a translatable format, and list in the table the message_key a Search for GetBotTextOrDefault in the codebase for examples. --> Does this change add bot messages to translate? -- [ ] No -- [ ] Yes (**list messages in the table**) + - - [ ] No + - - [ ] Yes (**list messages in the table**) | Message key | Default message | | --------------- | ------------------ | @@ -82,8 +82,8 @@ AI assistance is allowed, but all submitted code must be fully understood, revie We expect contributors to be honest about what they do and do not understand. --> Was AI assistance used while working on this change? -- [ ] No -- [ ] Yes (**explain below**) + - - [ ] No + - - [ ] Yes (**explain below**) diff --git a/src/Ai/Base/Actions/FollowActions.cpp b/src/Ai/Base/Actions/FollowActions.cpp index c27de01ff..48ce74ad3 100644 --- a/src/Ai/Base/Actions/FollowActions.cpp +++ b/src/Ai/Base/Actions/FollowActions.cpp @@ -5,18 +5,211 @@ #include "FollowActions.h" +#include +#include +#include + #include "Event.h" #include "Formations.h" #include "LastMovementValue.h" +#include "MotionMaster.h" #include "PlayerbotAI.h" #include "Playerbots.h" #include "ServerFacade.h" +#include "Transport.h" +#include "Map.h" + +namespace +{ + Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref, uint32 phaseMask, float x, float y, float z) + { + if (!map || !ref) + return nullptr; + + std::array const probes = { z, z + 0.5f, z + 1.5f, z - 0.5f }; + for (float const pz : probes) + { + if (Transport* t = map->GetTransportForPos(phaseMask, x, y, pz, ref)) + return t; + } + + return nullptr; + } + + // Attempts to find a point on the leader's transport that is closer to the bot, + // by probing along the segment from master -> bot and returning the last point + // that is still detected as being on the expected transport. + bool FindBoardingPointOnTransport(Map* map, Transport* expectedTransport, WorldObject* ref, + float masterX, float masterY, float masterZ, + float botX, float botY, float botZ, + float& outX, float& outY, float& outZ) + { + if (!map || !expectedTransport || !ref) + return false; + + uint32 const phaseMask = ref->GetPhaseMask(); + + // Ensure master is actually detected on that transport (tolerant). + if (GetTransportForPosTolerant(map, ref, phaseMask, masterX, masterY, masterZ) != expectedTransport) + return false; + + // The raycast in GetTransportForPos starts at (z + 2). Probe with a safe Z. + float const probeZ = std::max(masterZ, botZ); + + // Adaptive step count: small platforms need tighter sampling. + float const dx2 = botX - masterX; + float const dy2 = botY - masterY; + float const dist2d = std::sqrt(dx2 * dx2 + dy2 * dy2); + int32 const steps = std::clamp(static_cast(dist2d / 0.75f), 10, 28); + + float const dx = (botX - masterX) / static_cast(steps); + float const dy = (botY - masterY) / static_cast(steps); + + // Master must actually be on the expected transport for this to work. + if (map->GetTransportForPos(ref->GetPhaseMask(), masterX, masterY, probeZ, ref) != expectedTransport) + return false; + + float lastX = masterX; + float lastY = masterY; + bool found = false; + + for (int32 i = 1; i <= steps; ++i) + { + float const px = masterX + dx * i; + float const py = masterY + dy * i; + + Transport* const t = GetTransportForPosTolerant(map, ref, phaseMask, px, py, probeZ); + if (t != expectedTransport) + break; + + lastX = px; + lastY = py; + found = true; + } + + if (!found) + return false; + + outX = lastX; + outY = lastY; + outZ = masterZ; // keep deck-level Z to encourage stepping onto the platform/boat + return true; + } +} bool FollowAction::Execute(Event /*event*/) { Formation* formation = AI_VALUE(Formation*, "formation"); std::string const target = formation->GetTargetName(); + // Transport handling for moving transports only (boats/zeppelins). + Player* master = botAI->GetMaster(); + if (master && master->IsInWorld() && bot->IsInWorld() && bot->GetMapId() == master->GetMapId()) + { + Map* map = master->GetMap(); + uint32 const mapId = bot->GetMapId(); + Transport* transport = nullptr; + bool masterOnTransport = false; + + if (master->GetTransport()) + { + transport = master->GetTransport(); + masterOnTransport = true; + } + else if (map) + { + transport = GetTransportForPosTolerant(map, master, master->GetPhaseMask(), + master->GetPositionX(), master->GetPositionY(), master->GetPositionZ()); + masterOnTransport = (transport != nullptr); + } + + // Ignore static transports (elevators/trams): only keep boats/zeppelins here. + if (transport && transport->IsStaticTransport()) + transport = nullptr; + + if (transport && map && bot->GetTransport() != transport) + { + float const botProbeZ = std::max(bot->GetPositionZ(), transport->GetPositionZ()); + Transport* botSurfaceTransport = GetTransportForPosTolerant(map, bot, bot->GetPhaseMask(), + bot->GetPositionX(), bot->GetPositionY(), botProbeZ); + + if (botSurfaceTransport == transport) + { + transport->AddPassenger(bot, true); + bot->StopMovingOnCurrentPos(); + return true; + } + + float const boardingAssistDistance = 60.0f; + float const dist2d = ServerFacade::instance().GetDistance2d(bot, master); + bool const inAssist = ServerFacade::instance().IsDistanceLessOrEqualThan(dist2d, boardingAssistDistance); + + if (inAssist) + { + float destX = masterOnTransport ? master->GetPositionX() : transport->GetPositionX(); + float destY = masterOnTransport ? master->GetPositionY() : transport->GetPositionY(); + float destZ = masterOnTransport ? master->GetPositionZ() : transport->GetPositionZ(); + float edgeX = 0.0f; + float edgeY = 0.0f; + float edgeZ = 0.0f; + + if (masterOnTransport && + FindBoardingPointOnTransport(map, transport, master, + master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), + bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), + edgeX, edgeY, edgeZ)) + { + destX = edgeX; + destY = edgeY; + destZ = edgeZ; + } + + MovementPriority const priority = botAI->GetState() == BOT_STATE_COMBAT + ? MovementPriority::MOVEMENT_COMBAT + : MovementPriority::MOVEMENT_NORMAL; + + bool const movingAllowed = IsMovingAllowed(mapId, destX, destY, destZ); + bool const dupMove = IsDuplicateMove(mapId, destX, destY, destZ); + bool const waiting = IsWaitingForLastMove(priority); + + if (movingAllowed && !dupMove && !waiting) + { + if (bot->IsSitState()) + bot->SetStandState(UNIT_STAND_STATE_STAND); + + if (bot->IsNonMeleeSpellCast(true)) + { + bot->CastStop(); + botAI->InterruptSpell(); + } + + if (MotionMaster* mm = bot->GetMotionMaster()) + { + mm->MovePoint( + /*id*/ 0, + /*coords*/ destX, destY, destZ, + /*forcedMovement*/ FORCED_MOVEMENT_NONE, + /*speed*/ 0.0f, + /*orientation*/ 0.0f, + /*generatePath*/ false, + /*forceDestination*/ false); + } + else + return false; + + float delay = 1000.0f * MoveDelay(bot->GetExactDist(destX, destY, destZ)); + delay = std::clamp(delay, 0.0f, static_cast(sPlayerbotAIConfig.maxWaitForMove)); + + AI_VALUE(LastMovement&, "last movement") + .Set(mapId, destX, destY, destZ, bot->GetOrientation(), delay, priority); + ClearIdleState(); + return true; + } + } + } + } + // end unified transport handling + bool moved = false; if (!target.empty()) { diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h index d5bfe0bcf..f2712d74d 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h @@ -37,10 +37,10 @@ public: uint32 GetCurrWaypoint() override; }; -class GrobblulusMoveCenterAction : public MoveInsideAction +class GrobbulusMoveCenterAction : public MoveInsideAction { public: - GrobblulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {} + GrobbulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {} }; class GrobbulusMoveAwayAction : public MovementAction @@ -173,26 +173,26 @@ private: RazuviousBossHelper helper; }; -class HorsemanAttractAlternativelyAction : public AttackAction +class FourHorsemenAttractAlternativelyAction : public AttackAction { public: - HorsemanAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attract alternatively"), helper(ai) + FourHorsemenAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "four horsemen attract alternatively"), helper(ai) { } bool Execute(Event event) override; protected: - FourhorsemanBossHelper helper; + FourHorsemenBossHelper helper; }; -class HorsemanAttactInOrderAction : public AttackAction +class FourHorsemenAttackInOrderAction : public AttackAction { public: - HorsemanAttactInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attact in order"), helper(ai) {} + FourHorsemenAttackInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "four horsemen attack in order"), helper(ai) {} bool Execute(Event event) override; protected: - FourhorsemanBossHelper helper; + FourHorsemenBossHelper helper; }; // class SapphironGroundMainTankPositionAction : public MovementAction diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp index 7ff77eadc..4391ba76c 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp @@ -1,7 +1,6 @@ -#include "RaidNaxxActions.h" - #include "ObjectGuid.h" #include "Playerbots.h" +#include "RaidNaxxActions.h" bool AnubrekhanChooseTargetAction::Execute(Event /*event*/) { @@ -66,13 +65,10 @@ bool AnubrekhanPositionAction::Execute(Event /*event*/) { uint32 nearest = FindNearestWaypoint(); uint32 next_point; - if (inPhase) - next_point = (nearest + 1) % intervals; - else - next_point = nearest; + next_point = (nearest + 1) % intervals; - return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, bot->GetPositionZ(), false, false, - false, false, MovementPriority::MOVEMENT_COMBAT); + return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, + bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); } else return MoveInside(533, 3272.49f, -3476.27f, bot->GetPositionZ(), 3.0f, MovementPriority::MOVEMENT_COMBAT); diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorseman.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorsemen.cpp similarity index 87% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorseman.cpp rename to src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorsemen.cpp index 567363887..291b225e7 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorseman.cpp +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorsemen.cpp @@ -2,7 +2,7 @@ #include "Playerbots.h" -bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/) +bool FourHorsemenAttractAlternativelyAction::Execute(Event /*event*/) { if (!helper.UpdateBossAI()) return false; @@ -13,13 +13,13 @@ bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/) return true; Unit* attackTarget = helper.CurrentAttackTarget(); - if (context->GetValue("current target")->Get() != attackTarget) + if (attackTarget && context->GetValue("current target")->Get() != attackTarget) return Attack(attackTarget); return false; } -bool HorsemanAttactInOrderAction::Execute(Event /*event*/) +bool FourHorsemenAttackInOrderAction::Execute(Event /*event*/) { if (!helper.UpdateBossAI()) return false; diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp index af906da8b..5fb6d8686 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp @@ -70,7 +70,6 @@ bool SapphironFlightPositionAction::MoveToNearestIcebolt() if (!group) return false; - Group::MemberSlotList const& slots = group->GetMemberSlots(); Player* playerWithIcebolt = nullptr; float minDistance; for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp index 0935f0545..145604da0 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp @@ -13,7 +13,7 @@ bool ThaddiusAttackNearestPetAction::isUseful() return false; Unit* target = helper.GetNearestPet(); - if (!bot->IsWithinDistInMap(target, 50.0f)) + if (!target || !bot->IsWithinDistInMap(target, 50.0f)) return false; return true; @@ -22,7 +22,7 @@ bool ThaddiusAttackNearestPetAction::isUseful() bool ThaddiusAttackNearestPetAction::Execute(Event /*event*/) { Unit* target = helper.GetNearestPet(); - if (!bot->IsWithinLOSInMap(target)) + if (!target || !bot->IsWithinLOSInMap(target)) return MoveTo(target, 0, MovementPriority::MOVEMENT_COMBAT); if (AI_VALUE(Unit*, "current target") != target) diff --git a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp b/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp index d34b48e6d..39e7668fa 100644 --- a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp +++ b/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp @@ -245,7 +245,7 @@ float AnubrekhanGenericMultiplier::GetValue(Action* action) return 1.0f; } -float FourhorsemanGenericMultiplier::GetValue(Action* action) +float FourHorsemenGenericMultiplier::GetValue(Action* action) { Unit* boss = AI_VALUE2(Unit*, "find target", "sir zeliek"); if (!boss) diff --git a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h b/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h index af702f605..f51420a2a 100644 --- a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h +++ b/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h @@ -1,6 +1,6 @@ -#ifndef _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H -#define _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H +#ifndef _PLAYERBOT_RAIDNAXXMULTIPLIERS_H +#define _PLAYERBOT_RAIDNAXXMULTIPLIERS_H #include "Multiplier.h" #include "RaidNaxxBossHelper.h" @@ -84,10 +84,10 @@ public: virtual float GetValue(Action* action); }; -class FourhorsemanGenericMultiplier : public Multiplier +class FourHorsemenGenericMultiplier : public Multiplier { public: - FourhorsemanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "fourhorseman generic") {} + FourHorsemenGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "four horsemen generic") {} public: virtual float GetValue(Action* action); diff --git a/src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h b/src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h index 45941d679..4aaf9462e 100644 --- a/src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h +++ b/src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h @@ -31,8 +31,8 @@ public: creators["razuvious use obedience crystal"] = &RaidNaxxActionContext::razuvious_use_obedience_crystal; creators["razuvious target"] = &RaidNaxxActionContext::razuvious_target; - creators["horseman attract alternatively"] = &RaidNaxxActionContext::horseman_attract_alternatively; - creators["horseman attack in order"] = &RaidNaxxActionContext::horseman_attack_in_order; + creators["four horsemen attract alternatively"] = &RaidNaxxActionContext::four_horsemen_attract_alternatively; + creators["four horsemen attack in order"] = &RaidNaxxActionContext::four_horsemen_attack_in_order; creators["sapphiron ground position"] = &RaidNaxxActionContext::sapphiron_ground_position; creators["sapphiron flight position"] = &RaidNaxxActionContext::sapphiron_flight_position; @@ -56,7 +56,7 @@ public: private: static Action* go_behind_the_boss(PlayerbotAI* ai) { return new GrobbulusGoBehindAction(ai); } static Action* rotate_grobbulus(PlayerbotAI* ai) { return new GrobbulusRotateAction(ai); } - static Action* grobbulus_move_center(PlayerbotAI* ai) { return new GrobblulusMoveCenterAction(ai); } + static Action* grobbulus_move_center(PlayerbotAI* ai) { return new GrobbulusMoveCenterAction(ai); } static Action* grobbulus_move_away(PlayerbotAI* ai) { return new GrobbulusMoveAwayAction(ai); } //static Action* heigan_dance_melee(PlayerbotAI* ai) { return new HeiganDanceMeleeAction(ai); } //static Action* heigan_dance_ranged(PlayerbotAI* ai) { return new HeiganDanceRangedAction(ai); } @@ -70,11 +70,8 @@ private: { return new RazuviousUseObedienceCrystalAction(ai); } - static Action* horseman_attract_alternatively(PlayerbotAI* ai) - { - return new HorsemanAttractAlternativelyAction(ai); - } - static Action* horseman_attack_in_order(PlayerbotAI* ai) { return new HorsemanAttactInOrderAction(ai); } + static Action* four_horsemen_attract_alternatively(PlayerbotAI* ai) { return new FourHorsemenAttractAlternativelyAction(ai); } + static Action* four_horsemen_attack_in_order(PlayerbotAI* ai) { return new FourHorsemenAttackInOrderAction(ai); } // static Action* sapphiron_ground_main_tank_position(PlayerbotAI* ai) { return new // SapphironGroundMainTankPositionAction(ai); } static Action* sapphiron_ground_position(PlayerbotAI* ai) { return new SapphironGroundPositionAction(ai); } diff --git a/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h b/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h index d980b4768..4d1557d56 100644 --- a/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h +++ b/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h @@ -30,8 +30,8 @@ public: creators["razuvious tank"] = &RaidNaxxTriggerContext::razuvious_tank; creators["razuvious nontank"] = &RaidNaxxTriggerContext::razuvious_nontank; - creators["horseman attractors"] = &RaidNaxxTriggerContext::horseman_attractors; - creators["horseman except attractors"] = &RaidNaxxTriggerContext::horseman_except_attractors; + creators["four horsemen attractors"] = &RaidNaxxTriggerContext::four_horsemen_attractors; + creators["four horsemen except attractors"] = &RaidNaxxTriggerContext::four_horsemen_except_attractors; creators["sapphiron ground"] = &RaidNaxxTriggerContext::sapphiron_ground; creators["sapphiron flight"] = &RaidNaxxTriggerContext::sapphiron_flight; @@ -66,8 +66,8 @@ private: static Trigger* razuvious_tank(PlayerbotAI* ai) { return new RazuviousTankTrigger(ai); } static Trigger* razuvious_nontank(PlayerbotAI* ai) { return new RazuviousNontankTrigger(ai); } - static Trigger* horseman_attractors(PlayerbotAI* ai) { return new HorsemanAttractorsTrigger(ai); } - static Trigger* horseman_except_attractors(PlayerbotAI* ai) { return new HorsemanExceptAttractorsTrigger(ai); } + static Trigger* four_horsemen_attractors(PlayerbotAI* ai) { return new FourHorsemenAttractorsTrigger(ai); } + static Trigger* four_horsemen_except_attractors(PlayerbotAI* ai) { return new FourHorsemenExceptAttractorsTrigger(ai); } static Trigger* sapphiron_ground(PlayerbotAI* ai) { return new SapphironGroundTrigger(ai); } static Trigger* sapphiron_flight(PlayerbotAI* ai) { return new SapphironFlightTrigger(ai); } diff --git a/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp b/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp index e0c3421d1..51f59f755 100644 --- a/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp +++ b/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp @@ -97,13 +97,13 @@ void RaidNaxxStrategy::InitTriggers(std::vector& triggers) { NextAction("razuvious target", ACTION_RAID + 1) } )); - // four horseman - triggers.push_back(new TriggerNode("horseman attractors", - { NextAction("horseman attract alternatively", ACTION_RAID + 1) } + // four horsemen + triggers.push_back(new TriggerNode("four horsemen attractors", + { NextAction("four horsemen attract alternatively", ACTION_RAID + 1) } )); - triggers.push_back(new TriggerNode("horseman except attractors", - { NextAction("horseman attack in order", ACTION_RAID + 1) } + triggers.push_back(new TriggerNode("four horsemen except attractors", + { NextAction("four horsemen attack in order", ACTION_RAID + 1) } )); // sapphiron @@ -150,7 +150,7 @@ void RaidNaxxStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new InstructorRazuviousGenericMultiplier(botAI)); multipliers.push_back(new KelthuzadGenericMultiplier(botAI)); multipliers.push_back(new AnubrekhanGenericMultiplier(botAI)); - multipliers.push_back(new FourhorsemanGenericMultiplier(botAI)); + multipliers.push_back(new FourHorsemenGenericMultiplier(botAI)); // multipliers.push_back(new GothikGenericMultiplier(botAI)); multipliers.push_back(new GluthGenericMultiplier(botAI)); } diff --git a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp b/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp index f4fb38e1d..3f0fc98b3 100644 --- a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp +++ b/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp @@ -114,7 +114,7 @@ bool RazuviousNontankTrigger::IsActive() return helper.UpdateBossAI() && !(bot->getClass() == CLASS_PRIEST); } -bool HorsemanAttractorsTrigger::IsActive() +bool FourHorsemenAttractorsTrigger::IsActive() { if (!helper.UpdateBossAI()) return false; @@ -122,7 +122,7 @@ bool HorsemanAttractorsTrigger::IsActive() return helper.IsAttracter(bot); } -bool HorsemanExceptAttractorsTrigger::IsActive() +bool FourHorsemenExceptAttractorsTrigger::IsActive() { if (!helper.UpdateBossAI()) return false; diff --git a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h b/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h index 6f7727cf2..c48cadd79 100644 --- a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h +++ b/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h @@ -186,24 +186,24 @@ private: ThaddiusBossHelper helper; }; -class HorsemanAttractorsTrigger : public Trigger +class FourHorsemenAttractorsTrigger : public Trigger { public: - HorsemanAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen attractors"), helper(ai) {} + FourHorsemenAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "four horsemen attractors"), helper(ai) {} bool IsActive() override; private: - FourhorsemanBossHelper helper; + FourHorsemenBossHelper helper; }; -class HorsemanExceptAttractorsTrigger : public Trigger +class FourHorsemenExceptAttractorsTrigger : public Trigger { public: - HorsemanExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen except attractors"), helper(ai) {} + FourHorsemenExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "four horsemen except attractors"), helper(ai) {} bool IsActive() override; private: - FourhorsemanBossHelper helper; + FourHorsemenBossHelper helper; }; class SapphironGroundTrigger : public Trigger diff --git a/src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h b/src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h index 9b87bb583..a13a5b893 100644 --- a/src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h +++ b/src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h @@ -202,7 +202,7 @@ public: } bool FindPosToAvoidChill(std::vector& dest) { - Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill25}); + Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill10, NaxxSpellIds::Chill25}); if (!aura) { // Fallback to name for custom spell data. @@ -363,13 +363,13 @@ private: Unit* _unit = nullptr; }; -class FourhorsemanBossHelper : public AiObject +class FourHorsemenBossHelper : public AiObject { public: const float posZ = 241.27f; const std::pair attractPos[2] = {{2502.03f, -2910.90f}, {2484.61f, -2947.07f}}; // left (sir zeliek), right (lady blaumeux) - FourhorsemanBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} + FourHorsemenBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} bool UpdateBossAI() { if (!bot->IsInCombat()) @@ -497,7 +497,8 @@ public: if (feugen && feugen->IsAlive()) unit = feugen; - if (stalagg && stalagg->IsAlive() && (!feugen || bot->GetDistance(stalagg) < bot->GetDistance(feugen))) + if (stalagg && stalagg->IsAlive() && + (!feugen || !feugen->IsAlive() || bot->GetDistance(stalagg) < bot->GetDistance(feugen))) unit = stalagg; return unit; diff --git a/src/Ai/Raid/Naxxramas/Util/RaidNaxxSpellIds.h b/src/Ai/Raid/Naxxramas/Util/RaidNaxxSpellIds.h index 53299e7e9..94c013eb7 100644 --- a/src/Ai/Raid/Naxxramas/Util/RaidNaxxSpellIds.h +++ b/src/Ai/Raid/Naxxramas/Util/RaidNaxxSpellIds.h @@ -58,6 +58,7 @@ namespace NaxxSpellIds // Sapphiron static constexpr uint32 Icebolt10 = 28522; static constexpr uint32 Icebolt25 = 28526; + static constexpr uint32 Chill10 = 28547; static constexpr uint32 Chill25 = 55699; /* // Fight diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp index 7aa3eda35..0b31a1c13 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 cbd237402..08ad4c48a 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 c99cafa3c..c841c7dcb 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 6630dc206..19ba73528 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 e6dce1694..1e2c44b49 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 13135bf3e..737fd3a38 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 139667dc6..624049c81 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 3c2c05f58..a994600ba 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 e77e63642..c3e753223 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 e106b58f3..4bf9e7416 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 7bda085be..4ebeb6f88 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 a725e28fc..72ee52c8c 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( diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index ba0e3643d..9859877f9 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -1796,7 +1796,7 @@ PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player) if (itr != _playerbotsAIMap.end()) { if (itr->second->IsBotAI()) - return reinterpret_cast(itr->second); + return dynamic_cast(itr->second); } return nullptr; @@ -1812,7 +1812,7 @@ PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player) if (itr != _playerbotsMgrMap.end()) { if (!itr->second->IsBotAI()) - return reinterpret_cast(itr->second); + return dynamic_cast(itr->second); } return nullptr; diff --git a/src/Mgr/Guild/GuildTaskMgr.cpp b/src/Mgr/Guild/GuildTaskMgr.cpp index 96162c044..d73662a79 100644 --- a/src/Mgr/Guild/GuildTaskMgr.cpp +++ b/src/Mgr/Guild/GuildTaskMgr.cpp @@ -1070,7 +1070,7 @@ void GuildTaskMgr::CheckKillTaskInternal(Player* player, Unit* victim) if (!victim->IsCreature()) return; - Creature* creature = reinterpret_cast(victim); + Creature* creature = dynamic_cast(victim); if (!creature) return; diff --git a/src/Mgr/Travel/TravelNode.cpp b/src/Mgr/Travel/TravelNode.cpp index 100fdd155..3e304677f 100644 --- a/src/Mgr/Travel/TravelNode.cpp +++ b/src/Mgr/Travel/TravelNode.cpp @@ -1753,190 +1753,75 @@ void TravelNodeMap::generateTransportNodes() for (auto const& itr : *sObjectMgr->GetGameObjectTemplates()) { GameObjectTemplate const* data = &itr.second; - if (data && (data->type == GAMEOBJECT_TYPE_TRANSPORT || data->type == GAMEOBJECT_TYPE_MO_TRANSPORT)) + if (!data || (data->type != GAMEOBJECT_TYPE_TRANSPORT && data->type != GAMEOBJECT_TYPE_MO_TRANSPORT)) + continue; + + uint32 pathId = data->moTransport.taxiPathId; + float moveSpeed = data->moTransport.moveSpeed; + if (pathId >= sTaxiPathNodesByPath.size()) + continue; + + TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId]; + + // Keep only transports with taxi paths (boats/zeppelins). + if (path.empty()) + continue; + + std::vector ppath; + TravelNode* prevNode = nullptr; + + // Loop over the path and connect stop locations. + for (auto& p : path) { - TransportAnimation const* animation = sTransportMgr->GetTransportAnimInfo(itr.first); + WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0); - uint32 pathId = data->moTransport.taxiPathId; - float moveSpeed = data->moTransport.moveSpeed; - if (pathId >= sTaxiPathNodesByPath.size()) - continue; + if (prevNode) + ppath.push_back(pos); - TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId]; - - std::vector ppath; - TravelNode* prevNode = nullptr; - - // Elevators/Trams - if (path.empty()) + if (p->delay > 0) { - if (animation) + TravelNode* node = TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first); + + if (!prevNode) { - TransportPathContainer aPath = animation->Path; - float timeStart; - - for (auto& transport : WorldPosition().getGameObjectsNear(0, itr.first)) - { - prevNode = nullptr; - WorldPosition basePos(transport->mapid, transport->posX, transport->posY, transport->posZ, - transport->orientation); - WorldPosition lPos = WorldPosition(); - - for (auto& p : aPath) - { - float dx = -1 * p.second->X; - float dy = -1 * p.second->Y; - - WorldPosition pos = - WorldPosition(basePos.GetMapId(), basePos.GetPositionX() + dx, - basePos.GetPositionY() + dy, basePos.GetPositionZ() + p.second->Z, - basePos.GetOrientation()); - - if (prevNode) - { - ppath.push_back(pos); - } - - if (pos.distance(&lPos) == 0) - { - TravelNode* node = - TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first); - - if (!prevNode) - { - ppath.push_back(pos); - timeStart = p.second->TimeSeg; - } - else - { - float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f; - - TravelNodePath travelPath(0.1f, totalTime, (uint8)TravelNodePathType::transport, - itr.first, true); - node->setPathTo(prevNode, travelPath); - ppath.clear(); - ppath.push_back(pos); - timeStart = p.second->TimeSeg; - } - - prevNode = node; - } - - lPos = pos; - } - - if (prevNode) - { - for (auto& p : aPath) - { - float dx = -1 * p.second->X; - float dy = -1 * p.second->Y; - WorldPosition pos = - WorldPosition(basePos.GetMapId(), basePos.GetPositionX() + dx, - basePos.GetPositionY() + dy, basePos.GetPositionZ() + p.second->Z, - basePos.GetOrientation()); - - ppath.push_back(pos); - - if (pos.distance(&lPos) == 0) - { - TravelNode* node = - TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first); - if (node != prevNode) - { - if (p.second->TimeSeg < timeStart) - timeStart = 0; - - float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f; - - TravelNodePath travelPath(0.1f, totalTime, (uint8)TravelNodePathType::transport, - itr.first, true); - travelPath.setPath(ppath); - node->setPathTo(prevNode, travelPath); - ppath.clear(); - ppath.push_back(pos); - timeStart = p.second->TimeSeg; - } - } - - lPos = pos; - } - } - - ppath.clear(); - } + ppath.push_back(pos); } - } - else // Boats/Zepelins - { - // Loop over the path and connect stop locations. - for (auto& p : path) + else { - WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0); - - // if (data->displayId == 3015) - // pos.setZ(pos.getZ() + 6.0f); - // else if (data->displayId == 3031) - // pos.setZ(pos.getZ() - 17.0f); - - if (prevNode) - { - ppath.push_back(pos); - } - - if (p->delay > 0) - { - TravelNode* node = TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first); - - if (!prevNode) - { - ppath.push_back(pos); - } - else - { - TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true); - travelPath.setPathAndCost(ppath, moveSpeed); - node->setPathTo(prevNode, travelPath); - ppath.clear(); - ppath.push_back(pos); - } - - prevNode = node; - } + TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true); + travelPath.setPathAndCost(ppath, moveSpeed); + node->setPathTo(prevNode, travelPath); + ppath.clear(); + ppath.push_back(pos); } - if (prevNode) - { - // Continue from start until first stop and connect to end. - for (auto& p : path) - { - WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0); - - // if (data->displayId == 3015) - // pos.setZ(pos.getZ() + 6.0f); - // else if (data->displayId == 3031) - // pos.setZ(pos.getZ() - 17.0f); - - ppath.push_back(pos); - - if (p->delay > 0) - { - TravelNode* node = TravelNodeMap::instance().getNode(pos, nullptr, 5.0f); - - if (node != prevNode) - { - TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, - true); - travelPath.setPathAndCost(ppath, moveSpeed); - - node->setPathTo(prevNode, travelPath); - } - } - } - } - ppath.clear(); + prevNode = node; } } + + if (!prevNode) + continue; + + // Continue from start until first stop and connect to end. + for (auto& p : path) + { + WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0); + ppath.push_back(pos); + + if (p->delay > 0) + { + TravelNode* node = TravelNodeMap::instance().getNode(pos, nullptr, 5.0f); + + if (node != prevNode) + { + TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true); + travelPath.setPathAndCost(ppath, moveSpeed); + + node->setPathTo(prevNode, travelPath); + } + } + } + ppath.clear(); } } diff --git a/src/Script/PlayerbotsSecureLogin.cpp b/src/Script/PlayerbotsSecureLogin.cpp index fc54a82b7..098107566 100644 --- a/src/Script/PlayerbotsSecureLogin.cpp +++ b/src/Script/PlayerbotsSecureLogin.cpp @@ -52,15 +52,14 @@ public: PlayerbotsSecureLoginServerScript() : ServerScript("PlayerbotsSecureLoginServerScript", { SERVERHOOK_CAN_PACKET_RECEIVE }) {} - bool CanPacketReceive(WorldSession* /*session*/, WorldPacket& packet) override + bool CanPacketReceive(WorldSession* /*session*/, WorldPacket const& packet) override { if (packet.GetOpcode() != CMSG_PLAYER_LOGIN) return true; - auto const oldPos = packet.rpos(); + WorldPacket pkt(packet); ObjectGuid loginGuid; - packet >> loginGuid; - packet.rpos(oldPos); + pkt >> loginGuid; if (!loginGuid) return true;