From 2144c95311ae5a49dceddc86dc71d7225ea6c80e Mon Sep 17 00:00:00 2001 From: crow Date: Thu, 7 Aug 2025 16:00:55 -0500 Subject: [PATCH 1/9] increase flexibility of multiple bot chatfiltering --- src/ChatFilter.cpp | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/ChatFilter.cpp b/src/ChatFilter.cpp index e1f90608..b07b0848 100644 --- a/src/ChatFilter.cpp +++ b/src/ChatFilter.cpp @@ -46,6 +46,14 @@ public: if (melee && botAI->IsRanged(bot)) return ""; + bool rangeddps = message.find("@rangeddps") == 0; + if (rangeddps && (!botAI->IsRanged(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot))) + return ""; + + bool meleedps = message.find("@meleedps") == 0; + if (meleedps && (!botAI->IsMelee(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot))) + return ""; + if (tank || dps || heal || ranged || melee) return ChatFilter::Filter(message); @@ -246,21 +254,41 @@ public: if (message.find("@group") == 0) { - std::string const pnum = message.substr(6, message.find(" ")); - uint32 from = atoi(pnum.c_str()); - uint32 to = from; - if (pnum.find("-") != std::string::npos) + size_t spacePos = message.find(" "); + if (spacePos == std::string::npos) + return message; + + std::string pnum = message.substr(6, spacePos - 6); + std::string actualMessage = message.substr(spacePos + 1); + + std::set targets; + std::istringstream ss(pnum); + std::string token; + + while (std::getline(ss, token, ',')) { - from = atoi(pnum.substr(pnum.find("@") + 1, pnum.find("-")).c_str()); - to = atoi(pnum.substr(pnum.find("-") + 1, pnum.find(" ")).c_str()); + size_t dashPos = token.find("-"); + if (dashPos != std::string::npos) + { + uint32 from = atoi(token.substr(0, dashPos).c_str()); + uint32 to = atoi(token.substr(dashPos + 1).c_str()); + if (from > to) std::swap(from, to); + for (uint32 i = from; i <= to; ++i) + targets.insert(i); + } + else + { + uint32 index = atoi(token.c_str()); + targets.insert(index); + } } if (!bot->GetGroup()) return message; uint32 sg = bot->GetSubGroup() + 1; - if (sg >= from && sg <= to) - return ChatFilter::Filter(message); + if (targets.find(sg) != targets.end()) + return ChatFilter::Filter(actualMessage); } return message; From 77c2354c3fb4e4cb57d369cf269e4133f017e641 Mon Sep 17 00:00:00 2001 From: kadeshar Date: Mon, 18 Aug 2025 12:02:19 +0200 Subject: [PATCH 2/9] Yogg-Saron strategy (#1565) * - wip * - Added Yogg-Saron strategy * - Added Yogg-Saron sanity strategy * - WIP * - WIP * - WIP * - WIP * - Added Yogg-Saron strategy * - code refactoring * - Code fix after pr --- src/PlayerbotAI.cpp | 160 +++- src/PlayerbotAI.h | 10 +- src/strategy/actions/BossAuraActions.cpp | 4 + src/strategy/actions/MovementActions.cpp | 174 ++++ src/strategy/actions/MovementActions.h | 30 + .../raids/ulduar/RaidUlduarActionContext.h | 36 + .../raids/ulduar/RaidUlduarActions.cpp | 607 +++++++++++++- src/strategy/raids/ulduar/RaidUlduarActions.h | 122 +++ .../raids/ulduar/RaidUlduarStrategy.cpp | 60 ++ .../raids/ulduar/RaidUlduarTriggerContext.h | 36 + .../raids/ulduar/RaidUlduarTriggers.cpp | 791 +++++++++++++++++- .../raids/ulduar/RaidUlduarTriggers.h | 215 ++++- src/strategy/triggers/BossAuraTriggers.cpp | 8 +- src/strategy/triggers/RangeTriggers.cpp | 81 ++ src/strategy/triggers/RangeTriggers.h | 24 + src/strategy/values/RtiTargetValue.h | 8 + src/strategy/values/ValueContext.h | 2 +- 17 files changed, 2320 insertions(+), 48 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 09a6ef82..2b2bc89b 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -1765,7 +1765,7 @@ bool PlayerbotAI::IsRangedDps(Player* player, bool bySpec) { return IsRanged(pla bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index) { - Group* group = bot->GetGroup(); + Group* group = player->GetGroup(); if (!group) { return false; @@ -1777,6 +1777,11 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index) { Player* member = ref->GetSource(); + if (!member) + { + continue; + } + if (IsHeal(member)) // Check if the member is a healer { bool isAssistant = group->IsAssistant(member->GetGUID()); @@ -1796,7 +1801,7 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index) bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index) { - Group* group = bot->GetGroup(); + Group* group = player->GetGroup(); if (!group) { return false; @@ -1808,6 +1813,11 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index) { Player* member = ref->GetSource(); + if (!member) + { + continue; + } + if (IsRangedDps(member)) // Check if the member is a ranged DPS { bool isAssistant = group->IsAssistant(member->GetGUID()); @@ -1840,6 +1850,35 @@ bool PlayerbotAI::HasAggro(Unit* unit) return false; } +int32 PlayerbotAI::GetAssistTankIndex(Player* player) +{ + Group* group = player->GetGroup(); + if (!group) + { + return -1; + } + int counter = 0; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + + if (player == member) + { + return counter; + } + if (IsTank(member, true) && group->IsAssistant(member->GetGUID())) + { + counter++; + } + } + return 0; +} + int32 PlayerbotAI::GetGroupSlotIndex(Player* player) { Group* group = bot->GetGroup(); @@ -1851,6 +1890,12 @@ int32 PlayerbotAI::GetGroupSlotIndex(Player* player) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + if (player == member) { return counter; @@ -1875,6 +1920,12 @@ int32 PlayerbotAI::GetRangedIndex(Player* player) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + if (player == member) { return counter; @@ -1902,6 +1953,12 @@ int32 PlayerbotAI::GetClassIndex(Player* player, uint8 cls) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + if (player == member) { return counter; @@ -1928,6 +1985,12 @@ int32 PlayerbotAI::GetRangedDpsIndex(Player* player) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + if (player == member) { return counter; @@ -1955,6 +2018,12 @@ int32 PlayerbotAI::GetMeleeIndex(Player* player) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + if (player == member) { return counter; @@ -2126,6 +2195,12 @@ bool PlayerbotAI::IsMainTank(Player* player) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + if (IsTank(member) && member->IsAlive()) { return player->GetGUID() == member->GetGUID(); @@ -2134,6 +2209,62 @@ bool PlayerbotAI::IsMainTank(Player* player) return false; } +bool PlayerbotAI::IsBotMainTank(Player* player) +{ + if (!player->GetSession()->IsBot() || !IsTank(player)) + { + return false; + } + + if (IsMainTank(player)) + { + return true; + } + + Group* group = player->GetGroup(); + if (!group) + { + return true; // If no group, consider the bot as main tank + } + + uint32 botAssistTankIndex = GetAssistTankIndex(player); + + if (botAssistTankIndex == -1) + { + return false; + } + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->GetSource(); + if (!member) + { + continue; + } + + uint32 memberAssistTankIndex = GetAssistTankIndex(member); + + if (memberAssistTankIndex == -1) + { + continue; + } + + if (memberAssistTankIndex == botAssistTankIndex && player == member) + { + return true; + } + + if (memberAssistTankIndex < botAssistTankIndex && member->GetSession()->IsBot()) + { + return false; + } + + return false; + } + + return false; +} + uint32 PlayerbotAI::GetGroupTankNum(Player* player) { Group* group = player->GetGroup(); @@ -2145,6 +2276,12 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + if (IsTank(member) && member->IsAlive()) { result++; @@ -2157,7 +2294,7 @@ bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMai bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index) { - Group* group = bot->GetGroup(); + Group* group = player->GetGroup(); if (!group) { return false; @@ -2166,6 +2303,12 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) { if (index == counter) @@ -2179,6 +2322,12 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); + + if (!member) + { + continue; + } + if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) { if (index == counter) @@ -2386,6 +2535,11 @@ std::vector PlayerbotAI::GetPlayersInGroup() { Player* member = ref->GetSource(); + if (!member) + { + continue; + } + if (GET_PLAYERBOT_AI(member) && !GET_PLAYERBOT_AI(member)->IsRealPlayer()) continue; diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 3fa7b5b6..575a3940 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -423,13 +423,15 @@ public: static bool IsCaster(Player* player, bool bySpec = false); static bool IsRangedDps(Player* player, bool bySpec = false); static bool IsCombo(Player* player); + static bool IsBotMainTank(Player* player); static bool IsMainTank(Player* player); static uint32 GetGroupTankNum(Player* player); - bool IsAssistTank(Player* player); - bool IsAssistTankOfIndex(Player* player, int index); - bool IsHealAssistantOfIndex(Player* player, int index); - bool IsRangedDpsAssistantOfIndex(Player* player, int index); + static bool IsAssistTank(Player* player); + static bool IsAssistTankOfIndex(Player* player, int index); + static bool IsHealAssistantOfIndex(Player* player, int index); + static bool IsRangedDpsAssistantOfIndex(Player* player, int index); bool HasAggro(Unit* unit); + static int32 GetAssistTankIndex(Player* player); int32 GetGroupSlotIndex(Player* player); int32 GetRangedIndex(Player* player); int32 GetClassIndex(Player* player, uint8 cls); diff --git a/src/strategy/actions/BossAuraActions.cpp b/src/strategy/actions/BossAuraActions.cpp index 9e42698e..d91186c6 100644 --- a/src/strategy/actions/BossAuraActions.cpp +++ b/src/strategy/actions/BossAuraActions.cpp @@ -22,6 +22,7 @@ bool BossFireResistanceAction::Execute(Event event) { PaladinFireResistanceStrategy paladinFireResistanceStrategy(botAI); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFireResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); + botAI->DoSpecificAction("fire resistance aura", Event(), true); return true; } @@ -35,6 +36,7 @@ bool BossFrostResistanceAction::Execute(Event event) { PaladinFrostResistanceStrategy paladinFrostResistanceStrategy(botAI); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFrostResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); + botAI->DoSpecificAction("frost resistance aura", Event(), true); return true; } @@ -48,6 +50,7 @@ bool BossNatureResistanceAction::Execute(Event event) { HunterNatureResistanceStrategy hunterNatureResistanceStrategy(botAI); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + hunterNatureResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); + botAI->DoSpecificAction("aspect of the wild", Event(), true); return true; } @@ -61,5 +64,6 @@ bool BossShadowResistanceAction::Execute(Event event) { PaladinShadowResistanceStrategy paladinShadowResistanceStrategy(botAI); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinShadowResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); + botAI->DoSpecificAction("shadow resistance aura", Event(), true); return true; } diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index d3e596e3..b2a36efd 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -2767,3 +2767,177 @@ bool MoveFromGroupAction::Execute(Event event) distance = 20.0f; // flee distance from config is too small for this return MoveFromGroup(distance); } + +bool MoveAwayFromCreatureAction::Execute(Event event) +{ + GuidVector targets = AI_VALUE(GuidVector, "nearest npcs"); + Creature* nearestCreature = bot->FindNearestCreature(creatureId, range, alive); + + // Find all creatures with the specified Id + std::vector creatures; + for (const auto& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId) + { + creatures.push_back(unit); + } + } + + if (creatures.empty()) + { + return false; + } + + // Search for a safe position + const int directions = 8; + const float increment = 3.0f; + float bestX = bot->GetPositionX(); + float bestY = bot->GetPositionY(); + float bestZ = bot->GetPositionZ(); + float maxSafetyScore = -1.0f; + + for (int i = 0; i < directions; ++i) + { + float angle = (i * 2 * M_PI) / directions; + for (float distance = increment; distance <= 30.0f; distance += increment) + { + float moveX = bot->GetPositionX() + distance * cos(angle); + float moveY = bot->GetPositionY() + distance * sin(angle); + float moveZ = bot->GetPositionZ(); + + // Check creature distance constraints + bool isSafeFromCreatures = true; + float minCreatureDist = std::numeric_limits::max(); + for (Unit* creature : creatures) + { + float dist = creature->GetExactDist2d(moveX, moveY); + if (dist < range) + { + isSafeFromCreatures = false; + break; + } + if (dist < minCreatureDist) + { + minCreatureDist = dist; + } + } + + if (isSafeFromCreatures && bot->IsWithinLOS(moveX, moveY, moveZ)) + { + // A simple safety score: the minimum distance to any creature. Higher is better. + if (minCreatureDist > maxSafetyScore) + { + maxSafetyScore = minCreatureDist; + bestX = moveX; + bestY = moveY; + bestZ = moveZ; + } + } + } + } + + // Move to the best position found + if (maxSafetyScore > 0.0f) + { + return MoveTo(bot->GetMapId(), bestX, bestY, bestZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + + return false; +} + +bool MoveAwayFromCreatureAction::isPossible() +{ + return bot->CanFreeMove(); +} + +bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event) +{ + Player* closestPlayer = nullptr; + float minDistance = 0.0f; + + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + std::vector debuffedPlayers; + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->GetSource(); + if (player && player->IsAlive() && player->HasAura(spellId)) + { + debuffedPlayers.push_back(player); + } + } + + if (debuffedPlayers.empty()) + { + return false; + } + + // Search for a safe position + const int directions = 8; + const float increment = 3.0f; + float bestX = bot->GetPositionX(); + float bestY = bot->GetPositionY(); + float bestZ = bot->GetPositionZ(); + float maxSafetyScore = -1.0f; + + for (int i = 0; i < directions; ++i) + { + float angle = (i * 2 * M_PI) / directions; + for (float distance = increment; distance <= (range + 5.0f); distance += increment) + { + float moveX = bot->GetPositionX() + distance * cos(angle); + float moveY = bot->GetPositionY() + distance * sin(angle); + float moveZ = bot->GetPositionZ(); + + // Check creature distance constraints + bool isSafeFromDebuffedPlayer = true; + float minDebuffedPlayerDistance = std::numeric_limits::max(); + for (Unit* debuffedPlayer : debuffedPlayers) + { + float dist = debuffedPlayer->GetExactDist2d(moveX, moveY); + if (dist < range) + { + isSafeFromDebuffedPlayer = false; + break; + } + if (dist < minDebuffedPlayerDistance) + { + minDebuffedPlayerDistance = dist; + } + } + + if (isSafeFromDebuffedPlayer && bot->IsWithinLOS(moveX, moveY, moveZ)) + { + // A simple safety score: the minimum distance to any debuffed player. Higher is better. + if (minDebuffedPlayerDistance > maxSafetyScore) + { + maxSafetyScore = minDebuffedPlayerDistance; + bestX = moveX; + bestY = moveY; + bestZ = moveZ; + } + } + } + } + + // Move to the best position found + if (maxSafetyScore > 0.0f) + { + return MoveTo(bot->GetMapId(), bestX, bestY, bestZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true); + } + + return false; +} + +bool MoveAwayFromPlayerWithDebuffAction::isPossible() +{ + return bot->CanFreeMove(); +} diff --git a/src/strategy/actions/MovementActions.h b/src/strategy/actions/MovementActions.h index c647027a..e639c5cb 100644 --- a/src/strategy/actions/MovementActions.h +++ b/src/strategy/actions/MovementActions.h @@ -294,4 +294,34 @@ public: bool Execute(Event event) override; }; +class MoveAwayFromCreatureAction : public MovementAction +{ +public: + MoveAwayFromCreatureAction(PlayerbotAI* botAI, std::string name, uint32 creatureId, float range, bool alive = true) + : MovementAction(botAI, name), creatureId(creatureId), range(range), alive(alive) {} + + bool Execute(Event event) override; + bool isPossible() override; + +private: + uint32 creatureId; + float range; + bool alive; +}; + +class MoveAwayFromPlayerWithDebuffAction : public MovementAction +{ +public: + MoveAwayFromPlayerWithDebuffAction(PlayerbotAI* botAI, std::string name, uint32 spellId, float range) + : MovementAction(botAI, name), spellId(spellId), range(range) {} + + bool Execute(Event event) override; + bool isPossible() override; + +private: + uint32 spellId; + float range; + bool alive; +}; + #endif diff --git a/src/strategy/raids/ulduar/RaidUlduarActionContext.h b/src/strategy/raids/ulduar/RaidUlduarActionContext.h index c27a2580..3dcd3195 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActionContext.h +++ b/src/strategy/raids/ulduar/RaidUlduarActionContext.h @@ -65,6 +65,24 @@ public: creators["vezax cheat action"] = &RaidUlduarActionContext::vezax_cheat_action; creators["vezax shadow crash action"] = &RaidUlduarActionContext::vezax_shadow_crash_action; creators["vezax mark of the faceless action"] = &RaidUlduarActionContext::vezax_mark_of_the_faceless_action; + creators["vezax shadow resistance action"] = &RaidUlduarActionContext::vezax_shadow_resistance_action; + creators["sara shadow resistance action"] = &RaidUlduarActionContext::sara_shadow_resistance_action; + creators["yogg-saron shadow resistance action"] = &RaidUlduarActionContext::yogg_saron_shadow_resistance_action; + creators["yogg-saron ominous cloud cheat action"] = &RaidUlduarActionContext::yogg_saron_ominous_cloud_cheat_action; + creators["yogg-saron guardian positioning action"] = &RaidUlduarActionContext::yogg_saron_guardian_positioning_action; + creators["yogg-saron sanity action"] = &RaidUlduarActionContext::yogg_saron_sanity_action; + creators["yogg-saron death orb action"] = &RaidUlduarActionContext::yogg_saron_death_orb_action; + creators["yogg-saron malady of the mind action"] = &RaidUlduarActionContext::yogg_saron_malady_of_the_mind_action; + creators["yogg-saron mark target action"] = &RaidUlduarActionContext::yogg_saron_mark_target_action; + creators["yogg-saron brain link action"] = &RaidUlduarActionContext::yogg_saron_brain_link_action; + creators["yogg-saron move to enter portal action"] = &RaidUlduarActionContext::yogg_saron_move_to_enter_portal_action; + creators["yogg-saron use portal action"] = &RaidUlduarActionContext::yogg_saron_use_portal_action; + creators["yogg-saron fall from floor action"] = &RaidUlduarActionContext::yogg_saron_fall_from_floor_action; + creators["yogg-saron boss room movement cheat action"] = &RaidUlduarActionContext::yogg_saron_boss_room_movement_cheat_action; + creators["yogg-saron illusion room action"] = &RaidUlduarActionContext::yogg_saron_illusion_room_action; + creators["yogg-saron move to exit portal action"] = &RaidUlduarActionContext::yogg_saron_move_to_exit_portal_action; + creators["yogg-saron lunatic gaze action"] = &RaidUlduarActionContext::yogg_saron_lunatic_gaze_action; + creators["yogg-saron phase 3 positioning action"] = &RaidUlduarActionContext::yogg_saron_phase_3_positioning_action; } private: @@ -117,6 +135,24 @@ private: static Action* vezax_cheat_action(PlayerbotAI* ai) { return new VezaxCheatAction(ai); } static Action* vezax_shadow_crash_action(PlayerbotAI* ai) { return new VezaxShadowCrashAction(ai); } static Action* vezax_mark_of_the_faceless_action(PlayerbotAI* ai) { return new VezaxMarkOfTheFacelessAction(ai); } + static Action* vezax_shadow_resistance_action(PlayerbotAI* ai) { return new BossShadowResistanceAction(ai, "general vezax"); } + static Action* sara_shadow_resistance_action(PlayerbotAI* ai) { return new BossShadowResistanceAction(ai, "sara"); } + static Action* yogg_saron_shadow_resistance_action(PlayerbotAI* ai) { return new BossShadowResistanceAction(ai, "yogg-saron"); } + static Action* yogg_saron_ominous_cloud_cheat_action(PlayerbotAI* ai) { return new YoggSaronOminousCloudCheatAction(ai); } + static Action* yogg_saron_guardian_positioning_action(PlayerbotAI* ai) { return new YoggSaronGuardianPositioningAction(ai); } + static Action* yogg_saron_sanity_action(PlayerbotAI* ai) { return new YoggSaronSanityAction(ai); } + static Action* yogg_saron_death_orb_action(PlayerbotAI* ai) { return new YoggSaronDeathOrbAction(ai); } + static Action* yogg_saron_malady_of_the_mind_action(PlayerbotAI* ai) { return new YoggSaronMaladyOfTheMindAction(ai); } + static Action* yogg_saron_mark_target_action(PlayerbotAI* ai) { return new YoggSaronMarkTargetAction(ai); } + static Action* yogg_saron_brain_link_action(PlayerbotAI* ai) { return new YoggSaronBrainLinkAction(ai); } + static Action* yogg_saron_move_to_enter_portal_action(PlayerbotAI* ai) { return new YoggSaronMoveToEnterPortalAction(ai); } + static Action* yogg_saron_use_portal_action(PlayerbotAI* ai) { return new YoggSaronUsePortalAction(ai); } + static Action* yogg_saron_fall_from_floor_action(PlayerbotAI* ai) { return new YoggSaronFallFromFloorAction(ai); } + static Action* yogg_saron_boss_room_movement_cheat_action(PlayerbotAI* ai) { return new YoggSaronBossRoomMovementCheatAction(ai); } + static Action* yogg_saron_illusion_room_action(PlayerbotAI* ai) { return new YoggSaronIllusionRoomAction(ai); } + static Action* yogg_saron_move_to_exit_portal_action(PlayerbotAI* ai) { return new YoggSaronMoveToExitPortalAction(ai); } + static Action* yogg_saron_lunatic_gaze_action(PlayerbotAI* ai) { return new YoggSaronLunaticGazeAction(ai); } + static Action* yogg_saron_phase_3_positioning_action(PlayerbotAI* ai) { return new YoggSaronPhase3PositioningAction(ai); } }; #endif diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.cpp b/src/strategy/raids/ulduar/RaidUlduarActions.cpp index 3d180820..a9b48822 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarActions.cpp @@ -20,13 +20,14 @@ #include "RaidUlduarBossHelper.h" #include "RaidUlduarScripts.h" #include "RaidUlduarStrategy.h" -#include "RaidUlduarTriggers.h" #include "RtiValue.h" #include "ScriptedCreature.h" #include "ServerFacade.h" #include "SharedDefines.h" #include "Unit.h" #include "Vehicle.h" +#include +#include const std::string ADD_STRATEGY_CHAR = "+"; const std::string REMOVE_STRATEGY_CHAR = "-"; @@ -42,6 +43,12 @@ const Position ULDUAR_KOLOGARN_RESTORE_POSITION = Position(1764.3749f, -24.02903 const Position ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION = Position(1781.2051f, 9.34402f, 449.0f, 0.00087690353f); const Position ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION = Position(1763.2561f, -24.44305f, 449.0f, 0.00087690353f); const Position ULDUAR_THORIM_JUMP_START_POINT = Position(2137.137f, -291.19025f, 438.24753f, 1.7059844f); +const Position ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT = Position(1928.8923f, -24.871964f, 324.88956f, 6.247805f); + +const Position yoggPortalLoc[] = { + {1970.48f, -9.75f, 325.5f}, {1992.76f, -10.21f, 325.5f}, {1995.53f, -39.78f, 325.5f}, {1969.25f, -42.00f, 325.5f}, + {1960.62f, -32.00f, 325.5f}, {1981.98f, -5.69f, 325.5f}, {1982.78f, -45.73f, 325.5f}, {2000.66f, -29.68f, 325.5f}, + {1999.88f, -19.61f, 325.5f}, {1961.37f, -19.54f, 325.5f}}; bool FlameLeviathanVehicleAction::Execute(Event event) { @@ -1817,24 +1824,24 @@ bool ThorimMarkDpsTargetAction::Execute(Event event) if (!group) return false; - ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); + ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex); Unit* currentMoonUnit = botAI->GetUnit(currentMoonTarget); Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); if (!currentMoonUnit && boss && boss->IsAlive() && boss->GetPositionZ() > ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD) { - group->SetTargetIcon(moonIndex, bot->GetGUID(), boss->GetGUID()); + group->SetTargetIcon(RtiTargetValue::moonIndex, bot->GetGUID(), boss->GetGUID()); } if (currentMoonUnit && boss && currentMoonUnit->GetEntry() == boss->GetEntry() && boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD) { - group->SetTargetIcon(skullIndex, bot->GetGUID(), boss->GetGUID()); + group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID()); return true; } if (botAI->IsMainTank(bot)) { - ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex); + ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); if (currentSkullUnit && !currentSkullUnit->IsAlive()) { @@ -1855,7 +1862,7 @@ bool ThorimMarkDpsTargetAction::Execute(Event event) } else if (botAI->IsAssistTankOfIndex(bot, 0)) { - ObjectGuid currentCrossTarget = group->GetTargetIcon(crossIndex); + ObjectGuid currentCrossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex); Unit* currentCrossUnit = botAI->GetUnit(currentCrossTarget); if (currentCrossUnit && !currentCrossUnit->IsAlive()) { @@ -1891,13 +1898,13 @@ bool ThorimMarkDpsTargetAction::Execute(Event event) if (botAI->IsMainTank(bot)) { - group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID()); + group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), targetToMark->GetGUID()); return true; } if (botAI->IsAssistTankOfIndex(bot, 0)) { - group->SetTargetIcon(crossIndex, bot->GetGUID(), targetToMark->GetGUID()); + group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), targetToMark->GetGUID()); return true; } @@ -2438,20 +2445,20 @@ bool MimironAerialCommandUnitAction::Execute(Event event) if (bombBot) { - group->SetTargetIcon(crossIndex, bot->GetGUID(), bombBot->GetGUID()); + group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), bombBot->GetGUID()); } else if (boss) { - group->SetTargetIcon(crossIndex, bot->GetGUID(), boss->GetGUID()); + group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), boss->GetGUID()); } if (assaultBot) { - ObjectGuid skullTarget = group->GetTargetIcon(skullIndex); + ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); Unit* skullUnit = botAI->GetUnit(skullTarget); if (!skullTarget || !skullUnit || !skullUnit->IsAlive()) { - group->SetTargetIcon(skullIndex, bot->GetGUID(), assaultBot->GetGUID()); + group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), assaultBot->GetGUID()); } } @@ -2591,7 +2598,7 @@ bool MimironPhase4MarkDpsAction::Execute(Event event) highestHealthUnit = aerialCommandUnit; } - group->SetTargetIcon(skullIndex, bot->GetGUID(), highestHealthUnit->GetGUID()); + group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), highestHealthUnit->GetGUID()); if (highestHealthUnit == leviathanMkII) { if (AI_VALUE(std::string, "rti") == "skull") @@ -2601,7 +2608,7 @@ bool MimironPhase4MarkDpsAction::Execute(Event event) } else { - group->SetTargetIcon(crossIndex, bot->GetGUID(), leviathanMkII->GetGUID()); + group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), leviathanMkII->GetGUID()); if (AI_VALUE(std::string, "rti") != "cross") { botAI->GetAiObjectContext()->GetValue("rti")->Set("cross"); @@ -2707,3 +2714,575 @@ bool VezaxMarkOfTheFacelessAction::Execute(Event event) ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } + +bool YoggSaronOminousCloudCheatAction::Execute(Event event) +{ + YoggSaronTrigger yoggSaronTrigger(botAI); + + Unit* boss = yoggSaronTrigger.GetSaraIfAlive(); + if (!boss) + { + return false; + } + + Creature* target = boss->FindNearestCreature(NPC_OMINOUS_CLOUD, 25.0f); + if (!target || !target->IsAlive()) + { + return false; + } + + target->Kill(bot, target); + return true; +} + +bool YoggSaronGuardianPositioningAction::Execute(Event event) +{ + return MoveTo(bot->GetMapId(), ULDUAR_YOGG_SARON_MIDDLE.GetPositionX(), ULDUAR_YOGG_SARON_MIDDLE.GetPositionY(), + ULDUAR_YOGG_SARON_MIDDLE.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); +} + +bool YoggSaronSanityAction::Execute(Event event) +{ + Creature* sanityWell = bot->FindNearestCreature(NPC_SANITY_WELL, 200.0f); + + return MoveTo(bot->GetMapId(), sanityWell->GetPositionX(), sanityWell->GetPositionY(), sanityWell->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED, + true, false); +} + +bool YoggSaronMarkTargetAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + YoggSaronTrigger yoggSaronTrigger(botAI); + if (yoggSaronTrigger.IsPhase2()) + { + if (botAI->HasCheat(BotCheatMask::raid)) + { + Unit* crusherTentacle = bot->FindNearestCreature(NPC_CRUSHER_TENTACLE, 200.0f, true); + if (crusherTentacle) + { + crusherTentacle->Kill(bot, crusherTentacle); + } + } + + ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex); + Creature* yogg_saron = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true); + if (!currentMoonTarget || currentMoonTarget != yogg_saron->GetGUID()) + { + group->SetTargetIcon(RtiTargetValue::moonIndex, bot->GetGUID(), yogg_saron->GetGUID()); + return true; + } + + ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); + + Creature* nextPossibleTarget = bot->FindNearestCreature(NPC_CONSTRICTOR_TENTACLE, 200.0f, true); + if (!nextPossibleTarget) + { + nextPossibleTarget = bot->FindNearestCreature(NPC_CORRUPTOR_TENTACLE, 200.0f, true); + if (!nextPossibleTarget) + { + return false; + } + } + + if (currentSkullTarget) + { + Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); + + if (currentSkullUnit && currentSkullUnit->IsAlive() && + currentSkullUnit->GetGUID() == nextPossibleTarget->GetGUID()) + { + return false; + } + } + + group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), nextPossibleTarget->GetGUID()); + } + else if (yoggSaronTrigger.IsPhase3()) + { + TankFaceStrategy tankFaceStrategy(botAI); + if (botAI->HasStrategy(tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT); + } + + TankAssistStrategy tankAssistStrategy(botAI); + if (!botAI->HasStrategy(tankAssistStrategy.getName(), BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy(ADD_STRATEGY_CHAR + tankAssistStrategy.getName(), BotState::BOT_STATE_COMBAT); + } + + GuidVector targets = AI_VALUE(GuidVector, "nearest npcs"); + + int lowestHealth = std::numeric_limits::max(); + Unit* lowestHealthUnit = nullptr; + for (const ObjectGuid& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + { + continue; + } + + if ((unit->GetEntry() == NPC_IMMORTAL_GUARDIAN || unit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN) && + unit->GetHealthPct() > 10) + { + if (unit->GetHealth() < lowestHealth) + { + lowestHealth = unit->GetHealth(); + lowestHealthUnit = unit; + } + } + } + + if (lowestHealthUnit) + { + // Added because lunatic gaze freeze all bots and they can't attack + // If someone fix it then this cheat can be removed + if (botAI->HasCheat(BotCheatMask::raid)) + { + lowestHealthUnit->Kill(bot, lowestHealthUnit); + } + else + { + group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), lowestHealthUnit->GetGUID()); + } + + return true; + } + + ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); + Unit* currentSkullUnit = nullptr; + if (currentSkullTarget) + { + currentSkullUnit = botAI->GetUnit(currentSkullTarget); + } + + if (!currentSkullUnit || currentSkullUnit->GetEntry() != NPC_YOGG_SARON) + { + Unit* yoggsaron = AI_VALUE2(Unit*, "find target", "yogg-saron"); + if (yoggsaron && yoggsaron->IsAlive()) + { + group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), yoggsaron->GetGUID()); + return true; + } + } + + return false; + } + + return false; +} + +bool YoggSaronBrainLinkAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->GetSource(); + if (player && player->IsAlive() && player->HasAura(SPELL_BRAIN_LINK) && player->GetGUID() != bot->GetGUID()) + { + return MoveNear(player, 10.0f, MovementPriority::MOVEMENT_FORCED); + } + } + + return false; +} + +bool YoggSaronMoveToEnterPortalAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + bool isInBrainRoomTeam = false; + int portalNumber = 0; + int brainRoomTeamCount = 10; + if (bot->GetRaidDifficulty() == Difficulty::RAID_DIFFICULTY_10MAN_NORMAL) + { + brainRoomTeamCount = 4; + } + + Player* master = botAI->GetMaster(); + if (master && !botAI->IsTank(master)) + { + portalNumber++; + brainRoomTeamCount--; + } + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->GetSource(); + if (!member || !member->IsAlive() || botAI->IsTank(member) || botAI->GetMaster()->GetGUID() == member->GetGUID()) + { + continue; + } + + portalNumber++; + if (member->GetGUID() == bot->GetGUID()) + { + isInBrainRoomTeam = true; + break; + } + + brainRoomTeamCount--; + if (brainRoomTeamCount == 0) + { + break; + } + } + + if (!isInBrainRoomTeam) + { + return false; + } + + Position assignedPortalPosition = yoggPortalLoc[portalNumber - 1]; + + botAI->GetAiObjectContext()->GetValue("rti")->Set("diamond"); + + if (botAI->HasCheat(BotCheatMask::raid)) + { + return bot->TeleportTo(bot->GetMapId(), assignedPortalPosition.GetPositionX(), + assignedPortalPosition.GetPositionY(), + assignedPortalPosition.GetPositionZ(), bot->GetOrientation()); + } + else + { + return MoveNear(bot->GetMapId(), assignedPortalPosition.GetPositionX(), + assignedPortalPosition.GetPositionY(), + assignedPortalPosition.GetPositionZ(), sPlayerbotAIConfig->contactDistance, + MovementPriority::MOVEMENT_FORCED); + } +} + +bool YoggSaronFallFromFloorAction::Execute(Event event) +{ + std::string rtiMark = AI_VALUE(std::string, "rti"); + if (rtiMark == "skull") + { + return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT.GetPositionX(), + ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT.GetPositionY(), + ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT.GetPositionZ(), + ULDUAR_YOGG_SARON_BOSS_ROOM_RESTORE_POINT.GetOrientation()); + } + if (rtiMark == "cross") + { + return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionX(), + ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionY(), + ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionZ(), + bot->GetOrientation()); + } + if (rtiMark == "circle") + { + return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionX(), + ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionY(), + ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionZ(), bot->GetOrientation()); + } + if (rtiMark == "star") + { + return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionX(), + ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionY(), + ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionZ(), bot->GetOrientation()); + } + return false; +} + +bool YoggSaronBossRoomMovementCheatAction::Execute(Event event) +{ + FollowMasterStrategy followMasterStrategy(botAI); + if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT)) + { + botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT); + } + + if (!botAI->HasCheat(BotCheatMask::raid)) + { + return false; + } + + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); + + if (!currentSkullTarget) + { + return false; + } + + Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); + + if (!currentSkullUnit || !currentSkullUnit->IsAlive()) + { + return false; + } + + return bot->TeleportTo(bot->GetMapId(), currentSkullUnit->GetPositionX(), currentSkullUnit->GetPositionY(), + currentSkullUnit->GetPositionZ(), bot->GetOrientation()); +} + +bool YoggSaronUsePortalAction::Execute(Event event) +{ + Creature* assignedPortal = bot->FindNearestCreature(NPC_DESCEND_INTO_MADNESS, 2.0f, true); + if (!assignedPortal) + { + return false; + } + + FollowMasterStrategy followMasterStrategy(botAI); + if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT)) + { + botAI->ChangeStrategy(ADD_STRATEGY_CHAR + followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT); + } + + return assignedPortal->HandleSpellClick(bot); +} + +bool YoggSaronIllusionRoomAction::Execute(Event event) +{ + YoggSaronTrigger yoggSaronTrigger(botAI); + + bool resultSetRtiMark = SetRtiMark(yoggSaronTrigger); + bool resultSetIllusionRtiTarget = SetIllusionRtiTarget(yoggSaronTrigger); + bool resultSetBrainRtiTarget = SetBrainRtiTarget(yoggSaronTrigger); + + return resultSetRtiMark || resultSetIllusionRtiTarget || resultSetBrainRtiTarget; +} + +bool YoggSaronIllusionRoomAction::SetRtiMark(YoggSaronTrigger yoggSaronTrigger) +{ + if (AI_VALUE(std::string, "rti") == "diamond") + { + if (yoggSaronTrigger.IsInStormwindKeeperIllusion()) + { + botAI->GetAiObjectContext()->GetValue("rti")->Set("cross"); + return true; + } + else if (yoggSaronTrigger.IsInIcecrownKeeperIllusion()) + { + botAI->GetAiObjectContext()->GetValue("rti")->Set("circle"); + return true; + } + else if (yoggSaronTrigger.IsInChamberOfTheAspectsIllusion()) + { + botAI->GetAiObjectContext()->GetValue("rti")->Set("star"); + return true; + } + } + return false; +} + +bool YoggSaronIllusionRoomAction::SetIllusionRtiTarget(YoggSaronTrigger yoggSaronTrigger) +{ + Unit* currentRtiTarget = yoggSaronTrigger.GetIllusionRoomRtiTarget(); + if (currentRtiTarget) + { + return false; + } + + Unit* nextRtiTarget = yoggSaronTrigger.GetNextIllusionRoomRtiTarget(); + if (!nextRtiTarget) + { + return false; + } + + // If proper adds handling in illusion room will be implemented, then this can be removed + if (botAI->HasCheat(BotCheatMask::raid)) + { + bot->TeleportTo(bot->GetMapId(), nextRtiTarget->GetPositionX(), nextRtiTarget->GetPositionY(), + nextRtiTarget->GetPositionZ(), bot->GetOrientation()); + + Unit::DealDamage(bot->GetSession()->GetPlayer(), nextRtiTarget, nextRtiTarget->GetHealth(), nullptr, + DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false, true); + } + else + { + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + uint8 rtiIndex = RtiTargetValue::GetRtiIndex(AI_VALUE(std::string, "rti")); + group->SetTargetIcon(rtiIndex, bot->GetGUID(), nextRtiTarget->GetGUID()); + } + + return true; +} + +bool YoggSaronIllusionRoomAction::SetBrainRtiTarget(YoggSaronTrigger yoggSaronTrigger) +{ + if (AI_VALUE(std::string, "rti") == "square" || !yoggSaronTrigger.IsMasterIsInBrainRoom()) + { + return false; + } + + botAI->GetAiObjectContext()->GetValue("rti")->Set("square"); + + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + Creature* brain = bot->FindNearestCreature(NPC_BRAIN, 200.0f, true); + if (!brain) + { + return false; + } + + group->SetTargetIcon(RtiTargetValue::squareIndex, bot->GetGUID(), brain->GetGUID()); + + Position entrancePosition = yoggSaronTrigger.GetIllusionRoomEntrancePosition(); + + if (botAI->HasCheat(BotCheatMask::raid)) + { + if (Unit const* master = botAI->GetMaster()) + { + Position masterPosition = master->GetPosition(); + bot->TeleportTo(bot->GetMapId(), masterPosition.GetPositionX(), masterPosition.GetPositionY(), + masterPosition.GetPositionZ(), bot->GetOrientation()); + } + else + { + bot->TeleportTo(bot->GetMapId(), entrancePosition.GetPositionX(), entrancePosition.GetPositionY(), + entrancePosition.GetPositionZ(), bot->GetOrientation()); + } + } + else + { + MoveTo(bot->GetMapId(), entrancePosition.GetPositionX(), entrancePosition.GetPositionY(), + entrancePosition.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, + false); + } + + botAI->DoSpecificAction("attack rti target"); + return true; +} + +bool YoggSaronMoveToExitPortalAction::Execute(Event event) +{ + GameObject* portal = bot->FindNearestGameObject(GO_FLEE_TO_THE_SURFACE_PORTAL, 100.0f); + if (!portal) + { + return false; + } + + if (botAI->HasCheat(BotCheatMask::raid)) + { + bot->TeleportTo(bot->GetMapId(), portal->GetPositionX(), portal->GetPositionY(), portal->GetPositionZ(), + bot->GetOrientation()); + } + else + { + MoveTo(bot->GetMapId(), portal->GetPositionX(), portal->GetPositionY(), portal->GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_FORCED, + true, false); + } + + if (bot->GetDistance2d(portal) > 2.0f) + { + return false; + } + + portal->Use(bot); + + botAI->GetAiObjectContext()->GetValue("rti")->Set("skull"); + return true; +} + +bool YoggSaronLunaticGazeAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "yogg-saron"); + if (!boss || !boss->IsAlive()) + { + return false; + } + float angle = bot->GetAngle(boss); + float newAngle = Position::NormalizeOrientation(angle + M_PI); // Add 180 degrees (PI radians) + bot->SetFacingTo(newAngle); + + if (botAI->IsRangedDps(bot)) + { + if (AI_VALUE(std::string, "rti") != "cross") + { + botAI->GetAiObjectContext()->GetValue("rti")->Set("cross"); + } + } + + return true; +} + +bool YoggSaronPhase3PositioningAction::Execute(Event event) +{ + if (botAI->IsRanged(bot)) + { + if (botAI->HasCheat(BotCheatMask::raid)) + { + return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionX(), + ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionY(), + ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionZ(), + bot->GetOrientation()); + } + else + { + return MoveTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionX(), + ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionY(), + ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + } + } + + if (botAI->IsMelee(bot) && !botAI->IsTank(bot)) + { + if (botAI->HasCheat(BotCheatMask::raid)) + { + return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionZ(), bot->GetOrientation()); + } + else + { + return MoveTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + + if (botAI->IsTank(bot)) + { + if (bot->GetDistance(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT) > 30.0f) + { + if (botAI->HasCheat(BotCheatMask::raid)) + { + return bot->TeleportTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionZ(), bot->GetOrientation()); + } + } + + return MoveTo(bot->GetMapId(), ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.h b/src/strategy/raids/ulduar/RaidUlduarActions.h index 6fef3367..c3d5c0d6 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.h +++ b/src/strategy/raids/ulduar/RaidUlduarActions.h @@ -9,6 +9,7 @@ #include "PlayerbotAI.h" #include "Playerbots.h" #include "RaidUlduarBossHelper.h" +#include "RaidUlduarTriggers.h" #include "Vehicle.h" // @@ -378,4 +379,125 @@ public: bool Execute(Event event) override; }; +class YoggSaronOminousCloudCheatAction : public Action +{ +public: + YoggSaronOminousCloudCheatAction(PlayerbotAI* ai) : Action(ai, "yogg-saron ominous cloud cheat action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronGuardianPositioningAction : public MovementAction +{ +public: + YoggSaronGuardianPositioningAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron guardian positioning action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronSanityAction : public MovementAction +{ +public: + YoggSaronSanityAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron sanity action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronDeathOrbAction : public MoveAwayFromCreatureAction +{ +public: + YoggSaronDeathOrbAction(PlayerbotAI* ai) : MoveAwayFromCreatureAction(ai, "yogg-saron death orb action", NPC_DEATH_ORB, 10.0f) {} +}; + +class YoggSaronMaladyOfTheMindAction : public MoveAwayFromPlayerWithDebuffAction +{ +public: + YoggSaronMaladyOfTheMindAction(PlayerbotAI* ai) : MoveAwayFromPlayerWithDebuffAction(ai, "yogg-saron malady of the mind action", SPELL_MALADY_OF_THE_MIND, 15.0f) {} +}; + +class YoggSaronMarkTargetAction : public Action +{ +public: + YoggSaronMarkTargetAction(PlayerbotAI* ai) : Action(ai, "yogg-saron mark target action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronBrainLinkAction : public MovementAction +{ +public: + YoggSaronBrainLinkAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron brain link action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronMoveToEnterPortalAction : public MovementAction +{ +public: + YoggSaronMoveToEnterPortalAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron move to enter portal action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronFallFromFloorAction : public MovementAction +{ +public: + YoggSaronFallFromFloorAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron fall from floor action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronBossRoomMovementCheatAction : public MovementAction +{ +public: + YoggSaronBossRoomMovementCheatAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron boss room movement cheat action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronUsePortalAction : public Action +{ +public: + YoggSaronUsePortalAction(PlayerbotAI* ai) : Action(ai, "yogg-saron use portal action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronIllusionRoomAction : public MovementAction +{ +public: + YoggSaronIllusionRoomAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron illusion room action") {} + + bool Execute(Event event) override; + +private: + bool SetRtiMark(YoggSaronTrigger yoggSaronTrigger); + bool SetIllusionRtiTarget(YoggSaronTrigger yoggSaronTrigger); + bool SetBrainRtiTarget(YoggSaronTrigger yoggSaronTrigger); +}; + +class YoggSaronMoveToExitPortalAction : public MovementAction +{ +public: + YoggSaronMoveToExitPortalAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron move to exit portal action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronLunaticGazeAction : public MovementAction +{ +public: + YoggSaronLunaticGazeAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron lunatic gaze action") {} + + bool Execute(Event event) override; +}; + +class YoggSaronPhase3PositioningAction : public MovementAction +{ +public: + YoggSaronPhase3PositioningAction(PlayerbotAI* ai) : MovementAction(ai, "yogg-saron phase 3 positioning action") {} + + bool Execute(Event event) override; +}; + #endif diff --git a/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp b/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp index 1b08fa7a..417faaa1 100644 --- a/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp @@ -255,6 +255,66 @@ void RaidUlduarStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode( "yogg-saron shadow resistance trigger", NextAction::array(0, new NextAction("yogg-saron shadow resistance action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron ominous cloud cheat trigger", + NextAction::array(0, new NextAction("yogg-saron ominous cloud cheat action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron guardian positioning trigger", + NextAction::array(0, new NextAction("yogg-saron guardian positioning action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron sanity trigger", + NextAction::array(0, new NextAction("yogg-saron sanity action", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron death orb trigger", + NextAction::array(0, new NextAction("yogg-saron death orb action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron malady of the mind trigger", + NextAction::array(0, new NextAction("yogg-saron malady of the mind action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron mark target trigger", + NextAction::array(0, new NextAction("yogg-saron mark target action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron brain link trigger", + NextAction::array(0, new NextAction("yogg-saron brain link action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron move to enter portal trigger", + NextAction::array(0, new NextAction("yogg-saron move to enter portal action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron use portal trigger", + NextAction::array(0, new NextAction("yogg-saron use portal action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron fall from floor trigger", + NextAction::array(0, new NextAction("yogg-saron fall from floor action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron boss room movement cheat trigger", + NextAction::array(0, new NextAction("yogg-saron boss room movement cheat action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron illusion room trigger", + NextAction::array(0, new NextAction("yogg-saron illusion room action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron move to exit portal trigger", + NextAction::array(0, new NextAction("yogg-saron move to exit portal action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron lunatic gaze trigger", + NextAction::array(0, new NextAction("yogg-saron lunatic gaze action", ACTION_EMERGENCY), nullptr))); + + triggers.push_back(new TriggerNode( + "yogg-saron phase 3 positioning trigger", + NextAction::array(0, new NextAction("yogg-saron phase 3 positioning action", ACTION_RAID), nullptr))); } void RaidUlduarStrategy::InitMultipliers(std::vector& multipliers) diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h b/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h index a412748b..b3c7f60a 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h +++ b/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h @@ -67,6 +67,24 @@ public: creators["vezax cheat trigger"] = &RaidUlduarTriggerContext::vezax_cheat_trigger; creators["vezax shadow crash trigger"] = &RaidUlduarTriggerContext::vezax_shadow_crash_trigger; creators["vezax mark of the faceless trigger"] = &RaidUlduarTriggerContext::vezax_mark_of_the_faceless_trigger; + creators["vezax shadow resistance trigger"] = &RaidUlduarTriggerContext::vezax_shadow_resistance_trigger; + creators["sara shadow resistance trigger"] = &RaidUlduarTriggerContext::sara_shadow_resistance_trigger; + creators["yogg-saron shadow resistance triggerr"] = &RaidUlduarTriggerContext::yogg_saron_shadow_resistance_trigger; + creators["yogg-saron ominous cloud cheat trigger"] = &RaidUlduarTriggerContext::yogg_saron_ominous_cloud_cheat_trigger; + creators["yogg-saron guardian positioning trigger"] = &RaidUlduarTriggerContext::yogg_saron_guardian_positioning_trigger; + creators["yogg-saron sanity trigger"] = &RaidUlduarTriggerContext::yogg_saron_sanity_trigger; + creators["yogg-saron death orb trigger"] = &RaidUlduarTriggerContext::yogg_saron_death_orb_trigger; + creators["yogg-saron malady of the mind trigger"] = &RaidUlduarTriggerContext::yogg_saron_malady_of_the_mind_trigger; + creators["yogg-saron mark target trigger"] = &RaidUlduarTriggerContext::yogg_saron_mark_target_trigger; + creators["yogg-saron brain link trigger"] = &RaidUlduarTriggerContext::yogg_saron_brain_link_trigger; + creators["yogg-saron move to enter portal trigger"] = &RaidUlduarTriggerContext::yogg_saron_move_to_enter_portal_trigger; + creators["yogg-saron use portal trigger"] = &RaidUlduarTriggerContext::yogg_saron_use_portal_trigger; + creators["yogg-saron fall from floor trigger"] = &RaidUlduarTriggerContext::yogg_saron_fall_from_floor_trigger; + creators["yogg-saron boss room movement cheat trigger"] = &RaidUlduarTriggerContext::yogg_saron_boss_room_movement_cheat_trigger; + creators["yogg-saron illusion room trigger"] = &RaidUlduarTriggerContext::yogg_saron_illusion_room_trigger; + creators["yogg-saron move to exit portal trigger"] = &RaidUlduarTriggerContext::yogg_saron_move_to_exit_portal_trigger; + creators["yogg-saron lunatic gaze trigger"] = &RaidUlduarTriggerContext::yogg_saron_lunatic_gaze_trigger; + creators["yogg-saron phase 3 positioning trigger"] = &RaidUlduarTriggerContext::yogg_saron_phase_3_positioning_trigger; } private: @@ -120,7 +138,25 @@ private: static Trigger* mimiron_cheat_trigger(PlayerbotAI* ai) { return new MimironCheatTrigger(ai); } static Trigger* vezax_cheat_trigger(PlayerbotAI* ai) { return new VezaxCheatTrigger(ai); } static Trigger* vezax_shadow_crash_trigger(PlayerbotAI* ai) { return new VezaxShadowCrashTrigger(ai); } + static Trigger* vezax_shadow_resistance_trigger(PlayerbotAI* ai) { return new BossShadowResistanceTrigger(ai, "general vezax"); } + static Trigger* sara_shadow_resistance_trigger(PlayerbotAI* ai) { return new BossShadowResistanceTrigger(ai, "sara"); } + static Trigger* yogg_saron_shadow_resistance_trigger(PlayerbotAI* ai) { return new BossShadowResistanceTrigger(ai, "yogg-saron"); } static Trigger* vezax_mark_of_the_faceless_trigger(PlayerbotAI* ai) { return new VezaxMarkOfTheFacelessTrigger(ai); } + static Trigger* yogg_saron_ominous_cloud_cheat_trigger(PlayerbotAI* ai) { return new YoggSaronOminousCloudCheatTrigger(ai); } + static Trigger* yogg_saron_guardian_positioning_trigger(PlayerbotAI* ai) { return new YoggSaronGuardianPositioningTrigger(ai); } + static Trigger* yogg_saron_sanity_trigger(PlayerbotAI* ai) { return new YoggSaronSanityTrigger(ai); } + static Trigger* yogg_saron_death_orb_trigger(PlayerbotAI* ai) { return new YoggSaronDeathOrbTrigger(ai); } + static Trigger* yogg_saron_malady_of_the_mind_trigger(PlayerbotAI* ai) { return new YoggSaronMaladyOfTheMindTrigger(ai); } + static Trigger* yogg_saron_mark_target_trigger(PlayerbotAI* ai) { return new YoggSaronMarkTargetTrigger(ai); } + static Trigger* yogg_saron_brain_link_trigger(PlayerbotAI* ai) { return new YoggSaronBrainLinkTrigger(ai); } + static Trigger* yogg_saron_move_to_enter_portal_trigger(PlayerbotAI* ai) { return new YoggSaronMoveToEnterPortalTrigger(ai); } + static Trigger* yogg_saron_use_portal_trigger(PlayerbotAI* ai) { return new YoggSaronUsePortalTrigger(ai); } + static Trigger* yogg_saron_fall_from_floor_trigger(PlayerbotAI* ai) { return new YoggSaronFallFromFloorTrigger(ai); } + static Trigger* yogg_saron_boss_room_movement_cheat_trigger(PlayerbotAI* ai) { return new YoggSaronBossRoomMovementCheatTrigger(ai); } + static Trigger* yogg_saron_illusion_room_trigger(PlayerbotAI* ai) { return new YoggSaronIllusionRoomTrigger(ai); } + static Trigger* yogg_saron_move_to_exit_portal_trigger(PlayerbotAI* ai) { return new YoggSaronMoveToExitPortalTrigger(ai); } + static Trigger* yogg_saron_lunatic_gaze_trigger(PlayerbotAI* ai) { return new YoggSaronLunaticGazeTrigger(ai); } + static Trigger* yogg_saron_phase_3_positioning_trigger(PlayerbotAI* ai) { return new YoggSaronPhase3PositioningTrigger(ai); } }; #endif diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp index 7e726eef..ca0c5fbb 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp @@ -12,11 +12,33 @@ #include "Trigger.h" #include "Vehicle.h" #include +#include +#include const std::vector availableVehicles = {NPC_VEHICLE_CHOPPER, NPC_SALVAGED_DEMOLISHER, NPC_SALVAGED_DEMOLISHER_TURRET, NPC_SALVAGED_SIEGE_ENGINE, NPC_SALVAGED_SIEGE_ENGINE_TURRET}; +const std::vector illusionMobs = +{ + NPC_INFLUENCE_TENTACLE, + NPC_RUBY_CONSORT, + NPC_AZURE_CONSORT, + NPC_BRONZE_CONSORT, + NPC_EMERALD_CONSORT, + NPC_OBSIDIAN_CONSORT, + NPC_ALEXTRASZA, + NPC_MALYGOS_ILLUSION, + NPC_NELTHARION, + NPC_YSERA, + NPC_DEATHSWORN_ZEALOT, + NPC_LICH_KING_ILLUSION, + NPC_IMMOLATED_CHAMPION, + NPC_SUIT_OF_ARMOR, + NPC_GARONA, + NPC_KING_LLANE +}; + bool FlameLeviathanOnVehicleTrigger::IsActive() { Unit* vehicleBase = bot->GetVehicleBase(); @@ -448,8 +470,8 @@ bool KologarnAttackDpsTargetTrigger::IsActive() if (!group) return false; - ObjectGuid skullTarget = group->GetTargetIcon(skullIndex); - ObjectGuid crossTarget = group->GetTargetIcon(crossIndex); + ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); + ObjectGuid crossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex); if (crossTarget && (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))) { @@ -745,7 +767,7 @@ bool ThorimUnbalancingStrikeTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); // Check boss and it is alive - if (!boss || !boss->IsAlive()) + if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot)) return false; return bot->HasAura(SPELL_UNBALANCING_STRIKE); @@ -762,7 +784,7 @@ bool ThorimMarkDpsTargetTrigger::IsActive() if (botAI->IsMainTank(bot)) { - ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex); + ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); if (currentSkullUnit && !currentSkullUnit->IsAlive()) { @@ -783,13 +805,13 @@ bool ThorimMarkDpsTargetTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); // Check boss and it is alive - if (!boss || !boss->IsAlive()) + if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot)) return false; if (boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD && (!currentSkullUnit || !currentSkullUnit->IsAlive())) { - group->SetTargetIcon(skullIndex, bot->GetGUID(), boss->GetGUID()); + group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID()); return true; } @@ -811,7 +833,7 @@ bool ThorimMarkDpsTargetTrigger::IsActive() if (mainTank && bot->GetDistance(mainTank) < 30.0f) return false; - ObjectGuid currentCrossTarget = group->GetTargetIcon(crossIndex); + ObjectGuid currentCrossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex); Unit* currentCrossUnit = botAI->GetUnit(currentCrossTarget); if (currentCrossUnit && !currentCrossUnit->IsAlive()) { @@ -963,7 +985,7 @@ bool ThorimArenaPositioningTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); // Check boss and it is alive - if (!boss || !boss->IsAlive()) + if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot)) return false; if (boss->GetPositionZ() < ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD) @@ -1061,7 +1083,7 @@ bool ThorimPhase2PositioningTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "thorim"); // Check boss and it is alive - if (!boss || !boss->IsAlive()) + if (!boss || !boss->IsAlive() || !boss->IsHostileTo(bot)) return false; if (boss->GetPositionZ() > ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD) @@ -1408,8 +1430,8 @@ bool MimironAerialCommandUnitTrigger::IsActive() return false; } - ObjectGuid skullTarget = group->GetTargetIcon(skullIndex); - ObjectGuid crossTarget = group->GetTargetIcon(crossIndex); + ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); + ObjectGuid crossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex); //if (bombBot && bombBot->GetGUID() != crossTarget) //{ @@ -1515,7 +1537,7 @@ bool MimironPhase4MarkDpsTrigger::IsActive() highestHealthUnit = aerialCommandUnit; } - ObjectGuid skullTarget = group->GetTargetIcon(skullIndex); + ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); if (!skullTarget) { return true; @@ -1619,3 +1641,748 @@ bool VezaxMarkOfTheFacelessTrigger::IsActive() return distance > 2.0f; } + +Unit* YoggSaronTrigger::GetSaraIfAlive() +{ + Unit* sara = AI_VALUE2(Unit*, "find target", "sara"); + if (!sara || !sara->IsAlive()) + { + return nullptr; + } + return sara; +} + +bool YoggSaronTrigger::IsPhase2() +{ + Creature* target = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true); + + return target && target->IsAlive() && target->HasAura(SPELL_SHADOW_BARRIER); +} + +bool YoggSaronTrigger::IsPhase3() +{ + Creature* target = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true); + Creature* guardian = bot->FindNearestCreature(NPC_GUARDIAN_OF_YS, 200.0f, true); + + return target && target->IsAlive() && !target->HasAura(SPELL_SHADOW_BARRIER) && !guardian; +} + +bool YoggSaronTrigger::IsInBrainLevel() +{ + return bot->GetPositionZ() > 230.0f && bot->GetPositionZ() < 250.0f; +} + +bool YoggSaronTrigger::IsYoggSaronFight() +{ + Unit* sara = AI_VALUE2(Unit*, "find target", "sara"); + Unit* yoggsaron = AI_VALUE2(Unit*, "find target", "yogg-saron"); + + if ((sara && sara->IsAlive()) || (yoggsaron && yoggsaron->IsAlive())) + { + return true; + } +} + +bool YoggSaronTrigger::IsInIllusionRoom() +{ + if (!IsInBrainLevel()) + { + return false; + } + + if (IsInStormwindKeeperIllusion()) + { + return true; + } + + if (IsInIcecrownKeeperIllusion()) + { + return true; + } + + if (IsInChamberOfTheAspectsIllusion()) + { + return true; + } + + return false; +} + +bool YoggSaronTrigger::IsInStormwindKeeperIllusion() +{ + return bot->GetDistance2d(ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionX(), + ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE.GetPositionY()) < + ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS; +} + +bool YoggSaronTrigger::IsInIcecrownKeeperIllusion() +{ + return bot->GetDistance2d(ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionX(), + ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE.GetPositionY()) < + ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS; +} + +bool YoggSaronTrigger::IsInChamberOfTheAspectsIllusion() +{ + return bot->GetDistance2d(ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionX(), + ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE.GetPositionY()) < + ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS; +} + +bool YoggSaronTrigger::IsMasterIsInIllusionGroup() +{ + Player* master = botAI->GetMaster(); + return master && !botAI->IsTank(master); +} + +bool YoggSaronTrigger::IsMasterIsInBrainRoom() +{ + Player* master = botAI->GetMaster(); + + if (!master) + { + return false; + } + + return master->GetDistance2d(ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE.GetPositionX(), + ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE.GetPositionY()) < + ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS && + master->GetPositionZ() > 230.0f && master->GetPositionZ() < 250.0f; +} + +Position YoggSaronTrigger::GetIllusionRoomEntrancePosition() +{ + if (IsInChamberOfTheAspectsIllusion()) + { + return ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE; + } + else if (IsInIcecrownKeeperIllusion()) + { + return ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE; + } + else if (IsInStormwindKeeperIllusion()) + { + return ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE; + } + else + { + return Position(); + } +} + +Unit* YoggSaronTrigger::GetIllusionRoomRtiTarget() +{ + Group* group = bot->GetGroup(); + if (!group) + { + return nullptr; + } + + uint8 rtiIndex = RtiTargetValue::GetRtiIndex(AI_VALUE(std::string, "rti")); + if (rtiIndex == -1) + { + return nullptr; // Invalid RTI mark + } + + ObjectGuid currentRtiTarget = group->GetTargetIcon(rtiIndex); + Unit* currentRtiTargetUnit = botAI->GetUnit(currentRtiTarget); + if (!currentRtiTargetUnit || !currentRtiTargetUnit->IsAlive()) + { + currentRtiTargetUnit = nullptr; + } + + return currentRtiTargetUnit; +} + +Unit* YoggSaronTrigger::GetNextIllusionRoomRtiTarget() +{ + float detectionRadius = 0.0f; + if (IsInStormwindKeeperIllusion()) + { + detectionRadius = ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS; + } + else if (IsInIcecrownKeeperIllusion()) + { + detectionRadius = ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS; + } + else if (IsInChamberOfTheAspectsIllusion()) + { + detectionRadius = ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS; + } + else + { + return nullptr; + } + + GuidVector targets = AI_VALUE(GuidVector, "nearest npcs"); + + if (botAI->HasCheat(BotCheatMask::raid)) + { + for (const ObjectGuid& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_LAUGHING_SKULL) + { + return unit; + } + } + } + + float nearestDistance = std::numeric_limits::max(); + Unit* nextIllusionRoomRtiTarget = nullptr; + + for (const uint32& creatureId : illusionMobs) + { + for (const ObjectGuid& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == creatureId) + { + float distance = bot->GetDistance(unit); + if (distance < nearestDistance) + { + nextIllusionRoomRtiTarget = unit; + nearestDistance = distance; + } + } + } + } + + if (nextIllusionRoomRtiTarget) + { + return nextIllusionRoomRtiTarget; + } + + if (IsInStormwindKeeperIllusion()) + { + Creature* target = bot->FindNearestCreature(NPC_SUIT_OF_ARMOR, detectionRadius, true); + if (target) + { + return target; + } + } + + return nullptr; +} + +bool YoggSaronOminousCloudCheatTrigger::IsActive() +{ + if (!botAI->HasCheat(BotCheatMask::raid)) + { + return false; + } + + Unit* boss = GetSaraIfAlive(); + if (!boss) + { + return false; + } + + if (!botAI->IsBotMainTank(bot)) + { + return false; + } + + if (bot->GetDistance2d(boss->GetPositionX(), boss->GetPositionY()) > 50.0f) + { + return false; + } + + Creature* target = boss->FindNearestCreature(NPC_OMINOUS_CLOUD, 25.0f, true); + + return target; +} + +bool YoggSaronGuardianPositioningTrigger::IsActive() +{ + if (!GetSaraIfAlive()) + { + return false; + } + + if (!botAI->IsTank(bot)) + { + return false; + } + + GuidVector targets = AI_VALUE(GuidVector, "nearest npcs"); + bool thereIsAnyGuardian = false; + + for (const ObjectGuid& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + { + continue; + } + + if (unit->GetEntry() == NPC_GUARDIAN_OF_YS) + { + thereIsAnyGuardian = true; + ObjectGuid unitTargetGuid = unit->GetTarget(); + Player* targetedPlayer = botAI->GetPlayer(unitTargetGuid); + if (!targetedPlayer || !botAI->IsTank(targetedPlayer)) + { + return false; + } + } + } + + return thereIsAnyGuardian && + bot->GetDistance2d(ULDUAR_YOGG_SARON_MIDDLE.GetPositionX(), ULDUAR_YOGG_SARON_MIDDLE.GetPositionY()) > 1.0f; +} + +bool YoggSaronSanityTrigger::IsActive() +{ + Aura* sanityAura = bot->GetAura(SPELL_SANITY); + + if (!sanityAura) + { + return false; + } + + int sanityAuraStacks = sanityAura->GetStackAmount(); + + Creature* sanityWell = bot->FindNearestCreature(NPC_SANITY_WELL, 200.0f); + + if (!sanityWell) + { + return false; + } + + float distanceToSanityWell = bot->GetDistance(sanityWell); + + return (distanceToSanityWell >= 1.0f && sanityAuraStacks < 40) || + (distanceToSanityWell < 1.0f && sanityAuraStacks < 100); +} + +bool YoggSaronDeathOrbTrigger::IsActive() +{ + TooCloseToCreatureTrigger tooCloseToDeathOrbTrigger(botAI); + return IsPhase2() && tooCloseToDeathOrbTrigger.TooCloseToCreature(NPC_DEATH_ORB, 10.0f); +} + +bool YoggSaronMaladyOfTheMindTrigger::IsActive() +{ + TooCloseToPlayerWithDebuffTrigger tooCloseToPlayerWithDebuffTrigger(botAI); + return IsPhase2() && tooCloseToPlayerWithDebuffTrigger.TooCloseToPlayerWithDebuff(SPELL_MALADY_OF_THE_MIND, 15.0f) && botAI->CanMove(); +} + +bool YoggSaronMarkTargetTrigger::IsActive() +{ + if (!IsYoggSaronFight()) + { + return false; + } + + if (!botAI->IsBotMainTank(bot)) + { + return false; + } + + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + if (IsPhase2()) + { + ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex); + Creature* yogg_saron = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true); + if (!currentMoonTarget || currentMoonTarget != yogg_saron->GetGUID()) + { + return true; + } + + ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); + + Creature* nextPossibleTarget = bot->FindNearestCreature(NPC_CONSTRICTOR_TENTACLE, 200.0f, true); + if (!nextPossibleTarget) + { + nextPossibleTarget = bot->FindNearestCreature(NPC_CORRUPTOR_TENTACLE, 200.0f, true); + if (!nextPossibleTarget) + { + return false; + } + } + + if (currentSkullTarget) + { + Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); + + if (!currentSkullUnit) + { + return true; + } + + if (currentSkullUnit->IsAlive() && currentSkullUnit->GetGUID() == nextPossibleTarget->GetGUID()) + { + return false; + } + } + + return true; + } + else if (IsPhase3()) + { + ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); + Unit* currentSkullUnit = nullptr; + if (currentSkullTarget) + { + currentSkullUnit = botAI->GetUnit(currentSkullTarget); + } + + if (currentSkullUnit && + (currentSkullUnit->GetEntry() == NPC_IMMORTAL_GUARDIAN || + currentSkullUnit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN) && + currentSkullUnit->GetHealthPct() > 10) + { + return false; + } + + GuidVector targets = AI_VALUE(GuidVector, "nearest npcs"); + for (const ObjectGuid& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + { + continue; + } + + if ((unit->GetEntry() == NPC_IMMORTAL_GUARDIAN || unit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN) && + unit->GetHealthPct() > 10) + { + return true; + } + } + + if (!currentSkullUnit || currentSkullUnit->GetEntry() != NPC_YOGG_SARON) + { + return true; + } + + return false; + } + + return false; +} + +bool YoggSaronBrainLinkTrigger::IsActive() +{ + TooFarFromPlayerWithAuraTrigger tooFarFromPlayerWithAuraTrigger(botAI); + return IsPhase2() && bot->HasAura(SPELL_BRAIN_LINK) && + tooFarFromPlayerWithAuraTrigger.TooFarFromPlayerWithAura(SPELL_BRAIN_LINK, 20.0f, false); +} + +bool YoggSaronMoveToEnterPortalTrigger::IsActive() +{ + if (!IsPhase2()) + { + return false; + } + + Creature* portal = bot->FindNearestCreature(NPC_DESCEND_INTO_MADNESS, 100.0f, true); + if (!portal) + { + return false; + } + + if (bot->GetDistance2d(portal->GetPositionX(), portal->GetPositionY()) < 2.0f) + { + return false; + } + + if (AI_VALUE(std::string, "rti") != "skull") + { + return false; + } + + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + int brainRoomTeamCount = 10; + if (bot->GetRaidDifficulty() == Difficulty::RAID_DIFFICULTY_10MAN_NORMAL) + { + brainRoomTeamCount = 4; + } + + if (IsMasterIsInIllusionGroup()) + { + brainRoomTeamCount--; + } + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->GetSource(); + if (!member || !member->IsAlive() || botAI->IsTank(member)) + { + continue; + } + + if (member->GetGUID() == bot->GetGUID()) + { + return true; + } + + brainRoomTeamCount--; + if (brainRoomTeamCount == 0) + { + break; + } + } + + return false; +} + +bool YoggSaronFallFromFloorTrigger::IsActive() +{ + if (!IsYoggSaronFight()) + { + return false; + } + + std::string rtiMark = AI_VALUE(std::string, "rti"); + + if (rtiMark == "skull" && bot->GetPositionZ() < ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT) + { + return true; + } + if ((rtiMark == "cross" || rtiMark == "circle" || rtiMark == "star") && bot->GetPositionZ() < ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT) + { + return true; + } + + return false; +} + +bool YoggSaronBossRoomMovementCheatTrigger::IsActive() +{ + if (!IsYoggSaronFight() || !IsPhase2()) + { + return false; + } + + FollowMasterStrategy followMasterStrategy(botAI); + if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT)) + { + return true; + } + + if (!botAI->HasCheat(BotCheatMask::raid)) + { + return false; + } + + if (AI_VALUE(std::string, "rti") != "skull") + { + return false; + } + + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); + + if (!currentSkullTarget) + { + return false; + } + + Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); + + if (!currentSkullUnit || !currentSkullUnit->IsAlive() || bot->GetDistance2d(currentSkullUnit->GetPositionX(), currentSkullUnit->GetPositionY()) < 40.0f) + { + return false; + } + + return true; +} + +bool YoggSaronUsePortalTrigger::IsActive() +{ + if (!IsPhase2()) + { + return false; + } + + if (AI_VALUE(std::string, "rti") != "diamond") + { + return false; + } + + return bot->FindNearestCreature(NPC_DESCEND_INTO_MADNESS, 2.0f, true) != nullptr; +} + +bool YoggSaronIllusionRoomTrigger::IsActive() +{ + if (!IsYoggSaronFight() || !IsInIllusionRoom() || AI_VALUE(std::string, "rti") == "square") + { + return false; + } + + if (SetRtiMarkRequired()) + { + return true; + } + + if (SetRtiTargetRequired()) + { + return true; + } + + if (GoToBrainRoomRequired()) + { + return true; + } + + return false; +} + +bool YoggSaronIllusionRoomTrigger::GoToBrainRoomRequired() +{ + if (AI_VALUE(std::string, "rti") == "square") + { + return false; + } + + return IsMasterIsInBrainRoom(); +} + +bool YoggSaronIllusionRoomTrigger::SetRtiMarkRequired() +{ + return AI_VALUE(std::string, "rti") == "diamond"; +} + +bool YoggSaronIllusionRoomTrigger::SetRtiTargetRequired() +{ + Unit const* currentRtiTarget = GetIllusionRoomRtiTarget(); + if (currentRtiTarget != nullptr) + { + return false; + } + + return GetNextIllusionRoomRtiTarget() != nullptr; +} + +bool YoggSaronMoveToExitPortalTrigger::IsActive() +{ + if (!IsYoggSaronFight() || !IsInBrainLevel()) + { + return false; + } + + Creature const* brain = bot->FindNearestCreature(NPC_BRAIN, 60.0f, true); + if (!brain || !brain->IsAlive()) + { + return false; + } + + if (brain->HasUnitState(UNIT_STATE_CASTING)) + { + Spell* induceMadnessSpell = brain->GetCurrentSpell(CURRENT_GENERIC_SPELL); + + if (induceMadnessSpell && induceMadnessSpell->m_spellInfo->Id == SPELL_INDUCE_MADNESS) + { + uint32 castingTimeLeft = induceMadnessSpell->GetCastTimeRemaining(); + if ((botAI->HasCheat(BotCheatMask::raid) && castingTimeLeft < 6000) || + (!botAI->HasCheat(BotCheatMask::raid) && castingTimeLeft < 8000)) + { + return true; + } + } + } + else if (brain->GetHealth() < brain->GetMaxHealth() * 0.3f) + { + return true; + } + + return false; +} + +bool YoggSaronLunaticGazeTrigger::IsActive() +{ + Unit* yoggsaron = AI_VALUE2(Unit*, "find target", "yogg-saron"); + + if (yoggsaron && yoggsaron->IsAlive() && yoggsaron->HasUnitState(UNIT_STATE_CASTING)) + { + Spell* currentSpell = yoggsaron->GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (currentSpell && currentSpell->m_spellInfo->Id == SPELL_LUNATIC_GAZE_YS) + { + return true; + } + } + + return false; +} + +bool YoggSaronPhase3PositioningTrigger::IsActive() +{ + if (!IsYoggSaronFight() || !IsPhase3()) + { + return false; + } + + YoggSaronSanityTrigger sanityTrigger(botAI); + if (sanityTrigger.IsActive()) + { + return false; + } + + if (botAI->IsRanged(bot) && bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionX(), + ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionY()) > 15.0f) + { + return true; + } + + if (botAI->IsMelee(bot) && !botAI->IsTank(bot) && + bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY()) > 15.0f) + { + return true; + } + + if (botAI->IsTank(bot)) + { + if (bot->GetDistance(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT) > 30.0f) + { + return true; + } + + GuidVector targets = AI_VALUE(GuidVector, "nearest npcs"); + bool thereIsAnyGuardian = false; + + for (const ObjectGuid& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + { + continue; + } + + if (unit->GetEntry() == NPC_IMMORTAL_GUARDIAN || unit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN) + { + thereIsAnyGuardian = true; + ObjectGuid unitTargetGuid = unit->GetTarget(); + Player* targetedPlayer = botAI->GetPlayer(unitTargetGuid); + if (!targetedPlayer || !botAI->IsTank(targetedPlayer)) + { + return false; + } + } + } + + if (thereIsAnyGuardian && bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(), + ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionY()) > 3.0f) + { + return true; + } + } + + return false; +} diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggers.h b/src/strategy/raids/ulduar/RaidUlduarTriggers.h index 6ebf1a7a..9be88c2e 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggers.h +++ b/src/strategy/raids/ulduar/RaidUlduarTriggers.h @@ -18,7 +18,7 @@ enum UlduarIDs SPELL_OVERLOAD_25_MAN_2 = 61886, SPELL_RUNE_OF_POWER = 64320, - //Kologarn + // Kologarn NPC_RIGHT_ARM = 32934, NPC_RUBBLE = 33768, SPELL_CRUNCH_ARMOR = 64002, @@ -27,13 +27,13 @@ enum UlduarIDs SPELL_FOCUSED_EYEBEAM_10 = 63347, SPELL_FOCUSED_EYEBEAM_25_2 = 63976, SPELL_FOCUSED_EYEBEAM_25 = 63977, - + // Hodir NPC_SNOWPACKED_ICICLE = 33174, NPC_TOASTY_FIRE = 33342, SPELL_FLASH_FREEZE = 61968, SPELL_BITING_COLD_PLAYER_AURA = 62039, - + // Freya NPC_SNAPLASHER = 32916, NPC_STORM_LASHER = 32919, @@ -44,7 +44,7 @@ enum UlduarIDs NPC_EONARS_GIFT = 33228, GOBJECT_NATURE_BOMB = 194902, - //Thorim + // Thorim NPC_DARK_RUNE_ACOLYTE_I = 32886, NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885, NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883, @@ -62,7 +62,7 @@ enum UlduarIDs NPC_IRON_HONOR_GUARD = 32875, SPELL_UNBALANCING_STRIKE = 62130, - //Mimiron + // Mimiron NPC_LEVIATHAN_MKII = 33432, NPC_VX001 = 33651, NPC_AERIAL_COMMAND_UNIT = 33670, @@ -78,23 +78,73 @@ enum UlduarIDs SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274, SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300, - //General Vezax + // General Vezax SPELL_MARK_OF_THE_FACELESS = 63276, SPELL_SHADOW_CRASH = 63277, + + // Yogg-Saron + ACTION_ILLUSION_DRAGONS = 1, + ACTION_ILLUSION_ICECROWN = 2, + ACTION_ILLUSION_STORMWIND = 3, + NPC_GUARDIAN_OF_YS = 33136, + NPC_YOGG_SARON = 33288, + NPC_OMINOUS_CLOUD = 33292, + NPC_RUBY_CONSORT = 33716, + NPC_AZURE_CONSORT = 33717, + NPC_BRONZE_CONSORT = 33718, + NPC_EMERALD_CONSORT = 33719, + NPC_OBSIDIAN_CONSORT = 33720, + NPC_ALEXTRASZA = 33536, + NPC_MALYGOS_ILLUSION = 33535, + NPC_NELTHARION = 33523, + NPC_YSERA = 33495, + GO_DRAGON_SOUL = 194462, + NPC_SARA_PHASE_1 = 33134, + NPC_LICH_KING_ILLUSION = 33441, + NPC_IMMOLATED_CHAMPION = 33442, + NPC_SUIT_OF_ARMOR = 33433, + NPC_GARONA = 33436, + NPC_KING_LLANE = 33437, + NPC_DEATHSWORN_ZEALOT = 33567, + NPC_INFLUENCE_TENTACLE = 33943, + NPC_DEATH_ORB = 33882, + NPC_BRAIN = 33890, + NPC_CRUSHER_TENTACLE = 33966, + NPC_CONSTRICTOR_TENTACLE = 33983, + NPC_CORRUPTOR_TENTACLE = 33985, + NPC_IMMORTAL_GUARDIAN = 33988, + NPC_LAUGHING_SKULL = 33990, + NPC_SANITY_WELL = 33991, + NPC_DESCEND_INTO_MADNESS = 34072, + NPC_MARKED_IMMORTAL_GUARDIAN = 36064, + SPELL_SANITY = 63050, + SPELL_BRAIN_LINK = 63802, + SPELL_MALADY_OF_THE_MIND = 63830, + SPELL_SHADOW_BARRIER = 63894, + SPELL_TELEPORT_TO_CHAMBER = 63997, + SPELL_TELEPORT_TO_ICECROWN = 63998, + SPELL_TELEPORT_TO_STORMWIND = 63989, + SPELL_TELEPORT_BACK = 63992, + SPELL_CANCEL_ILLUSION_AURA = 63993, + SPELL_INDUCE_MADNESS = 64059, + SPELL_LUNATIC_GAZE_YS = 64163, + GO_FLEE_TO_THE_SURFACE_PORTAL = 194625, // Buffs SPELL_FROST_TRAP = 13809 }; -const int8 skullIndex = 7; // Skull -const int8 crossIndex = 6; // Cross -const int8 moonIndex = 4; // Moon - const float ULDUAR_KOLOGARN_AXIS_Z_PATHING_ISSUE_DETECT = 420.0f; const float ULDUAR_KOLOGARN_EYEBEAM_RADIUS = 3.0f; const float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f; const float ULDUAR_THORIM_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f; const float ULDUAR_AURIAYA_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f; +const float ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 300.0f; +const float ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 200.0f; +const float ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS = 150.0f; +const float ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS = 150.0f; +const float ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS = 150.0f; +const float ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS = 50.0f; const Position ULDUAR_THORIM_NEAR_ARENA_CENTER = Position(2134.9854f, -263.11853f, 419.8465f); const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f); @@ -123,6 +173,16 @@ const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT = Position(2754.1294f, 2553 const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f); const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f); const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f); +const Position ULDUAR_YOGG_SARON_MIDDLE = Position(1980.28f, -25.5868f, 329.397f); +const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE = Position(1927.1511f, 68.507256f, 242.37657f); +const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE = Position(1925.6553f, -121.59296f, 239.98965f); +const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE = Position(2104.5667f, -25.509348f, 242.64679f); +const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE = Position(1980.1971f, -27.854689f, 236.06789f); +const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE = Position(1954.06f, 21.66f, 239.71f); +const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE = Position(1950.11f, -79.284f, 239.98982f); +const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE = Position(2048.63f, -25.5f, 239.72f); +const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT = Position(1998.5377f, -22.90317f, 324.8895f); +const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT = Position(2018.7628f, -18.896868f, 327.07245f); // // Flame Levi @@ -448,4 +508,139 @@ public: bool IsActive() override; }; +// +// Yogg-Saron +// +class YoggSaronTrigger : public Trigger +{ +public: + YoggSaronTrigger(PlayerbotAI* ai, std::string const name = "yogg saron trigger", int32 checkInteval = 1) + : Trigger(ai, name, checkInteval) {} + + bool IsYoggSaronFight(); + bool IsPhase2(); + bool IsPhase3(); + bool IsInBrainLevel(); + bool IsInIllusionRoom(); + bool IsInStormwindKeeperIllusion(); + bool IsInIcecrownKeeperIllusion(); + bool IsInChamberOfTheAspectsIllusion(); + bool IsMasterIsInIllusionGroup(); + bool IsMasterIsInBrainRoom(); + Position GetIllusionRoomEntrancePosition(); + Unit* GetIllusionRoomRtiTarget(); + Unit* GetNextIllusionRoomRtiTarget(); + Unit* GetSaraIfAlive(); +}; + +class YoggSaronOminousCloudCheatTrigger : public YoggSaronTrigger +{ +public: + YoggSaronOminousCloudCheatTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron ominous cloud cheat trigger") {} + bool IsActive() override; +}; + +class YoggSaronGuardianPositioningTrigger : public YoggSaronTrigger +{ +public: + YoggSaronGuardianPositioningTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron guardian positioning trigger") {} + bool IsActive() override; +}; + +class YoggSaronSanityTrigger : public YoggSaronTrigger +{ +public: + YoggSaronSanityTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron sanity trigger") {} + bool IsActive() override; +}; + +class YoggSaronDeathOrbTrigger : public YoggSaronTrigger +{ +public: + YoggSaronDeathOrbTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron death orb trigger") {} + bool IsActive() override; +}; + +class YoggSaronMaladyOfTheMindTrigger : public YoggSaronTrigger +{ +public: + YoggSaronMaladyOfTheMindTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron malady of the mind trigger") {} + bool IsActive() override; +}; + +class YoggSaronMarkTargetTrigger : public YoggSaronTrigger +{ +public: + YoggSaronMarkTargetTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron mark target trigger") {} + bool IsActive() override; +}; + +class YoggSaronBrainLinkTrigger : public YoggSaronTrigger +{ +public: + YoggSaronBrainLinkTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron brain link trigger") {} + bool IsActive() override; +}; + +class YoggSaronMoveToEnterPortalTrigger : public YoggSaronTrigger +{ +public: + YoggSaronMoveToEnterPortalTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron move to enter portal trigger") {} + bool IsActive() override; +}; + +class YoggSaronFallFromFloorTrigger : public YoggSaronTrigger +{ +public: + YoggSaronFallFromFloorTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron fall from floor trigger") {} + bool IsActive() override; +}; + +class YoggSaronBossRoomMovementCheatTrigger : public YoggSaronTrigger +{ +public: + YoggSaronBossRoomMovementCheatTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron boss room movement cheat trigger") {} + bool IsActive() override; +}; + +class YoggSaronUsePortalTrigger : public YoggSaronTrigger +{ +public: + YoggSaronUsePortalTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron use portal trigger") {} + bool IsActive() override; +}; + +class YoggSaronIllusionRoomTrigger : public YoggSaronTrigger +{ +public: + YoggSaronIllusionRoomTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron illusion room trigger") {} + bool IsActive() override; + +private: + bool GoToBrainRoomRequired(); + bool SetRtiMarkRequired(); + bool SetRtiTargetRequired(); +}; + +class YoggSaronMoveToExitPortalTrigger : public YoggSaronTrigger +{ +public: + YoggSaronMoveToExitPortalTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron move to exit portal trigger") {} + bool IsActive() override; +}; + +class YoggSaronLunaticGazeTrigger : public YoggSaronTrigger +{ +public: + YoggSaronLunaticGazeTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron lunatic gaze trigger") {} + bool IsActive() override; +}; + +class YoggSaronPhase3PositioningTrigger : public YoggSaronTrigger +{ +public: + YoggSaronPhase3PositioningTrigger(PlayerbotAI* ai) : YoggSaronTrigger(ai, "yogg-saron phase 3 positioning trigger") {} + bool IsActive() override; +}; + #endif diff --git a/src/strategy/triggers/BossAuraTriggers.cpp b/src/strategy/triggers/BossAuraTriggers.cpp index a91976d7..c1ce677a 100644 --- a/src/strategy/triggers/BossAuraTriggers.cpp +++ b/src/strategy/triggers/BossAuraTriggers.cpp @@ -15,7 +15,7 @@ bool BossFireResistanceTrigger::IsActive() { // Check boss and it is alive Unit* boss = AI_VALUE2(Unit*, "find target", bossName); - if (!boss || !boss->IsAlive()) + if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot)) return false; // Check if bot is paladin @@ -68,7 +68,7 @@ bool BossFrostResistanceTrigger::IsActive() { // Check boss and it is alive Unit* boss = AI_VALUE2(Unit*, "find target", bossName); - if (!boss || !boss->IsAlive()) + if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot)) return false; // Check if bot is paladin @@ -121,7 +121,7 @@ bool BossNatureResistanceTrigger::IsActive() { // Check boss and it is alive Unit* boss = AI_VALUE2(Unit*, "find target", bossName); - if (!boss || !boss->IsAlive()) + if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot)) return false; // Check if bot is alive @@ -176,7 +176,7 @@ bool BossShadowResistanceTrigger::IsActive() { // Check boss and it is alive Unit* boss = AI_VALUE2(Unit*, "find target", bossName); - if (!boss || !boss->IsAlive()) + if (!boss || !boss->IsAlive() || boss->IsFriendlyTo(bot)) return false; // Check if bot is paladin diff --git a/src/strategy/triggers/RangeTriggers.cpp b/src/strategy/triggers/RangeTriggers.cpp index 0095ba96..e75f81f6 100644 --- a/src/strategy/triggers/RangeTriggers.cpp +++ b/src/strategy/triggers/RangeTriggers.cpp @@ -214,3 +214,84 @@ bool FarFromMasterTrigger::IsActive() { return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), distance); } + +bool TooCloseToCreatureTrigger::TooCloseToCreature(uint32 creatureId, float range, bool alive) +{ + Creature* nearestCreature = bot->FindNearestCreature(creatureId, range, alive); + return nearestCreature != nullptr; +} + +bool TooCloseToPlayerWithDebuffTrigger::TooCloseToPlayerWithDebuff(uint32 spellId, float range) +{ + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + std::vector debuffedPlayers; + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->GetSource(); + if (player && player->IsAlive() && player->HasAura(spellId)) + { + debuffedPlayers.push_back(player); + } + } + + if (debuffedPlayers.empty()) + { + return false; + } + + for (Unit* debuffedPlayer : debuffedPlayers) + { + float dist = debuffedPlayer->GetExactDist2d(bot->GetPositionX(), bot->GetPositionY()); + if (dist < range) + { + return true; + } + } + + return false; +} + +bool TooFarFromPlayerWithAuraTrigger::TooFarFromPlayerWithAura(uint32 spellId, float range, bool selfInclude) +{ + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + std::vector debuffedPlayers; + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->GetSource(); + if (player && player->IsAlive() && player->HasAura(spellId) && + (selfInclude || (!selfInclude && player->GetGUID() != bot->GetGUID()))) + { + debuffedPlayers.push_back(player); + } + } + + return !debuffedPlayers.empty(); + + if (debuffedPlayers.empty()) + { + return false; + } + + for (Unit* debuffedPlayer : debuffedPlayers) + { + float dist = debuffedPlayer->GetExactDist2d(bot->GetPositionX(), bot->GetPositionY()); + if (dist > range) + { + return true; + } + } + + return false; +} diff --git a/src/strategy/triggers/RangeTriggers.h b/src/strategy/triggers/RangeTriggers.h index b975aca3..ed39a678 100644 --- a/src/strategy/triggers/RangeTriggers.h +++ b/src/strategy/triggers/RangeTriggers.h @@ -121,4 +121,28 @@ public: OutOfReactRangeTrigger(PlayerbotAI* botAI) : FarFromMasterTrigger(botAI, "out of react range", 50.0f, 5) {} }; +class TooCloseToCreatureTrigger : public Trigger +{ +public: + TooCloseToCreatureTrigger(PlayerbotAI* ai) : Trigger(ai, "too close to creature trigger") {} + + bool TooCloseToCreature(uint32 creatureId, float range, bool alive = true); +}; + +class TooCloseToPlayerWithDebuffTrigger : public Trigger +{ +public: + TooCloseToPlayerWithDebuffTrigger(PlayerbotAI* ai) : Trigger(ai, "too cloose to player with debuff trigger") {} + + bool TooCloseToPlayerWithDebuff(uint32 spellId, float range); +}; + +class TooFarFromPlayerWithAuraTrigger : public Trigger +{ +public: + TooFarFromPlayerWithAuraTrigger(PlayerbotAI* ai) : Trigger(ai, "too far from player with aura trigger") {} + + bool TooFarFromPlayerWithAura(uint32 spellId, float range, bool selfInclude = false); +}; + #endif diff --git a/src/strategy/values/RtiTargetValue.h b/src/strategy/values/RtiTargetValue.h index 94a8ac3d..d21aa701 100644 --- a/src/strategy/values/RtiTargetValue.h +++ b/src/strategy/values/RtiTargetValue.h @@ -21,6 +21,14 @@ public: static int32 GetRtiIndex(std::string const rti); Unit* Calculate() override; + static const int8 starIndex = 0; + static const int8 circleIndex = 1; + static const int8 diamondIndex = 2; + static const int8 triangleIndex = 3; + static const int8 moonIndex = 4; + static const int8 squareIndex = 5; + static const int8 crossIndex = 6; + static const int8 skullIndex = 7; private: std::string const type; diff --git a/src/strategy/values/ValueContext.h b/src/strategy/values/ValueContext.h index 2e735fc6..cb3a8ef3 100644 --- a/src/strategy/values/ValueContext.h +++ b/src/strategy/values/ValueContext.h @@ -556,7 +556,7 @@ private: static UntypedValue* last_flee_timestamp(PlayerbotAI* ai) { return new LastFleeTimestampValue(ai); } static UntypedValue* recently_flee_info(PlayerbotAI* ai) { return new RecentlyFleeInfo(ai); } // ------------------------------------------------------- - // Flag for cutom glyphs : true when /w bot glyph equip … + // Flag for cutom glyphs : true when /w bot glyph equip // ------------------------------------------------------- static UntypedValue* custom_glyphs(PlayerbotAI* ai) { From b661264c539228d804fe99cdc2ee683ae316d1a7 Mon Sep 17 00:00:00 2001 From: mtm84 Date: Mon, 18 Aug 2025 03:05:53 -0700 Subject: [PATCH 3/9] Update HunterActions.cpp (#1576) Removes compiler warning --- src/strategy/hunter/HunterActions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategy/hunter/HunterActions.cpp b/src/strategy/hunter/HunterActions.cpp index b0351cf6..90dd4352 100644 --- a/src/strategy/hunter/HunterActions.cpp +++ b/src/strategy/hunter/HunterActions.cpp @@ -40,7 +40,7 @@ bool CastArcaneShotAction::isUseful() return false; // Armor Penetration rating check - will not cast Arcane Shot above 435 ArP - int32 armorPenRating = bot->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_ARMOR_PENETRATION); + int32 armorPenRating = bot->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1) + bot->GetUInt32Value(CR_ARMOR_PENETRATION); if (armorPenRating > 435) return false; From 957a60cd1df20470448de71040d38ae8a240cbec Mon Sep 17 00:00:00 2001 From: HennyWilly <5954598+HennyWilly@users.noreply.github.com> Date: Wed, 20 Aug 2025 18:24:00 +0200 Subject: [PATCH 4/9] Ignore GameObject IDs from vanilla dungeons (#1518) * DisallowedGameObjects for vanilla dungeons * Bots ignore trap crates in Stratholme -> Remove * Updated AiPlayerbot.DisallowedGameObjects default value in source code * Removed duplicate * Added 123329 * Added 123329 and removed duplicated * Update playerbots.conf.dist 153464 - no reason to ignore it * 153464 - no reason to ignore it --------- Co-authored-by: bash <31279994+hermensbas@users.noreply.github.com> --- conf/playerbots.conf.dist | 8 +++++--- src/PlayerbotAIConfig.cpp | 10 +++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 51d57d9f..7419a97b 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1985,13 +1985,15 @@ AiPlayerbot.AllowedLogFiles = "" #################################################################################################### # A list of gameObject GUID's that are not allowed for bots to interact with. # -AiPlayerbot.DisallowedGameObjects = 176213,17155,2656,74448,19020,3719,3658,3705,3706,105579,75293,17155,2857,179490 +AiPlayerbot.DisallowedGameObjects = 176213,17155,2656,74448,19020,3719,3658,3705,3706,105579,75293,2857,179490,141596,160836,160845,179516,176224,181085,176112,128308,128403,165739,165738,175245,175970,176325,176327,123329 # # List of GUID's: # QuestItems: -# 176213 = Blood of Heroes, 17155 = Defias Gunpowder, 2656 = Waterlogged Envelope +# 176213 = Blood of Heroes, 17155 = Defias Gunpowder, 2656 = Waterlogged Envelope, 123329 = Baelogs Chest # Chests: -# Large Solid Chest = 74448, Box of Assorted Parts = 19020, Food Crate = 3719, Water Barrel = 3658, Barrel of Milk = 3705, Barrel of sweet Nectar = 3706, Tattered Chest = 105579, Large bettered Chest = 75293, Solid Chest = 2857, Battered Foodlocker = 179490 +# Large Solid Chest = 74448, Box of Assorted Parts = 19020, Food Crate = 3719, Water Barrel = 3658, Barrel of Milk = 3705, Barrel of sweet Nectar = 3706, Tattered Chest = 105579, Large bettered Chest = 75293, Solid Chest = 2857, Battered Foodlocker = 179490, Witch Doctor's Chest = 141596, Relic Coffer = 160836, Dark Coffer = 160845, Fengus's Chest = 179516, Supply Crate = 176224/181085, Malor's Strongbox = 176112 +# Other: +# Shallow Grave (Zul'Farrak) = 128308/128403, Grim Guzzler Boar (Blackrock Depths) = 165739, Dark Iron Ale Mug (Blackrock Depths) = 165738, Father Flame (Blackrock Spire) = 175245, Unforged Runic Breastplate (Blackrock Spire) = 175970, Blacksmithing Plans (Stratholme) = 176325/176327 # Feel free to edit and help to complete. # #################################################################################################### diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index e35d82e3..6c6aa322 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -157,8 +157,12 @@ bool PlayerbotAIConfig::Initialize() sConfigMgr->GetOption("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"), randomBotQuestIds); - LoadSet>(sConfigMgr->GetOption("AiPlayerbot.DisallowedGameObjects", "176213,17155"), - disallowedGameObjects); + LoadSet>( + sConfigMgr->GetOption("AiPlayerbot.DisallowedGameObjects", + "176213,17155,2656,74448,19020,3719,3658,3705,3706,105579,75293,2857," + "179490,141596,160836,160845,179516,176224,181085,176112,128308,128403," + "165739,165738,175245,175970,176325,176327,123329"), + disallowedGameObjects); botAutologin = sConfigMgr->GetOption("AiPlayerbot.BotAutologin", false); randomBotAutologin = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutologin", true); minRandomBots = sConfigMgr->GetOption("AiPlayerbot.MinRandomBots", 50); @@ -198,7 +202,7 @@ bool PlayerbotAIConfig::Initialize() "575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509," "531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"), restrictedHealerDPSMaps); - + //////////////////////////// ICC EnableICCBuffs = sConfigMgr->GetOption("AiPlayerbot.EnableICCBuffs", true); From c9b4cfa184f021abf38436a6c8739a458339891e Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Wed, 20 Aug 2025 20:13:45 +0200 Subject: [PATCH 5/9] [Revert] Threading leftover which belonged to other related PRs's (once green needs be merged) (#1583) * Revert "Correct side effects of merge f5ef5bd1c2039290be1b1108872d046e1e65ebe1 (#1512)" This reverts commit 966bf1d6afbf946ac92931b7d572fd76d79c56b0. * Revert "Fix ACCESS_VIOLATION in mod-playerbots: purge stale AIs, add thread-safety, and harden HasRealPlayerMaster (#1507)" This reverts commit f5ef5bd1c2039290be1b1108872d046e1e65ebe1. --- src/PlayerbotAI.cpp | 30 +++---------------- src/PlayerbotMgr.cpp | 71 +++++++------------------------------------- src/PlayerbotMgr.h | 26 ---------------- src/Playerbots.cpp | 4 --- 4 files changed, 14 insertions(+), 117 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 2b2bc89b..5dbe5cb9 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -4124,37 +4124,15 @@ bool IsAlliance(uint8 race) bool PlayerbotAI::HasRealPlayerMaster() { -// if (master) -// { -// PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master); -// return !masterBotAI || masterBotAI->IsRealPlayer(); -// } -// -// return false; - - // Removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) - /* 1) The "master" pointer can be null if the bot was created - without a master player or if the master was just removed. */ - if (!master) - return false; - - /* 2) Is the master player still present in the world? - If FindPlayer fails, we invalidate "master" and stop here. */ - if (!ObjectAccessor::FindPlayer(master->GetGUID())) + if (master) { - master = nullptr; // avoids repeating the check on the next tick - return false; + PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master); + return !masterBotAI || masterBotAI->IsRealPlayer(); } - /* 3) If the master is a bot, we check that it is itself controlled - by a real player. Otherwise, it's already a real player → true. */ - if (PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master)) - return masterBotAI->IsRealPlayer(); // bot controlled by a player? - - return true; // master = real player + return false; } - bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(master); } bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); } diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 0b6d3967..027e2bab 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -38,8 +38,6 @@ #include "WorldSessionMgr.h" #include "DatabaseEnv.h" // Added for gender choice #include // Added for gender choice -#include "Log.h" // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) -#include // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) class BotInitGuard { @@ -1728,70 +1726,21 @@ void PlayerbotsMgr::RemovePlayerBotData(ObjectGuid const& guid, bool is_AI) PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player) { - // if (!(sPlayerbotAIConfig->enabled) || !player) - // { + if (!(sPlayerbotAIConfig->enabled) || !player) + { + return nullptr; + } + // if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) { // return nullptr; // } - // // if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) { - // // return nullptr; - // // } - // auto itr = _playerbotsAIMap.find(player->GetGUID()); - // if (itr != _playerbotsAIMap.end()) - // { - // if (itr->second->IsBotAI()) - // return reinterpret_cast(itr->second); - // } - // - // return nullptr; - - // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) - if (!player || !sPlayerbotAIConfig->enabled) - return nullptr; - - // First read the GUID into a local variable, but ONLY after the check! - ObjectGuid guid = player->GetGUID(); // <-- OK here, we know that player != nullptr - { - std::shared_lock rlock(_aiMutex); - auto it = _playerbotsAIMap.find(guid); - if (it != _playerbotsAIMap.end() && it->second->IsBotAI()) - return static_cast(it->second); - } - - // Transient state: NEVER break the master ⇄ bots relationship here. - if (!ObjectAccessor::FindPlayer(guid)) + auto itr = _playerbotsAIMap.find(player->GetGUID()); + if (itr != _playerbotsAIMap.end()) { - RemovePlayerbotAI(guid, /*removeMgrEntry=*/false); - } - return nullptr; -} - -// removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) -PlayerbotAI* PlayerbotsMgr::GetPlayerbotAIByGuid(ObjectGuid guid) -{ - if (!sPlayerbotAIConfig->enabled) - return nullptr; - - std::shared_lock rlock(_aiMutex); - auto it = _playerbotsAIMap.find(guid); - if (it != _playerbotsAIMap.end() && it->second->IsBotAI()) - return static_cast(it->second); - return nullptr; -} - -void PlayerbotsMgr::RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntry /*= true*/) -{ - std::unique_lock wlock(_aiMutex); - - if (auto it = _playerbotsAIMap.find(guid); it != _playerbotsAIMap.end()) - { - delete it->second; - _playerbotsAIMap.erase(it); - LOG_DEBUG("playerbots", "Removed stale AI for GUID {}", - static_cast(guid.GetRawValue())); + if (itr->second->IsBotAI()) + return reinterpret_cast(itr->second); } - if (removeMgrEntry) - _playerbotsMgrMap.erase(guid); // we NO longer touch the relation in a "soft" purge + return nullptr; } PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player) diff --git a/src/PlayerbotMgr.h b/src/PlayerbotMgr.h index 17a49612..5ef31776 100644 --- a/src/PlayerbotMgr.h +++ b/src/PlayerbotMgr.h @@ -12,7 +12,6 @@ #include "PlayerbotAIBase.h" #include "QueryHolder.h" #include "QueryResult.h" -#include // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) class ChatHandler; class PlayerbotAI; @@ -115,38 +114,13 @@ public: void RemovePlayerBotData(ObjectGuid const& guid, bool is_AI); PlayerbotAI* GetPlayerbotAI(Player* player); - PlayerbotAI* GetPlayerbotAIByGuid(ObjectGuid guid); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) - // void RemovePlayerbotAI(ObjectGuid const& guid); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) - // removeMgrEntry = true => "hard" purge (AI + manager relation), for real logouts - // removeMgrEntry = false => "soft" purge (AI only), for detected "stale" cases - void RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntry = true); PlayerbotMgr* GetPlayerbotMgr(Player* player); private: std::unordered_map _playerbotsAIMap; std::unordered_map _playerbotsMgrMap; - mutable std::shared_mutex _aiMutex; // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) }; #define sPlayerbotsMgr PlayerbotsMgr::instance() -// Temporary addition If it keeps crashing, we will use them. -// Like -// BEFORE : PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); -// AFTER (safe) : PlayerbotAI* botAI = GET_PLAYERBOT_AI_SAFE(bot); -// BEFORE : if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player)) { ... } -// AFTER (safe) : if (PlayerbotAI* botAI = GET_PLAYERBOT_AI_SAFE(player)) { ... } -// --- SAFE helpers (append to PlayerbotMgr.h) --- -inline PlayerbotAI* GET_PLAYERBOT_AI_SAFE(Player* p) -{ - // Avoid any dereference during transient states (nullptr, teleport, flight, etc.) - return p ? sPlayerbotsMgr->GetPlayerbotAI(p) : nullptr; -} - -inline PlayerbotMgr* GET_PLAYERBOT_MGR_SAFE(Player* p) -{ - return p ? sPlayerbotsMgr->GetPlayerbotMgr(p) : nullptr; -} -// --- end SAFE helpers --- - #endif diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index 76b32efa..ed44a70f 100644 --- a/src/Playerbots.cpp +++ b/src/Playerbots.cpp @@ -377,10 +377,6 @@ public: void OnPlayerbotLogout(Player* player) override { - // immediate purge of the bot's AI upon disconnection - if (player && player->GetSession()->IsBot()) - sPlayerbotsMgr->RemovePlayerbotAI(player->GetGUID()); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) - if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player)) { PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); From 2dad8bf01df1d65f0a57639d67db94ff93ee293b Mon Sep 17 00:00:00 2001 From: Revision Date: Sun, 24 Aug 2025 18:18:00 +0200 Subject: [PATCH 6/9] Fixed a compiler warning (#1586) --- src/strategy/raids/ulduar/RaidUlduarTriggers.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp index ca0c5fbb..12a25965 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp @@ -1681,6 +1681,8 @@ bool YoggSaronTrigger::IsYoggSaronFight() { return true; } + + return false; } bool YoggSaronTrigger::IsInIllusionRoom() From 5f00b9bbd5a5f8fe5cd8223a8c1c6b9b472ba8ba Mon Sep 17 00:00:00 2001 From: NoxMax <50133316+NoxMax@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:25:19 -0600 Subject: [PATCH 7/9] Crash fix for RPG weights 0 (#1590) --- src/strategy/rpg/NewRpgBaseAction.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/strategy/rpg/NewRpgBaseAction.cpp b/src/strategy/rpg/NewRpgBaseAction.cpp index 5e71c5c5..9af540dc 100644 --- a/src/strategy/rpg/NewRpgBaseAction.cpp +++ b/src/strategy/rpg/NewRpgBaseAction.cpp @@ -1060,6 +1060,13 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector candidateSta probSum += sPlayerbotAIConfig->RpgStatusProbWeight[status]; } } + // Safety check. Default to "rest" if all RPG weights = 0 + if (availableStatus.empty() || probSum == 0) + { + botAI->rpgInfo.ChangeToRest(); + bot->SetStandState(UNIT_STAND_STATE_SIT); + return true; + } uint32 rand = urand(1, probSum); uint32 accumulate = 0; NewRpgStatus chosenStatus = RPG_STATUS_END; @@ -1218,4 +1225,4 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status) return false; } return false; -} \ No newline at end of file +} From 704e02e9ccd05022e5fe8a1ce2e7ca878b04bfac Mon Sep 17 00:00:00 2001 From: Crow Date: Tue, 26 Aug 2025 11:27:57 -0500 Subject: [PATCH 8/9] Add SMV area IDs to PvP Prohibited Areas (#1589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add SMV area IDs to PvP Prohibited Areas Sanctum of the Stars and the Altar of Sha’tar * Update source default pvpProhibitedAreaIDs * Update source default pvpProhibitedAreaIDs Now with Sanctum of the Stars & Altar of Sha'tar as well --- conf/playerbots.conf.dist | 2 +- src/PlayerbotAIConfig.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 7419a97b..b7694173 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1120,7 +1120,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0 AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951" # PvP Restricted Areas (bots don't pvp) -AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080" +AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754" # Improve reaction speeds in battlegrounds and arenas (may cause lag) AiPlayerbot.FastReactInBG = 1 diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 6c6aa322..5ee4a115 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -150,8 +150,10 @@ bool PlayerbotAIConfig::Initialize() "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565," "3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"), pvpProhibitedZoneIds); - LoadList>(sConfigMgr->GetOption("AiPlayerbot.PvpProhibitedAreaIds", "976,35"), - pvpProhibitedAreaIds); + LoadList>( + sConfigMgr->GetOption("AiPlayerbot.PvpProhibitedAreaIds", + "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"), + pvpProhibitedAreaIds); fastReactInBG = sConfigMgr->GetOption("AiPlayerbot.FastReactInBG", true); LoadList>( sConfigMgr->GetOption("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"), From bc737ecc68dfe7b46694f18d3a2c682c79536f5e Mon Sep 17 00:00:00 2001 From: kadeshar Date: Tue, 26 Aug 2025 18:28:42 +0200 Subject: [PATCH 9/9] - Changed standalone config on cheat (#1585) - Changed drink condition --- conf/playerbots.conf.dist | 7 ++----- src/PlayerbotAIConfig.cpp | 5 +++-- src/PlayerbotAIConfig.h | 4 ++-- src/factory/PlayerbotFactory.cpp | 2 +- src/strategy/actions/GiveItemAction.cpp | 4 ++-- src/strategy/actions/NonCombatActions.cpp | 10 +++++----- src/strategy/generic/UseFoodStrategy.cpp | 11 ++++++----- src/strategy/triggers/GenericTriggers.cpp | 4 ++-- 8 files changed, 23 insertions(+), 24 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index b7694173..4d9e89d9 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -174,10 +174,6 @@ AiPlayerbot.SummonWhenGroup = 1 # Selfbot permission level (0 = disabled, 1 = GM only (default), 2 = all players, 3 = activate on login) AiPlayerbot.SelfBotLevel = 1 -# Give free food to bots -# Default: 1 (enabled) -AiPlayerbot.FreeFood = 1 - # Non-GM player can only use init=auto to initialize bots based on their own level and gearscore # Default: 0 (non-GM player can use any intialization commands) AiPlayerbot.AutoInitOnly = 0 @@ -496,6 +492,7 @@ AiPlayerbot.AutoGearQualityLimit = 3 AiPlayerbot.AutoGearScoreLimit = 0 # Enable/Disable cheats for bots +# "food" (bots use cheat when eat or drink) # "gold" (bots have infinite gold) # "health" (bots have infinite health) # "mana" (bots have infinite mana) @@ -504,7 +501,7 @@ AiPlayerbot.AutoGearScoreLimit = 0 # "raid" (bots use cheats implemented into raid strategies) # To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi") # Default: taxi and raid are enabled -AiPlayerbot.BotCheats = "taxi,raid" +AiPlayerbot.BotCheats = "food,taxi,raid" # # diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 5ee4a115..f69cbf19 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -462,11 +462,13 @@ bool PlayerbotAIConfig::Initialize() } botCheats.clear(); - LoadListString>(sConfigMgr->GetOption("AiPlayerbot.BotCheats", "taxi,raid"), + LoadListString>(sConfigMgr->GetOption("AiPlayerbot.BotCheats", "food,taxi,raid"), botCheats); botCheatMask = 0; + if (std::find(botCheats.begin(), botCheats.end(), "food") != botCheats.end()) + botCheatMask |= (uint32)BotCheatMask::food; if (std::find(botCheats.begin(), botCheats.end(), "taxi") != botCheats.end()) botCheatMask |= (uint32)BotCheatMask::taxi; if (std::find(botCheats.begin(), botCheats.end(), "gold") != botCheats.end()) @@ -598,7 +600,6 @@ bool PlayerbotAIConfig::Initialize() RpgStatusProbWeight[RPG_REST] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.Rest", 5); syncLevelWithPlayers = sConfigMgr->GetOption("AiPlayerbot.SyncLevelWithPlayers", false); - freeFood = sConfigMgr->GetOption("AiPlayerbot.FreeFood", true); randomBotGroupNearby = sConfigMgr->GetOption("AiPlayerbot.RandomBotGroupNearby", true); // arena diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index a9a12fba..36faa98b 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -23,7 +23,8 @@ enum class BotCheatMask : uint32 mana = 8, power = 16, raid = 32, - maxMask = 64 + food = 64, + maxMask = 128 }; enum class HealingManaEfficiency : uint8 @@ -340,7 +341,6 @@ public: bool enableNewRpgStrategy; std::unordered_map RpgStatusProbWeight; bool syncLevelWithPlayers; - bool freeFood; bool autoLearnQuestSpells; bool autoTeleportForLevel; bool randomBotGroupNearby; diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index e239d449..b3ca0e9b 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -3244,7 +3244,7 @@ std::vector PlayerbotFactory::GetCurrentGemsCount() void PlayerbotFactory::InitFood() { - if (sPlayerbotAIConfig->freeFood) + if (!botAI->HasCheat(BotCheatMask::food)) { return; } diff --git a/src/strategy/actions/GiveItemAction.cpp b/src/strategy/actions/GiveItemAction.cpp index a68482c0..afdf19be 100644 --- a/src/strategy/actions/GiveItemAction.cpp +++ b/src/strategy/actions/GiveItemAction.cpp @@ -76,7 +76,7 @@ bool GiveFoodAction::isUseful() bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget()); - return !isRandomBot || (isRandomBot && !sPlayerbotAIConfig->freeFood); + return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food)); } Unit* GiveWaterAction::GetTarget() { return AI_VALUE(Unit*, "party member without water"); } @@ -88,5 +88,5 @@ bool GiveWaterAction::isUseful() bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget()); - return !isRandomBot || (isRandomBot && !sPlayerbotAIConfig->freeFood); + return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food)); } diff --git a/src/strategy/actions/NonCombatActions.cpp b/src/strategy/actions/NonCombatActions.cpp index 128e44ab..d3c49dec 100644 --- a/src/strategy/actions/NonCombatActions.cpp +++ b/src/strategy/actions/NonCombatActions.cpp @@ -17,7 +17,7 @@ bool DrinkAction::Execute(Event event) if (!hasMana) return false; - if (sPlayerbotAIConfig->freeFood) + if (botAI->HasCheat(BotCheatMask::food)) { // if (bot->IsNonMeleeSpellCast(true)) // return false; @@ -54,11 +54,11 @@ bool DrinkAction::Execute(Event event) return UseItemAction::Execute(event); } -bool DrinkAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < 85; } +bool DrinkAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < 100; } bool DrinkAction::isPossible() { - return !bot->IsInCombat() && (sPlayerbotAIConfig->freeFood || UseItemAction::isPossible()); + return !bot->IsInCombat() && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible()); } bool EatAction::Execute(Event event) @@ -66,7 +66,7 @@ bool EatAction::Execute(Event event) if (bot->IsInCombat()) return false; - if (sPlayerbotAIConfig->freeFood) + if (botAI->HasCheat(BotCheatMask::food)) { // if (bot->IsNonMeleeSpellCast(true)) // return false; @@ -107,5 +107,5 @@ bool EatAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8 bool EatAction::isPossible() { - return !bot->IsInCombat() && (sPlayerbotAIConfig->freeFood || UseItemAction::isPossible()); + return !bot->IsInCombat() && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible()); } diff --git a/src/strategy/generic/UseFoodStrategy.cpp b/src/strategy/generic/UseFoodStrategy.cpp index 04841e4a..e7f2049d 100644 --- a/src/strategy/generic/UseFoodStrategy.cpp +++ b/src/strategy/generic/UseFoodStrategy.cpp @@ -11,13 +11,14 @@ void UseFoodStrategy::InitTriggers(std::vector& triggers) { Strategy::InitTriggers(triggers); - if (sPlayerbotAIConfig->freeFood) + if (botAI->HasCheat(BotCheatMask::food)) + { triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("food", 3.0f), nullptr))); - else - triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("food", 3.0f), nullptr))); - - if (sPlayerbotAIConfig->freeFood) triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr))); + } else + { + triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("food", 3.0f), nullptr))); triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr))); + } } diff --git a/src/strategy/triggers/GenericTriggers.cpp b/src/strategy/triggers/GenericTriggers.cpp index 5f1935a9..2e0043c5 100644 --- a/src/strategy/triggers/GenericTriggers.cpp +++ b/src/strategy/triggers/GenericTriggers.cpp @@ -249,7 +249,7 @@ bool AoeTrigger::IsActive() bool NoFoodTrigger::IsActive() { bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot); - if (isRandomBot && sPlayerbotAIConfig->freeFood) + if (isRandomBot && botAI->HasCheat(BotCheatMask::food)) return false; return AI_VALUE2(std::vector, "inventory items", "conjured food").empty(); @@ -258,7 +258,7 @@ bool NoFoodTrigger::IsActive() bool NoDrinkTrigger::IsActive() { bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot); - if (isRandomBot && sPlayerbotAIConfig->freeFood) + if (isRandomBot && botAI->HasCheat(BotCheatMask::food)) return false; return AI_VALUE2(std::vector, "inventory items", "conjured water").empty();