From 4a79a46da5650ea2e6908d45296983ae6b661ef6 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 2 May 2026 21:18:54 +0200 Subject: [PATCH 01/63] Add argument "all" to "rep" command and new "emblems" command (#2035) ## Summary - restrict `reputation all` to a curated list of WotLK/BC/Classic faction IDs (filtered by team) - reuse a shared formatter for reputation lines - add an `emblems` chat command to report emblem counts ### Multibot will need a update image image image image --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- src/Ai/Base/Actions/TellEmblemsAction.cpp | 39 ++++++++ src/Ai/Base/Actions/TellEmblemsAction.h | 21 +++++ src/Ai/Base/Actions/TellReputationAction.cpp | 89 ++++++++++++++----- src/Ai/Base/Actions/TellReputationAction.h | 6 ++ src/Ai/Base/ChatActionContext.h | 3 + src/Ai/Base/ChatTriggerContext.h | 2 + .../Strategy/ChatCommandHandlerStrategy.cpp | 6 +- 7 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 src/Ai/Base/Actions/TellEmblemsAction.cpp create mode 100644 src/Ai/Base/Actions/TellEmblemsAction.h diff --git a/src/Ai/Base/Actions/TellEmblemsAction.cpp b/src/Ai/Base/Actions/TellEmblemsAction.cpp new file mode 100644 index 00000000000..4d69baa11b4 --- /dev/null +++ b/src/Ai/Base/Actions/TellEmblemsAction.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "TellEmblemsAction.h" + +#include + +#include "Event.h" +#include "Playerbots.h" + +bool TellEmblemsAction::Execute(Event /*event*/) +{ + static std::array const emblemIds = { + 29434, // Badge of Justice + 40752, // Emblem of Heroism + 40753, // Emblem of Valor + 45624, // Emblem of Conquest + 47241, // Emblem of Triumph + 49426 // Emblem of Frost + }; + + botAI->TellMaster("=== Emblems ==="); + + for (uint32 itemId : emblemIds) + { + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); + if (!proto) + continue; + + uint32 count = bot->GetItemCount(itemId, false); + std::ostringstream out; + out << chat->FormatItem(proto, count); + botAI->TellMaster(out); + } + + return true; +} diff --git a/src/Ai/Base/Actions/TellEmblemsAction.h b/src/Ai/Base/Actions/TellEmblemsAction.h new file mode 100644 index 00000000000..570fb2d045b --- /dev/null +++ b/src/Ai/Base/Actions/TellEmblemsAction.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_TELLEMBLEMSACTION_H +#define _PLAYERBOT_TELLEMBLEMSACTION_H + +#include "InventoryAction.h" + +class PlayerbotAI; + +class TellEmblemsAction : public InventoryAction +{ +public: + TellEmblemsAction(PlayerbotAI* botAI) : InventoryAction(botAI, "emblems") {} + + bool Execute(Event event) override; +}; + +#endif diff --git a/src/Ai/Base/Actions/TellReputationAction.cpp b/src/Ai/Base/Actions/TellReputationAction.cpp index 0ccff606a22..22c11e16529 100644 --- a/src/Ai/Base/Actions/TellReputationAction.cpp +++ b/src/Ai/Base/Actions/TellReputationAction.cpp @@ -5,34 +5,23 @@ #include "TellReputationAction.h" +#include + #include "Event.h" #include "PlayerbotAI.h" #include "ReputationMgr.h" -bool TellReputationAction::Execute(Event /*event*/) -{ - Player* master = GetMaster(); - if (!master) - return false; - - ObjectGuid selection = master->GetTarget(); - if (selection.IsEmpty()) - return false; +#include "SharedDefines.h" - Unit* unit = ObjectAccessor::GetUnit(*master, selection); - if (!unit) - return false; - - FactionTemplateEntry const* factionTemplate = unit->GetFactionTemplateEntry(); - uint32 faction = factionTemplate->faction; - FactionEntry const* entry = sFactionStore.LookupEntry(faction); - int32 reputation = bot->GetReputationMgr().GetReputation(faction); +std::string TellReputationAction::BuildReputationLine(FactionEntry const* entry) +{ + ReputationMgr& repMgr = bot->GetReputationMgr(); + ReputationRank rank = repMgr.GetRank(entry); + int32 reputation = repMgr.GetReputation(entry->ID); std::ostringstream out; - out << entry->name[0] << ": "; - out << "|cff"; + out << entry->name[0] << ": |cff"; - ReputationRank rank = bot->GetReputationMgr().GetRank(entry); switch (rank) { case REP_HATED: @@ -71,7 +60,65 @@ bool TellReputationAction::Execute(Event /*event*/) base -= ReputationMgr::PointsInRank[i]; out << " (" << (reputation - base) << "/" << ReputationMgr::PointsInRank[rank] << ")"; - botAI->TellMaster(out); + return out.str(); +} + +bool TellReputationAction::Execute(Event event) +{ + std::string const param = event.getParam(); + if (param == "all") + { + ReputationMgr& repMgr = bot->GetReputationMgr(); + std::vector lines; + + FactionStateList const& stateList = repMgr.GetStateList(); + lines.reserve(stateList.size()); + + for (auto const& itr : stateList) + { + FactionState const& faction = itr.second; + if (!(faction.Flags & FACTION_FLAG_VISIBLE)) + continue; + + if (faction.Flags & (FACTION_FLAG_HIDDEN | FACTION_FLAG_INVISIBLE_FORCED) && + !(faction.Flags & FACTION_FLAG_SPECIAL)) + continue; + + FactionEntry const* entry = sFactionStore.LookupEntry(faction.ID); + if (!entry) + continue; + + lines.push_back(BuildReputationLine(entry)); + } + + std::sort(lines.begin(), lines.end()); + + botAI->TellMaster("=== Reputations ==="); + for (auto const& line : lines) + botAI->TellMaster(line); + + return true; + } + + Player* master = GetMaster(); + if (!master) + return false; + + ObjectGuid selection = master->GetTarget(); + if (selection.IsEmpty()) + return false; + + Unit* unit = ObjectAccessor::GetUnit(*master, selection); + if (!unit) + return false; + + FactionTemplateEntry const* factionTemplate = unit->GetFactionTemplateEntry(); + + FactionEntry const* entry = sFactionStore.LookupEntry(factionTemplate->faction); + if (!entry) + return false; + + botAI->TellMaster(BuildReputationLine(entry)); return true; } diff --git a/src/Ai/Base/Actions/TellReputationAction.h b/src/Ai/Base/Actions/TellReputationAction.h index 3adaa66d568..d97d0d17774 100644 --- a/src/Ai/Base/Actions/TellReputationAction.h +++ b/src/Ai/Base/Actions/TellReputationAction.h @@ -6,8 +6,11 @@ #ifndef _PLAYERBOT_TELLREPUTATIONACTION_H #define _PLAYERBOT_TELLREPUTATIONACTION_H +#include + #include "Action.h" +struct FactionEntry; class PlayerbotAI; class TellReputationAction : public Action @@ -16,6 +19,9 @@ class TellReputationAction : public Action TellReputationAction(PlayerbotAI* botAI) : Action(botAI, "reputation") {} bool Execute(Event event) override; + +private: + std::string BuildReputationLine(FactionEntry const* entry); }; #endif diff --git a/src/Ai/Base/ChatActionContext.h b/src/Ai/Base/ChatActionContext.h index af51c23ae07..497ae2e9c13 100644 --- a/src/Ai/Base/ChatActionContext.h +++ b/src/Ai/Base/ChatActionContext.h @@ -66,6 +66,7 @@ #include "TaxiAction.h" #include "TeleportAction.h" #include "TellCastFailedAction.h" +#include "TellEmblemsAction.h" #include "TellItemCountAction.h" #include "TellLosAction.h" #include "TellReputationAction.h" @@ -120,6 +121,7 @@ class ChatActionContext : public NamedObjectContext creators["teleport"] = &ChatActionContext::teleport; creators["taxi"] = &ChatActionContext::taxi; creators["repair"] = &ChatActionContext::repair; + creators["emblems"] = &ChatActionContext::emblems; creators["use"] = &ChatActionContext::use; creators["item count"] = &ChatActionContext::item_count; creators["equip"] = &ChatActionContext::equip; @@ -276,6 +278,7 @@ class ChatActionContext : public NamedObjectContext static Action* item_count(PlayerbotAI* botAI) { return new TellItemCountAction(botAI); } static Action* use(PlayerbotAI* botAI) { return new UseItemAction(botAI); } static Action* repair(PlayerbotAI* botAI) { return new RepairAllAction(botAI); } + static Action* emblems(PlayerbotAI* botAI) { return new TellEmblemsAction(botAI); } static Action* taxi(PlayerbotAI* botAI) { return new TaxiAction(botAI); } static Action* teleport(PlayerbotAI* botAI) { return new TeleportAction(botAI); } static Action* release(PlayerbotAI* botAI) { return new ReleaseSpiritAction(botAI); } diff --git a/src/Ai/Base/ChatTriggerContext.h b/src/Ai/Base/ChatTriggerContext.h index 7742a9305be..ef8827c2927 100644 --- a/src/Ai/Base/ChatTriggerContext.h +++ b/src/Ai/Base/ChatTriggerContext.h @@ -41,6 +41,7 @@ class ChatTriggerContext : public NamedObjectContext creators["teleport"] = &ChatTriggerContext::teleport; creators["taxi"] = &ChatTriggerContext::taxi; creators["repair"] = &ChatTriggerContext::repair; + creators["emblems"] = &ChatTriggerContext::emblems; creators["u"] = &ChatTriggerContext::use; creators["use"] = &ChatTriggerContext::use; creators["c"] = &ChatTriggerContext::item_count; @@ -235,6 +236,7 @@ class ChatTriggerContext : public NamedObjectContext static Trigger* item_count(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "c"); } static Trigger* use(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "use"); } static Trigger* repair(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "repair"); } + static Trigger* emblems(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "emblems"); } static Trigger* taxi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "taxi"); } static Trigger* teleport(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "teleport"); } static Trigger* q(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "q"); } diff --git a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp index 8d5449ef3bb..2e4503c18e9 100644 --- a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp +++ b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp @@ -114,6 +114,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector& trigger triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) })); triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) })); triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) })); + triggers.push_back(new TriggerNode("emblems", { NextAction("emblems", relevance) })); } ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) @@ -138,6 +139,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("teleport"); supported.push_back("taxi"); supported.push_back("repair"); + supported.push_back("emblems"); supported.push_back("talents"); supported.push_back("spells"); supported.push_back("co"); @@ -202,8 +204,8 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("unlock items"); supported.push_back("unlock traded item"); supported.push_back("tame"); - supported.push_back("glyphs"); // Added for custom Glyphs - supported.push_back("glyph equip"); // Added for custom Glyphs + supported.push_back("glyphs"); + supported.push_back("glyph equip"); supported.push_back("pet"); supported.push_back("pet attack"); supported.push_back("wait for attack time"); From 410ce134fe1e09ac4b76a4bfd4699957db20a2ea Mon Sep 17 00:00:00 2001 From: HennyWilly <5954598+HennyWilly@users.noreply.github.com> Date: Sat, 2 May 2026 21:19:11 +0200 Subject: [PATCH 02/63] Fix Deep Breath issues during Onyxia encounter (#2318) ## Pull Request Description The current strategy for Onyxia causes bots to get hit by her breath attack relatively consistently during phase 2. The problem was that the safe zone coordinates always use the bot's z-coordinate. If the bots are standing at the lower altitude part of the arena, `SearchForBestPath` inside `MoveTo` causes `INVALID_HEIGHT`, resulting in the bot not moving at all and getting hit by the breath attack. This PR fixes this behavior by using the actual terrain z-coordinates for the predefined safe zones instead of always using the bot's z-coordinate. These values are chosen to ensure valid pathfinding regardless of the bot's current elevation. Additionally, bots now interrupt their spells if they are not inside a safe zone during the breath. This causes the bots to immediately start running instead of finishing their casts first. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. Replaced the use of `bot->GetPositionZ()` in `GetSafeZonesForBreath` with predefined safe zone z-coordinates to ensure valid pathfinding. Added `AttackStop` and `InterruptNonMeleeSpells` to guarantee immediate movement when outside safe zones. No additional condition checks or branching logic were introduced. - Describe the **processing cost** when this logic executes across many bots. Minimal. The logic only runs within the Onyxia encounter script and calling `AttackStop` and `InterruptNonMeleeSpells` should be negligible. ## How to Test the Changes Enter Onyxia's Lair (10, 25 or 40 (mod-individual-progression)) and engage Onyxia with the appropriate number of bots. During phase 2 (Onyxia takes off), check if the bots move to the safe zones during the breath attack. Tip: Mark Onyxia as moon (RTI), so that phase 2 doesn't end too quickly. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [x] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) The calls of `AttackStop` and `InterruptNonMeleeSpells` cause minimal overhead compared to the original strategy. This should be negligible. - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Yes (encounter-specific). Bots will now interrupt casts earlier during Onyxia phase 2 to prioritize movement to safe zones. - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers The strategy for Onyxia might need additional work: For example, the Onyxian Lair Guards are completely ignored while whelps are alive and their Blast Nova doesn't get handled at all. This PR focuses on fixing the Deep Breath behavior. Handling of Onyxian Lair Guards is not included and should be implemented in a separate PR. --- .../Raid/Onyxia/Action/RaidOnyxiaActions.cpp | 6 +++- src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.h | 30 ++++++++++--------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.cpp b/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.cpp index e2fe3cbe6d7..5b470ee5ca1 100644 --- a/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.cpp +++ b/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.cpp @@ -99,8 +99,12 @@ bool RaidOnyxiaMoveToSafeZoneAction::Execute(Event /*event*/) if (bot->IsWithinDist2d(bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->radius)) return false; // Already safe + // Stop current spell first + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + // bot->Yell("Moving to Safe Zone!", LANG_UNIVERSAL); - return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bot->GetPositionZ(), + return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->pos.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); } diff --git a/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.h b/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.h index 3943aaf6052..d5b8eafd999 100644 --- a/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.h +++ b/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.h @@ -2,7 +2,6 @@ #ifndef _PLAYERBOT_RAIDONYXIAACTIONS_H_ #define _PLAYERBOT_RAIDONYXIAACTIONS_H_ -#include "Action.h" #include "AttackAction.h" #include "GenericSpellActions.h" #include "MovementActions.h" @@ -45,42 +44,45 @@ class RaidOnyxiaMoveToSafeZoneAction : public MovementAction bool Execute(Event event) override; private: - std::vector GetSafeZonesForBreath(uint32 spellId) + static std::vector GetSafeZonesForBreath(uint32 spellId) { - // Define your safe zone coordinates based on the map - // Example assumes Onyxia's lair map coordinates - float z = bot->GetPositionZ(); // Stay at current height + // Safe zone coordinates based on the map + // Assumes Onyxia's lair map coordinates switch (spellId) { case 17086: // N to S case 18351: // S to N - return {SafeZone{Position(-10.0f, -180.0f, z), 5.0f}, - SafeZone{Position(-20.0f, -250.0f, z), 5.0f}}; // Bottom Safe Zone + return { + SafeZone{Position(-10.0f, -180.0f, -87.0f), 5.0f}, + SafeZone{Position(-20.0f, -250.0f, -88.0f), 5.0f} + }; // Bottom Safe Zone case 18576: // E to W case 18609: // W to E return { - SafeZone{Position(20.0f, -210.0f, z), 5.0f}, - SafeZone{Position(-75.0f, -210.0f, z), 5.0f}, + SafeZone{Position(20.0f, -210.0f, -85.5f), 5.0f}, + SafeZone{Position(-75.0f, -210.0f, -83.4f), 5.0f}, }; // Left Safe Zone case 18564: // SE to NW case 18584: // NW to SE return { - SafeZone{Position(-60.0f, -195.0f, z), 5.0f}, - SafeZone{Position(10.0f, -240.0f, z), 5.0f}, + SafeZone{Position(-60.0f, -195.0f, -85.0f), 5.0f}, + SafeZone{Position(10.0f, -240.0f, -85.9f), 5.0f}, }; // NW Safe Zone case 18596: // SW to NE case 18617: // NE to SW return { - SafeZone{Position(7.0f, -185.0f, z), 5.0f}, - SafeZone{Position(-60.0f, -240.0f, z), 5.0f}, + SafeZone{Position(7.0f, -185.0f, -86.2f), 5.0f}, + SafeZone{Position(-60.0f, -240.0f, -85.2f), 5.0f}, }; // NE Safe Zone default: - return {SafeZone{Position(0.0f, 0.0f, z), 5.0f}}; // Fallback center - shouldn't ever happen + return { + SafeZone{Position(-40.0f, -214.0f, -86.6f), 5.0f} + }; // Fallback center - shouldn't ever happen } } }; From c819516325a628913725ed4b9021ba37818ee4c7 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sat, 2 May 2026 12:19:23 -0700 Subject: [PATCH 03/63] Fix rpg travel flying (#2324) ## Pull Request Description Clean up values that were incorrectly translated from the sql search into the dbc search. Refactors structure for cities in TravelMgr to try to resolve some duplication issues. Change to position based search, so that bots dont get stuck if they fail to resolve the flightmaster game object when it hasnt spawned. TravelFlight state now stores flight master entry + world position instead of ObjectGuid, so the bot can move back into range and re-resolve the NPC locally via FindNearestCreature Bundles reliability cleanup in NewRpgTravelFlightAction: uses info.ChangeToIdle() consistently and adds the missing return true after a failed taxi path ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. No expected shanges. ## How to Test the Changes Run the server and check if zones are getting populated well. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) Run with 4k bots, no issues. - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) It should correctly send bots to the areas appropriate for their level in an equally weighted manner. - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - x ] Yes (**explain below**) Refactoring the data structure based on my instruction. All parts reviewed. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/World/Rpg/Action/NewRpgAction.cpp | 12 +- src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp | 22 +- src/Ai/World/Rpg/Action/NewRpgBaseAction.h | 2 +- src/Ai/World/Rpg/NewRpgInfo.cpp | 7 +- src/Ai/World/Rpg/NewRpgInfo.h | 5 +- src/Mgr/Travel/TravelMgr.cpp | 431 +++++++++++-------- src/Mgr/Travel/TravelMgr.h | 25 +- src/Mgr/Travel/TravelNode.cpp | 2 +- 8 files changed, 295 insertions(+), 211 deletions(-) diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.cpp b/src/Ai/World/Rpg/Action/NewRpgAction.cpp index ca0ca243360..290be0c0af9 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgAction.cpp @@ -3,6 +3,7 @@ #include #include +#include "AreaDefines.h" #include "BroadcastHelper.h" #include "ChatHelper.h" #include "G3D/Vector2.h" @@ -468,10 +469,14 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/) data.inFlight = true; return false; } - Creature* flightMaster = ObjectAccessor::GetCreature(*bot, data.fromFlightMaster); + + if (bot->GetDistance(data.flightMasterPos) > INTERACTION_DISTANCE) + return MoveFarTo(data.flightMasterPos); + + Creature* flightMaster = bot->FindNearestCreature(data.flightMasterEntry, INTERACTION_DISTANCE * 3); if (!flightMaster || !flightMaster->IsAlive()) { - botAI->rpgInfo.ChangeToIdle(); + info.ChangeToIdle(); return true; } if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE) @@ -487,7 +492,8 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/) { LOG_DEBUG("playerbots", "[New RPG] {} active taxi path {} (from {} to {}) failed", bot->GetName(), flightMaster->GetEntry(), nodes[0], nodes[nodes.size() - 1]); - botAI->rpgInfo.ChangeToIdle(); + info.ChangeToIdle(); + return true; } return true; } diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp index 092b115387f..336c7599dea 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp @@ -1027,19 +1027,21 @@ WorldPosition NewRpgBaseAction::SelectRandomCampPos(Player* bot) return dest; } -bool NewRpgBaseAction::SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, std::vector& path) +bool NewRpgBaseAction::SelectRandomFlightTaxiNode(uint32& flightMasterEntry, WorldPosition& flightMasterPos, std::vector& path) { - flightMaster = sTravelMgr.GetNearestFlightMasterGuid(bot); - if (!flightMaster) + TravelMgr::FlightMasterInfo const* info = sTravelMgr.GetNearestFlightMasterInfo(bot); + if (!info) return false; std::vector> availablePaths = sTravelMgr.GetOptimalFlightDestinations(bot); if (availablePaths.empty()) return false; + flightMasterEntry = info->templateEntry; + flightMasterPos = info->pos; path = availablePaths[urand(0, availablePaths.size() - 1)]; LOG_DEBUG("playerbots", "[New RPG] Bot {} select random flight taxi node from:{} (node {}) to:{} ({} available)", - bot->GetName(), flightMaster.GetEntry(), path[0], path[path.size() - 1], availablePaths.size()); + bot->GetName(), flightMasterEntry, path[0], path[path.size() - 1], availablePaths.size()); return true; } @@ -1139,11 +1141,12 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector candidateSta } case RPG_TRAVEL_FLIGHT: { - ObjectGuid flightMaster; + uint32 flightMasterEntry = 0; + WorldPosition flightMasterPos; std::vector path; - if (SelectRandomFlightTaxiNode(flightMaster, path)) + if (SelectRandomFlightTaxiNode(flightMasterEntry, flightMasterPos, path)) { - botAI->rpgInfo.ChangeToTravelFlight(flightMaster, path); + botAI->rpgInfo.ChangeToTravelFlight(flightMasterEntry, flightMasterPos, path); return true; } return false; @@ -1220,9 +1223,10 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status) } case RPG_TRAVEL_FLIGHT: { - ObjectGuid flightMaster; + uint32 flightMasterEntry = 0; + WorldPosition flightMasterPos; std::vector path; - return SelectRandomFlightTaxiNode(flightMaster, path); + return SelectRandomFlightTaxiNode(flightMasterEntry, flightMasterPos, path); } case RPG_OUTDOOR_PVP: { diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h index eaba7244628..e73a2321966 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h @@ -54,7 +54,7 @@ class NewRpgBaseAction : public MovementAction bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector& poiInfo, bool toComplete = false); static WorldPosition SelectRandomGrindPos(Player* bot); static WorldPosition SelectRandomCampPos(Player* bot); - bool SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, std::vector& path); + bool SelectRandomFlightTaxiNode(uint32& flightMasterEntry, WorldPosition& flightMasterPos, std::vector& path); bool RandomChangeStatus(std::vector candidateStatus); bool CheckRpgStatusAvailable(NewRpgStatus status); diff --git a/src/Ai/World/Rpg/NewRpgInfo.cpp b/src/Ai/World/Rpg/NewRpgInfo.cpp index 4935503fc25..780430f6db5 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.cpp +++ b/src/Ai/World/Rpg/NewRpgInfo.cpp @@ -37,11 +37,12 @@ void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest) data = do_quest; } -void NewRpgInfo::ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector path) +void NewRpgInfo::ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition flightMasterPos, std::vector path) { startT = getMSTime(); TravelFlight flight; - flight.fromFlightMaster = fromFlightMaster; + flight.flightMasterEntry = flightMasterEntry; + flight.flightMasterPos = flightMasterPos; flight.path = std::move(path); flight.inFlight = false; data = flight; @@ -157,7 +158,7 @@ std::string NewRpgInfo::ToString() else if constexpr (std::is_same_v) { out << "TRAVEL_FLIGHT"; - out << "\nfromFlightMaster: " << arg.fromFlightMaster.GetEntry(); + out << "\nflightMasterEntry: " << arg.flightMasterEntry; out << "\nfromNode: " << arg.path[0]; out << "\ntoNode: " << arg.path[arg.path.size() - 1]; out << "\ninFlight: " << arg.inFlight; diff --git a/src/Ai/World/Rpg/NewRpgInfo.h b/src/Ai/World/Rpg/NewRpgInfo.h index 9e6abdda4c7..5896915a4d3 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.h +++ b/src/Ai/World/Rpg/NewRpgInfo.h @@ -49,7 +49,8 @@ struct NewRpgInfo // RPG_TRAVEL_FLIGHT struct TravelFlight { - ObjectGuid fromFlightMaster{}; + uint32 flightMasterEntry{0}; + WorldPosition flightMasterPos{}; std::vector path; bool inFlight{false}; }; @@ -96,7 +97,7 @@ struct NewRpgInfo void ChangeToWanderNpc(); void ChangeToWanderRandom(); void ChangeToDoQuest(uint32 questId, const Quest* quest); - void ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector path); + void ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition flightMasterPos, std::vector path); void ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId = 0); void ChangeToRest(); void ChangeToIdle(); diff --git a/src/Mgr/Travel/TravelMgr.cpp b/src/Mgr/Travel/TravelMgr.cpp index 1868bc2e386..adc1e4a3edd 100644 --- a/src/Mgr/Travel/TravelMgr.cpp +++ b/src/Mgr/Travel/TravelMgr.cpp @@ -8,6 +8,7 @@ #include #include +#include "AreaDefines.h" #include "Creature.h" #include "Log.h" #include "ObjectAccessor.h" @@ -28,67 +29,60 @@ // Navigation data -enum class CityId : uint8 -{ - STORMWIND, - IRONFORGE, - DARNASSUS, - EXODAR, - ORGRIMMAR, - UNDERCITY, - THUNDER_BLUFF, - SILVERMOON_CITY, - SHATTRATH_CITY, - DALARAN +struct Capital +{ + uint32 zoneId; + TeamId team; + char const* name; + std::vector bankers; }; -static const std::unordered_map> bankerToCity = { - {2455, {CityId::STORMWIND, TEAM_ALLIANCE}}, {2456, {CityId::STORMWIND, TEAM_ALLIANCE}}, {2457, {CityId::STORMWIND, TEAM_ALLIANCE}}, - {2460, {CityId::IRONFORGE, TEAM_ALLIANCE}}, {2461, {CityId::IRONFORGE, TEAM_ALLIANCE}}, {5099, {CityId::IRONFORGE, TEAM_ALLIANCE}}, - {4155, {CityId::DARNASSUS, TEAM_ALLIANCE}}, {4208, {CityId::DARNASSUS, TEAM_ALLIANCE}}, {4209, {CityId::DARNASSUS, TEAM_ALLIANCE}}, - {17773, {CityId::EXODAR, TEAM_ALLIANCE}}, {18350, {CityId::EXODAR, TEAM_ALLIANCE}}, {16710, {CityId::EXODAR, TEAM_ALLIANCE}}, - {3320, {CityId::ORGRIMMAR, TEAM_HORDE}}, {3309, {CityId::ORGRIMMAR, TEAM_HORDE}}, {3318, {CityId::ORGRIMMAR, TEAM_HORDE}}, - {4549, {CityId::UNDERCITY, TEAM_HORDE}}, {2459, {CityId::UNDERCITY, TEAM_HORDE}}, {2458, {CityId::UNDERCITY, TEAM_HORDE}}, {4550, {CityId::UNDERCITY, TEAM_HORDE}}, - {2996, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, {8356, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, {8357, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, - {17631, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {17632, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {17633, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, - {16615, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {16616, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {16617, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, - {19246, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, - {19034, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, - {30604, {CityId::DALARAN, TEAM_NEUTRAL}}, {30605, {CityId::DALARAN, TEAM_NEUTRAL}}, {30607, {CityId::DALARAN, TEAM_NEUTRAL}}, - {28675, {CityId::DALARAN, TEAM_NEUTRAL}}, {28676, {CityId::DALARAN, TEAM_NEUTRAL}}, {28677, {CityId::DALARAN, TEAM_NEUTRAL}} +static const std::vector capitals = { + { AREA_STORMWIND_CITY, TEAM_ALLIANCE, "Stormwind", {2455, 2456, 2457} }, + { AREA_IRONFORGE, TEAM_ALLIANCE, "Ironforge", {2460, 2461, 5099} }, + { AREA_DARNASSUS, TEAM_ALLIANCE, "Darnassus", {4155, 4208, 4209} }, + { AREA_THE_EXODAR, TEAM_ALLIANCE, "Exodar", {17773, 18350, 16710} }, + { AREA_ORGRIMMAR, TEAM_HORDE, "Orgrimmar", {3320, 3309, 3318} }, + { AREA_UNDERCITY, TEAM_HORDE, "Undercity", {4549, 2459, 2458, 4550} }, + { AREA_THUNDER_BLUFF, TEAM_HORDE, "Thunder Bluff", {2996, 8356, 8357} }, + { AREA_SILVERMOON_CITY, TEAM_HORDE, "Silvermoon", {17631, 17632, 17633, 16615, 16616, 16617} }, + { AREA_SHATTRATH_CITY, TEAM_NEUTRAL, "Shattrath", {19246, 19338, 19034, 19318} }, + { AREA_DALARAN, TEAM_NEUTRAL, "Dalaran", {30604, 30605, 30607, 28675, 28676, 28677, 29530} } }; -static const std::unordered_map> cityToBankers = { - {CityId::STORMWIND, {2455, 2456, 2457}}, - {CityId::IRONFORGE, {2460, 2461, 5099}}, - {CityId::DARNASSUS, {4155, 4208, 4209}}, - {CityId::EXODAR, {17773, 18350, 16710}}, - {CityId::ORGRIMMAR, {3320, 3309, 3318}}, - {CityId::UNDERCITY, {4549, 2459, 2458, 4550}}, - {CityId::THUNDER_BLUFF, {2996, 8356, 8357}}, - {CityId::SILVERMOON_CITY, {17631, 17632, 17633, 16615, 16616, 16617}}, - {CityId::SHATTRATH_CITY, {19246, 19338, 19034, 19318}}, - {CityId::DALARAN, {30604, 30605, 30607, 28675, 28676, 28677, 29530}} -}; +static Capital const* FindCapitalByZone(uint32 zoneId) +{ + for (Capital const& capital : capitals) + if (capital.zoneId == zoneId) + return &capital; + return nullptr; +} + +static Capital const* FindCapitalByBanker(uint16 bankerEntry) +{ + for (Capital const& capital : capitals) + for (uint16 bankerId : capital.bankers) + if (bankerId == bankerEntry) + return &capital; + return nullptr; +} -static int GetCityWeight(CityId city) +static int GetCityWeight(uint32 zoneId) { - int weight = 0; - switch (city) + switch (zoneId) { - case CityId::STORMWIND: weight = sPlayerbotAIConfig.weightTeleToStormwind; break; - case CityId::IRONFORGE: weight = sPlayerbotAIConfig.weightTeleToIronforge; break; - case CityId::DARNASSUS: weight = sPlayerbotAIConfig.weightTeleToDarnassus; break; - case CityId::EXODAR: weight = sPlayerbotAIConfig.weightTeleToExodar; break; - case CityId::ORGRIMMAR: weight = sPlayerbotAIConfig.weightTeleToOrgrimmar; break; - case CityId::UNDERCITY: weight = sPlayerbotAIConfig.weightTeleToUndercity; break; - case CityId::THUNDER_BLUFF: weight = sPlayerbotAIConfig.weightTeleToThunderBluff; break; - case CityId::SILVERMOON_CITY: weight = sPlayerbotAIConfig.weightTeleToSilvermoonCity; break; - case CityId::SHATTRATH_CITY: weight = sPlayerbotAIConfig.weightTeleToShattrathCity; break; - case CityId::DALARAN: weight = sPlayerbotAIConfig.weightTeleToDalaran; break; - default: weight = 0; break; + case AREA_STORMWIND_CITY: return sPlayerbotAIConfig.weightTeleToStormwind; + case AREA_IRONFORGE: return sPlayerbotAIConfig.weightTeleToIronforge; + case AREA_DARNASSUS: return sPlayerbotAIConfig.weightTeleToDarnassus; + case AREA_THE_EXODAR: return sPlayerbotAIConfig.weightTeleToExodar; + case AREA_ORGRIMMAR: return sPlayerbotAIConfig.weightTeleToOrgrimmar; + case AREA_UNDERCITY: return sPlayerbotAIConfig.weightTeleToUndercity; + case AREA_THUNDER_BLUFF: return sPlayerbotAIConfig.weightTeleToThunderBluff; + case AREA_SILVERMOON_CITY: return sPlayerbotAIConfig.weightTeleToSilvermoonCity; + case AREA_SHATTRATH_CITY: return sPlayerbotAIConfig.weightTeleToShattrathCity; + case AREA_DALARAN: return sPlayerbotAIConfig.weightTeleToDalaran; } - return weight; + return 0; } WorldPosition::WorldPosition(std::string const str) @@ -4369,76 +4363,117 @@ void TravelMgr::Init() LOG_INFO("playerbots", "Playerbots Taxi graph and destination cache built."); } -Creature* TravelMgr::GetNearestFlightMaster(Player* bot) +TravelMgr::FlightMasterInfo const* TravelMgr::GetNearestFlightMasterInfo(Player* bot) const { - std::map& flightMasterCache = + auto const& flightMasterCache = (bot->GetTeamId() == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache; - Creature* nearestFlightMaster = nullptr; + FlightMasterInfo const* nearest = nullptr; float nearestDistance = std::numeric_limits::max(); - for (auto const& [entry, pos] : flightMasterCache) + for (auto const& [dbGuid, info] : flightMasterCache) { - if (pos.GetMapId() != bot->GetMapId()) - continue; - - float distance = bot->GetExactDist2dSq(pos); - if (distance > nearestDistance) + if (info.pos.GetMapId() != bot->GetMapId()) continue; - Creature* flightMaster = ObjectAccessor::GetSpawnedCreatureByDBGUID(bot->GetMapId(), entry); - if (flightMaster) + float distance = bot->GetExactDist2dSq(info.pos); + if (distance < nearestDistance) { nearestDistance = distance; - nearestFlightMaster = flightMaster; + nearest = &info; } } - return nearestFlightMaster; + return nearest; } -ObjectGuid TravelMgr::GetNearestFlightMasterGuid(Player* bot) +std::vector TravelMgr::GetFlightNodesInZone(uint32 zoneId, TeamId team, uint32 excludeNode) const { - Creature* nearestFlightMaster = GetNearestFlightMaster(bot); - if (!nearestFlightMaster) - return ObjectGuid::Empty; - - return nearestFlightMaster->GetGUID(); + auto const& cache = (team == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache; + std::unordered_set seen; + std::vector result; + for (auto const& [entry, info] : cache) + { + if (info.zoneId != zoneId || info.taxiNodeId == 0 || info.taxiNodeId == excludeNode) + continue; + if (seen.insert(info.taxiNodeId).second) + result.push_back(info.taxiNodeId); + } + return result; } std::vector> TravelMgr::GetOptimalFlightDestinations(Player* bot) { std::vector> validDestinations; - Creature* nearestFlightMaster = GetNearestFlightMaster(bot); - if (!nearestFlightMaster || bot->GetDistance(nearestFlightMaster) > 500.0f) + FlightMasterInfo const* nearestFlightMaster = GetNearestFlightMasterInfo(bot); + if (!nearestFlightMaster || bot->GetDistance(nearestFlightMaster->pos) > 500.0f) return validDestinations; - uint32 fromNode = sObjectMgr->GetNearestTaxiNode(nearestFlightMaster->GetPositionX(), nearestFlightMaster->GetPositionY(), - nearestFlightMaster->GetPositionZ(), nearestFlightMaster->GetMapId(), - bot->GetTeamId()); + uint32 fromNode = nearestFlightMaster->taxiNodeId; if (!fromNode) return validDestinations; - std::vector candidateLocations; - if (bot->GetLevel() >= 10 && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100) - candidateLocations = GetCityLocations(bot); + TaxiNodesEntry const* startNode = sTaxiNodesStore.LookupEntry(fromNode); + if (!startNode) + return validDestinations; + + uint32 botLevel = bot->GetLevel(); - std::vector hubLocations = GetTravelHubs(bot); - candidateLocations.insert(candidateLocations.end(), hubLocations.begin(), hubLocations.end()); + // Bots already in a capital shouldn't have another capital picked as a + // flight destination — that just shuffles them between cities. + bool botInCapital = false; + if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(bot->GetZoneId())) + botInCapital = (area->flags & AREA_FLAG_CAPITAL) != 0; - for (auto const& loc : candidateLocations) + //Simplify destination delection. Its either target cities (Based on config value) or target world. + std::vector candidateZones; + if (botLevel >= 10 && !botInCapital && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100) { - uint32 candidateNode = sObjectMgr->GetNearestTaxiNode(loc.GetPositionX(), loc.GetPositionY(), - loc.GetPositionZ(), loc.GetMapId(), - bot->GetTeamId()); - if (!candidateNode) - continue; + TeamId botTeam = bot->GetTeamId(); + for (Capital const& capital : capitals) + { + if (capital.team != TEAM_NEUTRAL && capital.team != botTeam) + continue; + candidateZones.push_back(capital.zoneId); + } + } + if (candidateZones.empty()) + { + for (auto const& [zoneId, bracket] : zone2LevelBracket) + { + if (botLevel < bracket.low || botLevel > bracket.high) + continue; + if (GetFlightNodesInZone(zoneId, bot->GetTeamId(), fromNode).empty()) + continue; + candidateZones.push_back(zoneId); + } + } - std::vector path = sTravelNodeMap.FindTaxiPath(fromNode, candidateNode); - if (!path.empty()) - validDestinations.push_back(path); + if (candidateZones.empty()) + return validDestinations; + + while (!candidateZones.empty()) + { + uint32 zoneIndex = urand(0, candidateZones.size() - 1); + uint32 pickedZone = candidateZones[zoneIndex]; + + std::vector usableNodes = GetFlightNodesInZone(pickedZone, bot->GetTeamId(), fromNode); + + if (!usableNodes.empty()) + { + uint32 pickedNode = usableNodes[urand(0, usableNodes.size() - 1)]; + std::vector path = sTravelNodeMap.FindTaxiPath(fromNode, pickedNode); + if (!path.empty()) + { + validDestinations.push_back(std::move(path)); + return validDestinations; + } + } + + candidateZones.erase(candidateZones.begin() + zoneIndex); } + return validDestinations; } @@ -4472,34 +4507,34 @@ std::vector TravelMgr::GetCityLocations(Player* bot) return fallbackLocations; TeamId botTeamId = bot->GetTeamId(); - std::unordered_set validBankerCities; + std::unordered_set validBankerCities; for (auto& loc : bankerLocsPerLevelCache[level]) { - auto cityIt = bankerToCity.find(loc.entry); - if (cityIt == bankerToCity.end()) + Capital const* capital = FindCapitalByBanker(loc.entry); + if (!capital) continue; - TeamId cityTeamId = cityIt->second.second; + TeamId cityTeamId = capital->team; if (cityTeamId == botTeamId || (cityTeamId == TEAM_NEUTRAL) ) - validBankerCities.insert(cityIt->second.first); + validBankerCities.insert(capital->zoneId); } // Fallback if no valid cities if (validBankerCities.empty()) return fallbackLocations; // Apply weights to valid cities - std::vector weightedCities; - for (CityId city : validBankerCities) + std::vector weightedCities; + for (uint32 zoneId : validBankerCities) { - int weight = GetCityWeight(city); + int weight = GetCityWeight(zoneId); if (weight <= 0) continue; for (int i = 0; i < weight; ++i) - weightedCities.push_back(city); + weightedCities.push_back(zoneId); } // Fallback if no valid cities @@ -4507,9 +4542,11 @@ std::vector TravelMgr::GetCityLocations(Player* bot) return fallbackLocations; // Pick a weighted city randomly, then a random banker in that city - CityId selectedCity = weightedCities[urand(0, weightedCities.size() - 1)]; - - auto const& bankers = cityToBankers.at(selectedCity); + uint32 selectedCity = weightedCities[urand(0, weightedCities.size() - 1)]; + Capital const* selectedCapital = FindCapitalByZone(selectedCity); + if (!selectedCapital) + return fallbackLocations; + auto const& bankers = selectedCapital->bankers; uint32 selectedBankerEntry = bankers[urand(0, bankers.size() - 1)]; auto locIt = bankerEntryToLocation.find(selectedBankerEntry); if (locIt != bankerEntryToLocation.end()) @@ -4520,78 +4557,78 @@ std::vector TravelMgr::GetCityLocations(Player* bot) void TravelMgr::PrepareZone2LevelBracket() { - // Classic WoW - Low - level zones - zone2LevelBracket[1] = {5, 12}; // Dun Morogh - zone2LevelBracket[12] = {5, 12}; // Elwynn Forest - zone2LevelBracket[14] = {5, 12}; // Durotar - zone2LevelBracket[85] = {5, 12}; // Tirisfal Glades - zone2LevelBracket[141] = {5, 12}; // Teldrassil - zone2LevelBracket[215] = {5, 12}; // Mulgore - zone2LevelBracket[3430] = {5, 12}; // Eversong Woods - zone2LevelBracket[3524] = {5, 12}; // Azuremyst Isle - - // Classic WoW - Mid - level zones - zone2LevelBracket[17] = {10, 25}; // Barrens - zone2LevelBracket[38] = {10, 20}; // Loch Modan - zone2LevelBracket[40] = {10, 21}; // Westfall - zone2LevelBracket[130] = {10, 23}; // Silverpine Forest - zone2LevelBracket[148] = {10, 21}; // Darkshore - zone2LevelBracket[3433] = {10, 22}; // Ghostlands - zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle - - // Classic WoW - High - level zones - zone2LevelBracket[10] = {19, 33}; // Deadwind Pass - zone2LevelBracket[11] = {21, 30}; // Wetlands - zone2LevelBracket[44] = {16, 28}; // Redridge Mountains - zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills - zone2LevelBracket[331] = {18, 33}; // Ashenvale - zone2LevelBracket[400] = {24, 36}; // Thousand Needles - zone2LevelBracket[406] = {16, 29}; // Stonetalon Mountains - - // Classic WoW - Higher - level zones - zone2LevelBracket[3] = {36, 46}; // Badlands - zone2LevelBracket[8] = {36, 46}; // Swamp of Sorrows - zone2LevelBracket[15] = {35, 46}; // Dustwallow Marsh - zone2LevelBracket[16] = {45, 52}; // Azshara - zone2LevelBracket[33] = {32, 47}; // Stranglethorn Vale - zone2LevelBracket[45] = {30, 42}; // Arathi Highlands - zone2LevelBracket[47] = {42, 51}; // Hinterlands - zone2LevelBracket[51] = {45, 51}; // Searing Gorge - zone2LevelBracket[357] = {40, 52}; // Feralas - zone2LevelBracket[405] = {30, 41}; // Desolace - zone2LevelBracket[440] = {41, 52}; // Tanaris - - // Classic WoW - Top - level zones - zone2LevelBracket[4] = {52, 57}; // Blasted Lands - zone2LevelBracket[28] = {50, 60}; // Western Plaguelands - zone2LevelBracket[46] = {51, 60}; // Burning Steppes - zone2LevelBracket[139] = {54, 62}; // Eastern Plaguelands - zone2LevelBracket[361] = {47, 57}; // Felwood - zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater - zone2LevelBracket[618] = {54, 61}; // Winterspring - zone2LevelBracket[1377] = {54, 63}; // Silithus - - // The Burning Crusade - Zones - zone2LevelBracket[3483] = {58, 66}; // Hellfire Peninsula - zone2LevelBracket[3518] = {64, 70}; // Nagrand - zone2LevelBracket[3519] = {62, 73}; // Terokkar Forest - zone2LevelBracket[3520] = {66, 73}; // Shadowmoon Valley - zone2LevelBracket[3521] = {60, 67}; // Zangarmarsh - zone2LevelBracket[3522] = {64, 73}; // Blade's Edge Mountains - zone2LevelBracket[3523] = {67, 73}; // Netherstorm - zone2LevelBracket[4080] = {68, 73}; // Isle of Quel'Danas - - // Wrath of the Lich King - Zones - zone2LevelBracket[65] = {71, 77}; // Dragonblight - zone2LevelBracket[66] = {74, 80}; // Zul'Drak - zone2LevelBracket[67] = {77, 80}; // Storm Peaks - zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier - zone2LevelBracket[394] = {72, 78}; // Grizzly Hills - zone2LevelBracket[495] = {68, 74}; // Howling Fjord - zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest - zone2LevelBracket[3537] = {68, 75}; // Borean Tundra - zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin - zone2LevelBracket[4197] = {79, 80}; // Wintergrasp + // Classic WoW - starter zones + zone2LevelBracket[AREA_DUN_MOROGH] = {5, 12}; + zone2LevelBracket[AREA_ELWYNN_FOREST] = {5, 12}; + zone2LevelBracket[AREA_DUROTAR] = {5, 12}; + zone2LevelBracket[AREA_TIRISFAL_GLADES] = {5, 12}; + zone2LevelBracket[AREA_TELDRASSIL] = {5, 12}; + zone2LevelBracket[AREA_MULGORE] = {5, 12}; + zone2LevelBracket[AREA_EVERSONG_WOODS] = {5, 12}; + zone2LevelBracket[AREA_AZUREMYST_ISLE] = {5, 12}; + + // Classic WoW - low level zones + zone2LevelBracket[AREA_THE_BARRENS] = {10, 25}; + zone2LevelBracket[AREA_LOCH_MODAN] = {10, 20}; + zone2LevelBracket[AREA_WESTFALL] = {10, 21}; + zone2LevelBracket[AREA_SILVERPINE_FOREST] = {10, 23}; + zone2LevelBracket[AREA_DARKSHORE] = {10, 21}; + zone2LevelBracket[AREA_GHOSTLANDS] = {10, 22}; + zone2LevelBracket[AREA_BLOODMYST_ISLE] = {10, 21}; + + // Classic WoW - mid-level zones + zone2LevelBracket[AREA_DUSKWOOD] = {19, 33}; + zone2LevelBracket[AREA_WETLANDS] = {21, 30}; + zone2LevelBracket[AREA_REDRIDGE_MOUNTAINS] = {16, 28}; + zone2LevelBracket[AREA_HILLSBRAD_FOOTHILLS] = {20, 34}; + zone2LevelBracket[AREA_ASHENVALE] = {18, 33}; + zone2LevelBracket[AREA_THOUSAND_NEEDLES] = {24, 36}; + zone2LevelBracket[AREA_STONETALON_MOUNTAINS] = {16, 29}; + + // Classic WoW - 30-52 zones + zone2LevelBracket[AREA_BADLANDS] = {36, 46}; + zone2LevelBracket[AREA_SWAMP_OF_SORROWS] = {36, 46}; + zone2LevelBracket[AREA_DUSTWALLOW_MARSH] = {35, 46}; + zone2LevelBracket[AREA_AZSHARA] = {45, 52}; + zone2LevelBracket[AREA_STRANGLETHORN_VALE] = {32, 47}; + zone2LevelBracket[AREA_ARATHI_HIGHLANDS] = {30, 42}; + zone2LevelBracket[AREA_THE_HINTERLANDS] = {42, 51}; + zone2LevelBracket[AREA_SEARING_GORGE] = {45, 51}; + zone2LevelBracket[AREA_FERALAS] = {40, 52}; + zone2LevelBracket[AREA_DESOLACE] = {30, 41}; + zone2LevelBracket[AREA_TANARIS] = {41, 52}; + + // Classic WoW - top level zones + zone2LevelBracket[AREA_BLASTED_LANDS] = {52, 57}; + zone2LevelBracket[AREA_WESTERN_PLAGUELANDS] = {50, 60}; + zone2LevelBracket[AREA_BURNING_STEPPES] = {51, 60}; + zone2LevelBracket[AREA_EASTERN_PLAGUELANDS] = {54, 62}; + zone2LevelBracket[361] = {47, 57}; // Felwood (no AREA_ define) + zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater (no AREA_ define) + zone2LevelBracket[AREA_WINTERSPRING] = {54, 61}; + zone2LevelBracket[AREA_SILITHUS] = {54, 63}; + + // The Burning Crusade zones + zone2LevelBracket[AREA_HELLFIRE_PENINSULA] = {58, 66}; + zone2LevelBracket[AREA_NAGRAND] = {64, 70}; + zone2LevelBracket[AREA_TEROKKAR_FOREST] = {62, 73}; + zone2LevelBracket[AREA_SHADOWMOON_VALLEY] = {66, 73}; + zone2LevelBracket[AREA_ZANGARMARSH] = {60, 67}; + zone2LevelBracket[AREA_BLADES_EDGE_MOUNTAINS] = {64, 73}; + zone2LevelBracket[AREA_NETHERSTORM] = {67, 73}; + zone2LevelBracket[AREA_ISLE_OF_QUEL_DANAS] = {68, 73}; + + // Wrath of the Lich King zones + zone2LevelBracket[AREA_DRAGONBLIGHT] = {71, 77}; + zone2LevelBracket[AREA_ZUL_DRAK] = {74, 80}; + zone2LevelBracket[AREA_THE_STORM_PEAKS] = {77, 80}; + zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier (no AREA_ define) + zone2LevelBracket[AREA_GRIZZLY_HILLS] = {72, 78}; + zone2LevelBracket[AREA_HOWLING_FJORD] = {68, 74}; + zone2LevelBracket[AREA_CRYSTALSONG_FOREST] = {77, 80}; + zone2LevelBracket[AREA_BOREAN_TUNDRA] = {68, 75}; + zone2LevelBracket[AREA_SHOLAZAR_BASIN] = {75, 80}; + zone2LevelBracket[AREA_WINTERGRASP] = {79, 80}; // Override with values from config for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig.zoneBrackets) @@ -4650,13 +4687,15 @@ void TravelMgr::PrepareDestinationCache() (creatureTemplate->unit_flags & 4096) == 0 && creatureTemplate->rank == 0) { - uint32 roundX = (x / 50.0f) * 10.0f; - uint32 roundY = (y / 50.0f) * 10.0f; - uint32 roundZ = (z / 50.0f) * 10.0f; + uint32 roundX = static_cast(std::round(x / 50.0f)); + uint32 roundY = static_cast(std::round(y / 50.0f)); + uint32 roundZ = static_cast(std::round(z / 50.0f)); tempLocsCache[std::make_tuple(mapId, roundX, roundY, roundZ)].push_back(creatureData); tempCreatureCache[templateEntry][areaId].push_back(WorldLocation(mapId, x, y, z)); } // FLIGHT MASTERS + // Entry 29480 is Grimwing (Storm Peaks) + // Entry 3838 is Vesprystus in Rut'Theran. Need Travel Node system to resolve this one. else if ((creatureTemplate->npcflag & UNIT_NPC_FLAG_FLIGHTMASTER || creatureTemplate->npcflag & UNIT_NPC_FLAG_INNKEEPER) && creatureTemplate->Entry != 3838 && creatureTemplate->Entry != 29480) @@ -4669,23 +4708,39 @@ void TravelMgr::PrepareDestinationCache() { WorldPosition pos(mapId, x, y, z, orient); if (forHorde) - hordeFlightMasterCache[guid] = pos; + { + FlightMasterInfo info; + info.pos = pos; + info.zoneId = areaId; + info.taxiNodeId = sObjectMgr->GetNearestTaxiNode(x, y, z, mapId, TEAM_HORDE); + info.templateEntry = templateEntry; + info.dbGuid = guid; + hordeFlightMasterCache[guid] = info; + } if (forAlliance) - allianceFlightMasterCache[guid] = pos; + { + FlightMasterInfo info; + info.pos = pos; + info.zoneId = areaId; + info.taxiNodeId = sObjectMgr->GetNearestTaxiNode(x, y, z, mapId, TEAM_ALLIANCE); + info.templateEntry = templateEntry; + info.dbGuid = guid; + allianceFlightMasterCache[guid] = info; + } flightMastersCount++; // Zones that have flight masters but no innkeepers — use flight master as hub static const std::set zonesWithoutInnkeeper = { - 4, // Blasted Lands (52-57) - 16, // Azshara (45-52) - 28, // Western Plaguelands (50-60) - 46, // Burning Steppes (51-60) - 51, // Searing Gorge (45-51) + AREA_BLASTED_LANDS, + AREA_AZSHARA, + AREA_WESTERN_PLAGUELANDS, + AREA_BURNING_STEPPES, + AREA_SEARING_GORGE, 361, // Felwood (47-57) 490, // Un'Goro Crater (49-56) - 2817, // Crystalsong Forest (77-80) - 4197 // Wintergrasp (79-80) + AREA_CRYSTALSONG_FOREST, + AREA_WINTERGRASP }; if (zonesWithoutInnkeeper.count(areaId)) { @@ -4756,7 +4811,7 @@ void TravelMgr::PrepareDestinationCache() // Process temporary caches for (auto const& [gridTuple, creatureDataList] : tempLocsCache) { - if (creatureDataList.size() > 2) + if (creatureDataList.size() >= 2) { CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureDataList[0].id1); uint32 level = (creatureTemplate->minlevel + creatureTemplate->maxlevel + 1) / 2; diff --git a/src/Mgr/Travel/TravelMgr.h b/src/Mgr/Travel/TravelMgr.h index f300ae6361f..99c8c8e4c01 100644 --- a/src/Mgr/Travel/TravelMgr.h +++ b/src/Mgr/Travel/TravelMgr.h @@ -846,6 +846,21 @@ class TravelTarget : AiObject class TravelMgr { public: + struct NpcLocation + { + WorldLocation loc; + uint32 entry; + }; + + struct FlightMasterInfo + { + WorldPosition pos; + uint32 zoneId; // resolved once at cache load + uint32 taxiNodeId; // DBC taxi node nearest to this flight master + uint32 templateEntry; // creature template ID (for ObjectGuid construction) + uint32 dbGuid; // DB spawn GUID (for ObjectGuid construction) + }; + static TravelMgr& instance() { static TravelMgr instance; @@ -858,12 +873,14 @@ class TravelMgr // Navigation void Init(); - Creature* GetNearestFlightMaster(Player* bot); - ObjectGuid GetNearestFlightMasterGuid(Player* bot); + + FlightMasterInfo const* GetNearestFlightMasterInfo(Player* bot) const; std::vector> GetOptimalFlightDestinations(Player* bot); const std::vector GetTeleportLocations(Player* bot); const std::vector GetTravelHubs(Player* bot); std::vector GetCityLocations(Player* bot); + std::vector GetFlightNodesInZone(uint32 zoneId, TeamId team, uint32 excludeNode = 0) const; + bool SelectAuctioneerByMap(Player* bot, NpcLocation& outAuctioneer); const std::vector& GetLocsPerLevelCache(uint8 level) { return locsPerLevelCache[level]; } template @@ -975,8 +992,8 @@ class TravelMgr }; // Navigation caches - std::map allianceFlightMasterCache; - std::map hordeFlightMasterCache; + std::map allianceFlightMasterCache; + std::map hordeFlightMasterCache; std::map> allianceHubsPerLevelCache; std::map> hordeHubsPerLevelCache; std::map> bankerLocsPerLevelCache; diff --git a/src/Mgr/Travel/TravelNode.cpp b/src/Mgr/Travel/TravelNode.cpp index 3b4996e974e..9d25d4ea755 100644 --- a/src/Mgr/Travel/TravelNode.cpp +++ b/src/Mgr/Travel/TravelNode.cpp @@ -2467,7 +2467,7 @@ std::vector TravelNodeMap::FindTaxiPath(uint32 fromNode, uint32 toNode) TaxiNodesEntry const* startNode = sTaxiNodesStore.LookupEntry(fromNode); TaxiNodesEntry const* endNode = sTaxiNodesStore.LookupEntry(toNode); - if (!startNode || !endNode || startNode->map_id != endNode->map_id) + if (!startNode || !endNode) return {}; auto cacheItr = taxiPathCache.find(fromNode); From cc6f6c2c3ae7f0e286bde1767d2cef0db69c8a98 Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 2 May 2026 21:19:37 +0200 Subject: [PATCH 04/63] Thorns reapply fix (#2338) ## Pull Request Description Allowed druid cast Thorns on target which already have Thorns on him to extend duration. Related with: #2290 ## How to Test the Changes 1. Invite 2 bot (one of them must be druid which can cast thorns) 2. Select second bot and use commad `cast thorns` 3. Wait until buff timer decrease 4. Use again same command 5. Druid should cast spell ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) To understand reason. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Class/Druid/Action/DruidActions.cpp | 31 ++++++++++++++++++++++ src/Ai/Class/Druid/Action/DruidActions.h | 6 +++++ 2 files changed, 37 insertions(+) diff --git a/src/Ai/Class/Druid/Action/DruidActions.cpp b/src/Ai/Class/Druid/Action/DruidActions.cpp index 2eb809480f4..c01cc4342e3 100644 --- a/src/Ai/Class/Druid/Action/DruidActions.cpp +++ b/src/Ai/Class/Druid/Action/DruidActions.cpp @@ -11,6 +11,22 @@ #include "AoeValues.h" #include "TargetValue.h" +namespace +{ + bool PrepareThornsTarget(PlayerbotAI* botAI, Unit* target) + { + if (!target) + return false; + + Aura* existingThorns = botAI->GetAura("thorns", target, true); + if (!existingThorns) + return true; + + target->RemoveOwnedAura(existingThorns, AURA_REMOVE_BY_CANCEL); + return true; + } +} + std::vector CastAbolishPoisonAction::getAlternatives() { return NextAction::merge({ NextAction("cure poison") }, @@ -33,6 +49,21 @@ bool CastLifebloomOnMainTankAction::isUseful() return !lifebloom || lifebloom->GetStackAmount() < 3 || lifebloom->GetDuration() < 2000; } +bool CastThornsAction::Execute(Event event) +{ + return PrepareThornsTarget(botAI, GetTarget()) && CastBuffSpellAction::Execute(event); +} + +bool CastThornsOnPartyAction::Execute(Event event) +{ + return PrepareThornsTarget(botAI, GetTarget()) && BuffOnPartyAction::Execute(event); +} + +bool CastThornsOnMainTankAction::Execute(Event event) +{ + return PrepareThornsTarget(botAI, GetTarget()) && BuffOnMainTankAction::Execute(event); +} + Value* CastEntanglingRootsCcAction::GetTargetValue() { return context->GetValue("cc target", "entangling roots"); diff --git a/src/Ai/Class/Druid/Action/DruidActions.h b/src/Ai/Class/Druid/Action/DruidActions.h index 016c3dfc433..7e02a985fd0 100644 --- a/src/Ai/Class/Druid/Action/DruidActions.h +++ b/src/Ai/Class/Druid/Action/DruidActions.h @@ -114,18 +114,24 @@ class CastThornsAction : public CastBuffSpellAction { public: CastThornsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "thorns") {} + + bool Execute(Event event) override; }; class CastThornsOnPartyAction : public BuffOnPartyAction { public: CastThornsOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "thorns") {} + + bool Execute(Event event) override; }; class CastThornsOnMainTankAction : public BuffOnMainTankAction { public: CastThornsOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "thorns", false) {} + + bool Execute(Event event) override; }; class CastLifebloomOnMainTankAction : public BuffOnMainTankAction From 063eabc16e48f2288d1a1a3fa37dec22c0e033e3 Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 2 May 2026 21:19:51 +0200 Subject: [PATCH 05/63] Spam guild fix (#2341) ## Pull Request Description Removed messages in failed attempts of buying tabard. Related with: #1885 ## How to Test the Changes Invite bot with guild strategy. Spam should not appear. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Actions/BuyAction.cpp | 8 +------- src/Ai/Base/Actions/GuildCreateActions.cpp | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Ai/Base/Actions/BuyAction.cpp b/src/Ai/Base/Actions/BuyAction.cpp index f0729f2518e..e3d454dd93d 100644 --- a/src/Ai/Base/Actions/BuyAction.cpp +++ b/src/Ai/Base/Actions/BuyAction.cpp @@ -213,13 +213,7 @@ bool BuyAction::Execute(Event event) } } - if (!vendored) - { - botAI->TellError("There are no vendors nearby"); - return false; - } - - return true; + return vendored; } bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto) diff --git a/src/Ai/Base/Actions/GuildCreateActions.cpp b/src/Ai/Base/Actions/GuildCreateActions.cpp index c536475f101..59696ecea09 100644 --- a/src/Ai/Base/Actions/GuildCreateActions.cpp +++ b/src/Ai/Base/Actions/GuildCreateActions.cpp @@ -296,7 +296,7 @@ bool PetitionTurnInAction::isUseful() bool BuyTabardAction::Execute(Event /*event*/) { - bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:")); + bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:"), true); if (canBuy && AI_VALUE2(uint32, "item count", chat->FormatQItem(5976))) return true; From 94195c3b9b4b4c1fd76fd84a8df4ddacb0ea7443 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 2 May 2026 14:20:03 -0500 Subject: [PATCH 06/63] Bots Don't Autoequip Tools & Other Misc Weapons (#2346) ## Pull Request Description Solve the rest of #2344 Now, bots won't autoequip any weapon from ITEM_SUBCLASS_WEAPON_MISC, which includes all of the basic tools and some other crap that they have no need to autoequip, either. Bots are still eligible to equip those weapons (such as through the "e" command). Note that MISC includes the Argent Tournament lances. I've not played WotLK, but I assume those might be relevant for a strategy. It shouldn't be a problem though because I've intentionally not made bots ineligible for MISC weapons; they just won't consider them upgrades on their own. I also cleaned up ItemUsageValue::QueryItemUsageForEquip to consolidate checks and so on. None of that should be functional, or I screwed up. The check for MISC is on lines 219 through 221. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes Activate selfbot. Unequip all weapons and have nothing in the inventory except for a MISC weapon such as a skinning knife. Whisper self "equip upgrade"--nothing should happen. Whisper self "e [LINK TO WEAPON]"--the bot should equip the weapon. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [x] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) There's an extra check but totally meaningless with respect to performance. - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) They won't auto-equip crap that will prevent them from using abilities. - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) I had GPT-5.4 evaluate different spots where I thought an exclusion could be added before settling on this one. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Value/ItemUsageValue.cpp | 57 ++++++++-------------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/src/Ai/Base/Value/ItemUsageValue.cpp b/src/Ai/Base/Value/ItemUsageValue.cpp index c3d976f0fd0..a1f9688d277 100644 --- a/src/Ai/Base/Value/ItemUsageValue.cpp +++ b/src/Ai/Base/Value/ItemUsageValue.cpp @@ -180,19 +180,11 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto, delete pItem; if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) - { return ITEM_USAGE_NONE; - } - // Check is unique items are equipped or not - bool needToCheckUnique = false; - if (result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) - { - needToCheckUnique = true; - } - else if (itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE)) - { - needToCheckUnique = true; - } + + // Check if unique items are equipped or not + bool needToCheckUnique = result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS || + itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE); if (needToCheckUnique) { @@ -206,28 +198,27 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto, bool isEquipped = (totalItemCount > bagItemCount); if (isEquipped) - { return ITEM_USAGE_NONE; // Item is already equipped - } // If not equipped, continue processing } - if (itemProto->Class == ITEM_CLASS_QUIVER) - if (bot->getClass() != CLASS_HUNTER) - return ITEM_USAGE_NONE; + if (itemProto->Class == ITEM_CLASS_QUIVER && bot->getClass() != CLASS_HUNTER) + return ITEM_USAGE_NONE; if (itemProto->Class == ITEM_CLASS_CONTAINER) { if (itemProto->SubClass != ITEM_SUBCLASS_CONTAINER) return ITEM_USAGE_NONE; // Todo add logic for non-bag containers. We want to look at professions/class and // only replace if non-bag is larger than bag. - if (GetSmallestBagSize() >= itemProto->ContainerSlots) return ITEM_USAGE_NONE; return ITEM_USAGE_EQUIP; } + if (itemProto->Class == ITEM_CLASS_WEAPON && itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MISC) + return ITEM_USAGE_NONE; + bool shouldEquip = false; // uint32 statWeight = sRandomItemMgr.GetLiveStatWeight(bot, itemProto->ItemId); StatsWeightCalculator calculator(bot); @@ -254,19 +245,14 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto, uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); // Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead // This occurs with unique items that are already in the bots bags when CanEquipItem is called - if (dest == 0) + if (dest == 0 && dstSlot != NULL_SLOT) { - if (dstSlot != NULL_SLOT) - { - // Construct dest from dstSlot - dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot; - } + // Construct dest from dstSlot + dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot; } if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1) - { possibleSlots = 2; - } // Check weapon case separately to keep things a bit cleaner bool have2HWeapon = false; @@ -283,14 +269,9 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto, itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2); // If the bot can Titan Grip, ignore any 2H weapon that isn't a 2H sword, mace, or axe. - if (bot->CanTitanGrip()) - { - // If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all. - if (itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon) - { - return ITEM_USAGE_NONE; - } - } + // If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all. + if (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon) + return ITEM_USAGE_NONE; // Now handle the logic for equipping and possible offhand slots // If the bot can Dual Wield and: @@ -317,9 +298,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto, if (shouldEquipInSlot) return ITEM_USAGE_EQUIP; else - { return ITEM_USAGE_BAD_EQUIP; - } } ItemTemplate const* oldItemProto = oldItem->GetTemplate(); @@ -328,22 +307,16 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto, { // uint32 oldStatWeight = sRandomItemMgr.GetLiveStatWeight(bot, oldItemProto->ItemId); if (itemScore || oldScore) - { shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig.equipUpgradeThreshold; - } } // Bigger quiver if (itemProto->Class == ITEM_CLASS_QUIVER) { if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots) - { return ITEM_USAGE_EQUIP; - } else - { return ITEM_USAGE_NONE; - } } bool existingShouldEquip = true; From 104a1b9ee1c33c1889ffee0460e998fb94a3f7ab Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 2 May 2026 14:20:18 -0500 Subject: [PATCH 07/63] Clean up unnecessary includes in raid strategy and trigger-context headers (#2347) ## Pull Request Description This PR trims redundant includes from raid Strategy.h and TriggerContext.h headers. I noticed a consistent pattern of including Multiplier.h when it was not needed in Strategy.h and including AiObjectContext.h in TriggerContext.h when only the narrower NamedObjectContext.h is needed (both of which I was guilty of also). Since we make new raid strategies based on existing raid strategies, I figure let's go for the low-hanging fruit and just fix this so we stop doing it wrong going forward. While I was at it, I removed other unnecessary includes but in those two files only (across dungeon and raid strategies). Edit: Made a couple of other minor code cleanups I'd been intending to do. Notably, we shouldn't be including a .cpp in PlayerbotAI.cpp. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) I had GPT-5.4 do the actual work because doing it myself file-by-file would've been such a snoozefest. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- conf/playerbots.conf.dist | 2 +- src/Ai/Raid/Aq20/RaidAq20TriggerContext.h | 1 - src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.h | 2 -- src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h | 1 - src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h | 2 -- src/Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h | 1 - src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.h | 2 -- src/Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h | 2 +- src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.h | 1 - src/Ai/Raid/Icecrown/RaidIccTriggerContext.h | 1 - src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.h | 3 --- src/Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h | 2 +- src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.h | 1 - src/Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h | 2 +- src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.h | 1 - src/Ai/Raid/MoltenCore/RaidMcTriggerContext.h | 1 - src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.h | 2 -- src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h | 1 - src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h | 2 -- src/Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h | 1 - src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.h | 2 -- src/Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h | 1 - src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h | 2 +- src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h | 1 - src/Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h | 2 +- src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.h | 1 - src/Ai/Raid/Ulduar/RaidUlduarTriggerContext.h | 1 - src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.h | 1 - src/Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h | 1 - src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.h | 4 ---- src/Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h | 2 +- src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.h | 1 - src/Bot/PlayerbotAI.cpp | 4 ++-- 33 files changed, 9 insertions(+), 45 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 3d0866f44e0..3e72a5058ee 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -32,7 +32,7 @@ # LEVELS # GEAR # QUESTS -# ACTIVITIES +# ACTIVITY # SPELLS # STRATEGIES # RPG STRATEGY diff --git a/src/Ai/Raid/Aq20/RaidAq20TriggerContext.h b/src/Ai/Raid/Aq20/RaidAq20TriggerContext.h index b49ae1c6b79..b0307ca6a28 100644 --- a/src/Ai/Raid/Aq20/RaidAq20TriggerContext.h +++ b/src/Ai/Raid/Aq20/RaidAq20TriggerContext.h @@ -1,7 +1,6 @@ #ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H #define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "NamedObjectContext.h" #include "RaidAq20Triggers.h" diff --git a/src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.h b/src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.h index 97ff7453a47..86bcf8e4764 100644 --- a/src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.h +++ b/src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.h @@ -1,8 +1,6 @@ #ifndef _PLAYERBOT_RAIDAQ20STRATEGY_H #define _PLAYERBOT_RAIDAQ20STRATEGY_H -#include "AiObjectContext.h" -#include "Multiplier.h" #include "Strategy.h" class RaidAq20Strategy : public Strategy diff --git a/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h b/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h index aa6b57c9f9e..de2ce005867 100644 --- a/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h +++ b/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h @@ -1,7 +1,6 @@ #ifndef _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "NamedObjectContext.h" #include "RaidBwlTriggers.h" diff --git a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h b/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h index 4308871c856..e09ea2f3ee5 100644 --- a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h +++ b/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h @@ -2,8 +2,6 @@ #ifndef _PLAYERBOT_RAIDBWLSTRATEGY_H #define _PLAYERBOT_RAIDBWLSTRATEGY_H -#include "AiObjectContext.h" -#include "Multiplier.h" #include "Strategy.h" class RaidBwlStrategy : public Strategy diff --git a/src/Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h b/src/Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h index 0c58f6cbffb..c545e10eba6 100644 --- a/src/Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h +++ b/src/Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h @@ -1,7 +1,6 @@ #ifndef _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H #define _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "NamedObjectContext.h" #include "RaidEoETriggers.h" diff --git a/src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.h b/src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.h index eb7a147bd6f..ba9802116a1 100644 --- a/src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.h +++ b/src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.h @@ -1,8 +1,6 @@ #ifndef _PLAYERBOT_RAIDEOESTRATEGY_H #define _PLAYERBOT_RAIDEOESTRATEGY_H -#include "AiObjectContext.h" -#include "Multiplier.h" #include "Strategy.h" class RaidEoEStrategy : public Strategy diff --git a/src/Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h b/src/Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h index d12b0ce4672..35a0f138eda 100644 --- a/src/Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h +++ b/src/Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H #include "RaidGruulsLairTriggers.h" -#include "AiObjectContext.h" +#include "NamedObjectContext.h" class RaidGruulsLairTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.h b/src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.h index ba6f33f0763..0d41d57a800 100644 --- a/src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.h +++ b/src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.h @@ -2,7 +2,6 @@ #define _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H #include "Strategy.h" -#include "Multiplier.h" class RaidGruulsLairStrategy : public Strategy { diff --git a/src/Ai/Raid/Icecrown/RaidIccTriggerContext.h b/src/Ai/Raid/Icecrown/RaidIccTriggerContext.h index 64c320c7207..83f3004668e 100644 --- a/src/Ai/Raid/Icecrown/RaidIccTriggerContext.h +++ b/src/Ai/Raid/Icecrown/RaidIccTriggerContext.h @@ -1,7 +1,6 @@ #ifndef _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "NamedObjectContext.h" #include "RaidIccTriggers.h" diff --git a/src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.h b/src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.h index 53967c33441..fbd54cc6482 100644 --- a/src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.h +++ b/src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.h @@ -1,10 +1,7 @@ #ifndef _PLAYERBOT_RAIDICCSTRATEGY_H #define _PLAYERBOT_RAIDICCSTRATEGY_H -#include "AiObjectContext.h" -#include "Multiplier.h" #include "Strategy.h" -#include "RaidIccMultipliers.h" class RaidIccStrategy : public Strategy { diff --git a/src/Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h b/src/Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h index e3f606c9497..a9c430734f5 100644 --- a/src/Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h +++ b/src/Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H #include "RaidKarazhanTriggers.h" -#include "AiObjectContext.h" +#include "NamedObjectContext.h" class RaidKarazhanTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.h b/src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.h index 7d6b16deef7..4f95bf7b4a4 100644 --- a/src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.h +++ b/src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.h @@ -2,7 +2,6 @@ #define _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_ #include "Strategy.h" -#include "Multiplier.h" class RaidKarazhanStrategy : public Strategy { diff --git a/src/Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h b/src/Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h index 525fe496e81..482152e0ee6 100644 --- a/src/Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h +++ b/src/Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H #include "RaidMagtheridonTriggers.h" -#include "AiObjectContext.h" +#include "NamedObjectContext.h" class RaidMagtheridonTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.h b/src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.h index 7b8ab8f9b18..4d21464aece 100644 --- a/src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.h +++ b/src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.h @@ -2,7 +2,6 @@ #define _PLAYERBOT_RAIDMAGTHERIDONSTRATEGY_H #include "Strategy.h" -#include "Multiplier.h" class RaidMagtheridonStrategy : public Strategy { diff --git a/src/Ai/Raid/MoltenCore/RaidMcTriggerContext.h b/src/Ai/Raid/MoltenCore/RaidMcTriggerContext.h index a62d851dc0d..1f694fe6502 100644 --- a/src/Ai/Raid/MoltenCore/RaidMcTriggerContext.h +++ b/src/Ai/Raid/MoltenCore/RaidMcTriggerContext.h @@ -1,7 +1,6 @@ #ifndef _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "BossAuraTriggers.h" #include "NamedObjectContext.h" #include "RaidMcTriggers.h" diff --git a/src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.h b/src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.h index 45b503e9333..6e77910ec44 100644 --- a/src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.h +++ b/src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.h @@ -1,8 +1,6 @@ #ifndef _PLAYERBOT_RAIDMCSTRATEGY_H #define _PLAYERBOT_RAIDMCSTRATEGY_H -#include "AiObjectContext.h" -#include "Multiplier.h" #include "Strategy.h" class RaidMcStrategy : public Strategy diff --git a/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h b/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h index 4d1557d566b..83afc273d79 100644 --- a/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h +++ b/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h @@ -6,7 +6,6 @@ #ifndef _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "NamedObjectContext.h" #include "RaidNaxxTriggers.h" diff --git a/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h b/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h index 4b8a9a7c095..d2ce821a8f7 100644 --- a/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h +++ b/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h @@ -2,8 +2,6 @@ #ifndef _PLAYERBOT_RAIDNAXXSTRATEGY_H #define _PLAYERBOT_RAIDNAXXSTRATEGY_H -#include "AiObjectContext.h" -#include "Multiplier.h" #include "Strategy.h" class RaidNaxxStrategy : public Strategy diff --git a/src/Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h b/src/Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h index b8a1f4b316f..3c1d406928a 100644 --- a/src/Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h +++ b/src/Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h @@ -1,7 +1,6 @@ #ifndef _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "NamedObjectContext.h" #include "RaidOsTriggers.h" diff --git a/src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.h b/src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.h index 44983f1fa8e..0d9ae7871be 100644 --- a/src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.h +++ b/src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.h @@ -1,8 +1,6 @@ #ifndef _PLAYERBOT_RAIDOSSTRATEGY_H #define _PLAYERBOT_RAIDOSSTRATEGY_H -#include "AiObjectContext.h" -#include "Multiplier.h" #include "Strategy.h" class RaidOsStrategy : public Strategy diff --git a/src/Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h b/src/Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h index dba18f56420..daf624a0bad 100644 --- a/src/Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h +++ b/src/Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h @@ -1,7 +1,6 @@ #ifndef _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H #define _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "NamedObjectContext.h" #include "RaidOnyxiaTriggers.h" diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h b/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h index 737fd3a387a..5b0f8d5e3b1 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h @@ -7,7 +7,7 @@ #define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H #include "RaidSSCTriggers.h" -#include "AiObjectContext.h" +#include "NamedObjectContext.h" class RaidSSCTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h index a994600ba76..08d315d5abe 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h +++ b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h @@ -7,7 +7,6 @@ #define _PLAYERBOT_RAIDSSCSTRATEGY_H_ #include "Strategy.h" -#include "Multiplier.h" class RaidSSCStrategy : public Strategy { diff --git a/src/Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h b/src/Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h index c6b4922d7b7..0bf1d0fdcc8 100644 --- a/src/Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h +++ b/src/Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H #include "RaidTempestKeepTriggers.h" -#include "AiObjectContext.h" +#include "NamedObjectContext.h" class RaidTempestKeepTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.h b/src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.h index 77fd29c360a..b19600bab6e 100644 --- a/src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.h +++ b/src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.h @@ -2,7 +2,6 @@ #define _PLAYERBOT_RAIDTEMPESTKEEPSTRATEGY_H_ #include "Strategy.h" -#include "Multiplier.h" class RaidTempestKeepStrategy : public Strategy { diff --git a/src/Ai/Raid/Ulduar/RaidUlduarTriggerContext.h b/src/Ai/Raid/Ulduar/RaidUlduarTriggerContext.h index e4243fb1062..e093f579745 100644 --- a/src/Ai/Raid/Ulduar/RaidUlduarTriggerContext.h +++ b/src/Ai/Raid/Ulduar/RaidUlduarTriggerContext.h @@ -6,7 +6,6 @@ #ifndef _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "NamedObjectContext.h" #include "RaidUlduarTriggers.h" #include "BossAuraTriggers.h" diff --git a/src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.h b/src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.h index bb2feefe4af..c391f6bdb6f 100644 --- a/src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.h +++ b/src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.h @@ -2,7 +2,6 @@ #ifndef _PLAYERBOT_RAIDULDUARSTRATEGY_H #define _PLAYERBOT_RAIDULDUARSTRATEGY_H -#include "AiObjectContext.h" #include "Strategy.h" class RaidUlduarStrategy : public Strategy diff --git a/src/Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h b/src/Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h index 6566793fde1..6cb5e0f386b 100644 --- a/src/Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h +++ b/src/Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h @@ -6,7 +6,6 @@ #ifndef _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H #define _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "BossAuraTriggers.h" #include "NamedObjectContext.h" #include "RaidVoATriggers.h" diff --git a/src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.h b/src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.h index 04ed2ac3a2f..c30261fe806 100644 --- a/src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.h +++ b/src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.h @@ -3,10 +3,6 @@ #define _PLAYERBOT_RAIDVOASTRATEGY_H #include "Strategy.h" -#include "PlayerbotAI.h" -#include "string" -#include "Trigger.h" -#include "vector" class RaidVoAStrategy : public Strategy { diff --git a/src/Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h b/src/Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h index 5be8bad7f08..cb8bac864c0 100644 --- a/src/Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h +++ b/src/Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h @@ -7,7 +7,7 @@ #define _PLAYERBOT_RAIDZULAMANTRIGGERCONTEXT_H #include "RaidZulAmanTriggers.h" -#include "AiObjectContext.h" +#include "NamedObjectContext.h" class RaidZulAmanTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.h b/src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.h index c49e088886d..2cb5e8171ea 100644 --- a/src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.h +++ b/src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.h @@ -7,7 +7,6 @@ #define _PLAYERBOT_RAIDZULAMANSTRATEGY_H_ #include "Strategy.h" -#include "Multiplier.h" class RaidZulAmanStrategy : public Strategy { diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 357678928fe..8feb87cb585 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -54,9 +54,9 @@ #include "Unit.h" #include "UpdateTime.h" #include "Vehicle.h" -#include "../../../../src/server/scripts/Spells/spell_dk.cpp" -const int SPELL_TITAN_GRIP = 49152; +constexpr uint32 SPELL_TITAN_GRIP = 49152; +constexpr uint32 SPELL_DK_FROST_PRESENCE = 48263; std::vector PlayerbotAI::dispel_whitelist = { "mutating injection", From ccce14238e6f5842e7ee90a3f63c3165fc2576d1 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sun, 3 May 2026 07:16:34 -0700 Subject: [PATCH 08/63] Core Update, change to DeserterCheck and signature (#2354) ## Pull Request Description Required change for https://github.com/azerothcore/azerothcore-wotlk/pull/24641 ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [ ] Yes (**explain below**) ## Final Checklist - - [ ] Stability is not compromised. - - [ ] Performance impact is understood, tested, and acceptable. - - [ ] Added logic complexity is justified and explained. - - [ ] Any new bot dialogue lines are translated. - - [ ] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Actions/BattleGroundJoinAction.cpp | 2 +- src/Ai/Base/Trigger/PvpTriggers.cpp | 2 +- src/Bot/Factory/RandomPlayerbotFactory.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ai/Base/Actions/BattleGroundJoinAction.cpp b/src/Ai/Base/Actions/BattleGroundJoinAction.cpp index ab897a1b21a..58ee42cfaf9 100644 --- a/src/Ai/Base/Actions/BattleGroundJoinAction.cpp +++ b/src/Ai/Base/Actions/BattleGroundJoinAction.cpp @@ -343,7 +343,7 @@ bool BGJoinAction::isUseful() return false; // check Deserter debuff - if (!bot->CanJoinToBattleground()) + if (bot->IsDeserter()) return false; // check if has free queue slots (pointless as already making sure not in queue) diff --git a/src/Ai/Base/Trigger/PvpTriggers.cpp b/src/Ai/Base/Trigger/PvpTriggers.cpp index 31fcd0357e3..b20c5df979a 100644 --- a/src/Ai/Base/Trigger/PvpTriggers.cpp +++ b/src/Ai/Base/Trigger/PvpTriggers.cpp @@ -297,7 +297,7 @@ bool PlayerWantsInBattlegroundTrigger::IsActive() if (bot->GetBattleground() && bot->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS) return false; - if (!bot->CanJoinToBattleground()) + if (bot->IsDeserter()) return false; return true; diff --git a/src/Bot/Factory/RandomPlayerbotFactory.cpp b/src/Bot/Factory/RandomPlayerbotFactory.cpp index 617e4006cdf..5307151916f 100644 --- a/src/Bot/Factory/RandomPlayerbotFactory.cpp +++ b/src/Bot/Factory/RandomPlayerbotFactory.cpp @@ -619,7 +619,7 @@ void RandomPlayerbotFactory::CreateRandomBots() else password = accountName; - AccountMgr::CreateAccount(accountName, password); + sAccountMgr->CreateAccount(accountName, password); LOG_DEBUG("playerbots", "Account {} created for random bots", accountName.c_str()); } From 38caa1daa7399f272267cd6ffc9d2d4f154b7f3e Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sun, 3 May 2026 12:41:45 -0700 Subject: [PATCH 09/63] Randombots respect realm PVP setting (#2342) ## Pull Request Description Fix an issue where bots would eventually have pvp set by reset. THis ensures bot pvp states are consistent with realm type. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Corrects behavior to match server intent. - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) searching code, writing it. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Bot/RandomPlayerbotMgr.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Bot/RandomPlayerbotMgr.cpp b/src/Bot/RandomPlayerbotMgr.cpp index 5c0922fb9ed..3c0a9054c09 100644 --- a/src/Bot/RandomPlayerbotMgr.cpp +++ b/src/Bot/RandomPlayerbotMgr.cpp @@ -2077,7 +2077,7 @@ void RandomPlayerbotMgr::Refresh(Player* bot) bot->DurabilityRepairAll(false, 1.0f, false); bot->SetFullHealth(); - bot->SetPvP(true); + bot->SetPvP(sWorld->IsPvPRealm()); PlayerbotFactory factory(bot, bot->GetLevel()); factory.Refresh(); @@ -2642,6 +2642,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) { // ObjectGuid::LowType guid = player->GetGUID().GetCounter(); //not used, conditional could be rewritten for // simplicity. line marked for removal. + player->SetPvP(sWorld->IsPvPRealm()); } else { From 5d9761c9e857dfbdda29ef166b361bd2e342c04c Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 9 May 2026 00:39:32 -0500 Subject: [PATCH 10/63] Implement Battle for Mount Hyjal Strategies (#2258) ### Contingent on https://github.com/mod-playerbots/mod-playerbots/pull/2295/ ## Pull Request Description This PR implements raid strategies for all bosses in everybody's favorite TBC instance, the Battle for Mount Hyjal. As before, I have designed these all to work with IP with 50% damage and healing. I also did not merge the 1.88x buff to Vanilla & TBC healing items that IP recently implemented. The next post will outline all implemented strategies. Note: Set to draft for now as I may tweak Archimonde some more, but I generally consider Hyjal complete, subject to comments. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. As with previous strategies, I've worked within the existing context of actions/multipliers/triggers. This strategy does implement new spell hooks, but I don't think that is problematic for performance, and I'll explain why they are necessary in the next post. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [x] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) There are many new triggers, multipliers, and actions, but they will be evaluated only if the "hyjal" strategy is added. Additionally, I've attempted to order and implement checks in a manner to limit performance impact and have tested with .pmon active. In general, I consider performance to be highly important so am always working on ways to limit the impact (e.g., trying to use the most targeted grid searches available when needed). - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Only in the Hyjal instance, for obvious reasons. - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [x] Yes (**explain below**) New decision branches apply only in the new Hyjal instance, for obvious reasons. Maintenance complexity should not be increased as code outside of the new Hyjal files is not impacted, except to the extent needed to register and implement the strategy in the same manner as all existing strategies. Exception: As noted, I did add new spell hooks, but if they become problematic, they can easily be removed. ## Messages to Translate - Does this change add bot messages to translate? - - [x] No - - [ ] Yes (**list messages in the table**) | Message key | Default message | | --------------- | ------------------ | | | | | | | ## AI Assistance - Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) Claude Sonnet 4.6 was used for more complex implementation and occasionally GPT-5 mini was used for simple questions. I do not use AI for brainstorming or developing strategies, only for implementation and review of code. Most of this was written by me directly, but most notably I needed AI support to implement the spell hooks and triggers/actions that relied on them. Everything was reviewed and tested many times. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. Caveat: The full impact of implementing the spell hooks on a broad scale is beyond my knowledge to evaluate. - - [x] Added logic complexity is justified and explained. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- .../Action/RaidHyjalSummitActions.cpp | 1200 +++++++++++++++++ .../Action/RaidHyjalSummitActions.h | 277 ++++ .../Multiplier/RaidHyjalSummitMultipliers.cpp | 295 ++++ .../Multiplier/RaidHyjalSummitMultipliers.h | 125 ++ .../RaidHyjalSummitActionContext.h | 218 +++ .../RaidHyjalSummitTriggerContext.h | 218 +++ .../Strategy/RaidHyjalSummitStrategy.cpp | 137 ++ .../Strategy/RaidHyjalSummitStrategy.h | 22 + .../Trigger/RaidHyjalSummitTriggers.cpp | 357 +++++ .../Trigger/RaidHyjalSummitTriggers.h | 271 ++++ .../Util/RaidHyjalSummitHelpers.cpp | 268 ++++ .../HyjalSummit/Util/RaidHyjalSummitHelpers.h | 143 ++ .../Util/RaidHyjalSummitScripts.cpp | 211 +++ src/Ai/Raid/RaidStrategyContext.h | 3 + src/Bot/Engine/BuildSharedActionContexts.cpp | 2 + src/Bot/Engine/BuildSharedTriggerContexts.cpp | 2 + src/Bot/PlayerbotAI.cpp | 14 +- src/Script/Playerbots.cpp | 2 + 18 files changed, 3760 insertions(+), 5 deletions(-) create mode 100644 src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp create mode 100644 src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h create mode 100644 src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp create mode 100644 src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h create mode 100644 src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h create mode 100644 src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h create mode 100644 src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp create mode 100644 src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.h create mode 100644 src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp create mode 100644 src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h create mode 100644 src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp create mode 100644 src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h create mode 100644 src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp diff --git a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp new file mode 100644 index 00000000000..4a5f36d0214 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp @@ -0,0 +1,1200 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidHyjalSummitActions.h" +#include "RaidHyjalSummitHelpers.h" +#include "Playerbots.h" +#include "RaidBossHelpers.h" +#include "Timer.h" + +using namespace HyjalSummitHelpers; + +// General + +bool HyjalSummitEraseTrackersAction::Execute(Event /*event*/) +{ + const ObjectGuid guid = bot->GetGUID(); + const uint32 instanceId = bot->GetMap()->GetInstanceId(); + + bool erased = false; + if (botAI->IsTank(bot)) + { + if (!AI_VALUE2(Unit*, "find target", "kaz'rogal")) + { + if (kazrogalTankStep.erase(guid) > 0) + erased = true; + + if (isBelowManaThreshold.erase(guid) > 0) + erased = true; + } + + if (!AI_VALUE2(Unit*, "find target", "azgalor")) + { + if (azgalorTankStep.erase(guid) > 0) + erased = true; + + if (rainOfFirePosition.erase(instanceId) > 0) + erased = true; + } + + return erased; + } + else + { + if (!AI_VALUE2(Unit*, "find target", "rage winterchill")) + { + if (hasReachedWinterchillPosition.erase(guid) > 0) + erased = true; + + if (deathAndDecayPosition.erase(instanceId) > 0) + erased = true; + } + + if (!AI_VALUE2(Unit*, "find target", "anetheron") && + hasReachedAnetheronPosition.erase(guid) > 0) + erased = true; + + if (!AI_VALUE2(Unit*, "find target", "kaz'rogal") && + isBelowManaThreshold.erase(guid) > 0) + erased = true; + + if (!AI_VALUE2(Unit*, "find target", "archimonde") && + doomfireTrails.erase(instanceId) > 0) + erased = true; + + return erased; + } +} + +// Rage Winterchill + +bool RageWinterchillMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill"); + if (!winterchill) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(HyjalSummitSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", winterchill)) + return botAI->CastSpell("steady shot", winterchill); + + return false; +} + +// Position is back towards the center of the base to give some more room to manuever +bool RageWinterchillMainTankPositionBossAction::Execute(Event /*event*/) +{ + Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill"); + if (!winterchill) + return false; + + if (bot->GetVictim() != winterchill) + return Attack(winterchill); + + if (winterchill->GetVictim() == bot) + { + const Position& position = WINTERCHILL_TANK_POSITION; + const float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 4.0f) + { + float moveX, moveY, moveZ; + constexpr float moveDist = 5.0f; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + } + + return false; +} + +// Spread ranged DPS in a circle initially--after the initial spread, movement is free +bool RageWinterchillSpreadRangedInCircleAction::Execute(Event /*event*/) +{ + RangedGroups groups = GetRangedGroups(botAI, bot); + + if (groups.healers.empty() && groups.rangedDps.empty()) + return false; + + const ObjectGuid guid = bot->GetGUID(); + + if (!hasReachedWinterchillPosition[guid]) + { + auto [botIndex, count] = GetBotCircleIndexAndCount(botAI, bot, groups); + const float radius = botAI->IsHeal(bot) ? 25.0f : 35.0f; + float angle = 0.0f; + + constexpr float arcSpan = 2.0f * M_PI; + constexpr float arcCenter = 0.0f; + constexpr float arcStart = arcCenter - arcSpan / 2.0f; + + angle = (count == 1) ? arcCenter : + (arcStart + arcSpan * static_cast(botIndex) / static_cast(count - 1)); + + const Position& position = WINTERCHILL_TANK_POSITION; + float targetX = position.GetPositionX() + radius * std::cos(angle); + float targetY = position.GetPositionY() + radius * std::sin(angle); + + if (bot->GetExactDist2d(targetX, targetY) > 2.0f) + { + float moveX, moveY, moveZ; + constexpr float moveDist = 10.0f; + if (GetGroundedStepPosition(bot, targetX, targetY, moveDist, + moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + else + { + hasReachedWinterchillPosition[guid] = true; + } + } + + return false; +} + +bool RageWinterchillMeleeGetOutOfDeathAndDecayAction::Execute(Event /*event*/) +{ + Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill"); + if (!winterchill) + return false; + + DeathAndDecayData* data = + GetActiveWinterchillDeathAndDecay(bot->GetMap()->GetInstanceId()); + if (!data) + return false; + + constexpr float moveDist = 10.0f; + + const float centerX = data->position.GetPositionX(); + const float centerY = data->position.GetPositionY(); + const float currentDistance = bot->GetExactDist2d(centerX, centerY); + float escapeAngle = + std::atan2(bot->GetPositionY() - centerY, bot->GetPositionX() - centerX); + + if (currentDistance <= 0.1f) + { + escapeAngle = std::atan2(centerY - winterchill->GetPositionY(), + centerX - winterchill->GetPositionX()); + } + + for (float delta = 0.0f; delta <= M_PI; delta += M_PI / 8.0f) + { + for (float angle : { escapeAngle + delta, escapeAngle - delta }) + { + if (delta == 0.0f && angle != escapeAngle) + continue; + + const float targetX = centerX + std::cos(angle) * DEATH_AND_DECAY_SAFE_RADIUS; + const float targetY = centerY + std::sin(angle) * DEATH_AND_DECAY_SAFE_RADIUS; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, targetX, targetY, moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + } + + return false; +} + +// Anetheron + +bool AnetheronMisdirectBossAndInfernalsToTanksAction::Execute(Event /*event*/) +{ + Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron"); + if (!anetheron) + return false; + + if (anetheron->GetHealthPct() > 95.0f) + { + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(HyjalSummitSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", anetheron)) + return botAI->CastSpell("steady shot", anetheron); + } + + if (Unit* infernal = AI_VALUE2(Unit*, "find target", "towering infernal"); + infernal && infernal->GetHealthPct() > 50.0f) + { + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); + if (!firstAssistTank) + return false; + + if (botAI->CanCastSpell("misdirection", firstAssistTank)) + return botAI->CastSpell("misdirection", firstAssistTank); + + if (bot->HasAura(static_cast(HyjalSummitSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", infernal)) + return botAI->CastSpell("steady shot", infernal); + } + + return false; +} + +// Position is back towards the center of the base, near the crossroads +bool AnetheronMainTankPositionBossAction::Execute(Event /*event*/) +{ + Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron"); + if (!anetheron) + return false; + + MarkTargetWithSquare(bot, anetheron); + SetRtiTarget(botAI, "square", anetheron); + + if (bot->GetVictim() != anetheron) + return Attack(anetheron); + + if (anetheron->GetVictim() == bot) + { + const Position& position = ANETHERON_TANK_POSITION; + const float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 4.0f) + { + float moveX, moveY, moveZ; + constexpr float moveDist = 5.0f; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + } + + return false; +} + +bool AnetheronSpreadRangedInCircleAction::Execute(Event /*event*/) +{ + RangedGroups groups = GetRangedGroups(botAI, bot); + + if (groups.healers.empty() && groups.rangedDps.empty()) + return false; + + const ObjectGuid guid = bot->GetGUID(); + + if (!hasReachedAnetheronPosition[guid]) + { + auto [botIndex, count] = GetBotCircleIndexAndCount(botAI, bot, groups); + const float radius = botAI->IsHeal(bot) ? 27.0f : 34.0f; + float angle = 0.0f; + + constexpr float arcSpan = M_PI * 2.0f; + constexpr float arcCenter = 0.0f; + constexpr float arcStart = arcCenter - arcSpan / 2.0f; + + angle = (count == 1) ? arcCenter : + (arcStart + arcSpan * static_cast(botIndex) / static_cast(count - 1)); + + const Position& position = ANETHERON_TANK_POSITION; + + float targetX = position.GetPositionX() + radius * std::sin(angle); + float targetY = position.GetPositionY() + radius * std::cos(angle); + + if (bot->GetExactDist2d(targetX, targetY) > 2.0f) + { + constexpr float moveDist = 10.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, targetX, targetY, moveDist, + moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + else + { + hasReachedAnetheronPosition[guid] = true; + } + } + else + { + constexpr float safeDistFromPlayer = 6.0f; + constexpr uint32 minInterval = 2000; + if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistFromPlayer)) + return FleePosition(nearestPlayer->GetPosition(), safeDistFromPlayer, minInterval); + } + + return false; +} + +// Run to the nearest of two Infernal tanking spots, East and West of Anetheron +bool AnetheronBringInfernalToInfernalTankAction::Execute(Event /*event*/) +{ + const Position& position = GetClosestInfernalTankPosition(bot); + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + { + constexpr float moveDist = 10.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_FORCED, true, false); + } + } + + return false; +} + +// Pick up the Infernal and bring it to the closest Infernal tanking position +bool AnetheronFirstAssistTankPickUpInfernalsAction::Execute(Event /*event*/) +{ + Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron"); + if (!anetheron) + return false; + + Player* infernoTarget = GetInfernoTarget(anetheron); + if (infernoTarget && infernoTarget != bot) + { + float distToInfernoTarget = bot->GetExactDist2d(infernoTarget); + if (distToInfernoTarget > 5.0f) + { + constexpr float moveDist = 10.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, infernoTarget->GetPositionX(), + infernoTarget->GetPositionY(), moveDist, + moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_FORCED, true, false); + } + } + } + + Unit* infernal = AI_VALUE2(Unit*, "find target", "towering infernal"); + if (!infernal) + return false; + + MarkTargetWithDiamond(bot, infernal); + SetRtiTarget(botAI, "diamond", infernal); + + if (bot->GetVictim() != infernal) + return Attack(infernal); + + if ((infernoTarget && infernoTarget == bot) || + (infernal->GetVictim() == bot && bot->IsWithinMeleeRange(infernal))) + { + const Position& position = GetClosestInfernalTankPosition(bot); + const float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 3.0f) + { + constexpr float moveDist = 5.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + } + + return false; +} + +// Only nearbyish ranged DPS should attack Infernals +bool AnetheronAssignDpsPriorityAction::Execute(Event /*event*/) +{ + Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron"); + if (!anetheron) + return false; + + if (botAI->IsMelee(bot)) + { + SetRtiTarget(botAI, "square", anetheron); + + if (bot->GetVictim() != anetheron) + return Attack(anetheron); + + return false; + } + if (Unit* infernal = AI_VALUE2(Unit*, "find target", "towering infernal")) + { + constexpr float safeDistFromInfernal = 10.0f; + constexpr uint32 minInterval = 0; + if (infernal->GetVictim() != bot && + bot->GetDistance2d(infernal) < safeDistFromInfernal) + { + return FleePosition(infernal->GetPosition(), safeDistFromInfernal, minInterval); + } + + if (anetheron->GetHealthPct() > 10.0f && botAI->IsRangedDps(bot) && + bot->GetDistance2d(infernal) < 50.0f) + { + if (Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); + !firstAssistTank || infernal->GetVictim() == firstAssistTank) + { + SetRtiTarget(botAI, "diamond", infernal); + + if (bot->GetTarget() != infernal->GetGUID()) + return Attack(infernal); + } + } + } + else if (botAI->IsRangedDps(bot)) + { + SetRtiTarget(botAI, "square", anetheron); + + if (bot->GetTarget() != anetheron->GetGUID()) + return Attack(anetheron); + } + + return false; +} + +// Kaz'rogal + +bool KazrogalMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal"); + if (!kazrogal) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(HyjalSummitSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", kazrogal)) + return botAI->CastSpell("steady shot", kazrogal); + + return false; +} + +// Position is near the gate so the raid can get start on DPS ASAP +bool KazrogalMainTankPositionBossAction::Execute(Event /*event*/) +{ + Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal"); + if (!kazrogal) + return false; + + if (bot->GetVictim() != kazrogal) + return Attack(kazrogal); + + if (kazrogal->GetVictim() == bot && bot->IsWithinMeleeRange(kazrogal)) + { + const ObjectGuid guid = bot->GetGUID(); + TankPositionState state = kazrogalTankStep.count(guid) ? + kazrogalTankStep[guid] : TankPositionState::MovingToTransition; + + constexpr float maxDistance = 2.0f; + const Position& position = state == TankPositionState::MovingToTransition ? + KAZROGAL_TANK_TRANSITION_POSITION : KAZROGAL_TANK_FINAL_POSITION; + const float distToPosition = bot->GetExactDist2d(position); + + if (distToPosition > maxDistance) + { + constexpr float moveDist = 5.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + + if (state == TankPositionState::MovingToTransition && distToPosition <= maxDistance) + { + kazrogalTankStep[guid] = TankPositionState::MovingToFinal; + } + else if (state != TankPositionState::MovingToTransition && + distToPosition <= maxDistance) + { + const float orientation = atan2(kazrogal->GetPositionY() - bot->GetPositionY(), + kazrogal->GetPositionX() - bot->GetPositionX()); + bot->SetFacingTo(orientation); + kazrogalTankStep[guid] = TankPositionState::Positioned; + } + } + + return false; +} + +// To spread cleave damage +bool KazrogalAssistTanksMoveInFrontOfBossAction::Execute(Event /*event*/) +{ + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (bot->GetExactDist2d(mainTank) > 4.0f) + { + constexpr float moveDist = 10.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, mainTank->GetPositionX(), mainTank->GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + + return false; +} + +bool KazrogalSpreadRangedInArcAction::Execute(Event /*event*/) +{ + Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal"); + if (!kazrogal) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector rangedMembers; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !botAI->IsRanged(member)) + continue; + + rangedMembers.push_back(member); + } + + if (rangedMembers.empty()) + return false; + + size_t count = rangedMembers.size(); + auto findIt = std::find(rangedMembers.begin(), rangedMembers.end(), bot); + size_t botIndex = (findIt != rangedMembers.end()) ? + std::distance(rangedMembers.begin(), findIt) : 0; + + constexpr float arcSpan = M_PI / 3.0f; + constexpr float arcCenter = 4.225f; + constexpr float arcStart = arcCenter - arcSpan / 2.0f; + + constexpr float radius = 20.0f; + float angle = (count == 1) ? arcCenter : + (arcStart + arcSpan * static_cast(botIndex) / static_cast(count - 1)); + + float targetX = kazrogal->GetPositionX() + radius * std::cos(angle); + float targetY = kazrogal->GetPositionY() + radius * std::sin(angle); + + if (bot->GetExactDist2d(targetX, targetY) > 0.5f) + { + constexpr float moveDist = 10.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, targetX, targetY, moveDist, + moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + + return false; +} + +bool KazrogalLowManaBotTakeDefensiveMeasuresAction::Execute(Event /*event*/) +{ + switch (bot->getClass()) + { + case CLASS_HUNTER: + if (!botAI->HasAura("aspect of the viper", bot) && + botAI->CanCastSpell("aspect of the viper", bot)) + { + return botAI->CastSpell("aspect of the viper", bot); + } + return false; + + case CLASS_WARLOCK: + if (botAI->CanCastSpell("life tap", bot) && + botAI->CastSpell("life tap", bot)) + { + return true; + } + break; + + case CLASS_MAGE: + if (bot->HasAura(static_cast(HyjalSummitSpells::SPELL_MARK_OF_KAZROGAL)) && + bot->GetPower(POWER_MANA) <= 1200 && botAI->CanCastSpell("ice block", bot) && + botAI->CastSpell("ice block", bot)) + { + return true; + } + break; + + case CLASS_PALADIN: + if (bot->HasAura(static_cast(HyjalSummitSpells::SPELL_MARK_OF_KAZROGAL)) && + bot->GetPower(POWER_MANA) <= 1200 && botAI->CanCastSpell("divine shield", bot) && + botAI->CastSpell("divine shield", bot)) + { + return true; + } + break; + + default: + break; + } + + if (bot->GetPower(POWER_MANA) <= 3200) + isBelowManaThreshold.try_emplace(bot->GetGUID(), true); + + constexpr float safeDistance = 16.0f; + + Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistance); + if (!nearestPlayer) + return false; + + const float currentDistance = bot->GetDistance2d(nearestPlayer); + if (currentDistance < safeDistance) + { + Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal"); + if (!kazrogal) + return false; + + if (bot->GetExactDist2d(kazrogal) > 36.0f) + return MoveAway(nearestPlayer, safeDistance - currentDistance); + else + return MoveFromGroup(safeDistance); + } + + return false; +} + +// Warlocks: Use Shadow Ward if Mark is applied and mana is <= 3000 +// Paladins: Use Shadow Resistance Aura if Priest Shadow Protection is not up +bool KazrogalCastShadowProtectionSpellAction::Execute(Event /*event*/) +{ + if (bot->getClass() == CLASS_WARLOCK && bot->GetPower(POWER_MANA) <= 3000 && + botAI->CanCastSpell("shadow ward", bot)) + return botAI->CastSpell("shadow ward", bot); + + if (bot->getClass() == CLASS_PALADIN && + botAI->CanCastSpell("shadow resistance aura", bot)) + return botAI->CastSpell("shadow resistance aura", bot); + + return false; +} + +// Azgalor + +bool AzgalorMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (!azgalor) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(HyjalSummitSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", azgalor)) + return botAI->CastSpell("steady shot", azgalor); + + return false; +} + +// Two-step move: back up toward the base, then move back toward the base entrance +// to turn Azgalor away from the raid +bool AzgalorMainTankPositionBossAction::Execute(Event /*event*/) +{ + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (!azgalor) + return false; + + MarkTargetWithStar(bot, azgalor); + SetRtiTarget(botAI, "star", azgalor); + + if (bot->GetVictim() != azgalor) + return Attack(azgalor); + + if (azgalor->GetVictim() == bot && bot->IsWithinMeleeRange(azgalor)) + { + const ObjectGuid guid = bot->GetGUID(); + auto it = azgalorTankStep.try_emplace( + guid, TankPositionState::MovingToTransition).first; + TankPositionState state = it->second; + + constexpr float maxDistance = 2.0f; + const Position& position = state == TankPositionState::MovingToTransition ? + AZGALOR_TANK_TRANSITION_POSITION : AZGALOR_TANK_FINAL_POSITION; + const float distToPosition = bot->GetExactDist2d(position); + + if (distToPosition > maxDistance) + { + constexpr float moveDist = 5.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + + if (state == TankPositionState::MovingToTransition && distToPosition <= maxDistance) + { + azgalorTankStep[guid] = TankPositionState::MovingToFinal; + } + else if (state != TankPositionState::MovingToTransition && + distToPosition <= maxDistance) + { + const float orientation = atan2(azgalor->GetPositionY() - bot->GetPositionY(), + azgalor->GetPositionX() - bot->GetPositionX()); + bot->SetFacingTo(orientation); + azgalorTankStep[guid] = TankPositionState::Positioned; + } + } + + return false; +} + +bool AzgalorDisperseRangedAction::Execute(Event /*event*/) +{ + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (!azgalor) + return false; + + TankPositionState tankState = GetAzgalorTankPositionState(botAI, bot); + const float safeDistFromBoss = + (tankState == TankPositionState::MovingToTransition ? 35.0f : 29.0f); + constexpr uint32 minInterval = 0; + + if (bot->GetExactDist2d(azgalor) < safeDistFromBoss && + FleePosition(azgalor->GetPosition(), safeDistFromBoss, minInterval)) + return true; + + Unit* doomguard = AI_VALUE2(Unit*, "find target", "lesser doomguard"); + constexpr float safeDistFromDoomguard = 14.0f; + constexpr float safeDistFromPlayer = 5.0f; + + if (doomguard && bot->GetExactDist2d(doomguard) < safeDistFromDoomguard) + { + return FleePosition(doomguard->GetPosition(), safeDistFromDoomguard); + } + else if (!doomguard || bot->GetTarget() != doomguard->GetGUID()) + { + Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistFromPlayer); + if (nearestPlayer) + return FleePosition(nearestPlayer->GetPosition(), safeDistFromPlayer); + } + + return false; +} + +bool AzgalorMeleeGetOutOfFireAndSwapTargetsAction::Execute(Event /*event*/) +{ + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (!azgalor) + return false; + + constexpr float singleTickMoveAwayDist = 6.0f; + if (!IsInRainOfFire(bot, RAIN_OF_FIRE_RADIUS + singleTickMoveAwayDist)) + { + SetRtiTarget(botAI, "star", azgalor); + return false; + } + + Unit* doomguard = AI_VALUE2(Unit*, "find target", "lesser doomguard"); + Unit* desiredTarget = doomguard; + + if (!desiredTarget) + { + SetRtiTarget(botAI, "star", azgalor); + return MoveAway(azgalor, 5.0f); + } + + SetRtiTarget(botAI, "circle", desiredTarget); + + if (!bot->IsWithinMeleeRange(desiredTarget)) + { + constexpr float moveDist = 10.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, desiredTarget->GetPositionX(), + desiredTarget->GetPositionY(), moveDist, + moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + + if (bot->GetVictim() != desiredTarget || bot->GetTarget() != desiredTarget->GetGUID()) + return Attack(desiredTarget); + + return false; +} + +// Wait for the tank to get to the transition position (i.e., move in to attack as +// Azgalor turns away from the raid) +bool AzgalorWaitAtSafePositionAction::Execute(Event /*event*/) +{ + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (!azgalor) + return false; + + SetRtiTarget(botAI, "star", azgalor); + + const Position& position = AZGALOR_DOOMGUARD_POSITION; + constexpr float moveDist = 10.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + botAI->Reset(); + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +// The spot is between the paths leading from Thrall's keep +bool AzgalorMoveToDoomguardTankAction::Execute(Event /*event*/) +{ + const Position& position = AZGALOR_DOOMGUARD_POSITION; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 5.0f) + { + constexpr float moveDist = 10.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_FORCED, true, false); + } + } + + return false; +} + +bool AzgalorFirstAssistTankPositionDoomguardAction::Execute(Event /*event*/) +{ + const Position& position = AZGALOR_DOOMGUARD_POSITION; + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + float moveDist = 0.0f; + bool shouldMove = false; + bool moveBackwards = false; + + if (Unit* doomguard = AI_VALUE2(Unit*, "find target", "lesser doomguard")) + { + MarkTargetWithCircle(bot, doomguard); + SetRtiTarget(botAI, "circle", doomguard); + + if (bot->GetVictim() != doomguard) + return Attack(doomguard); + + if (doomguard->GetVictim() == bot && bot->IsWithinMeleeRange(doomguard) && + distToPosition > 3.0f) + { + moveDist = std::min(5.0f, distToPosition); + shouldMove = true; + moveBackwards = true; + } + } + else if (distToPosition > 3.0f) + { + moveDist = std::min(10.0f, distToPosition); + shouldMove = true; + moveBackwards = false; + } + else + { + return true; + } + + if (shouldMove) + { + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, moveBackwards); + } + } + + return false; +} + +// Only nearbyish ranged DPS should attack Doomguards; 65 yards should get to the +// side of Azgalor but not bring in any ranged standing in front +bool AzgalorRangedDpsPrioritizeDoomguardsAction::Execute(Event /*event*/) +{ + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (!azgalor) + return false; + + if (azgalor->GetHealthPct() > 10.0f) + { + if (Unit* doomguard = AI_VALUE2(Unit*, "find target", "lesser doomguard"); + doomguard && bot->GetDistance2d(doomguard) < 65.0f) + { + SetRtiTarget(botAI, "circle", doomguard); + + if (bot->GetTarget() != doomguard->GetGUID()) + return Attack(doomguard); + } + } + else + { + SetRtiTarget(botAI, "star", azgalor); + + if (bot->GetTarget() != azgalor->GetGUID()) + return Attack(azgalor); + } + + return false; +} + +// Archimonde + +bool ArchimondeMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + if (!archimonde) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(HyjalSummitSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", archimonde)) + return botAI->CastSpell("steady shot", archimonde); + + return false; +} + +// Initially move Archimonde up the hill a bit to get space from the World Tree +bool ArchimondeMoveBossToInitialPositionAction::Execute(Event /*event*/) +{ + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + if (!archimonde) + return false; + + if (bot->GetVictim() != archimonde) + return Attack(archimonde); + + if (archimonde->GetVictim() == bot && bot->IsWithinMeleeRange(archimonde) && + bot->GetHealthPct() > 50.0f) + { + const Position& position = ARCHIMONDE_INITIAL_POSITION; + const float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 3.0f) + { + constexpr float moveDist = 5.0f; + float moveX, moveY, moveZ; + if (GetGroundedStepPosition(bot, position.GetPositionX(), position.GetPositionY(), + moveDist, moveX, moveY, moveZ)) + { + return MoveTo(HYJAL_SUMMIT_MAP_ID, moveX, moveY, moveZ, false, false, false, + false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + } + + return false; +} + +bool ArchimondeCastFearImmunitySpellAction::Execute(Event /*event*/) +{ + if (bot->getClass() == CLASS_PRIEST) + return CastFearWardOnMainTank(); + else + return UseTremorTotemStrategy(); +} + +bool ArchimondeCastFearImmunitySpellAction::CastFearWardOnMainTank() +{ + Player* mainTank = GetGroupMainTank(botAI, bot); + if (mainTank && botAI->CanCastSpell("fear ward", mainTank)) + return botAI->CastSpell("fear ward", mainTank); + + return false; +} + +bool ArchimondeCastFearImmunitySpellAction::UseTremorTotemStrategy() +{ + if (!botAI->HasStrategy("tremor", BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("+tremor", BOT_STATE_COMBAT); + return botAI->HasStrategy("tremor", BOT_STATE_COMBAT); + } + + return false; +} + +// (1) Try to run away from the Air Burst target +// (2) At the beginning of the fight, spread ranged in anticipation of Air Burst +bool ArchimondeSpreadToAvoidAirBurstAction::Execute(Event /*event*/) +{ + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + if (!archimonde) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (mainTank && bot != mainTank) + { + const float distanceToMainTank = bot->GetDistance2d(mainTank); + bool shouldMoveFromMainTank = false; + if (AirBurstData* data = GetRecentArchimondeAirBurst(bot->GetMap()->GetInstanceId())) + { + bool isRelevantAirBurstTarget = + data->targetGuid == mainTank->GetGUID() || data->targetGuid == bot->GetGUID(); + shouldMoveFromMainTank = + isRelevantAirBurstTarget && distanceToMainTank < AIR_BURST_SAFE_DISTANCE; + } + + if (!shouldMoveFromMainTank && archimonde->HasUnitState(UNIT_STATE_CASTING)) + { + Spell* spell = archimonde->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (spell && spell->m_spellInfo->Id == + static_cast(HyjalSummitSpells::SPELL_AIR_BURST)) + { + Unit* spellTarget = spell->m_targets.GetUnitTarget(); + if ((spellTarget == mainTank || spellTarget == bot) && + distanceToMainTank < AIR_BURST_SAFE_DISTANCE) + { + shouldMoveFromMainTank = true; + } + } + } + + if (shouldMoveFromMainTank) + return MoveAway(mainTank, AIR_BURST_SAFE_DISTANCE - distanceToMainTank); + } + + if (archimonde->GetHealthPct() < 90.0f) + return false; + + constexpr float safeDistFromPlayer = 10.0f; + constexpr uint32 minInterval = 1000; + + if (botAI->IsRanged(bot)) + { + Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistFromPlayer); + if (nearestPlayer && + FleePosition(nearestPlayer->GetPosition(), safeDistFromPlayer, minInterval)) + { + return true; + } + } + + return false; +} + +bool ArchimondeAvoidDoomfireAction::Execute(Event /*event*/) +{ + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + if (!archimonde) + return false; + + constexpr float dangerDist = 10.0f; + constexpr uint32 trailDuration = 18000; + + const uint32 instanceId = bot->GetMap()->GetInstanceId(); + const uint32 now = getMSTime(); + + auto it = doomfireTrails.find(instanceId); + if (it == doomfireTrails.end() || it->second.empty()) + return false; + + it->second.erase(std::remove_if(it->second.begin(), it->second.end(), + [now](const DoomfireTrailData& d) + { + return getMSTimeDiff(d.recordTime, now) > trailDuration; + }), it->second.end()); + + float totalDx = 0.0f, totalDy = 0.0f; + for (auto const& data : it->second) + { + const float d = bot->GetExactDist2d(data.position.GetPositionX(), + data.position.GetPositionY()); + + if (d < dangerDist && d > 0.0f) + { + const float weight = (dangerDist - d) / dangerDist; + totalDx += (bot->GetPositionX() - data.position.GetPositionX()) / d * weight; + totalDy += (bot->GetPositionY() - data.position.GetPositionY()) / d * weight; + } + } + + if (totalDx != 0.0f || totalDy != 0.0f) + { + const float norm = std::sqrt(totalDx * totalDx + totalDy * totalDy); + const float moveDist = std::min(norm * dangerDist, dangerDist); + if (moveDist < 0.5f) + return false; + + const float targetX = bot->GetPositionX() + (totalDx / norm) * moveDist; + const float targetY = bot->GetPositionY() + (totalDy / norm) * moveDist; + + const MovementPriority priority = botAI->IsHeal(bot) ? + MovementPriority::MOVEMENT_COMBAT : MovementPriority::MOVEMENT_FORCED; + + const bool backwards = archimonde->GetVictim() == bot; + + return MoveTo(HYJAL_SUMMIT_MAP_ID, targetX, targetY, bot->GetPositionZ(), + false, false, false, false, priority, true, backwards); + } + + return false; +} + +bool ArchimondeRemoveDoomfireDotAction::Execute(Event /*event*/) +{ + switch (bot->getClass()) + { + case CLASS_MAGE: + return botAI->CanCastSpell("ice block", bot) && + botAI->CastSpell("ice block", bot); + + case CLASS_PALADIN: + return botAI->CanCastSpell("divine shield", bot) && + botAI->CastSpell("divine shield", bot); + + case CLASS_ROGUE: + return botAI->CanCastSpell("cloak of shadows", bot) && + botAI->CastSpell("cloak of shadows", bot); + + default: + return false; + } +} diff --git a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h new file mode 100644 index 00000000000..e741a7d2635 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDHYJALSUMMITACTIONS_H +#define _PLAYERBOT_RAIDHYJALSUMMITACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "MovementActions.h" + +// General + +class HyjalSummitEraseTrackersAction : public Action +{ +public: + HyjalSummitEraseTrackersAction( + PlayerbotAI* botAI) : Action(botAI, "hyjal summit erase trackers") {} + bool Execute(Event event) override; +}; + +// Rage Winterchill + +class RageWinterchillMisdirectBossToMainTankAction : public AttackAction +{ +public: + RageWinterchillMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "rage winterchill misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class RageWinterchillMainTankPositionBossAction : public AttackAction +{ +public: + RageWinterchillMainTankPositionBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "rage winterchill main tank position boss") {} + bool Execute(Event event) override; +}; + +class RageWinterchillSpreadRangedInCircleAction : public MovementAction +{ +public: + RageWinterchillSpreadRangedInCircleAction( + PlayerbotAI* botAI) : MovementAction(botAI, "rage winterchill spread ranged in circle") {} + bool Execute(Event event) override; +}; + +class RageWinterchillMeleeGetOutOfDeathAndDecayAction : public AttackAction +{ +public: + RageWinterchillMeleeGetOutOfDeathAndDecayAction( + PlayerbotAI* botAI) : AttackAction(botAI, "rage winterchill melee get out of death and decay") {} + bool Execute(Event event) override; +}; + +// Anetheron + +class AnetheronMisdirectBossAndInfernalsToTanksAction : public AttackAction +{ +public: + AnetheronMisdirectBossAndInfernalsToTanksAction( + PlayerbotAI* botAI) : AttackAction(botAI, "anetheron misdirect boss and infernals to tanks") {} + bool Execute(Event event) override; +}; + +class AnetheronMainTankPositionBossAction : public AttackAction +{ +public: + AnetheronMainTankPositionBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "anetheron main tank position boss") {} + bool Execute(Event event) override; +}; + +class AnetheronSpreadRangedInCircleAction : public MovementAction +{ +public: + AnetheronSpreadRangedInCircleAction( + PlayerbotAI* botAI) : MovementAction(botAI, "anetheron spread ranged in circle") {} + bool Execute(Event event) override; +}; + +class AnetheronBringInfernalToInfernalTankAction : public MovementAction +{ +public: + AnetheronBringInfernalToInfernalTankAction( + PlayerbotAI* botAI) : MovementAction(botAI, "anetheron bring infernal to infernal tank") {} + bool Execute(Event event) override; +}; + +class AnetheronFirstAssistTankPickUpInfernalsAction : public AttackAction +{ +public: + AnetheronFirstAssistTankPickUpInfernalsAction( + PlayerbotAI* botAI) : AttackAction(botAI, "anetheron first assist tank pick up infernals") {} + bool Execute(Event event) override; +}; + +class AnetheronAssignDpsPriorityAction : public AttackAction +{ +public: + AnetheronAssignDpsPriorityAction( + PlayerbotAI* botAI) : AttackAction(botAI, "anetheron assign dps priority") {} + bool Execute(Event event) override; +}; + +// Kaz'rogal + +class KazrogalMisdirectBossToMainTankAction : public AttackAction +{ +public: + KazrogalMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "kaz'rogal misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class KazrogalMainTankPositionBossAction : public AttackAction +{ +public: + KazrogalMainTankPositionBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "kaz'rogal main tank position boss") {} + bool Execute(Event event) override; +}; + +class KazrogalAssistTanksMoveInFrontOfBossAction : public AttackAction +{ +public: + KazrogalAssistTanksMoveInFrontOfBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "kaz'rogal assist tanks move in front of boss") {} + bool Execute(Event event) override; +}; + +class KazrogalSpreadRangedInArcAction : public MovementAction +{ +public: + KazrogalSpreadRangedInArcAction( + PlayerbotAI* botAI) : MovementAction(botAI, "kaz'rogal spread ranged in arc") {} + bool Execute(Event event) override; +}; + +class KazrogalLowManaBotTakeDefensiveMeasuresAction : public MovementAction +{ +public: +KazrogalLowManaBotTakeDefensiveMeasuresAction( + PlayerbotAI* botAI) : MovementAction(botAI, "kaz'rogal low mana bot take defensive measures") {} + bool Execute(Event event) override; +}; + +class KazrogalCastShadowProtectionSpellAction : public Action +{ +public: + KazrogalCastShadowProtectionSpellAction( + PlayerbotAI* botAI) : Action(botAI, "kaz'rogal cast shadow protection spell") {} + bool Execute(Event event) override; +}; + +// Azgalor + +class AzgalorMisdirectBossToMainTankAction : public AttackAction +{ +public: + AzgalorMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "azgalor misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class AzgalorMainTankPositionBossAction : public AttackAction +{ +public: + AzgalorMainTankPositionBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "azgalor main tank position boss") {} + bool Execute(Event event) override; +}; + +class AzgalorWaitAtSafePositionAction : public MovementAction +{ +public: + AzgalorWaitAtSafePositionAction( + PlayerbotAI* botAI) : MovementAction(botAI, "azgalor wait at safe position") {} + bool Execute(Event event) override; +}; + +class AzgalorDisperseRangedAction : public MovementAction +{ +public: + AzgalorDisperseRangedAction( + PlayerbotAI* botAI) : MovementAction(botAI, "azgalor disperse ranged") {} + bool Execute(Event event) override; +}; + +class AzgalorMeleeGetOutOfFireAndSwapTargetsAction : public AttackAction +{ +public: + AzgalorMeleeGetOutOfFireAndSwapTargetsAction( + PlayerbotAI* botAI) : AttackAction(botAI, "azgalor melee get out of fire and swap targets") {} + bool Execute(Event event) override; +}; + +class AzgalorMoveToDoomguardTankAction : public MovementAction +{ +public: + AzgalorMoveToDoomguardTankAction( + PlayerbotAI* botAI) : MovementAction(botAI, "azgalor move to doomguard tank") {} + bool Execute(Event event) override; +}; + +class AzgalorFirstAssistTankPositionDoomguardAction : public AttackAction +{ +public: + AzgalorFirstAssistTankPositionDoomguardAction( + PlayerbotAI* botAI) : AttackAction(botAI, "azgalor first assist tank position doomguard") {} + bool Execute(Event event) override; +}; + +class AzgalorRangedDpsPrioritizeDoomguardsAction : public AttackAction +{ +public: + AzgalorRangedDpsPrioritizeDoomguardsAction( + PlayerbotAI* botAI) : AttackAction(botAI, "azgalor ranged dps prioritize doomguards") {} + bool Execute(Event event) override; +}; + +// Archimonde + +class ArchimondeMisdirectBossToMainTankAction : public AttackAction +{ +public: + ArchimondeMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "archimonde misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class ArchimondeMoveBossToInitialPositionAction : public AttackAction +{ +public: + ArchimondeMoveBossToInitialPositionAction( + PlayerbotAI* botAI) : AttackAction(botAI, "archimonde move boss to initial position") {} + bool Execute(Event event) override; +}; + +class ArchimondeCastFearImmunitySpellAction : public Action +{ +public: + ArchimondeCastFearImmunitySpellAction( + PlayerbotAI* botAI) : Action(botAI, "archimonde cast fear immunity spell") {} + bool Execute(Event event) override; + +private: + bool CastFearWardOnMainTank(); + bool UseTremorTotemStrategy(); +}; + +class ArchimondeSpreadToAvoidAirBurstAction : public MovementAction +{ +public: + ArchimondeSpreadToAvoidAirBurstAction( + PlayerbotAI* botAI) : MovementAction(botAI, "archimonde spread to avoid air burst") {} + bool Execute(Event event) override; +}; + +class ArchimondeAvoidDoomfireAction : public MovementAction +{ +public: + ArchimondeAvoidDoomfireAction( + PlayerbotAI* botAI) : MovementAction(botAI, "archimonde avoid doomfire") {} + bool Execute(Event event) override; +}; + +class ArchimondeRemoveDoomfireDotAction : public Action +{ +public: + ArchimondeRemoveDoomfireDotAction( + PlayerbotAI* botAI) : Action(botAI, "archimonde remove doomfire dot") {} + bool Execute(Event event) override; +}; + +#endif diff --git a/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp b/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp new file mode 100644 index 00000000000..a9f66646411 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidHyjalSummitMultipliers.h" +#include "RaidHyjalSummitActions.h" +#include "RaidHyjalSummitHelpers.h" +#include "AiFactory.h" +#include "ChooseTargetActions.h" +#include "DKActions.h" +#include "DruidBearActions.h" +#include "HunterActions.h" +#include "PaladinActions.h" +#include "RaidBossHelpers.h" +#include "ReachTargetActions.h" +#include "ShamanActions.h" +#include "WarriorActions.h" + +using namespace HyjalSummitHelpers; + +// Without this multiplier, Bloodlust/Heroism will not be available for +// bosses because it will be used on cooldown during trash waves +float HyjalSummitTimeBloodlustAndHeroismMultiplier::GetValue(Action* action) +{ + if (bot->getClass() != CLASS_SHAMAN) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action)) + { + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + if (archimonde && archimonde->GetHealthPct() < 90.0f) + return 1.0f; + + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (azgalor && azgalor->GetHealthPct() < 90.0f) + return 1.0f; + + Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal"); + if (kazrogal && kazrogal->GetHealthPct() < 90.0f) + return 1.0f; + + Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron"); + if (anetheron && anetheron->GetHealthPct() < 85.0f) + return 1.0f; + + Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill"); + if (winterchill && winterchill->GetHealthPct() < 90.0f) + return 1.0f; + + return 0.0f; + } + + return 1.0f; +} + +// Rage Winterchill + +float RageWinterchillDisableCombatFormationMoveMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "rage winterchill")) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float RageWinterchillMeleeControlAvoidanceMultiplier::GetValue(Action* action) +{ + if (botAI->IsRanged(bot)) + return 1.0f; + + Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill"); + if (!winterchill) + return 1.0f; + + if (IsInDeathAndDecay(bot, DEATH_AND_DECAY_SAFE_RADIUS + 2.0f)) + { + if (dynamic_cast(action)) + return 0.0f; + + if (botAI->IsMainTank(bot) || winterchill->GetVictim() == bot) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Anetheron + +float AnetheronDisableTankActionsMultiplier::GetValue(Action* action) +{ + if (!botAI->IsTank(bot) || !AI_VALUE2(Unit*, "find target", "anetheron")) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (bot->GetVictim() != nullptr && + dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float AnetheronDisableCombatFormationMoveMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "anetheron")) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float AnetheronControlMisdirectionMultiplier::GetValue(Action* action) +{ + if (bot->getClass() != CLASS_HUNTER || + !AI_VALUE2(Unit*, "find target", "anetheron")) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// Kaz'rogal + +float KazrogalLowManaBotStayAwayFromGroupMultiplier::GetValue(Action* action) +{ + if (bot->getClass() == CLASS_WARRIOR || bot->getClass() == CLASS_ROGUE || + bot->getClass() == CLASS_DEATH_KNIGHT || bot->getClass() == CLASS_HUNTER) + return 1.0f; + + uint8 tab = AiFactory::GetPlayerSpecTab(bot); + if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL) + return 1.0f; + + if (!AI_VALUE2(Unit*, "find target", "kaz'rogal")) + return 1.0f; + + if (!isBelowManaThreshold.count(bot->GetGUID())) + return 1.0f; + + if (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; + + return 1.0f; +} + +float KazrogalKeepAspectOfTheViperActiveMultiplier::GetValue(Action* action) +{ + if (bot->getClass() != CLASS_HUNTER || bot->GetPower(POWER_MANA) > 4000 || + !AI_VALUE2(Unit*, "find target", "kaz'rogal")) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float KazrogalControlMovementMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "kaz'rogal")) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (botAI->IsRanged(bot) && dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// Azgalor + +float AzgalorDisableTankActionsMultiplier::GetValue(Action* action) +{ + if (bot->GetVictim() == nullptr) + return 1.0f; + + if (!botAI->IsTank(bot) || !AI_VALUE2(Unit*, "find target", "azgalor")) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) || dynamic_cast(action)) + { + if (botAI->IsMainTank(bot)) + { + return 0.0f; + } + else if (botAI->IsAssistTank(bot) && (AnyGroupMemberHasDoom(bot) || + AI_VALUE2(Unit*, "find target", "lesser doomguard"))) + { + return 0.0f; + } + } + + return 1.0f; +} + +float AzgalorDoomedBotPrioritizePositioningMultiplier::GetValue(Action* action) +{ + if (!bot->HasAura(static_cast(HyjalSummitSpells::SPELL_DOOM))) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action) && + !dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float AzgalorMeleeDpsControlAvoidanceMultiplier::GetValue(Action* action) +{ + if (botAI->IsRanged(bot) || botAI->IsTank(bot)) + return 1.0f; + + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (!azgalor) + return 1.0f; + + constexpr float singleTickMoveAwayDist = 6.0f; + if (IsInRainOfFire(bot, RAIN_OF_FIRE_RADIUS + singleTickMoveAwayDist)) + { + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + } + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank || !GET_PLAYERBOT_AI(mainTank)) + return 1.0f; + + TankPositionState tankState = GetAzgalorTankPositionState(botAI, bot); + if ((tankState == TankPositionState::Unknown || + tankState == TankPositionState::MovingToTransition) && + dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +// Archimonde + +float ArchimondeDisableCombatFormationMoveMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "archimonde")) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} diff --git a/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h b/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h new file mode 100644 index 00000000000..9fa9dd885d6 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDHYJALSUMMITMULTIPLIERS_H +#define _PLAYERBOT_RAIDHYJALSUMMITMULTIPLIERS_H + +#include "Multiplier.h" + +class HyjalSummitTimeBloodlustAndHeroismMultiplier : public Multiplier +{ +public: + HyjalSummitTimeBloodlustAndHeroismMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "hyjal summit time bloodlust and heroism multiplier") {} + virtual float GetValue(Action* action); +}; + +// Rage Winterchill + +class RageWinterchillDisableCombatFormationMoveMultiplier : public Multiplier +{ +public: + RageWinterchillDisableCombatFormationMoveMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "rage winterchill disable combat formation move multiplier") {} + virtual float GetValue(Action* action); +}; + +class RageWinterchillMeleeControlAvoidanceMultiplier : public Multiplier +{ +public: + RageWinterchillMeleeControlAvoidanceMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "rage winterchill melee control avoidance multiplier") {} + virtual float GetValue(Action* action); +}; + +// Anetheron + +class AnetheronDisableTankActionsMultiplier : public Multiplier +{ +public: + AnetheronDisableTankActionsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "anetheron disable tank actions multiplier") {} + virtual float GetValue(Action* action); +}; + +class AnetheronDisableCombatFormationMoveMultiplier : public Multiplier +{ +public: + AnetheronDisableCombatFormationMoveMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "anetheron disable combat formation move multiplier") {} + virtual float GetValue(Action* action); +}; + +class AnetheronControlMisdirectionMultiplier : public Multiplier +{ +public: + AnetheronControlMisdirectionMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "anetheron control misdirection multiplier") {} + virtual float GetValue(Action* action); +}; + +// Kaz'rogal + +class KazrogalLowManaBotStayAwayFromGroupMultiplier : public Multiplier +{ +public: + KazrogalLowManaBotStayAwayFromGroupMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "kaz'rogal low mana bot stay away from group multiplier") {} + virtual float GetValue(Action* action); +}; + +class KazrogalKeepAspectOfTheViperActiveMultiplier : public Multiplier +{ +public: + KazrogalKeepAspectOfTheViperActiveMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "kaz'rogal keep aspect of the viper active multiplier") {} + virtual float GetValue(Action* action); +}; + +class KazrogalControlMovementMultiplier : public Multiplier +{ +public: + KazrogalControlMovementMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "kaz'rogal control movement multiplier") {} + virtual float GetValue(Action* action); +}; + +// Azgalor + +class AzgalorDisableTankActionsMultiplier : public Multiplier +{ +public: + AzgalorDisableTankActionsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "azgalor disable tank actions multiplier") {} + virtual float GetValue(Action* action); +}; + +class AzgalorDoomedBotPrioritizePositioningMultiplier : public Multiplier +{ +public: + AzgalorDoomedBotPrioritizePositioningMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "azgalor doomed bot prioritize positioning multiplier") {} + virtual float GetValue(Action* action); +}; + +class AzgalorMeleeDpsControlAvoidanceMultiplier : public Multiplier +{ +public: + AzgalorMeleeDpsControlAvoidanceMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "azgalor melee dps control avoidance multiplier") {} + virtual float GetValue(Action* action); +}; + +// Archimonde + +class ArchimondeDisableCombatFormationMoveMultiplier : public Multiplier +{ +public: + ArchimondeDisableCombatFormationMoveMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "archimonde disable combat formation move multiplier") {} + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h b/src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h new file mode 100644 index 00000000000..02164df4683 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDHYJALSUMMITACTIONCONTEXT_H +#define _PLAYERBOT_RAIDHYJALSUMMITACTIONCONTEXT_H + +#include "RaidHyjalSummitActions.h" +#include "NamedObjectContext.h" + +class RaidHyjalSummitActionContext : public NamedObjectContext +{ +public: + RaidHyjalSummitActionContext() + { + // General + creators["hyjal summit erase trackers"] = + &RaidHyjalSummitActionContext::hyjal_summit_erase_trackers; + + // Rage Winterchill + creators["rage winterchill misdirect boss to main tank"] = + &RaidHyjalSummitActionContext::rage_winterchill_misdirect_boss_to_main_tank; + + creators["rage winterchill main tank position boss"] = + &RaidHyjalSummitActionContext::rage_winterchill_main_tank_position_boss; + + creators["rage winterchill spread ranged in circle"] = + &RaidHyjalSummitActionContext::rage_winterchill_spread_ranged_in_circle; + + creators["rage winterchill melee get out of death and decay"] = + &RaidHyjalSummitActionContext::rage_winterchill_melee_get_out_of_death_and_decay; + + // Anetheron + creators["anetheron misdirect boss and infernals to tanks"] = + &RaidHyjalSummitActionContext::anetheron_misdirect_boss_and_infernals_to_tanks; + + creators["anetheron main tank position boss"] = + &RaidHyjalSummitActionContext::anetheron_main_tank_position_boss; + + creators["anetheron spread ranged in circle"] = + &RaidHyjalSummitActionContext::anetheron_spread_ranged_in_circle; + + creators["anetheron bring infernal to infernal tank"] = + &RaidHyjalSummitActionContext::anetheron_bring_infernal_to_infernal_tank; + + creators["anetheron first assist tank pick up infernals"] = + &RaidHyjalSummitActionContext::anetheron_first_assist_tank_pick_up_infernals; + + creators["anetheron assign dps priority"] = + &RaidHyjalSummitActionContext::anetheron_assign_dps_priority; + + // Kaz'rogal + creators["kaz'rogal misdirect boss to main tank"] = + &RaidHyjalSummitActionContext::kazrogal_misdirect_boss_to_main_tank; + + creators["kaz'rogal main tank position boss"] = + &RaidHyjalSummitActionContext::kazrogal_main_tank_position_boss; + + creators["kaz'rogal assist tanks move in front of boss"] = + &RaidHyjalSummitActionContext::kazrogal_assist_tanks_move_in_front_of_boss; + + creators["kaz'rogal spread ranged in arc"] = + &RaidHyjalSummitActionContext::kazrogal_spread_ranged_in_arc; + + creators["kaz'rogal low mana bot take defensive measures"] = + &RaidHyjalSummitActionContext::kazrogal_low_mana_bot_take_defensive_measures; + + creators["kaz'rogal cast shadow protection spell"] = + &RaidHyjalSummitActionContext::kazrogal_cast_shadow_protection_spell; + + // Azgalor + creators["azgalor misdirect boss to main tank"] = + &RaidHyjalSummitActionContext::azgalor_misdirect_boss_to_main_tank; + + creators["azgalor main tank position boss"] = + &RaidHyjalSummitActionContext::azgalor_main_tank_position_boss; + + creators["azgalor wait at safe position"] = + &RaidHyjalSummitActionContext::azgalor_wait_at_safe_position; + + creators["azgalor disperse ranged"] = + &RaidHyjalSummitActionContext::azgalor_disperse_ranged; + + creators["azgalor melee get out of fire and swap targets"] = + &RaidHyjalSummitActionContext::azgalor_melee_get_out_of_fire_and_swap_targets; + + creators["azgalor move to doomguard tank"] = + &RaidHyjalSummitActionContext::azgalor_move_to_doomguard_tank; + + creators["azgalor first assist tank position doomguard"] = + &RaidHyjalSummitActionContext::azgalor_first_assist_tank_position_doomguard; + + creators["azgalor ranged dps prioritize doomguards"] = + &RaidHyjalSummitActionContext::azgalor_ranged_dps_prioritize_doomguards; + + // Archimonde + creators["archimonde misdirect boss to main tank"] = + &RaidHyjalSummitActionContext::archimonde_misdirect_boss_to_main_tank; + + creators["archimonde move boss to initial position"] = + &RaidHyjalSummitActionContext::archimonde_move_boss_to_initial_position; + + creators["archimonde cast fear immunity spell"] = + &RaidHyjalSummitActionContext::archimonde_cast_fear_immunity_spell; + + creators["archimonde spread to avoid air burst"] = + &RaidHyjalSummitActionContext::archimonde_spread_to_avoid_air_burst; + + creators["archimonde avoid doomfire"] = + &RaidHyjalSummitActionContext::archimonde_avoid_doomfire; + + creators["archimonde remove doomfire dot"] = + &RaidHyjalSummitActionContext::archimonde_remove_doomfire_dot; + } + +private: + // General + static Action* hyjal_summit_erase_trackers( + PlayerbotAI* botAI) { return new HyjalSummitEraseTrackersAction(botAI); } + + // Rage Winterchill + static Action* rage_winterchill_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new RageWinterchillMisdirectBossToMainTankAction(botAI); } + + static Action* rage_winterchill_main_tank_position_boss( + PlayerbotAI* botAI) { return new RageWinterchillMainTankPositionBossAction(botAI); } + + static Action* rage_winterchill_spread_ranged_in_circle( + PlayerbotAI* botAI) { return new RageWinterchillSpreadRangedInCircleAction(botAI); } + + static Action* rage_winterchill_melee_get_out_of_death_and_decay( + PlayerbotAI* botAI) { return new RageWinterchillMeleeGetOutOfDeathAndDecayAction(botAI); } + + // Anetheron + static Action* anetheron_misdirect_boss_and_infernals_to_tanks( + PlayerbotAI* botAI) { return new AnetheronMisdirectBossAndInfernalsToTanksAction(botAI); } + + static Action* anetheron_main_tank_position_boss( + PlayerbotAI* botAI) { return new AnetheronMainTankPositionBossAction(botAI); } + + static Action* anetheron_spread_ranged_in_circle( + PlayerbotAI* botAI) { return new AnetheronSpreadRangedInCircleAction(botAI); } + + static Action* anetheron_bring_infernal_to_infernal_tank( + PlayerbotAI* botAI) { return new AnetheronBringInfernalToInfernalTankAction(botAI); } + + static Action* anetheron_first_assist_tank_pick_up_infernals( + PlayerbotAI* botAI) { return new AnetheronFirstAssistTankPickUpInfernalsAction(botAI); } + + static Action* anetheron_assign_dps_priority( + PlayerbotAI* botAI) { return new AnetheronAssignDpsPriorityAction(botAI); } + + // Kaz'rogal + static Action* kazrogal_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new KazrogalMisdirectBossToMainTankAction(botAI); } + + static Action* kazrogal_main_tank_position_boss( + PlayerbotAI* botAI) { return new KazrogalMainTankPositionBossAction(botAI); } + + static Action* kazrogal_assist_tanks_move_in_front_of_boss( + PlayerbotAI* botAI) { return new KazrogalAssistTanksMoveInFrontOfBossAction(botAI); } + + static Action* kazrogal_spread_ranged_in_arc( + PlayerbotAI* botAI) { return new KazrogalSpreadRangedInArcAction(botAI); } + + static Action* kazrogal_low_mana_bot_take_defensive_measures( + PlayerbotAI* botAI) { return new KazrogalLowManaBotTakeDefensiveMeasuresAction(botAI); } + + static Action* kazrogal_cast_shadow_protection_spell( + PlayerbotAI* botAI) { return new KazrogalCastShadowProtectionSpellAction(botAI); } + + // Azgalor + static Action* azgalor_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new AzgalorMisdirectBossToMainTankAction(botAI); } + + static Action* azgalor_main_tank_position_boss( + PlayerbotAI* botAI) { return new AzgalorMainTankPositionBossAction(botAI); } + + static Action* azgalor_wait_at_safe_position( + PlayerbotAI* botAI) { return new AzgalorWaitAtSafePositionAction(botAI); } + + static Action* azgalor_disperse_ranged( + PlayerbotAI* botAI) { return new AzgalorDisperseRangedAction(botAI); } + + static Action* azgalor_melee_get_out_of_fire_and_swap_targets( + PlayerbotAI* botAI) { return new AzgalorMeleeGetOutOfFireAndSwapTargetsAction(botAI); } + + static Action* azgalor_move_to_doomguard_tank( + PlayerbotAI* botAI) { return new AzgalorMoveToDoomguardTankAction(botAI); } + + static Action* azgalor_first_assist_tank_position_doomguard( + PlayerbotAI* botAI) { return new AzgalorFirstAssistTankPositionDoomguardAction(botAI); } + + static Action* azgalor_ranged_dps_prioritize_doomguards( + PlayerbotAI* botAI) { return new AzgalorRangedDpsPrioritizeDoomguardsAction(botAI); } + + // Archimonde + static Action* archimonde_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new ArchimondeMisdirectBossToMainTankAction(botAI); } + + static Action* archimonde_move_boss_to_initial_position( + PlayerbotAI* botAI) { return new ArchimondeMoveBossToInitialPositionAction(botAI); } + + static Action* archimonde_cast_fear_immunity_spell( + PlayerbotAI* botAI) { return new ArchimondeCastFearImmunitySpellAction(botAI); } + + static Action* archimonde_spread_to_avoid_air_burst( + PlayerbotAI* botAI) { return new ArchimondeSpreadToAvoidAirBurstAction(botAI); } + + static Action* archimonde_avoid_doomfire( + PlayerbotAI* botAI) { return new ArchimondeAvoidDoomfireAction(botAI); } + + static Action* archimonde_remove_doomfire_dot( + PlayerbotAI* botAI) { return new ArchimondeRemoveDoomfireDotAction(botAI); } +}; + +#endif diff --git a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h b/src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h new file mode 100644 index 00000000000..a7c564ee00a --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDHYJALSUMMITTRIGGERCONTEXT_H +#define _PLAYERBOT_RAIDHYJALSUMMITTRIGGERCONTEXT_H + +#include "RaidHyjalSummitTriggers.h" +#include "NamedObjectContext.h" + +class RaidHyjalSummitTriggerContext : public NamedObjectContext +{ +public: + RaidHyjalSummitTriggerContext() + { + // General + creators["hyjal summit bot is not in combat"] = + &RaidHyjalSummitTriggerContext::hyjal_summit_bot_is_not_in_combat; + + // Rage Winterchill + creators["rage winterchill pulling boss"] = + &RaidHyjalSummitTriggerContext::rage_winterchill_pulling_boss; + + creators["rage winterchill boss engaged by main tank"] = + &RaidHyjalSummitTriggerContext::rage_winterchill_boss_engaged_by_main_tank; + + creators["rage winterchill boss casts death and decay on ranged"] = + &RaidHyjalSummitTriggerContext::rage_winterchill_boss_casts_death_and_decay_on_ranged; + + creators["rage winterchill melee is standing in death and decay"] = + &RaidHyjalSummitTriggerContext::rage_winterchill_melee_is_standing_in_death_and_decay; + + // Anetheron + creators["anetheron pulling boss or infernal"] = + &RaidHyjalSummitTriggerContext::anetheron_pulling_boss_or_infernal; + + creators["anetheron boss engaged by main tank"] = + &RaidHyjalSummitTriggerContext::anetheron_boss_engaged_by_main_tank; + + creators["anetheron boss casts carrion swarm"] = + &RaidHyjalSummitTriggerContext::anetheron_boss_casts_carrion_swarm; + + creators["anetheron bot is targeted by infernal"] = + &RaidHyjalSummitTriggerContext::anetheron_bot_is_targeted_by_infernal; + + creators["anetheron infernals need to be kept away from raid"] = + &RaidHyjalSummitTriggerContext::anetheron_infernals_need_to_be_kept_away_from_raid; + + creators["anetheron infernals continue to spawn"] = + &RaidHyjalSummitTriggerContext::anetheron_infernals_continue_to_spawn; + + // Kaz'rogal + creators["kaz'rogal pulling boss"] = + &RaidHyjalSummitTriggerContext::kazrogal_pulling_boss; + + creators["kaz'rogal boss engaged by main tank"] = + &RaidHyjalSummitTriggerContext::kazrogal_boss_engaged_by_main_tank; + + creators["kaz'rogal boss engaged by assist tanks"] = + &RaidHyjalSummitTriggerContext::kazrogal_boss_engaged_by_assist_tanks; + + creators["kaz'rogal bot is low on mana"] = + &RaidHyjalSummitTriggerContext::kazrogal_bot_is_low_on_mana; + + creators["kaz'rogal low mana bots need escape path"] = + &RaidHyjalSummitTriggerContext::kazrogal_low_mana_bots_need_escape_path; + + creators["kaz'rogal mark deals shadow damage"] = + &RaidHyjalSummitTriggerContext::kazrogal_mark_deals_shadow_damage; + + // Azgalor + creators["azgalor pulling boss"] = + &RaidHyjalSummitTriggerContext::azgalor_pulling_boss; + + creators["azgalor boss engaged by main tank"] = + &RaidHyjalSummitTriggerContext::azgalor_boss_engaged_by_main_tank; + + creators["azgalor main tank is positioning boss"] = + &RaidHyjalSummitTriggerContext::azgalor_main_tank_is_positioning_boss; + + creators["azgalor boss engaged by ranged"] = + &RaidHyjalSummitTriggerContext::azgalor_boss_engaged_by_ranged; + + creators["azgalor boss casts rain of fire on melee"] = + &RaidHyjalSummitTriggerContext::azgalor_boss_casts_rain_of_fire_on_melee; + + creators["azgalor bot is doomed"] = + &RaidHyjalSummitTriggerContext::azgalor_bot_is_doomed; + + creators["azgalor doomguards must be controlled"] = + &RaidHyjalSummitTriggerContext::azgalor_doomguards_must_be_controlled; + + creators["azgalor doomguards must die"] = + &RaidHyjalSummitTriggerContext::azgalor_doomguards_must_die; + + // Archimonde + creators["archimonde pulling boss"] = + &RaidHyjalSummitTriggerContext::archimonde_pulling_boss; + + creators["archimonde boss engaged by main tank"] = + &RaidHyjalSummitTriggerContext::archimonde_boss_engaged_by_main_tank; + + creators["archimonde boss casts fear"] = + &RaidHyjalSummitTriggerContext::archimonde_boss_casts_fear; + + creators["archimonde boss casts air burst"] = + &RaidHyjalSummitTriggerContext::archimonde_boss_casts_air_burst; + + creators["archimonde boss summoned doomfire"] = + &RaidHyjalSummitTriggerContext::archimonde_boss_summoned_doomfire; + + creators["archimonde bot stood in doomfire"] = + &RaidHyjalSummitTriggerContext::archimonde_bot_stood_in_doomfire; + } + +private: + // General + static Trigger* hyjal_summit_bot_is_not_in_combat( + PlayerbotAI* botAI) { return new HyjalSummitBotIsNotInCombatTrigger(botAI); } + + // Rage Winterchill + static Trigger* rage_winterchill_pulling_boss( + PlayerbotAI* botAI) { return new RageWinterchillPullingBossTrigger(botAI); } + + static Trigger* rage_winterchill_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new RageWinterchillBossEngagedByMainTankTrigger(botAI); } + + static Trigger* rage_winterchill_boss_casts_death_and_decay_on_ranged( + PlayerbotAI* botAI) { return new RageWinterchillBossCastsDeathAndDecayOnRangedTrigger(botAI); } + + static Trigger* rage_winterchill_melee_is_standing_in_death_and_decay( + PlayerbotAI* botAI) { return new RageWinterchillMeleeIsStandingInDeathAndDecayTrigger(botAI); } + + // Anetheron + static Trigger* anetheron_pulling_boss_or_infernal( + PlayerbotAI* botAI) { return new AnetheronPullingBossOrInfernalTrigger(botAI); } + + static Trigger* anetheron_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new AnetheronBossEngagedByMainTankTrigger(botAI); } + + static Trigger* anetheron_boss_casts_carrion_swarm( + PlayerbotAI* botAI) { return new AnetheronBossCastsCarrionSwarmTrigger(botAI); } + + static Trigger* anetheron_bot_is_targeted_by_infernal( + PlayerbotAI* botAI) { return new AnetheronBotIsTargetedByInfernalTrigger(botAI); } + + static Trigger* anetheron_infernals_need_to_be_kept_away_from_raid( + PlayerbotAI* botAI) { return new AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger(botAI); } + + static Trigger* anetheron_infernals_continue_to_spawn( + PlayerbotAI* botAI) { return new AnetheronInfernalsContinueToSpawnTrigger(botAI); } + + // Kaz'rogal + static Trigger* kazrogal_pulling_boss( + PlayerbotAI* botAI) { return new KazrogalPullingBossTrigger(botAI); } + + static Trigger* kazrogal_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new KazrogalBossEngagedByMainTankTrigger(botAI); } + + static Trigger* kazrogal_boss_engaged_by_assist_tanks( + PlayerbotAI* botAI) { return new KazrogalBossEngagedByAssistTanksTrigger(botAI); } + + static Trigger* kazrogal_low_mana_bots_need_escape_path( + PlayerbotAI* botAI) { return new KazrogalLowManaBotsNeedEscapePathTrigger(botAI); } + + static Trigger* kazrogal_bot_is_low_on_mana( + PlayerbotAI* botAI) { return new KazrogalBotIsLowOnManaTrigger(botAI); } + + static Trigger* kazrogal_mark_deals_shadow_damage( + PlayerbotAI* botAI) { return new KazrogalMarkDealsShadowDamageTrigger(botAI); } + + // Azgalor + static Trigger* azgalor_pulling_boss( + PlayerbotAI* botAI) { return new AzgalorPullingBossTrigger(botAI); } + + static Trigger* azgalor_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new AzgalorBossEngagedByMainTankTrigger(botAI); } + + static Trigger* azgalor_main_tank_is_positioning_boss( + PlayerbotAI* botAI) { return new AzgalorMainTankIsPositioningBossTrigger(botAI); } + + static Trigger* azgalor_boss_engaged_by_ranged( + PlayerbotAI* botAI) { return new AzgalorBossEngagedByRangedTrigger(botAI); } + + static Trigger* azgalor_boss_casts_rain_of_fire_on_melee( + PlayerbotAI* botAI) { return new AzgalorBossCastsRainOfFireOnMeleeTrigger(botAI); } + + static Trigger* azgalor_bot_is_doomed( + PlayerbotAI* botAI) { return new AzgalorBotIsDoomedTrigger(botAI); } + + static Trigger* azgalor_doomguards_must_be_controlled( + PlayerbotAI* botAI) { return new AzgalorDoomguardsMustBeControlledTrigger(botAI); } + + static Trigger* azgalor_doomguards_must_die( + PlayerbotAI* botAI) { return new AzgalorDoomguardsMustDieTrigger(botAI); } + + // Archimonde + static Trigger* archimonde_pulling_boss( + PlayerbotAI* botAI) { return new ArchimondePullingBossTrigger(botAI); } + + static Trigger* archimonde_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new ArchimondeBossEngagedByMainTankTrigger(botAI); } + + static Trigger* archimonde_boss_casts_fear( + PlayerbotAI* botAI) { return new ArchimondeBossCastsFearTrigger(botAI); } + + static Trigger* archimonde_boss_casts_air_burst( + PlayerbotAI* botAI) { return new ArchimondeBossCastsAirBurstTrigger(botAI); } + + static Trigger* archimonde_boss_summoned_doomfire( + PlayerbotAI* botAI) { return new ArchimondeBossSummonedDoomfireTrigger(botAI); } + + static Trigger* archimonde_bot_stood_in_doomfire( + PlayerbotAI* botAI) { return new ArchimondeBotStoodInDoomfireTrigger(botAI); } +}; + +#endif diff --git a/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp b/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp new file mode 100644 index 00000000000..77486fb4859 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidHyjalSummitStrategy.h" +#include "RaidHyjalSummitMultipliers.h" + +void RaidHyjalSummitStrategy::InitTriggers(std::vector& triggers) +{ + // General + triggers.push_back(new TriggerNode("hyjal summit bot is not in combat", { + NextAction("hyjal summit erase trackers", ACTION_EMERGENCY + 11) })); + + // Rage Winterchill + triggers.push_back(new TriggerNode("rage winterchill pulling boss", { + NextAction("rage winterchill misdirect boss to main tank", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("rage winterchill boss engaged by main tank", { + NextAction("rage winterchill main tank position boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("rage winterchill boss casts death and decay on ranged", { + NextAction("rage winterchill spread ranged in circle", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("rage winterchill melee is standing in death and decay", { + NextAction("rage winterchill melee get out of death and decay", ACTION_EMERGENCY + 1) })); + + // Anetheron + triggers.push_back(new TriggerNode("anetheron pulling boss or infernal", { + NextAction("anetheron misdirect boss and infernals to tanks", ACTION_RAID + 3) })); + + triggers.push_back(new TriggerNode("anetheron boss engaged by main tank", { + NextAction("anetheron main tank position boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("anetheron boss casts carrion swarm", { + NextAction("anetheron spread ranged in circle", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("anetheron bot is targeted by infernal", { + NextAction("anetheron bring infernal to infernal tank", ACTION_EMERGENCY + 2) })); + + triggers.push_back(new TriggerNode("anetheron infernals need to be kept away from raid", { + NextAction("anetheron first assist tank pick up infernals", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("anetheron infernals continue to spawn", { + NextAction("anetheron assign dps priority", ACTION_RAID + 1) })); + + // Kaz'rogal + triggers.push_back(new TriggerNode("kaz'rogal pulling boss", { + NextAction("kaz'rogal misdirect boss to main tank", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("kaz'rogal boss engaged by main tank", { + NextAction("kaz'rogal main tank position boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("kaz'rogal boss engaged by assist tanks", { + NextAction("kaz'rogal assist tanks move in front of boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("kaz'rogal low mana bots need escape path", { + NextAction("kaz'rogal spread ranged in arc", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("kaz'rogal bot is low on mana", { + NextAction("kaz'rogal low mana bot take defensive measures", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("kaz'rogal mark deals shadow damage", { + NextAction("kaz'rogal cast shadow protection spell", ACTION_EMERGENCY + 6) })); + + // Azgalor + triggers.push_back(new TriggerNode("azgalor pulling boss", { + NextAction("azgalor misdirect boss to main tank", ACTION_RAID + 3) })); + + triggers.push_back(new TriggerNode("azgalor boss engaged by main tank", { + NextAction("azgalor main tank position boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("azgalor main tank is positioning boss", { + NextAction("azgalor wait at safe position", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("azgalor boss engaged by ranged", { + NextAction("azgalor disperse ranged", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("azgalor boss casts rain of fire on melee", { + NextAction("azgalor melee get out of fire and swap targets", ACTION_EMERGENCY + 2) })); + + triggers.push_back(new TriggerNode("azgalor bot is doomed", { + NextAction("azgalor move to doomguard tank", ACTION_EMERGENCY + 3) })); + + triggers.push_back(new TriggerNode("azgalor doomguards must be controlled", { + NextAction("azgalor first assist tank position doomguard", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("azgalor doomguards must die", { + NextAction("azgalor ranged dps prioritize doomguards", ACTION_RAID + 1) })); + + // Archimonde + triggers.push_back(new TriggerNode("archimonde pulling boss", { + NextAction("archimonde misdirect boss to main tank", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("archimonde boss engaged by main tank", { + NextAction("archimonde move boss to initial position", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("archimonde boss casts fear", { + NextAction("archimonde cast fear immunity spell", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("archimonde boss casts air burst", { + NextAction("archimonde spread to avoid air burst", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("archimonde boss summoned doomfire", { + NextAction("archimonde avoid doomfire", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("archimonde bot stood in doomfire", { + NextAction("archimonde remove doomfire dot", ACTION_EMERGENCY + 7) })); +} + +void RaidHyjalSummitStrategy::InitMultipliers(std::vector& multipliers) +{ + // Trash + multipliers.push_back(new HyjalSummitTimeBloodlustAndHeroismMultiplier(botAI)); + + // Rage Winterchill + multipliers.push_back(new RageWinterchillDisableCombatFormationMoveMultiplier(botAI)); + multipliers.push_back(new RageWinterchillMeleeControlAvoidanceMultiplier(botAI)); + + // Anetheron + multipliers.push_back(new AnetheronDisableTankActionsMultiplier(botAI)); + multipliers.push_back(new AnetheronDisableCombatFormationMoveMultiplier(botAI)); + multipliers.push_back(new AnetheronControlMisdirectionMultiplier(botAI)); + + // Kaz'rogal + multipliers.push_back(new KazrogalLowManaBotStayAwayFromGroupMultiplier(botAI)); + multipliers.push_back(new KazrogalKeepAspectOfTheViperActiveMultiplier(botAI)); + multipliers.push_back(new KazrogalControlMovementMultiplier(botAI)); + + // Azgalor + multipliers.push_back(new AzgalorDisableTankActionsMultiplier(botAI)); + multipliers.push_back(new AzgalorDoomedBotPrioritizePositioningMultiplier(botAI)); + multipliers.push_back(new AzgalorMeleeDpsControlAvoidanceMultiplier(botAI)); + + // Archimonde + multipliers.push_back(new ArchimondeDisableCombatFormationMoveMultiplier(botAI)); +} diff --git a/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.h b/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.h new file mode 100644 index 00000000000..8f0e0d88268 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDHYJALSUMMITSTRATEGY_H_ +#define _PLAYERBOT_RAIDHYJALSUMMITSTRATEGY_H_ + +#include "Strategy.h" + +class RaidHyjalSummitStrategy : public Strategy +{ +public: + RaidHyjalSummitStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + std::string const getName() override { return "hyjal"; } + + void InitTriggers(std::vector& triggers) override; + void InitMultipliers(std::vector& multipliers) override; +}; + +#endif diff --git a/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp b/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp new file mode 100644 index 00000000000..3b9a6455d5d --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidHyjalSummitTriggers.h" +#include "RaidHyjalSummitHelpers.h" +#include "RaidHyjalSummitActions.h" +#include "AiFactory.h" +#include "Playerbots.h" +#include "RaidBossHelpers.h" + +using namespace HyjalSummitHelpers; + +// General + +bool HyjalSummitBotIsNotInCombatTrigger::IsActive() +{ + return !bot->IsInCombat() && bot->GetMapId() == HYJAL_SUMMIT_MAP_ID; +} + +// Rage Winterchill + +bool RageWinterchillPullingBossTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill"); + return winterchill && winterchill->GetHealthPct() > 95.0f; +} + +bool RageWinterchillBossEngagedByMainTankTrigger::IsActive() +{ + return botAI->IsMainTank(bot) && + AI_VALUE2(Unit*, "find target", "rage winterchill"); +} + +bool RageWinterchillBossCastsDeathAndDecayOnRangedTrigger::IsActive() +{ + return botAI->IsRanged(bot) && + AI_VALUE2(Unit*, "find target", "rage winterchill"); +} + +bool RageWinterchillMeleeIsStandingInDeathAndDecayTrigger::IsActive() +{ + if (botAI->IsRanged(bot)) + return false; + + Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill"); + if (!winterchill || winterchill->GetVictim() == bot) + return false; + + if (botAI->IsMainTank(bot)) + return false; + + return IsInDeathAndDecay(bot, DEATH_AND_DECAY_SAFE_RADIUS); +} + +// Anetheron + +bool AnetheronPullingBossOrInfernalTrigger::IsActive() +{ + return bot->getClass() == CLASS_HUNTER && + AI_VALUE2(Unit*, "find target", "anetheron"); +} + +bool AnetheronBossEngagedByMainTankTrigger::IsActive() +{ + return botAI->IsMainTank(bot) && AI_VALUE2(Unit*, "find target", "anetheron"); +} + +bool AnetheronBossCastsCarrionSwarmTrigger::IsActive() +{ + if (botAI->IsMelee(bot)) + return false; + + Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron"); + if (!anetheron) + return false; + + return GetInfernoTarget(anetheron) != bot; +} + +bool AnetheronBotIsTargetedByInfernalTrigger::IsActive() +{ + Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron"); + if (!anetheron || botAI->IsMainTank(bot)) + return false; + + return GetInfernoTarget(anetheron) == bot; +} + +bool AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger::IsActive() +{ + return botAI->IsAssistTankOfIndex(bot, 0, true) && + AI_VALUE2(Unit*, "find target", "towering infernal"); +} + +bool AnetheronInfernalsContinueToSpawnTrigger::IsActive() +{ + return !botAI->IsTank(bot) && AI_VALUE2(Unit*, "find target", "anetheron"); +} + +// Kaz'rogal + +bool KazrogalPullingBossTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal"); + return kazrogal && kazrogal->GetHealthPct() > 95.0f; +} + +bool KazrogalBossEngagedByMainTankTrigger::IsActive() +{ + return botAI->IsMainTank(bot) && AI_VALUE2(Unit*, "find target", "kaz'rogal"); +} + +bool KazrogalBossEngagedByAssistTanksTrigger::IsActive() +{ + if (!botAI->IsAssistTank(bot)) + return false; + + if (!AI_VALUE2(Unit*, "find target", "kaz'rogal")) + return false; + + return bot->GetPower(POWER_MANA) > 3000; +} + +bool KazrogalLowManaBotsNeedEscapePathTrigger::IsActive() +{ + if (bot->getClass() == CLASS_WARRIOR || bot->getClass() == CLASS_ROGUE || + bot->getClass() == CLASS_DEATH_KNIGHT) + return false; + + uint8 tab = AiFactory::GetPlayerSpecTab(bot); + if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL) + return false; + + if (!AI_VALUE2(Unit*, "find target", "kaz'rogal")) + return false; + + if (bot->getClass() == CLASS_HUNTER) + { + return true; + } + else if (bot->GetPower(POWER_MANA) > 4000) + { + isBelowManaThreshold.erase(bot->GetGUID()); + if (botAI->IsMelee(bot)) + return false; + else + return true; + } + + return false; +} + +bool KazrogalBotIsLowOnManaTrigger::IsActive() +{ + if (bot->getClass() == CLASS_WARRIOR || bot->getClass() == CLASS_ROGUE || + bot->getClass() == CLASS_DEATH_KNIGHT) + return false; + + uint8 tab = AiFactory::GetPlayerSpecTab(bot); + if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL) + return false; + + if (!AI_VALUE2(Unit*, "find target", "kaz'rogal")) + return false; + + if (botAI->HasAnyAuraOf(bot, "ice block", "divine shield", nullptr)) + return false; + + if (isBelowManaThreshold.count(bot->GetGUID()) || + bot->GetPower(POWER_MANA) <= 3200) + return true; + + return false; +} + +bool KazrogalMarkDealsShadowDamageTrigger::IsActive() +{ + if (bot->getClass() != CLASS_PALADIN && bot->getClass() != CLASS_WARLOCK) + return false; + + if (!AI_VALUE2(Unit*, "find target", "kaz'rogal")) + return false; + + if (bot->getClass() == CLASS_PALADIN && + (botAI->HasAura("shadow resistance aura", bot) || + botAI->HasAura("prayer of shadow protection", bot) || + botAI->HasAura("shadow protection", bot))) + return false; + + return bot->HasAura( + static_cast(HyjalSummitSpells::SPELL_MARK_OF_KAZROGAL)); +} + +// Azgalor + +bool AzgalorPullingBossTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + return azgalor && azgalor->GetHealthPct() > 95.0f; +} + +bool AzgalorBossEngagedByMainTankTrigger::IsActive() +{ + return botAI->IsMainTank(bot) && AI_VALUE2(Unit*, "find target", "azgalor"); +} + +bool AzgalorMainTankIsPositioningBossTrigger::IsActive() +{ + if (botAI->IsRanged(bot)) + return false; + + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (!azgalor || azgalor->GetVictim() == bot) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank || !GET_PLAYERBOT_AI(mainTank) || botAI->IsMainTank(bot)) + return false; + + TankPositionState tankState = GetAzgalorTankPositionState(botAI, bot); + return tankState == TankPositionState::Unknown || + tankState == TankPositionState::MovingToTransition; +} + +bool AzgalorBossEngagedByRangedTrigger::IsActive() +{ + if (botAI->IsMelee(bot)) + return false; + + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + return azgalor && azgalor->GetVictim() != bot && + !bot->HasAura(static_cast(HyjalSummitSpells::SPELL_DOOM)); +} + +bool AzgalorBossCastsRainOfFireOnMeleeTrigger::IsActive() +{ + if (botAI->IsRanged(bot) || botAI->IsTank(bot)) + return false; + + Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor"); + if (!azgalor || azgalor->GetVictim() == bot || + bot->HasAura(static_cast(HyjalSummitSpells::SPELL_DOOM))) + return false; + + return IsInRainOfFire(bot, RAIN_OF_FIRE_RADIUS); +} + +bool AzgalorBotIsDoomedTrigger::IsActive() +{ + return bot->HasAura(static_cast(HyjalSummitSpells::SPELL_DOOM)); +} + +bool AzgalorDoomguardsMustBeControlledTrigger::IsActive() +{ + if (!botAI->IsAssistTank(bot) || + !AI_VALUE2(Unit*, "find target", "azgalor")) + return false; + + if (botAI->IsAssistTankOfIndex(bot, 0, true)) + { + return AI_VALUE2(Unit*, "find target", "lesser doomguard") || + AnyGroupMemberHasDoom(bot); + } + + if (botAI->IsAssistTankOfIndex(bot, 1, true)) + { + // Trigger for second assist tank only if first assist tank has Doom + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); + if (firstAssistTank && + !firstAssistTank->HasAura(static_cast(HyjalSummitSpells::SPELL_DOOM))) + return false; + + return AI_VALUE2(Unit*, "find target", "lesser doomguard") || + AnyGroupMemberHasDoom(bot); + } + + return false; +} + +bool AzgalorDoomguardsMustDieTrigger::IsActive() +{ + return botAI->IsRangedDps(bot) && AI_VALUE2(Unit*, "find target", "azgalor"); +} + +// Archimonde + +bool ArchimondePullingBossTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + return archimonde && archimonde->GetHealthPct() > 95.0f; +} + +bool ArchimondeBossEngagedByMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + return archimonde && archimonde->GetHealthPct() > 95.0f; +} + +bool ArchimondeBossCastsFearTrigger::IsActive() +{ + if (bot->getClass() != CLASS_PRIEST && + bot->getClass() != CLASS_SHAMAN) + return false; + + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + return archimonde && archimonde->GetHealthPct() > 10.0f; +} + +bool ArchimondeBossCastsAirBurstTrigger::IsActive() +{ + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + if (!archimonde || archimonde->GetHealthPct() <= 10.0f || + archimonde->GetVictim() == bot) + return false; + + return !botAI->IsMainTank(bot); +} + +bool ArchimondeBossSummonedDoomfireTrigger::IsActive() +{ + Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde"); + if (!archimonde || archimonde->GetHealthPct() <= 10.0f) + return false; + + // If I don't make an exception, bots actually refuse to enter the + // Doomfire even when feared + return !bot->HasAura( + static_cast(HyjalSummitSpells::SPELL_ARCHIMONDE_FEAR)); +} + +bool ArchimondeBotStoodInDoomfireTrigger::IsActive() +{ + if (bot->getClass() != CLASS_MAGE && bot->getClass() != CLASS_ROGUE && + bot->getClass() != CLASS_PALADIN) + return false; + + return bot->GetHealthPct() < 40.0f && + (bot->HasAura(static_cast(HyjalSummitSpells::SPELL_DOOMFIRE)) || + bot->HasAura(static_cast(HyjalSummitSpells::SPELL_DOOMFIRE_DOT))); +} diff --git a/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h b/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h new file mode 100644 index 00000000000..5c8fc9f0103 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDHYJALSUMMITTRIGGERS_H +#define _PLAYERBOT_RAIDHYJALSUMMITTRIGGERS_H + +#include "Trigger.h" + +// General + +class HyjalSummitBotIsNotInCombatTrigger : public Trigger +{ +public: + HyjalSummitBotIsNotInCombatTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "hyjal summit bot is not in combat") {} + bool IsActive() override; +}; + +// Rage Winterchill + +class RageWinterchillPullingBossTrigger : public Trigger +{ +public: + RageWinterchillPullingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill pulling boss") {} + bool IsActive() override; +}; + +class RageWinterchillBossEngagedByMainTankTrigger : public Trigger +{ +public: + RageWinterchillBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill boss engaged by main tank") {} + bool IsActive() override; +}; + +class RageWinterchillBossCastsDeathAndDecayOnRangedTrigger : public Trigger +{ +public: + RageWinterchillBossCastsDeathAndDecayOnRangedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill boss casts death and decay on ranged") {} + bool IsActive() override; +}; + +class RageWinterchillMeleeIsStandingInDeathAndDecayTrigger : public Trigger +{ +public: + RageWinterchillMeleeIsStandingInDeathAndDecayTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill melee is standing in death and decay") {} + bool IsActive() override; +}; + +// Anetheron + +class AnetheronPullingBossOrInfernalTrigger : public Trigger +{ +public: + AnetheronPullingBossOrInfernalTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "anetheron pulling boss or infernal") {} + bool IsActive() override; +}; + +class AnetheronBossEngagedByMainTankTrigger : public Trigger +{ +public: + AnetheronBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "anetheron boss engaged by main tank") {} + bool IsActive() override; +}; + +class AnetheronBossCastsCarrionSwarmTrigger : public Trigger +{ +public: + AnetheronBossCastsCarrionSwarmTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "anetheron boss casts carrion swarm") {} + bool IsActive() override; +}; + +class AnetheronBotIsTargetedByInfernalTrigger : public Trigger +{ +public: + AnetheronBotIsTargetedByInfernalTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "anetheron bot is targeted by infernal") {} + bool IsActive() override; +}; + +class AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger : public Trigger +{ +public: + AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "anetheron infernals need to be kept away from raid") {} + bool IsActive() override; +}; + +class AnetheronInfernalsContinueToSpawnTrigger : public Trigger +{ +public: + AnetheronInfernalsContinueToSpawnTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "anetheron infernals continue to spawn") {} + bool IsActive() override; +}; + +// Kaz'rogal + +class KazrogalPullingBossTrigger : public Trigger +{ +public: + KazrogalPullingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal pulling boss") {} + bool IsActive() override; +}; + +class KazrogalBossEngagedByMainTankTrigger : public Trigger +{ +public: + KazrogalBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal boss engaged by main tank") {} + bool IsActive() override; +}; + +class KazrogalBossEngagedByAssistTanksTrigger : public Trigger +{ +public: + KazrogalBossEngagedByAssistTanksTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal boss engaged by assist tanks") {} + bool IsActive() override; +}; + +class KazrogalLowManaBotsNeedEscapePathTrigger : public Trigger +{ +public: + KazrogalLowManaBotsNeedEscapePathTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal low mana bots need escape path") {} + bool IsActive() override; +}; + +class KazrogalBotIsLowOnManaTrigger : public Trigger +{ +public: + KazrogalBotIsLowOnManaTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal bot is low on mana") {} + bool IsActive() override; +}; + +class KazrogalMarkDealsShadowDamageTrigger : public Trigger +{ +public: + KazrogalMarkDealsShadowDamageTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal mark deals shadow damage") {} + bool IsActive() override; +}; + +// Azgalor + +class AzgalorPullingBossTrigger : public Trigger +{ +public: + AzgalorPullingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "azgalor pulling boss") {} + bool IsActive() override; +}; + +class AzgalorBossEngagedByMainTankTrigger : public Trigger +{ +public: + AzgalorBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "azgalor boss engaged by main tank") {} + bool IsActive() override; +}; + +class AzgalorMainTankIsPositioningBossTrigger : public Trigger +{ +public: + AzgalorMainTankIsPositioningBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "azgalor main tank is positioning boss") {} + bool IsActive() override; +}; + +class AzgalorBossEngagedByRangedTrigger : public Trigger +{ +public: + AzgalorBossEngagedByRangedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "azgalor boss engaged by ranged") {} + bool IsActive() override; +}; + +class AzgalorBossCastsRainOfFireOnMeleeTrigger : public Trigger +{ +public: + AzgalorBossCastsRainOfFireOnMeleeTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "azgalor boss casts rain of fire on melee") {} + bool IsActive() override; +}; + +class AzgalorBotIsDoomedTrigger : public Trigger +{ +public: + AzgalorBotIsDoomedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "azgalor bot is doomed") {} + bool IsActive() override; +}; + +class AzgalorDoomguardsMustBeControlledTrigger : public Trigger +{ +public: + AzgalorDoomguardsMustBeControlledTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "azgalor doomguards must be controlled") {} + bool IsActive() override; +}; + +class AzgalorDoomguardsMustDieTrigger : public Trigger +{ +public: + AzgalorDoomguardsMustDieTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "azgalor doomguards must die") {} + bool IsActive() override; +}; + +// Archimonde + +class ArchimondePullingBossTrigger : public Trigger +{ +public: + ArchimondePullingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "archimonde pulling boss") {} + bool IsActive() override; +}; + +class ArchimondeBossEngagedByMainTankTrigger : public Trigger +{ +public: + ArchimondeBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss engaged by main tank") {} + bool IsActive() override; +}; + +class ArchimondeBossCastsFearTrigger : public Trigger +{ +public: + ArchimondeBossCastsFearTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss casts fear") {} + bool IsActive() override; +}; + +class ArchimondeBossCastsAirBurstTrigger : public Trigger +{ +public: + ArchimondeBossCastsAirBurstTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss casts air burst") {} + bool IsActive() override; +}; + +class ArchimondeBossSummonedDoomfireTrigger : public Trigger +{ +public: + ArchimondeBossSummonedDoomfireTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss summoned doomfire") {} + bool IsActive() override; +}; + +class ArchimondeBotStoodInDoomfireTrigger : public Trigger +{ +public: + ArchimondeBotStoodInDoomfireTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "archimonde bot stood in doomfire") {} + bool IsActive() override; +}; + +#endif diff --git a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp new file mode 100644 index 00000000000..04ed97cd098 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidHyjalSummitHelpers.h" + +#include + +#include "Playerbots.h" +#include "RaidBossHelpers.h" +#include "Timer.h" + +namespace HyjalSummitHelpers +{ + // General + + bool GetGroundedStepPosition( + Player* bot, float destinationX, float destinationY, float moveDist, + float& stepX, float& stepY, float& stepZ) + { + const float distance = bot->GetExactDist2d(destinationX, destinationY); + if (distance <= 0.0f) + return false; + + const float stepDistance = std::min(moveDist, distance); + const float deltaX = destinationX - bot->GetPositionX(); + const float deltaY = destinationY - bot->GetPositionY(); + stepX = bot->GetPositionX() + (deltaX / distance) * stepDistance; + stepY = bot->GetPositionY() + (deltaY / distance) * stepDistance; + stepZ = bot->GetMapWaterOrGroundLevel(stepX, stepY, bot->GetPositionZ()); + if (stepZ <= INVALID_HEIGHT) + stepZ = bot->GetPositionZ(); + + bot->GetMap()->CheckCollisionAndGetValidCoords( + bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), + stepX, stepY, stepZ, false); + + return true; + } + + RangedGroups GetRangedGroups(PlayerbotAI* botAI, Player* bot) + { + RangedGroups result; + Group* group = bot->GetGroup(); + if (!group) + return result; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !botAI->IsRanged(member)) + continue; + + if (botAI->IsHeal(member)) + result.healers.push_back(member); + else + result.rangedDps.push_back(member); + } + + return result; + } + + std::pair GetBotCircleIndexAndCount(PlayerbotAI* botAI, Player* bot, + const RangedGroups& groups) + { + const std::vector& vec = botAI->IsHeal(bot) ? groups.healers : groups.rangedDps; + auto it = std::find(vec.begin(), vec.end(), bot); + size_t index = (it != vec.end()) ? std::distance(vec.begin(), it) : 0; + + return {index, vec.size()}; + } + + // Rage Winterchill + + const Position WINTERCHILL_TANK_POSITION = { 5031.061f, -1784.521f, 1321.626f }; + std::unordered_map hasReachedWinterchillPosition; + std::unordered_map deathAndDecayPosition; + + DeathAndDecayData* GetActiveWinterchillDeathAndDecay(uint32 instanceId) + { + auto instanceIt = deathAndDecayPosition.find(instanceId); + if (instanceIt == deathAndDecayPosition.end()) + return nullptr; + + const uint32 now = getMSTime(); + const uint32 elapsed = getMSTimeDiff(instanceIt->second.spawnTime, now); + if (elapsed >= DEATH_AND_DECAY_REACQUIRE_DELAY) + { + deathAndDecayPosition.erase(instanceIt); + return nullptr; + } + + if (elapsed >= DEATH_AND_DECAY_DURATION) + return nullptr; + + return &instanceIt->second; + } + + bool IsInDeathAndDecay(Player* bot, float radius) + { + const uint32 instanceId = bot->GetMap()->GetInstanceId(); + Aura* aura = bot->GetAura(static_cast(HyjalSummitSpells::SPELL_DEATH_AND_DECAY)); + if (aura) + { + DynamicObject* dynObj = aura->GetDynobjOwner(); + if (dynObj && dynObj->IsInWorld()) + { + const uint32 now = getMSTime(); + auto instanceIt = deathAndDecayPosition.find(instanceId); + if (instanceIt == deathAndDecayPosition.end() || + getMSTimeDiff(instanceIt->second.spawnTime, now) >= DEATH_AND_DECAY_REACQUIRE_DELAY) + { + deathAndDecayPosition[instanceId] = + DeathAndDecayData{ dynObj->GetPosition(), now }; + } + } + } + + DeathAndDecayData* data = GetActiveWinterchillDeathAndDecay(instanceId); + if (!data) + return false; + + return bot->GetExactDist2d(data->position) < radius; + } + + // Anetheron + + const Position ANETHERON_TANK_POSITION = { 5033.177f, -1765.996f, 1324.195f }; + const Position ANETHERON_E_INFERNAL_POSITION = { 5016.578f, -1800.233f, 1323.070f }; + const Position ANETHERON_W_INFERNAL_POSITION = { 5048.911f, -1722.164f, 1321.408f }; + std::unordered_map hasReachedAnetheronPosition; + + Player* GetInfernoTarget(Unit* anetheron) + { + if (!anetheron) + return nullptr; + + Spell* spell = anetheron->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (spell && spell->m_spellInfo->Id == + static_cast(HyjalSummitSpells::SPELL_INFERNO)) + { + Unit* spellTarget = spell->m_targets.GetUnitTarget(); + if (spellTarget && spellTarget->IsPlayer()) + return spellTarget->ToPlayer(); + } + + return nullptr; + } + + const Position& GetClosestInfernalTankPosition(Player* bot) + { + const Position& east = ANETHERON_E_INFERNAL_POSITION; + const Position& west = ANETHERON_W_INFERNAL_POSITION; + return (bot->GetExactDist2d(east.GetPositionX(), east.GetPositionY()) <= + bot->GetExactDist2d(west.GetPositionX(), west.GetPositionY())) ? east : west; + } + + // Kaz'rogal + + const Position KAZROGAL_TANK_TRANSITION_POSITION = { 5528.792f, -2636.486f, 1481.293f }; + const Position KAZROGAL_TANK_FINAL_POSITION = { 5511.514f, -2662.466f, 1480.288f }; + std::unordered_map kazrogalTankStep; + std::unordered_map isBelowManaThreshold; + + TankPositionState GetKazrogalTankPositionState(PlayerbotAI* botAI, Player* bot) + { + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return TankPositionState::Unknown; + + auto it = kazrogalTankStep.find(mainTank->GetGUID()); + if (it != kazrogalTankStep.end()) + return it->second; + + return TankPositionState::Unknown; + } + + // Azgalor + + const Position AZGALOR_TANK_TRANSITION_POSITION = { 5486.787f, -2696.215f, 1482.007f }; + const Position AZGALOR_TANK_FINAL_POSITION = { 5496.379f, -2675.265f, 1481.053f }; + const Position AZGALOR_DOOMGUARD_POSITION = { 5485.555f, -2731.659f, 1485.555f }; + std::unordered_map azgalorTankStep; + std::unordered_map rainOfFirePosition; + + RainOfFireData* GetActiveAzgalorRainOfFire(uint32 instanceId) + { + auto instanceIt = rainOfFirePosition.find(instanceId); + if (instanceIt == rainOfFirePosition.end()) + return nullptr; + + const uint32 now = getMSTime(); + const uint32 elapsed = getMSTimeDiff(instanceIt->second.spawnTime, now); + if (elapsed >= RAIN_OF_FIRE_REACQUIRE_DELAY) + { + rainOfFirePosition.erase(instanceIt); + return nullptr; + } + + if (elapsed >= RAIN_OF_FIRE_DURATION) + return nullptr; + + return &instanceIt->second; + } + + TankPositionState GetAzgalorTankPositionState(PlayerbotAI* botAI, Player* bot) + { + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return TankPositionState::Unknown; + + auto it = azgalorTankStep.find(mainTank->GetGUID()); + if (it != azgalorTankStep.end()) + return it->second; + + return TankPositionState::Unknown; + } + + bool IsInRainOfFire(Player* bot, float radius) + { + RainOfFireData* data = GetActiveAzgalorRainOfFire(bot->GetMap()->GetInstanceId()); + if (!data) + return false; + + return bot->GetExactDist2d(data->position) < radius; + } + + bool AnyGroupMemberHasDoom(Player* bot) + { + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && + member->HasAura(static_cast(HyjalSummitSpells::SPELL_DOOM))) + return true; + } + } + + return false; + } + + // Archimonde + + const Position ARCHIMONDE_INITIAL_POSITION = { 5640.502f, -3421.238f, 1587.453f }; + std::unordered_map archimondeAirBurstTargets; + std::unordered_map> doomfireTrails; + std::unordered_map doomfireLastSampleTime; + + AirBurstData* GetRecentArchimondeAirBurst(uint32 instanceId) + { + auto instanceIt = archimondeAirBurstTargets.find(instanceId); + if (instanceIt == archimondeAirBurstTargets.end()) + return nullptr; + + constexpr uint32 airBurstReactionWindow = 2000; + const uint32 now = getMSTime(); + if (getMSTimeDiff(instanceIt->second.castTime, now) >= airBurstReactionWindow) + { + archimondeAirBurstTargets.erase(instanceIt); + return nullptr; + } + + return &instanceIt->second; + } +} diff --git a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h new file mode 100644 index 00000000000..d5c8f999789 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDHYJALSUMMITHELPERS_H_ +#define _PLAYERBOT_RAIDHYJALSUMMITHELPERS_H_ + +#include +#include +#include + +#include "AiObject.h" +#include "Position.h" +#include "Unit.h" + +namespace HyjalSummitHelpers +{ + enum class HyjalSummitSpells : uint32 + { + // Rage Winterchill + SPELL_DEATH_AND_DECAY = 31258, + + // Anetheron + SPELL_INFERNO = 31299, + + // Kaz'rogal + SPELL_MARK_OF_KAZROGAL = 31447, + + // Azgalor + SPELL_RAIN_OF_FIRE = 31340, + SPELL_DOOM = 31347, + + // Archimonde + SPELL_DOOMFIRE = 31944, // Damaging part of trail + SPELL_DOOMFIRE_DOT = 31969, // DoT after exiting trail + SPELL_ARCHIMONDE_FEAR = 31970, + SPELL_AIR_BURST = 32014, + + // Hunter + SPELL_MISDIRECTION = 35079, + + // Priest + SPELL_FEAR_WARD = 6346, + }; + + enum class HyjalSummitNpcs : uint32 + { + // Archimonde + NPC_DOOMFIRE = 18095, + }; + + enum class TankPositionState : uint8 + { + MovingToTransition = 0, + MovingToFinal = 1, + Positioned = 2, + Unknown = 255, + }; + + // General + constexpr uint32 HYJAL_SUMMIT_MAP_ID = 534; + struct RangedGroups + { + std::vector healers; + std::vector rangedDps; + }; + bool GetGroundedStepPosition( + Player* bot, float destinationX, float destinationY, float moveDist, + float& stepX, float& stepY, float& stepZ); + RangedGroups GetRangedGroups(PlayerbotAI* botAI, Player* bot); + std::pair GetBotCircleIndexAndCount(PlayerbotAI* botAI, Player* bot, + const RangedGroups& groups); + + // Rage Winterchill + extern const Position WINTERCHILL_TANK_POSITION; + extern std::unordered_map hasReachedWinterchillPosition; + constexpr uint32 DEATH_AND_DECAY_DURATION = 15000; + constexpr uint32 DEATH_AND_DECAY_REACQUIRE_DELAY = 20000; + constexpr float DEATH_AND_DECAY_SAFE_RADIUS = 22.0f; // 20y radius + 1.5y player hitbox + 0.5y buffer + struct DeathAndDecayData + { + Position position; + uint32 spawnTime; + }; + extern std::unordered_map deathAndDecayPosition; + DeathAndDecayData* GetActiveWinterchillDeathAndDecay(uint32 instanceId); + bool IsInDeathAndDecay(Player* bot, float radius); + + // Anetheron + extern const Position ANETHERON_TANK_POSITION; + extern const Position ANETHERON_E_INFERNAL_POSITION; + extern const Position ANETHERON_W_INFERNAL_POSITION; + extern std::unordered_map hasReachedAnetheronPosition; + Player* GetInfernoTarget(Unit* anetheron); + const Position& GetClosestInfernalTankPosition(Player* bot); + + // Kaz'rogal + extern const Position KAZROGAL_TANK_TRANSITION_POSITION; + extern const Position KAZROGAL_TANK_FINAL_POSITION; + extern std::unordered_map kazrogalTankStep; + extern std::unordered_map isBelowManaThreshold; + TankPositionState GetKazrogalTankPositionState(PlayerbotAI* botAI, Player* bot); + + // Azgalor + extern const Position AZGALOR_TANK_TRANSITION_POSITION; + extern const Position AZGALOR_TANK_FINAL_POSITION; + extern const Position AZGALOR_DOOMGUARD_POSITION; + extern std::unordered_map azgalorTankStep; + constexpr uint32 RAIN_OF_FIRE_DURATION = 10000; + constexpr uint32 RAIN_OF_FIRE_REACQUIRE_DELAY = 15000; + constexpr float RAIN_OF_FIRE_RADIUS = 17.0f; // 15y radius + 1.5y player hitbox + 0.5y buffer + struct RainOfFireData + { + Position position; + uint32 spawnTime; + }; + extern std::unordered_map rainOfFirePosition; + TankPositionState GetAzgalorTankPositionState(PlayerbotAI* botAI, Player* bot); + RainOfFireData* GetActiveAzgalorRainOfFire(uint32 instanceId); + bool IsInRainOfFire(Player* bot, float radius); + bool AnyGroupMemberHasDoom(Player* bot); + + // Archimonde + constexpr float AIR_BURST_SAFE_DISTANCE = 15.0f; + struct AirBurstData + { + ObjectGuid targetGuid; + uint32 castTime; + }; + struct DoomfireTrailData + { + Position position; + uint32 recordTime; + }; + extern const Position ARCHIMONDE_INITIAL_POSITION; + extern std::unordered_map archimondeAirBurstTargets; + extern std::unordered_map> doomfireTrails; + extern std::unordered_map doomfireLastSampleTime; + AirBurstData* GetRecentArchimondeAirBurst(uint32 instanceId); +} + +#endif diff --git a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp new file mode 100644 index 00000000000..a1ccf4005f7 --- /dev/null +++ b/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidHyjalSummitHelpers.h" +#include "AllCreatureScript.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "RaidBossHelpers.h" +#include "DynamicObjectScript.h" +#include "Playerbots.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "Timer.h" + +using namespace HyjalSummitHelpers; + +static Player* GetFirstPlayerSpellTarget(Spell* spell, Unit* caster) +{ + if (!spell || !caster) + return nullptr; + + if (Unit* unitTarget = spell->m_targets.GetUnitTarget()) + return unitTarget->ToPlayer(); + + std::list const& targets = *spell->GetUniqueTargetInfo(); + for (TargetInfo const& targetInfo : targets) + { + if (Player* target = ObjectAccessor::GetPlayer(*caster, targetInfo.targetGUID)) + return target; + } + + return nullptr; +} + +static bool ShouldInterruptForArchimondeAirBurst(PlayerbotAI* botAI, Player* bot, Player* target) +{ + if (!target) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank || bot == mainTank) + return false; + + float distanceToMainTank = bot->GetExactDist2d(mainTank); + + return (target == mainTank || target == bot) && + distanceToMainTank < AIR_BURST_SAFE_DISTANCE; +} + +// Records the active Rain of Fire dynamic object so that melee bots can avoid it by running +// away from Azgalor or swapping to a Doomguard; the standard FleePosition() logic to avoid aoe +// can take melee in front of Azgalor, resulting in them getting cleaved +class AzgalorRainOfFireScript : public DynamicObjectScript +{ +public: + AzgalorRainOfFireScript() : DynamicObjectScript("AzgalorRainOfFireScript") {} + + void OnUpdate(DynamicObject* dynobj, uint32 /*diff*/) override + { + if (dynobj->GetSpellId() != static_cast(HyjalSummitSpells::SPELL_RAIN_OF_FIRE)) + return; + + uint32 instanceId = dynobj->GetMap()->GetInstanceId(); + if (GetActiveAzgalorRainOfFire(instanceId)) + return; + + uint32 now = getMSTime(); + auto instanceIt = rainOfFirePosition.find(instanceId); + if (instanceIt != rainOfFirePosition.end() && + getMSTimeDiff(instanceIt->second.spawnTime, now) < RAIN_OF_FIRE_REACQUIRE_DELAY) + { + return; + } + + bool shouldTrackRainOfFire = false; + Map::PlayerList const& players = dynobj->GetMap()->GetPlayers(); + for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it) + { + Player* player = it->GetSource(); + if (!player || !player->IsAlive()) + continue; + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); + if (!botAI || !botAI->HasStrategy("hyjal", BOT_STATE_COMBAT)) + continue; + + shouldTrackRainOfFire = true; + break; + } + + if (!shouldTrackRainOfFire) + return; + + rainOfFirePosition[instanceId] = RainOfFireData{ dynobj->GetPosition(), now }; + } +}; + +// Records the position of each Doomfire NPC at regular intervals so that bots can avoid +// the persistent fire trail it leaves behind. Each sample is tagged with a timestamp and +// expires after TRAIL_DURATION ms, matching the lifetime of a Doomfire DynamicObject (18s) +class ArchimondeDoomfireTrailScript : public AllCreatureScript +{ +public: + ArchimondeDoomfireTrailScript() : AllCreatureScript("ArchimondeDoomfireTrailScript") {} + + void OnAllCreatureUpdate(Creature* creature, uint32 /*diff*/) override + { + if (creature->GetEntry() != static_cast(HyjalSummitNpcs::NPC_DOOMFIRE)) + return; + + uint32 now = getMSTime(); + ObjectGuid guid = creature->GetGUID(); + + auto& lastSample = doomfireLastSampleTime[guid]; + if (getMSTimeDiff(lastSample, now) < 500) + return; + + lastSample = now; + + uint32 instanceId = creature->GetMap()->GetInstanceId(); + auto& trail = doomfireTrails[instanceId]; + + DoomfireTrailData data; + data.position = creature->GetPosition(); + data.recordTime = now; + trail.push_back(data); + + constexpr uint32 TRAIL_DURATION = 18000; + trail.erase(std::remove_if(trail.begin(), trail.end(), + [now](const DoomfireTrailData& d) + { + return getMSTimeDiff(d.recordTime, now) > TRAIL_DURATION; + }), trail.end()); + + constexpr float DOOMFIRE_DANGER_RANGE = 10.0f; + Map::PlayerList const& players = creature->GetMap()->GetPlayers(); + for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it) + { + Player* player = it->GetSource(); + if (!player || !player->IsAlive()) + continue; + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); + if (!botAI || !botAI->HasStrategy("hyjal", BOT_STATE_COMBAT) || + creature->GetDistance(player) > DOOMFIRE_DANGER_RANGE) + { + continue; + } + + botAI->RequestSpellInterrupt(); + } + } + + void OnCreatureRemoveWorld(Creature* creature) override + { + if (creature->GetEntry() != static_cast(HyjalSummitNpcs::NPC_DOOMFIRE)) + return; + + doomfireLastSampleTime.erase(creature->GetGUID()); + } +}; + +class ArchimondeAirBurstSpellListenerScript : public AllSpellScript +{ +public: + ArchimondeAirBurstSpellListenerScript() : + AllSpellScript("ArchimondeAirBurstSpellListenerScript") {} + + void OnSpellCast( + Spell* spell, Unit* caster, SpellInfo const* spellInfo, bool /*skipCheck*/) override + { + if (!spell || !caster || !spellInfo) + return; + + if (spellInfo->Id != static_cast(HyjalSummitSpells::SPELL_AIR_BURST)) + return; + + Player* target = GetFirstPlayerSpellTarget(spell, caster); + if (!target) + return; + + archimondeAirBurstTargets[caster->GetMap()->GetInstanceId()] = + AirBurstData{ target->GetGUID(), getMSTime() }; + + Map::PlayerList const& players = caster->GetMap()->GetPlayers(); + for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it) + { + Player* player = it->GetSource(); + if (!player || !player->IsAlive()) + continue; + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); + if (!botAI || !botAI->HasStrategy("hyjal", BOT_STATE_COMBAT) || + !ShouldInterruptForArchimondeAirBurst(botAI, player, target)) + { + continue; + } + + botAI->RequestSpellInterrupt(); + } + } +}; + +void AddSC_HyjalSummitBotScripts() +{ + new AzgalorRainOfFireScript(); + new ArchimondeDoomfireTrailScript(); + new ArchimondeAirBurstSpellListenerScript(); +} diff --git a/src/Ai/Raid/RaidStrategyContext.h b/src/Ai/Raid/RaidStrategyContext.h index 970ecf4aa59..bdc76c4a731 100644 --- a/src/Ai/Raid/RaidStrategyContext.h +++ b/src/Ai/Raid/RaidStrategyContext.h @@ -11,6 +11,7 @@ #include "RaidNaxxStrategy.h" #include "RaidSSCStrategy.h" #include "RaidTempestKeepStrategy.h" +#include "RaidHyjalSummitStrategy.h" #include "RaidZulAmanStrategy.h" #include "RaidOsStrategy.h" #include "RaidEoEStrategy.h" @@ -33,6 +34,7 @@ class RaidStrategyContext : public NamedObjectContext creators["naxx"] = &RaidStrategyContext::naxx; creators["ssc"] = &RaidStrategyContext::ssc; creators["tempestkeep"] = &RaidStrategyContext::tempestkeep; + creators["hyjal"] = &RaidStrategyContext::hyjal; creators["zulaman"] = &RaidStrategyContext::zulaman; creators["wotlk-os"] = &RaidStrategyContext::wotlk_os; creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe; @@ -52,6 +54,7 @@ class RaidStrategyContext : public NamedObjectContext static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); } static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); } static Strategy* tempestkeep(PlayerbotAI* botAI) { return new RaidTempestKeepStrategy(botAI); } + static Strategy* hyjal(PlayerbotAI* botAI) { return new RaidHyjalSummitStrategy(botAI); } static Strategy* zulaman(PlayerbotAI* botAI) { return new RaidZulAmanStrategy(botAI); } static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); } static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); } diff --git a/src/Bot/Engine/BuildSharedActionContexts.cpp b/src/Bot/Engine/BuildSharedActionContexts.cpp index 7e243eadb2f..4c2c8fad3cd 100644 --- a/src/Bot/Engine/BuildSharedActionContexts.cpp +++ b/src/Bot/Engine/BuildSharedActionContexts.cpp @@ -11,6 +11,7 @@ #include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h" #include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h" #include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h" +#include "Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h" #include "Ai/Raid/ZulAman/RaidZulAmanActionContext.h" #include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h" #include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h" @@ -34,6 +35,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList allInstanceStrategies = { - "aq20", "bwl", "karazhan", "gruulslair", "icc", "magtheridon", "moltencore", - "naxx", "onyxia", "ssc", "tbc-ac", "tempestkeep", "ulduar", "voa", "wotlk-an", "wotlk-cos", - "wotlk-dtk", "wotlk-eoe", "wotlk-fos", "wotlk-gd", "wotlk-hol", "wotlk-hor", - "wotlk-hos", "wotlk-nex", "wotlk-occ", "wotlk-ok", "wotlk-os", "wotlk-pos", - "wotlk-toc", "wotlk-uk", "wotlk-up", "wotlk-vh", "zulaman" + "aq20", "bwl", "karazhan", "gruulslair", "hyjal", "icc", "magtheridon", + "moltencore", "naxx", "onyxia", "ssc", "tbc-ac", "tempestkeep", "ulduar", + "voa", "wotlk-an", "wotlk-cos", "wotlk-dtk", "wotlk-eoe", "wotlk-fos", + "wotlk-gd", "wotlk-hol", "wotlk-hor", "wotlk-hos", "wotlk-nex", "wotlk-occ", + "wotlk-ok", "wotlk-os", "wotlk-pos", "wotlk-toc", "wotlk-uk", "wotlk-up", + "wotlk-vh", "zulaman" }; for (const std::string& strat : allInstanceStrategies) @@ -1620,6 +1621,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) case 533: strategyName = "naxx"; // Naxxramas break; + case 534: + strategyName = "hyjal"; // The Battle for Mount Hyjal (Hyjal Summit) + break; case 544: strategyName = "magtheridon"; // Magtheridon's Lair break; diff --git a/src/Script/Playerbots.cpp b/src/Script/Playerbots.cpp index 5be7e8855dc..ab92729a760 100644 --- a/src/Script/Playerbots.cpp +++ b/src/Script/Playerbots.cpp @@ -526,6 +526,7 @@ class PlayerbotsBattlefieldScript : public BattlefieldScript void AddPlayerbotsSecureLoginScripts(); void AddSC_TempestKeepBotScripts(); +void AddSC_HyjalSummitBotScripts(); void AddPlayerbotsScripts() { @@ -541,4 +542,5 @@ void AddPlayerbotsScripts() AddPlayerbotsCommandscripts(); PlayerBotsGuildValidationScript(); AddSC_TempestKeepBotScripts(); + AddSC_HyjalSummitBotScripts(); } From 8caf37af97b74545f8fa65172095803466ad8a06 Mon Sep 17 00:00:00 2001 From: Ivan Novokhatski Date: Sat, 9 May 2026 07:39:55 +0200 Subject: [PATCH 11/63] Add EnableAutoTradeOnItemMention config option (#2323) ## Pull Request Description This PR adds a config parameter `AiPlayerbot.EnableAutoTradeOnItemMention` that controls whether trade dialogues and inventory listings will be triggered for messages that contain keywords anywhere in their text (for example "got some food?"). The default value is `1/true`, so for existing installs there will be no change. This is useful for other mods that could utilise game chats for other purposes, specifically my [mod-playerbots-characters](https://github.com/deseven/mod-playerbots-characters) and @DustinHendrickson 's [mod-ollama-chat](https://github.com/DustinHendrickson/mod-ollama-chat). Individual users might also benefit from the ability to disable this functionality. ## Feature Evaluation N/A ## How to Test the Changes 1. Start the server with default config and join the game. 2. Get into a party with one or more bots. 3. Write `got some food?` to the party chat. 4. A trade dialogue along with the whispers from the bots should pop up. 5. Stop the server, change `AiPlayerbot.EnableAutoTradeOnItemMention` to `0`. 6. Start the server, join the game. 7. Get into a party with one or more bots. 8. Write `got some food?` to the party chat. 9. Nothing should happen. > [!NOTE] > In both cases the commands `t something` and `c something` should still work. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers N/A --- conf/playerbots.conf.dist | 8 ++++++++ src/Bot/Engine/ExternalEventHelper.cpp | 7 +++++-- src/PlayerbotAIConfig.cpp | 1 + src/PlayerbotAIConfig.h | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 3e72a5058ee..41ca0b6b412 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -2240,6 +2240,14 @@ AiPlayerbot.CommandPrefix = "" # Separator for bot chat commands AiPlayerbot.CommandSeparator = "\\\\" +# Enable automatic item count/trade trigger when a chat message contains +# item-related keywords. When enabled (1), mentioning items in chat +# (e.g. "food", "potion", "ammo") will automatically show inventory and +# open a trade window with the bot. Explicit "c" and "t" commands still +# work regardless of this setting. +# Default: 1 (enabled) +AiPlayerbot.EnableAutoTradeOnItemMention = 1 + # Enable bots talking (say / yell / general chatting / lfg) AiPlayerbot.RandomBotTalk = 1 # Enable bots emoting diff --git a/src/Bot/Engine/ExternalEventHelper.cpp b/src/Bot/Engine/ExternalEventHelper.cpp index 3a62fbda9ac..912f6587722 100644 --- a/src/Bot/Engine/ExternalEventHelper.cpp +++ b/src/Bot/Engine/ExternalEventHelper.cpp @@ -33,8 +33,11 @@ bool ExternalEventHelper::ParseChatCommand(std::string const command, Player* ow if (!ChatHelper::parseableItem(command)) return false; - HandleCommand("c", command, owner); - HandleCommand("t", command, owner); + if (sPlayerbotAIConfig.enableAutoTradeOnItemMention) + { + HandleCommand("c", command, owner); + HandleCommand("t", command, owner); + } return true; } diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 242febd17fb..8a0c6b0a0fb 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -519,6 +519,7 @@ bool PlayerbotAIConfig::Initialize() LoadListString>(sConfigMgr->GetOption("AiPlayerbot.AllowedLogFiles", ""), allowedLogFiles); + enableAutoTradeOnItemMention = sConfigMgr->GetOption("AiPlayerbot.EnableAutoTradeOnItemMention", true); LoadListString>(sConfigMgr->GetOption("AiPlayerbot.TradeActionExcludedPrefixes", ""), tradeActionExcludedPrefixes); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 210e03ef95d..1a343db4dac 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -306,6 +306,7 @@ class PlayerbotAIConfig uint32 iterationsPerTick; std::mutex m_logMtx; + bool enableAutoTradeOnItemMention; std::vector tradeActionExcludedPrefixes; std::vector allowedLogFiles; std::unordered_map> logFiles; From 7af675e712023fc553fc7ac595ff2e638bd4f0f5 Mon Sep 17 00:00:00 2001 From: Ivan Novokhatski Date: Sat, 9 May 2026 07:40:16 +0200 Subject: [PATCH 12/63] Respect worldserver's PreventAFKLogout value (#2328) ## Pull Request Description This adds checks to prevent bots from logging out when the master isn't actually logging out, respecting the `PreventAFKLogout` setting in `worldserver.conf`. Otherwise, returning to the game after a long pause means your bots are offline and you have to re-add them, which is annoying. ## Feature Evaluation N/A ## How to Test the Changes 1. Set `PreventAFKLogout` to 1 or 2 in `worldserver.conf`. 2. Start the server, log in, add some bots to your party. 3. Go to a sanctuary if you set `PreventAFKLogout` to 1 or just start idling anywhere otherwise. 4. Both you and the bots will stay in-game no matter how much time has passed. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) Researching the issue and determining what checks need to be implemented. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers N/A --- src/Bot/PlayerbotMgr.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index 68eb2fd1ed7..9d3d61ca335 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -1542,6 +1542,24 @@ void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) // if master is logging out, log out all bots case CMSG_LOGOUT_REQUEST: { + Player* master = GetMaster(); + if (master) + { + // Replicate the AFK logout prevention checks from WorldSession::HandleLogoutRequestOpcode + // so bots are not logged out when the master's own logout is going to be prevented. + AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(master->GetAreaId()); + bool preventAfkSanctuaryLogout = sWorld->getIntConfig(CONFIG_AFK_PREVENT_LOGOUT) == 1 + && master->isAFK() && areaEntry && areaEntry->IsSanctuary(); + + bool preventAfkLogout = sWorld->getIntConfig(CONFIG_AFK_PREVENT_LOGOUT) == 2 + && master->isAFK(); + + if (preventAfkSanctuaryLogout || preventAfkLogout) + { + break; + } + } + LogoutAllBots(); break; } From 826887133d01405e1021b731f19d213bdabc0772 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 9 May 2026 00:40:35 -0500 Subject: [PATCH 13/63] Exclude Invalid Weapons from Shaman Enchants & Refactor Temporary Enchant Spellcasting (#2345) ## Pull Request Description Fix for issue #2343 I excluded the MISC and FISHING POLE weapon subclasses from weapon enchants. MISC includes the entry profession "weapons" (skinning knife, mining pick, blacksmithing hammer, arclight spanner) and some other crap that I suspect is not enchantable, but even if it is there's no good reason to do so (like Brewfest steins). The subclass doesn't include weapons that can be used for professions but you might actually want to use for fighting (like Finkle's Skinner). To clean things up overall, I removed the intermediate class CastEnchantItemAction between CastSpellAction and CastEnchantItemMainHandAction and CastEnchantItemOffHandAction. CastEnchantItemAction is not doing anything helpful that can't easily be replicated in the MH/OH classes, and I can't think of any future reason for keeping CastEnchantItemAction. I also brought the CanCastSpell check into the MH/OH classes--previously it just wasn't run for the weapon enchant spells, and I can't think of any good reason why it shouldn't be. I also added Execute functions to both CastEnchantItemMainHandAction and CastEnchantItemOffHandAction so they actually directly cast the enchant on the specified hand instead of running through CastSpellAction's Execute (and thus going through item for spell). I wasn't having problems with the wrong hand being applied under the prior approach, but this is a more direct and better approach anyway. Other changes are just formatting. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. The new path is very similar to the old one but just adds a check that is common to all spells and early returns to avoid invalid results. ## How to Test the Changes 1. Log into a Shaman and activate selfbot 2. Check to make sure the correct enchantments are applied (e.g., MH Windfury and OH Flametongue for a dual-wielding Enhancement Shaman) 3. Equip a profession weapon such as a skinning knife and make sure the Shaman does not attempt to enchant it ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) This just stops Shamans from trying to enchant stuff that they can't. - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) I kicked around some ideas with GPT-5.4 with respect to the refactoring aspect of the PR after I had fixed the bug. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Actions/GenericSpellActions.cpp | 79 ++++++++++----------- src/Ai/Base/Actions/GenericSpellActions.h | 17 ++--- 2 files changed, 44 insertions(+), 52 deletions(-) diff --git a/src/Ai/Base/Actions/GenericSpellActions.cpp b/src/Ai/Base/Actions/GenericSpellActions.cpp index c81aca21467..587862a29fe 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.cpp +++ b/src/Ai/Base/Actions/GenericSpellActions.cpp @@ -17,7 +17,7 @@ #include "WorldPacket.h" #include "Group.h" #include "Chat.h" -#include "Ai/Base/Util/GenericBuffUtils.h" +#include "GenericBuffUtils.h" #include "PlayerbotAI.h" using ai::buff::MakeAuraQualifierForBuff; @@ -134,7 +134,8 @@ bool CastSpellAction::isPossible() return botAI->CanCastSpell(spell, GetTarget()); } -CastMeleeSpellAction::CastMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) +CastMeleeSpellAction::CastMeleeSpellAction( + PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) { range = ATTACK_DISTANCE; } @@ -182,56 +183,47 @@ bool CastAuraSpellAction::isUseful() return false; } -CastEnchantItemAction::CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell) - : CastSpellAction(botAI, spell) -{ - range = botAI->GetRange("spell"); -} +CastEnchantItemMainHandAction::CastEnchantItemMainHandAction( + PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {} -bool CastEnchantItemAction::isPossible() +bool CastEnchantItemMainHandAction::Execute(Event /*event*/) { - // if (!CastSpellAction::isPossible()) - // { - // botAI->TellMasterNoFacing("Impossible: " + spell); - // return false; - // } - - uint32 spellId = AI_VALUE2(uint32, "spell id", spell); - - // bool ok = AI_VALUE2(Item*, "item for spell", spellId); - // Item* item = AI_VALUE2(Item*, "item for spell", spellId); - // botAI->TellMasterNoFacing("spell: " + spell + ", spell id: " + std::to_string(spellId) + " item for spell: " + - // std::to_string(ok)); - return spellId && AI_VALUE2(Item*, "item for spell", spellId); + Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + return item && botAI->CastSpell(spell, bot, item); } -CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell) - : CastEnchantItemAction(botAI, spell) {} - bool CastEnchantItemMainHandAction::isPossible() { - if (!CastEnchantItemAction::isPossible()) + Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + if (!item || item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_MISC || + item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE || + item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + { return false; + } - Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); - return item && !item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) && - item->GetTemplate()->Class == ITEM_CLASS_WEAPON; + return botAI->CanCastSpell(spell, bot, item); } -CastEnchantItemOffHandAction::CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell) - : CastEnchantItemAction(botAI, spell) {} +CastEnchantItemOffHandAction::CastEnchantItemOffHandAction( + PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {} -bool CastEnchantItemOffHandAction::isPossible() +bool CastEnchantItemOffHandAction::Execute(Event /*event*/) { - if (!CastEnchantItemAction::isPossible()) - return false; + Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + return item && botAI->CastSpell(spell, bot, item); +} +bool CastEnchantItemOffHandAction::isPossible() +{ Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); - if (!item || item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + if (!item || item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_MISC || + item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + { return false; + } - uint32 invType = item->GetTemplate()->InventoryType; - return invType == INVTYPE_WEAPON || invType == INVTYPE_WEAPONOFFHAND; + return botAI->CanCastSpell(spell, bot, item); } CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount, @@ -245,7 +237,8 @@ bool CastHealingSpellAction::isUseful() { return CastAuraSpellAction::isUseful() bool CastAoeHealSpellAction::isUseful() { return CastSpellAction::isUseful(); } -CastCureSpellAction::CastCureSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) +CastCureSpellAction::CastCureSpellAction( + PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) { range = botAI->GetRange("heal"); } @@ -267,13 +260,15 @@ bool BuffOnPartyAction::Execute(Event /*event*/) std::string castName = spell; // default = mono auto SendGroupRP = ai::chat::MakeGroupAnnouncer(bot); - castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP); + castName = ai::buff::UpgradeToGroupIfAppropriate( + bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP); return botAI->CastSpell(castName, GetTarget()); } // End greater buff fix -CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0) +CastShootAction::CastShootAction( + PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0) { if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED)) { @@ -327,7 +322,8 @@ Value* CastDebuffSpellOnMeleeAttackerAction::GetTargetValue() return context->GetValue("melee attacker without aura", spell); } -CastBuffSpellAction::CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration) +CastBuffSpellAction::CastBuffSpellAction( + PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration) : CastAuraSpellAction(botAI, spell, checkIsOwner, false, beforeDuration) { range = botAI->GetRange("spell"); @@ -448,7 +444,8 @@ bool UseTrinketAction::UseTrinket(Item* item) uint32 spellId = 0; for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { - if (item->GetTemplate()->Spells[i].SpellId > 0 && item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) + if (item->GetTemplate()->Spells[i].SpellId > 0 && + item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) { spellId = item->GetTemplate()->Spells[i].SpellId; const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); diff --git a/src/Ai/Base/Actions/GenericSpellActions.h b/src/Ai/Base/Actions/GenericSpellActions.h index e9dacb7d2fe..b87bd0a1fcc 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.h +++ b/src/Ai/Base/Actions/GenericSpellActions.h @@ -121,26 +121,21 @@ class CastBuffSpellAction : public CastAuraSpellAction std::string const GetTargetName() override { return "self target"; } }; -class CastEnchantItemAction : public CastSpellAction -{ -public: - CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell); - - bool isPossible() override; - std::string const GetTargetName() override { return "self target"; } -}; - -class CastEnchantItemMainHandAction : public CastEnchantItemAction +class CastEnchantItemMainHandAction : public CastSpellAction { public: CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell); + std::string const GetTargetName() override { return "self target"; } + bool Execute(Event event) override; bool isPossible() override; }; -class CastEnchantItemOffHandAction : public CastEnchantItemAction +class CastEnchantItemOffHandAction : public CastSpellAction { public: CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell); + std::string const GetTargetName() override { return "self target"; } + bool Execute(Event event) override; bool isPossible() override; }; From b8ff5996f802de76608cb1ccfd09256b0595d5a6 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Fri, 8 May 2026 22:41:13 -0700 Subject: [PATCH 14/63] Flying mount fixes and self-bot (#2351) ## Pull Request Description This PR does a few things. 1. Enable Selfbots to mount up. Because they have masters, but are their own masters, they would never mount up because their master never mounted. 2. Fix flag state handling after processing the aura change. 3. Add in the Dismount packet handler. This is intended to implement fall animations and have bots touch the ground when dismounting instead of floating off the ground. (It was cleared anyway after the first move, but this should make it more seamless.) ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes self bot should mount up, and select area appropriate mounts. Bots in your team should mount up, and on your dismount properly snap to the ground. should test at low Z (<1.0 off the ground) and higher z (> 1.0 off the ground) ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [x] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) The processing of a fall path has some impact, but I dont think itll be too much. - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Add natural falling when dismounting. May incurr fall damange.... - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) Comparison of code bases, searching for flags, adding diagnostic logging, and processing of said logging. iterating and brainstorming. Code was also written, but fully reviewed by me, and fixed where appropriate. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: Claude Opus 4.7 (1M context) --- src/Ai/Base/Actions/CheckMountStateAction.cpp | 92 +++++++++++++++---- src/Ai/Base/Actions/CheckMountStateAction.h | 2 + src/Bot/PlayerbotAI.cpp | 12 +++ 3 files changed, 89 insertions(+), 17 deletions(-) diff --git a/src/Ai/Base/Actions/CheckMountStateAction.cpp b/src/Ai/Base/Actions/CheckMountStateAction.cpp index 0d7fe432166..0e3abb72431 100644 --- a/src/Ai/Base/Actions/CheckMountStateAction.cpp +++ b/src/Ai/Base/Actions/CheckMountStateAction.cpp @@ -4,9 +4,11 @@ */ #include "CheckMountStateAction.h" +#include "AreaDefines.h" #include "BattleGroundTactics.h" #include "BattlegroundEY.h" #include "BattlegroundWS.h" +#include "DBCStores.h" #include "Event.h" #include "PlayerbotAI.h" #include "PlayerbotAIConfig.h" @@ -14,6 +16,8 @@ #include "ServerFacade.h" #include "SpellAuraEffects.h" +static constexpr uint32 SPELL_COLD_WEATHER_FLYING = 54197; + // Define the static map / init bool for caching bot preferred mount data globally std::unordered_map CheckMountStateAction::mountCache; bool CheckMountStateAction::preferredMountTableChecked = false; @@ -94,9 +98,10 @@ bool CheckMountStateAction::Execute(Event /*event*/) } bool inBattleground = bot->InBattleground(); + bool const noRealMaster = (!master || master == bot); // If there is a master and bot not in BG, follow master's mount state regardless of group leader - if (master && !inBattleground) + if (!noRealMaster && !inBattleground) { if (ShouldFollowMasterMountState(master, noAttackers, shouldMount)) return Mount(); @@ -110,8 +115,8 @@ bool CheckMountStateAction::Execute(Event /*event*/) return false; } - // If there is no master or bot in BG - if ((!master || inBattleground) && !bot->IsMounted() && + // No real master (random bot or self-bot) OR bot in BG + if ((noRealMaster || inBattleground) && !bot->IsMounted() && noAttackers && shouldMount && !bot->IsInCombat()) return Mount(); @@ -228,6 +233,39 @@ void CheckMountStateAction::Dismount() WorldPacket emptyPacket; bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); + + bool const wantsFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura(); + bool const isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); + bool const isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING); + bool const hasGravityDisabled = bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); + if (!wantsFly && !isWaterWalking && (isFlying || hasGravityDisabled)) + { + bot->RemoveUnitMovementFlag( + MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY); + if (!bot->IsRooted()) + bot->SendMovementFlagUpdate(); + } +} + +void CheckMountStateAction::CompleteDismount(Player* bot) +{ + if (!bot || !bot->IsInWorld()) + return; + + float const x = bot->GetPositionX(); + float const y = bot->GetPositionY(); + float const startZ = bot->GetPositionZ(); + + float groundZ = startZ; + bot->UpdateAllowedPositionZ(x, y, groundZ); + + bot->GetMotionMaster()->MoveFall(); + MovementInfo fallInfo = bot->m_movementInfo; + // Need to set the start of the fall, otherwise the fall may start from too high of a Z and kill the bot. + bot->SetFallInformation(0, startZ); + fallInfo.pos.Relocate(x, y, groundZ); + bot->HandleFall(fallInfo); + bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR); } bool CheckMountStateAction::TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const @@ -434,6 +472,24 @@ bool CheckMountStateAction::ShouldDismountForMaster(Player* master) const return !isMasterMounted && bot->IsMounted(); } +static bool BotCanUseFlyingMount(Player const* bot) +{ + if (bot->GetPureSkillValue(SKILL_RIDING) < 225) + return false; + + AreaTableEntry const* area = sAreaTableStore.LookupEntry(bot->GetAreaId()); + if (!area || !area->IsFlyable()) + return false; + if (area->flags & AREA_FLAG_NO_FLY_ZONE) + return false; + + uint32 const vmap = GetVirtualMapForMapAndZone(bot->GetMapId(), bot->GetZoneId()); + if (vmap == MAP_NORTHREND && !bot->HasSpell(SPELL_COLD_WEATHER_FLYING)) + return false; + + return true; +} + int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const MountData& mountData) const { // Check riding skill and level requirements @@ -443,8 +499,10 @@ int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const Mou if (ridingSkill <= 75 && botLevel < static_cast(sPlayerbotAIConfig.useFastGroundMountAtMinLevel)) return 59; - // If there is a master and bot not in BG, use master's aura effects. - if (master && !bot->InBattleground()) + // check if bot has master and if master is self + bool const noRealMaster = (!master || master == bot); + + if (!noRealMaster && !bot->InBattleground()) { auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED); if (!auraEffects.empty()) @@ -458,27 +516,27 @@ int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const Mou return 279; else if (masterInShapeshiftForm == FORM_FLIGHT) return 149; + return 59; // walk pace } - else - { - // Bots on their own. - int32 speed = mountData.maxSpeed; - if (bot->InBattleground() && speed > 99) - return 99; - return speed; - } + // No real master OR battleground: pick speed by skill tier. + if (!bot->InBattleground() && BotCanUseFlyingMount(bot)) + return (ridingSkill >= 300) ? 279 : 149; - return 59; + int32 maxGround = (ridingSkill >= 150) ? 99 : 59; + if (bot->InBattleground() && maxGround > 99) + maxGround = 99; + return maxGround; } uint32 CheckMountStateAction::GetMountType(Player* master) const { - if (!master) - return 0; + bool const noRealMaster = (!master || master == bot); - auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED); + if (noRealMaster) + return (!bot->InBattleground() && BotCanUseFlyingMount(bot)) ? 1 : 0; + auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED); if (!auraEffects.empty()) { SpellInfo const* masterSpell = auraEffects.front()->GetSpellInfo(); diff --git a/src/Ai/Base/Actions/CheckMountStateAction.h b/src/Ai/Base/Actions/CheckMountStateAction.h index 9a21838e173..d1faa379802 100644 --- a/src/Ai/Base/Actions/CheckMountStateAction.h +++ b/src/Ai/Base/Actions/CheckMountStateAction.h @@ -42,6 +42,8 @@ class CheckMountStateAction : public UseItemAction bool isPossible() override { return true; } bool Mount(); + static void CompleteDismount(Player* bot); + private: Player* master; ShapeshiftForm masterInShapeshiftForm; diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index b2631f7d1d8..8b20b2e88c9 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -15,6 +15,7 @@ #include "ChannelMgr.h" #include "CharacterPackets.h" #include "ChatHelper.h" +#include "CheckMountStateAction.h" #include "Common.h" #include "CreatureData.h" #include "EmoteAction.h" @@ -1365,6 +1366,17 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) // */ return; } + case SMSG_DISMOUNT: + { + WorldPacket p(packet); + p.rpos(0); + ObjectGuid guid; + p >> guid.ReadAsPacked(); + if (guid != bot->GetGUID()) + return; + CheckMountStateAction::CompleteDismount(bot); + return; + } default: botOutgoingPacketHandlers.AddPacket(packet); } From 6b0df4ff6c1c4d3d1646d22ed37e770dc261c6c5 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 9 May 2026 00:41:54 -0500 Subject: [PATCH 15/63] Fix ambiguous item parsing in bot text (#2356) ## Pull Request Description This change fixes two cases of broken bot text with respect to inventory items. 1. Reserved inventory qualifiers such as "mount," "food," "drink," etc. no longer also trigger generic item name matching. I first noticed this problem when my resto Shaman who had the "Mounting Vengeance" weapon in her inventory would repeatedly give error messages of failing to use it while mounting (because mounting also causes bots to use items that fit the reserved "mount," which due to this bug, also caused bots to try to use any item with "mount" in its name). 2. Custom cast output text no longer reports an inferred bag item as the spell target for normal unit-targeted casts such as "cast chain heal on Keleborn." There was a bug where the action would first parse the actual target and then parse the spell text and then try to match the last word of the string to a bag item (so the bot would say it was casting chain heal on a healing potion, even though the heal was in fact cast correctly on a player). ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Add a reserved-qualifier check in InventoryAction::parseItems() so reserved selectors do not also run through FindNamedItemVisitor. - In custom cast output text, choose the displayed target based on the actual target type already resolved for the cast. - It does not change mount selection behavior itself. - It does not add new spell-target parsing rules. - Describe the **processing cost** when this logic executes across many bots. - None. ## How to Test the Changes Reserved qualifiers: 1. Give a bot in your party the "Mounting Vengeance" weapon. 2. Mount up, and the bot should mount too without saying anything (before the fix, the bot would say it is using the weapon and that the item was not found). Spell cast text: 1. Give a bot an inventory item whose name overlaps with part of a spell name, such as a healing potion. 2. Command a bot to cast some heal on a player. 3. The bot should cast the spell on the intended player (as was the case previously), and the status text names the player instead of the inventory item. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - [x] No, not at all - [ ] Minimal impact (**explain below**) - Does this change modify default bot behavior? - [x] No - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - [ ] No - [x] Yes (**explain below**) Very minor changes. InventoryAction gets one explicit reserved-qualifier guard. Custom cast text selection becomes more explicit about which target type should be displayed. ## AI Assistance Was AI assistance used while working on this change? - [ ] No - [x] Yes (**explain below**) GPT-5.4 was used to trace the relevant code paths for the errors and propose the changes. ## Final Checklist - [x] Stability is not compromised. - [x] Performance impact is understood, tested, and acceptable. - [x] Added logic complexity is justified and explained. - [x] Any new bot dialogue lines are translated. - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- src/Ai/Base/Actions/CastCustomSpellAction.cpp | 11 +++--- src/Ai/Base/Actions/InventoryAction.cpp | 34 +++++++++++++++++-- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Ai/Base/Actions/CastCustomSpellAction.cpp b/src/Ai/Base/Actions/CastCustomSpellAction.cpp index 15c35ee430f..3defe55d1f0 100644 --- a/src/Ai/Base/Actions/CastCustomSpellAction.cpp +++ b/src/Ai/Base/Actions/CastCustomSpellAction.cpp @@ -143,14 +143,17 @@ bool CastCustomSpellAction::Execute(Event event) std::ostringstream spellName; spellName << ChatHelper::FormatSpell(spellInfo) << " on "; + bool const hasItemTarget = itemTarget && + (spellInfo->Targets & TARGET_FLAG_ITEM || spellInfo->Targets & TARGET_FLAG_GAMEOBJECT_ITEM); + if (bot->GetTrader()) spellName << "trade item"; - else if (itemTarget) + else if (hasItemTarget) spellName << chat->FormatItem(itemTarget->GetTemplate()); - else if (target == bot) - spellName << "self"; - else + else if (target != bot) spellName << target->GetName(); + else + spellName << "self"; if (!bot->GetTrader() && !botAI->CanCastSpell(spell, target, true, itemTarget)) { diff --git a/src/Ai/Base/Actions/InventoryAction.cpp b/src/Ai/Base/Actions/InventoryAction.cpp index 83fc00f1244..8d3046061f7 100644 --- a/src/Ai/Base/Actions/InventoryAction.cpp +++ b/src/Ai/Base/Actions/InventoryAction.cpp @@ -10,6 +10,31 @@ #include "ItemVisitors.h" #include "Playerbots.h" +namespace +{ +bool isReservedQualifier(std::string const& text) +{ + static std::array const exactQualifiers = { + "ammo", + "conjured drink", + "conjured food", + "conjured water", + "drink", + "food", + "healing potion", + "mount", + "mana potion", + "pet", + "quest", + "recipe", + "water" + }; + + return std::find(exactQualifiers.begin(), exactQualifiers.end(), text) != exactQualifiers.end() || + text.rfind("usage ", 0) == 0; +} +} + void InventoryAction::IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask) { if (mask & ITERATE_ITEMS_IN_BAGS) @@ -292,9 +317,12 @@ std::vector InventoryAction::parseItems(std::string const text, IterateIt found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); } - FindNamedItemVisitor visitor(bot, text); - IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); - found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + if (!isReservedQualifier(text)) + { + FindNamedItemVisitor visitor(bot, text); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } uint32 quality = chat->parseItemQuality(text); if (quality != MAX_ITEM_QUALITY) From d2e54431099fde5200c9c51179bcc7b5fb742108 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 9 May 2026 07:42:07 +0200 Subject: [PATCH 16/63] =?UTF-8?q?Fix=20contradictory=20leader=20bot=20chec?= =?UTF-8?q?k=20in=20`LeaveLargeGuildTrigger::IsActi=E2=80=A6=20(#2361)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Pull Request Description The previous logic contained two contradictory guards back-to-back: ``` // First check: passes only if IsRealPlayer() == true if (!leader || !GET_PLAYERBOT_AI(leader) || !GET_PLAYERBOT_AI(leader)->IsRealPlayer()) return false; // Second check: returns false if IsRealPlayer() == true PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(leader); if (!leaderBotAI || leaderBotAI->IsRealPlayer()) return false; ``` The first guard (due to the erroneous `!` before `IsRealPlayer()`) only passes when the leader **is** a real player. The second guard then immediately returns `false` for the same reason, making the function incapable of ever returning `true`. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Trigger/GuildTriggers.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Ai/Base/Trigger/GuildTriggers.cpp b/src/Ai/Base/Trigger/GuildTriggers.cpp index afa40558171..cae955e8678 100644 --- a/src/Ai/Base/Trigger/GuildTriggers.cpp +++ b/src/Ai/Base/Trigger/GuildTriggers.cpp @@ -39,11 +39,8 @@ bool LeaveLargeGuildTrigger::IsActive() Player* leader = ObjectAccessor::FindPlayer(guild->GetLeaderGUID()); - // Only leave the guild if we know the leader is not a real player. - if (!leader || !GET_PLAYERBOT_AI(leader) || !GET_PLAYERBOT_AI(leader)->IsRealPlayer()) - return false; - - PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(leader); + // Only leave the guild if the leader is an online bot (not a real player). + PlayerbotAI* leaderBotAI = leader ? GET_PLAYERBOT_AI(leader) : nullptr; if (!leaderBotAI || leaderBotAI->IsRealPlayer()) return false; From 66d41e1d79868500200b067e8cd31c41c33bf2be Mon Sep 17 00:00:00 2001 From: NoxMax <50133316+NoxMax@users.noreply.github.com> Date: Fri, 8 May 2026 23:42:18 -0600 Subject: [PATCH 17/63] Feat: Selective reset to default of combat or non-combat strategies (#2365) ## Pull Request Description Adds the commands `co !` and `nc !`, which would reset either the combat or non-combat strategies of a follower bot, without affecting the other strategies or any other values. Also ChangeStrategyAction.cpp was refactored for duplicate code by introducing the helper function `HandleStrategyCommon`, that gets called by `ChangeCombatStrategyAction` and `ChangeNonCombatStrategyAction` ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. `reset botAI` already resets strategies back to default, but it resets ALL strategies and wipes values such as formations, stances, and everything else under the `value` key in playerbots_db_store>value. The new commands don't run across many bots, only on the bot the command is run on. ## How to Test the Changes 1. Run either `co ?` and `nc ?` to see current list of combat and non-combat strategies the bot has. 2. Add and remove strategies to both `co` and `nc`. 3. Confirm your changes with `co ?` and `nc ?`. 4. Run `co !` only. 5. Run `co ?` to confirm combat strategies have been reset to default, and `nc ?` to confirm it has not been affected. Then run `nc !` to reset it as well. 6. Do another test [inside an instance](https://github.com/mod-playerbots/mod-playerbots/wiki/Playerbot-Commands#raid-specific-strategies). Remove a bunch of `nc` strategies, including the strategy for the raid itself. 7. Run `nc !` and check that the defaults have been reset, but that the instance strategy has been re-added as well. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [x] Yes (**explain below**) Technically it adds a new case to ChangeCombatStrategyAction, but it's straightforward. ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) Review only in case I was missing something, and then to easily refactor duplicate code with HandleStrategyCommon. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers [Commands wiki](https://github.com/mod-playerbots/mod-playerbots/wiki/Playerbot-Commands#strategies) need to be modified to read: --- You can query the bot to report what strategies are currently being used: ``` co ? nc ? ``` You can reset either of the bot's strategies back to defaults: ``` co ! nc ! ``` --- Tangentially I also recommend [this section](https://github.com/mod-playerbots/mod-playerbots/wiki/Playerbot-Commands#non-combat-strategies) to be edit to this for more accuracy: --- General strategy | description :---|:--- ``food`` | enable bot's ability to eat/drink ``pvp`` | enable bot's ability to engage in PVP combat. Note: PVP mode wouldn't appear active until the bot starts combat ``loot`` | enable bot's ability to loot. Note: adding or removing that strategy for randombots requires GM level --- src/Ai/Base/Actions/ChangeStrategyAction.cpp | 60 +++++++++----------- src/Bot/PlayerbotAI.cpp | 21 +++++++ src/Bot/PlayerbotAI.h | 1 + 3 files changed, 48 insertions(+), 34 deletions(-) diff --git a/src/Ai/Base/Actions/ChangeStrategyAction.cpp b/src/Ai/Base/Actions/ChangeStrategyAction.cpp index b4fe5faaf26..e81096b2a5a 100644 --- a/src/Ai/Base/Actions/ChangeStrategyAction.cpp +++ b/src/Ai/Base/Actions/ChangeStrategyAction.cpp @@ -9,28 +9,36 @@ #include "PlayerbotRepository.h" #include "Playerbots.h" -bool ChangeCombatStrategyAction::Execute(Event event) +// Helper function for prefixes used by combat and non-combat strategy commands. +static void HandleStrategyCommon(PlayerbotAI* botAI, std::string const& text, BotState state) { - std::string const text = event.getParam(); - botAI->ChangeStrategy(text.empty() ? getName() : text, BOT_STATE_COMBAT); - if (event.GetSource() == "co") + std::vector splitted = split(text, ','); + for (std::vector::iterator i = splitted.begin(); i != splitted.end(); i++) { - std::vector splitted = split(text, ','); - for (std::vector::iterator i = splitted.begin(); i != splitted.end(); i++) + const char* name = i->c_str(); + switch (name[0]) { - const char* name = i->c_str(); - switch (name[0]) - { - case '+': - case '-': - case '~': - PlayerbotRepository::instance().Save(botAI); - break; - case '?': - break; - } + case '+': + case '-': + case '~': + PlayerbotRepository::instance().Save(botAI); + break; + case '!': + botAI->SelectiveResetStrategies(state); + PlayerbotRepository::instance().Save(botAI); + break; + case '?': + break; } } +} + +bool ChangeCombatStrategyAction::Execute(Event event) +{ + std::string const text = event.getParam(); + botAI->ChangeStrategy(text.empty() ? getName() : text, BOT_STATE_COMBAT); + if (event.GetSource() == "co") + HandleStrategyCommon(botAI, text, BOT_STATE_COMBAT); return true; } @@ -52,23 +60,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event) botAI->ChangeStrategy(text, BOT_STATE_NON_COMBAT); if (event.GetSource() == "nc") - { - std::vector splitted = split(text, ','); - for (std::vector::iterator i = splitted.begin(); i != splitted.end(); i++) - { - const char* name = i->c_str(); - switch (name[0]) - { - case '+': - case '-': - case '~': - PlayerbotRepository::instance().Save(botAI); - break; - case '?': - break; - } - } - } + HandleStrategyCommon(botAI, text, BOT_STATE_NON_COMBAT); return true; } diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 8b20b2e88c9..5937f0ef6f8 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -1585,6 +1585,27 @@ void PlayerbotAI::ClearStrategies(BotState type) e->removeAllStrategies(); } +// Resets only the combat or non-combat engine: wipe strategies, repopulate with class/spec defaults, +// re-apply current map's instance strategy (if any), and call Init() to rebuild trigger/action lists. +void PlayerbotAI::SelectiveResetStrategies(BotState type) +{ + Engine* e = engines[type]; + if (!e) + return; + + e->removeAllStrategies(); + + if (type == BOT_STATE_COMBAT) + AiFactory::AddDefaultCombatStrategies(bot, this, e); + else if (type == BOT_STATE_NON_COMBAT) + AiFactory::AddDefaultNonCombatStrategies(bot, this, e); + + if (sPlayerbotAIConfig.applyInstanceStrategies) + ApplyInstanceStrategies(bot->GetMapId()); + + e->Init(); +} + std::vector PlayerbotAI::GetStrategies(BotState type) { Engine* e = engines[type]; diff --git a/src/Bot/PlayerbotAI.h b/src/Bot/PlayerbotAI.h index dc7770ed8da..01924c46fb0 100644 --- a/src/Bot/PlayerbotAI.h +++ b/src/Bot/PlayerbotAI.h @@ -404,6 +404,7 @@ class PlayerbotAI : public PlayerbotAIBase std::string const qualifier = ""); void ChangeStrategy(std::string const name, BotState type); void ClearStrategies(BotState type); + void SelectiveResetStrategies(BotState type); std::vector GetStrategies(BotState type); Strategy* GetStrategy(std::string const name, BotState type); void ApplyInstanceStrategies(uint32 mapId, bool tellMaster = false); From 8cb847db5d3043e59a7a3e4571068ffd48599f9c Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sat, 9 May 2026 00:48:59 -0700 Subject: [PATCH 18/63] Fix location cache. (#2374) ## Pull Request Description Bot locations were not correctly registered, so they werent picking it as often as they should. In part related to #2369 ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [ ] Yes (**explain below**) ## Final Checklist - - [ ] Stability is not compromised. - - [ ] Performance impact is understood, tested, and acceptable. - - [ ] Added logic complexity is justified and explained. - - [ ] Any new bot dialogue lines are translated. - - [ ] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Mgr/Travel/TravelMgr.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mgr/Travel/TravelMgr.cpp b/src/Mgr/Travel/TravelMgr.cpp index adc1e4a3edd..24ebf3ee1a0 100644 --- a/src/Mgr/Travel/TravelMgr.cpp +++ b/src/Mgr/Travel/TravelMgr.cpp @@ -4821,7 +4821,10 @@ void TravelMgr::PrepareDestinationCache() if (l < 1 || l > maxLevel) continue; - locsPerLevelCache[(uint8)l].push_back(WorldLocation(std::get<0>(gridTuple))); + locsPerLevelCache[(uint8)l].push_back(WorldLocation(std::get<0>(gridTuple), + static_cast(std::get<1>(gridTuple)) * 50.0f, + static_cast(std::get<2>(gridTuple)) * 50.0f, + static_cast(std::get<3>(gridTuple)) * 50.0f)); } } } From 9118c9671a99a6f5ad12cb5cd043628cda3aa50f Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sat, 9 May 2026 10:14:05 -0700 Subject: [PATCH 19/63] Fix/travelValType (#2376) ## Pull Request Description Incorrect var types when I refactored away from SQL lookup. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Mgr/Travel/TravelMgr.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mgr/Travel/TravelMgr.cpp b/src/Mgr/Travel/TravelMgr.cpp index 24ebf3ee1a0..7192c2c2608 100644 --- a/src/Mgr/Travel/TravelMgr.cpp +++ b/src/Mgr/Travel/TravelMgr.cpp @@ -4687,9 +4687,9 @@ void TravelMgr::PrepareDestinationCache() (creatureTemplate->unit_flags & 4096) == 0 && creatureTemplate->rank == 0) { - uint32 roundX = static_cast(std::round(x / 50.0f)); - uint32 roundY = static_cast(std::round(y / 50.0f)); - uint32 roundZ = static_cast(std::round(z / 50.0f)); + int32 roundX = static_cast(std::lround(x / 50.0f)); + int32 roundY = static_cast(std::lround(y / 50.0f)); + int32 roundZ = static_cast(std::lround(z / 50.0f)); tempLocsCache[std::make_tuple(mapId, roundX, roundY, roundZ)].push_back(creatureData); tempCreatureCache[templateEntry][areaId].push_back(WorldLocation(mapId, x, y, z)); } From eb3c10195957ac82963140c55e1990e240158bdd Mon Sep 17 00:00:00 2001 From: Fiery <41521670+ScoobyPwnsOnU@users.noreply.github.com> Date: Fri, 15 May 2026 23:19:18 -0700 Subject: [PATCH 20/63] update to level 80 pve specs (#2366) ## Pull Request Description Some of the specs needed improvement. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [ ] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- conf/playerbots.conf.dist | 50 +++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 41ca0b6b412..ec5ec4a155d 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1430,15 +1430,15 @@ AiPlayerbot.PermanentlyInWorldTime = 31104000 # AiPlayerbot.PremadeSpecName.1.0 = arms pve -AiPlayerbot.PremadeSpecGlyph.1.0 = 43418,43395,43423,43399,49084,43421 +AiPlayerbot.PremadeSpecGlyph.1.0 = 43418,43395,43423,43399,43397,43421 AiPlayerbot.PremadeSpecLink.1.0.60 = 3022032023335100002012211231241 AiPlayerbot.PremadeSpecLink.1.0.80 = 3022032023335100102012213231251-305-2033 AiPlayerbot.PremadeSpecName.1.1 = fury pve -AiPlayerbot.PremadeSpecGlyph.1.1 = 43418,43395,43414,43399,49084,43432 +AiPlayerbot.PremadeSpecGlyph.1.1 = 43418,43395,43414,43396,49084,43432 AiPlayerbot.PremadeSpecLink.1.1.60 = -305053000500310053120501351 AiPlayerbot.PremadeSpecLink.1.1.80 = 32002300233-305053000500310153120511351 AiPlayerbot.PremadeSpecName.1.2 = prot pve -AiPlayerbot.PremadeSpecGlyph.1.2 = 43424,43395,43425,43399,49084,45793 +AiPlayerbot.PremadeSpecGlyph.1.2 = 43429,43397,43425,43399,49084,45797 AiPlayerbot.PremadeSpecLink.1.2.60 = --053351225000210521030113321 AiPlayerbot.PremadeSpecLink.1.2.80 = 3500030023-301-053351225000210521030113321 AiPlayerbot.PremadeSpecName.1.3 = arms pvp @@ -1467,13 +1467,13 @@ AiPlayerbot.PremadeSpecLink.1.5.80 = 0502300123-3-250031220223012521332113321 AiPlayerbot.PremadeSpecName.2.0 = holy pve AiPlayerbot.PremadeSpecGlyph.2.0 = 41106,43367,45741,43368,43365,41109 AiPlayerbot.PremadeSpecLink.2.0.60 = 50350151020013053100515221 -AiPlayerbot.PremadeSpecLink.2.0.80 = 50350152220013053100515221-503201312 +AiPlayerbot.PremadeSpecLink.2.0.80 = 50350152100013053100515221-50320104203 AiPlayerbot.PremadeSpecName.2.1 = prot pve -AiPlayerbot.PremadeSpecGlyph.2.1 = 41099,43367,43869,43368,43369,45745 +AiPlayerbot.PremadeSpecGlyph.2.1 = 41100,43367,43869,43368,43369,45745 AiPlayerbot.PremadeSpecLink.2.1.60 = -05005135203102311333112321 AiPlayerbot.PremadeSpecLink.2.1.80 = -05005135203102311333312321-502302012003 AiPlayerbot.PremadeSpecName.2.2 = ret pve -AiPlayerbot.PremadeSpecGlyph.2.2 = 41092,43367,41099,43368,43369,43869 +AiPlayerbot.PremadeSpecGlyph.2.2 = 41092,43367,41099,43368,43340,43869 AiPlayerbot.PremadeSpecLink.2.2.60 = --05230051203331302133231131 AiPlayerbot.PremadeSpecLink.2.2.65 = -05-05230051203331302133231131 AiPlayerbot.PremadeSpecLink.2.2.80 = 050501-05-05232051203331302133231331 @@ -1504,7 +1504,7 @@ AiPlayerbot.PremadeSpecName.3.0 = bm pve AiPlayerbot.PremadeSpecGlyph.3.0 = 42912,43350,42902,43351,43338,42914 AiPlayerbot.PremadeSpecLink.3.0.40 = 512002015051122301 AiPlayerbot.PremadeSpecLink.3.0.60 = 51200201505112233110531151 -AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112243130531351-005305101 +AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112233111531351-0323031-5 AiPlayerbot.PremadeSpecName.3.1 = mm pve AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,45625,43351,43338,42914 AiPlayerbot.PremadeSpecLink.3.1.60 = -035305101030013233115031151 @@ -1530,8 +1530,8 @@ AiPlayerbot.PremadeSpecLink.3.5.80 = -005305201-2300302510233330533135001031 # HUNTER PET # # Ferocity -AiPlayerbot.PremadeHunterPetLink.0.16 = 2100003030103010101 -AiPlayerbot.PremadeHunterPetLink.0.20 = 2100013030103010122 +AiPlayerbot.PremadeHunterPetLink.0.16 = 2100003130003010101 +AiPlayerbot.PremadeHunterPetLink.0.20 = 2100003130103010122 # Tenacity AiPlayerbot.PremadeHunterPetLink.1.16 = 21103000300120101001 AiPlayerbot.PremadeHunterPetLink.1.20 = 21303010300120101002 @@ -1552,7 +1552,7 @@ AiPlayerbot.PremadeHunterPetLink.2.20 = 21000203300002110221 AiPlayerbot.PremadeSpecName.4.0 = as pve AiPlayerbot.PremadeSpecGlyph.4.0 = 45768,43379,45761,43380,43378,45766 AiPlayerbot.PremadeSpecLink.4.0.60 = 005303104352100520103331051 -AiPlayerbot.PremadeSpecLink.4.0.80 = 005303104352100520103331051-005005005003-2 +AiPlayerbot.PremadeSpecLink.4.0.80 = 005303005352100520103331051-005005003-502 AiPlayerbot.PremadeSpecName.4.1 = combat pve AiPlayerbot.PremadeSpecGlyph.4.1 = 42962,43379,45762,43380,43378,42969 AiPlayerbot.PremadeSpecLink.4.1.60 = -0252051000035015223100501251 @@ -1593,7 +1593,7 @@ AiPlayerbot.PremadeSpecGlyph.5.1 = 42408,43371,42400,43374,43342,42396 AiPlayerbot.PremadeSpecLink.5.1.60 = -035050031301152530000331331 AiPlayerbot.PremadeSpecLink.5.1.80 = 05032031-235050032302152530000331351 AiPlayerbot.PremadeSpecName.5.2 = shadow pve -AiPlayerbot.PremadeSpecGlyph.5.2 = 42406,43371,42407,43374,43342,42415 +AiPlayerbot.PremadeSpecGlyph.5.2 = 45753,43371,42407,43374,43370,42415 AiPlayerbot.PremadeSpecLink.5.2.60 = --325003041203010323150301351 AiPlayerbot.PremadeSpecLink.5.2.80 = 0503203--325023051223010323152301351 AiPlayerbot.PremadeSpecName.5.3 = disc pvp @@ -1620,19 +1620,19 @@ AiPlayerbot.PremadeSpecLink.5.5.80 = 50332031003--005323241223112003102311351 # AiPlayerbot.PremadeSpecName.6.0 = blood pve -AiPlayerbot.PremadeSpecGlyph.6.0 = 45805,43673,43827,43544,43672,43554 +AiPlayerbot.PremadeSpecGlyph.6.0 = 45805,43673,43538,43544,43672,43542 AiPlayerbot.PremadeSpecLink.6.0.60 = 035502150300331320102013111-005 -AiPlayerbot.PremadeSpecLink.6.0.80 = 0355021533003313201020131351-005-005032 +AiPlayerbot.PremadeSpecLink.6.0.80 = 0055021533303310201020131-305020510002-00522 AiPlayerbot.PremadeSpecName.6.1 = frost pve AiPlayerbot.PremadeSpecGlyph.6.1 = 45805,43673,43547,43544,43672,43543 AiPlayerbot.PremadeSpecLink.6.1.60 = -32003350332203012300023101351 AiPlayerbot.PremadeSpecLink.6.1.80 = -32002350352203012300033101351-230200305003 AiPlayerbot.PremadeSpecName.6.2 = unholy pve -AiPlayerbot.PremadeSpecGlyph.6.2 = 43542,43673,43546,43544,43672,43549 +AiPlayerbot.PremadeSpecGlyph.6.2 = 43542,43673,43546,43535,43672,43549 AiPlayerbot.PremadeSpecLink.6.2.60 = --2301303050032151000150013131151 -AiPlayerbot.PremadeSpecLink.6.2.80 = -320033500002-2301303050032151000150013133151 +AiPlayerbot.PremadeSpecLink.6.2.80 = 23050202--2302303350032152000150003133151 AiPlayerbot.PremadeSpecName.6.3 = double aura blood pve -AiPlayerbot.PremadeSpecGlyph.6.3 = 45805,43673,43827,43544,43672,43554 +AiPlayerbot.PremadeSpecGlyph.6.3 = 45805,43673,43538,43544,43672,43554 AiPlayerbot.PremadeSpecLink.6.3.60 = 005512153330030320102013-305 AiPlayerbot.PremadeSpecLink.6.3.80 = 005512153330030320102013-3050505002023001-002 AiPlayerbot.PremadeSpecName.6.4 = blood pvp @@ -1662,13 +1662,13 @@ AiPlayerbot.PremadeSpecLink.6.6.80 = -320050410002-23013233010021522301012031331 AiPlayerbot.PremadeSpecName.7.0 = ele pve AiPlayerbot.PremadeSpecGlyph.7.0 = 41536,43385,41532,43386,44923,45776 AiPlayerbot.PremadeSpecLink.7.0.60 = 4530001520213351102301351 -AiPlayerbot.PremadeSpecLink.7.0.80 = 3530001523213351322301351-005050031 +AiPlayerbot.PremadeSpecLink.7.0.80 = 4530001523213351302301351-00525003 AiPlayerbot.PremadeSpecName.7.1 = enh pve -AiPlayerbot.PremadeSpecGlyph.7.1 = 41530,43385,41539,43386,44923,45771 +AiPlayerbot.PremadeSpecGlyph.7.1 = 41542,43385,41539,43386,43725,45771 AiPlayerbot.PremadeSpecLink.7.1.60 = -30305003105021333031121131051 AiPlayerbot.PremadeSpecLink.7.1.80 = 053030152-30305003105021333031131131051 AiPlayerbot.PremadeSpecName.7.2 = resto pve -AiPlayerbot.PremadeSpecGlyph.7.2 = 41527,43385,41517,43386,44923,45775 +AiPlayerbot.PremadeSpecGlyph.7.2 = 41527,43385,41517,43386,43725,45775 AiPlayerbot.PremadeSpecLink.7.2.60 = --50005301235310501102321251 AiPlayerbot.PremadeSpecLink.7.2.80 = -00502033-50005331335310501122331251 AiPlayerbot.PremadeSpecName.7.3 = ele pvp @@ -1702,7 +1702,7 @@ AiPlayerbot.PremadeSpecLink.8.0.80 = 230005231100330150323102505321-03-203303001 AiPlayerbot.PremadeSpecName.8.1 = fire pve AiPlayerbot.PremadeSpecGlyph.8.1 = 42739,43339,45737,43364,44920,42751 AiPlayerbot.PremadeSpecLink.8.1.60 = -0055030011302231053120321341 -AiPlayerbot.PremadeSpecLink.8.1.80 = 23000503110003-0055030011302331053120321351 +AiPlayerbot.PremadeSpecLink.8.1.80 = 23000503110003-0055032012303330053120300351 AiPlayerbot.PremadeSpecName.8.2 = frost pve AiPlayerbot.PremadeSpecGlyph.8.2 = 42742,43339,50045,43364,43361,42751 AiPlayerbot.PremadeSpecLink.8.2.60 = --0533030313203100030152231151 @@ -1780,15 +1780,15 @@ AiPlayerbot.PremadeSpecLink.11.0.80 = 5032203105331303213305301231--205003012 AiPlayerbot.PremadeSpecName.11.1 = bear pve AiPlayerbot.PremadeSpecGlyph.11.1 = 40897,43331,46372,43335,43332,40899 AiPlayerbot.PremadeSpecLink.11.1.60 = -500232130322110353100301310501 -AiPlayerbot.PremadeSpecLink.11.1.80 = -501232130322110353120303313511-20350001 +AiPlayerbot.PremadeSpecLink.11.1.80 = -503232132322010353120303013511-20350001 AiPlayerbot.PremadeSpecName.11.2 = resto pve -AiPlayerbot.PremadeSpecGlyph.11.2 = 40913,43331,40906,43335,44922,45602 +AiPlayerbot.PremadeSpecGlyph.11.2 = 40913,43331,40906,43335,43674,45602 AiPlayerbot.PremadeSpecLink.11.2.60 = --230033312031500531050113051 -AiPlayerbot.PremadeSpecLink.11.2.80 = 05320031--230033312031501531053313051 +AiPlayerbot.PremadeSpecLink.11.2.80 = 05320131003--230023312131500531050313051 AiPlayerbot.PremadeSpecName.11.3 = cat pve -AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43335,44922,45604 +AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43335,43674,45604 AiPlayerbot.PremadeSpecLink.11.3.60 = -552202032322010053100030310501 -AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053100030310511-205503012 +AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053120030310511-203503012 AiPlayerbot.PremadeSpecName.11.4 = balance pvp AiPlayerbot.PremadeSpecGlyph.11.4 = 40921,43331,45622,43674,43335,45623 AiPlayerbot.PremadeSpecLink.11.4.60 = 5012203115331002213032311231 From dbea1203b4cac17c1b02759f6eb95d13addc9cd8 Mon Sep 17 00:00:00 2001 From: NoxMax <50133316+NoxMax@users.noreply.github.com> Date: Sat, 16 May 2026 00:19:58 -0600 Subject: [PATCH 21/63] Refactor: Clean up triggers and reorganize supported commands (#2327) ## Pull Request Description Removed duplicate and dead trigger-action mappings in ChatCommandHandlerStrategy. Noticed while reviewing the recent edits to test-staging. Also when trigger name == action name, that belongs to supported in supported, so `wipe` and `roll` were moved there. Beyond that, `InitTriggers` commands were changed to be in a single line. Really it's easier to read that way, except multi-action lines, where I gave each action a line of its own. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. Logic has been further minimized to eliminate redundancies. ## How to Test the Changes Test the affected commands. They should work same as before. Removed duplicates from `InitTriggers` that are already in `supported`: 1. open items 2. unlock items 3. unlock traded item 4. tame 5. glyphs 6. glyph equip 7. pet 8. pet attack 9. emblems Dead entries that were in `supported` but already worked through `InitTriggers`: 1. qi 2. focus heal Triggers that were moved from `InitTriggers` to `supported`: 1. wipe 2. roll ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- .../Strategy/ChatCommandHandlerStrategy.cpp | 124 ++++++------------ 1 file changed, 43 insertions(+), 81 deletions(-) diff --git a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp index 2e4503c18e9..9bb30a1ca7f 100644 --- a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp +++ b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp @@ -20,20 +20,23 @@ class ChatCommandActionNodeFactoryInternal : public NamedObjectFactory& triggers) { PassTroughStrategy::InitTriggers(triggers); + // Keep single action triggers on one line, and multi-action triggers on multiple lines. triggers.push_back(new TriggerNode("rep", { NextAction("reputation", relevance) })); triggers.push_back(new TriggerNode("pvp stats", { NextAction("tell pvp stats", relevance) })); - triggers.push_back(new TriggerNode("q", { NextAction("query quest", relevance), - NextAction("query item usage", relevance) })); - triggers.push_back(new TriggerNode("add all loot", { NextAction("add all loot", relevance), - NextAction("loot", relevance) })); + triggers.push_back(new TriggerNode("q", + { NextAction("query quest", relevance), + NextAction("query item usage", relevance) })); + triggers.push_back(new TriggerNode("add all loot", + { NextAction("add all loot", relevance), + NextAction("loot", relevance) })); triggers.push_back(new TriggerNode("u", { NextAction("use", relevance) })); triggers.push_back(new TriggerNode("c", { NextAction("item count", relevance) })); - triggers.push_back( - new TriggerNode("items", { NextAction("item count", relevance) })); + triggers.push_back(new TriggerNode("items", { NextAction("item count", relevance) })); triggers.push_back(new TriggerNode("inv", { NextAction("item count", relevance) })); triggers.push_back(new TriggerNode("e", { NextAction("equip", relevance) })); triggers.push_back(new TriggerNode("ue", { NextAction("unequip", relevance) })); @@ -42,81 +45,40 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector& trigger triggers.push_back(new TriggerNode("s", { NextAction("sell", relevance) })); triggers.push_back(new TriggerNode("b", { NextAction("buy", relevance) })); triggers.push_back(new TriggerNode("r", { NextAction("reward", relevance) })); - triggers.push_back( - new TriggerNode("attack", { NextAction("attack my target", relevance) })); - triggers.push_back( - new TriggerNode("accept", { NextAction("accept quest", relevance) })); - triggers.push_back( - new TriggerNode("follow", { NextAction("follow chat shortcut", relevance) })); - triggers.push_back( - new TriggerNode("stay", { NextAction("stay chat shortcut", relevance) })); - triggers.push_back( - new TriggerNode("move from group", { NextAction("move from group chat shortcut", relevance) })); - triggers.push_back( - new TriggerNode("flee", { NextAction("flee chat shortcut", relevance) })); - triggers.push_back(new TriggerNode( - "tank attack", { NextAction("tank attack chat shortcut", relevance) })); - triggers.push_back( - new TriggerNode("grind", { NextAction("grind chat shortcut", relevance) })); - triggers.push_back( - new TriggerNode("talk", { NextAction("gossip hello", relevance), - NextAction("talk to quest giver", relevance) })); - triggers.push_back( - new TriggerNode("enter vehicle", { NextAction("enter vehicle", relevance) })); - triggers.push_back( - new TriggerNode("leave vehicle", { NextAction("leave vehicle", relevance) })); - triggers.push_back( - new TriggerNode("cast", { NextAction("cast custom spell", relevance) })); - triggers.push_back( - new TriggerNode("castnc", { NextAction("cast custom nc spell", relevance) })); - triggers.push_back( - new TriggerNode("revive", { NextAction("spirit healer", relevance) })); - triggers.push_back( - new TriggerNode("runaway", { NextAction("runaway chat shortcut", relevance) })); - triggers.push_back( - new TriggerNode("warning", { NextAction("runaway chat shortcut", relevance) })); - triggers.push_back( - new TriggerNode("max dps", { NextAction("max dps chat shortcut", relevance) })); - triggers.push_back( - new TriggerNode("attackers", { NextAction("tell attackers", relevance) })); - triggers.push_back( - new TriggerNode("target", { NextAction("tell target", relevance) })); - triggers.push_back( - new TriggerNode("pull", { NextAction("pull my target", relevance) })); - triggers.push_back( - new TriggerNode("pull back", { NextAction("pull my target", relevance) })); - triggers.push_back( - new TriggerNode("pull rti", { NextAction("pull rti target", relevance) })); - triggers.push_back( - new TriggerNode("ready", { NextAction("ready check", relevance) })); - triggers.push_back( - new TriggerNode("naxx", {NextAction("naxx chat shortcut", relevance)})); - triggers.push_back( - new TriggerNode("bwl", { NextAction("bwl chat shortcut", relevance) })); - triggers.push_back( - new TriggerNode("dps", { NextAction("tell estimated dps", relevance) })); - triggers.push_back( - new TriggerNode("disperse", { NextAction("disperse set", relevance) })); - triggers.push_back( - new TriggerNode("open items", { NextAction("open items", relevance) })); - triggers.push_back( - new TriggerNode("qi", { NextAction("query item usage", relevance) })); - triggers.push_back( - new TriggerNode("unlock items", { NextAction("unlock items", relevance) })); - triggers.push_back( - new TriggerNode("unlock traded item", { NextAction("unlock traded item", relevance) })); - triggers.push_back( - new TriggerNode("wipe", { NextAction("wipe", relevance) })); - triggers.push_back(new TriggerNode("tame", { NextAction("tame", relevance) })); - triggers.push_back(new TriggerNode("glyphs", { NextAction("glyphs", relevance) })); // Added for custom Glyphs - triggers.push_back(new TriggerNode("glyph equip", { NextAction("glyph equip", relevance) })); // Added for custom Glyphs - triggers.push_back(new TriggerNode("pet", { NextAction("pet", relevance) })); - triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) })); - triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) })); + triggers.push_back(new TriggerNode("attack", { NextAction("attack my target", relevance) })); + triggers.push_back(new TriggerNode("accept", { NextAction("accept quest", relevance) })); + triggers.push_back(new TriggerNode("follow", { NextAction("follow chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("stay", { NextAction("stay chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("move from group", { NextAction("move from group chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("flee", { NextAction("flee chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("tank attack", { NextAction("tank attack chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("grind", { NextAction("grind chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("talk", + { NextAction("gossip hello", relevance), + NextAction("talk to quest giver", relevance) })); + triggers.push_back(new TriggerNode("enter vehicle", { NextAction("enter vehicle", relevance) })); + triggers.push_back(new TriggerNode("leave vehicle", { NextAction("leave vehicle", relevance) })); + triggers.push_back(new TriggerNode("cast", { NextAction("cast custom spell", relevance) })); + triggers.push_back(new TriggerNode("castnc", { NextAction("cast custom nc spell", relevance) })); + triggers.push_back(new TriggerNode("revive", { NextAction("spirit healer", relevance) })); + triggers.push_back(new TriggerNode("runaway", { NextAction("runaway chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("warning", { NextAction("runaway chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("max dps", { NextAction("max dps chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("attackers", { NextAction("tell attackers", relevance) })); + triggers.push_back(new TriggerNode("target", { NextAction("tell target", relevance) })); + triggers.push_back(new TriggerNode("pull", { NextAction("pull my target", relevance) })); + triggers.push_back(new TriggerNode("pull back", { NextAction("pull my target", relevance) })); + triggers.push_back(new TriggerNode("pull rti", { NextAction("pull rti target", relevance) })); + triggers.push_back(new TriggerNode("ready", { NextAction("ready check", relevance) })); + triggers.push_back(new TriggerNode("naxx", {NextAction("naxx chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("bwl", { NextAction("bwl chat shortcut", relevance) })); + triggers.push_back(new TriggerNode("dps", { NextAction("tell estimated dps", relevance) })); + triggers.push_back(new TriggerNode("disperse", { NextAction("disperse set", relevance) })); + triggers.push_back(new TriggerNode("qi", { NextAction("query item usage", relevance) })); triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) })); - triggers.push_back(new TriggerNode("emblems", { NextAction("emblems", relevance) })); } +// Commands where trigger name == action name. ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) { actionNodeFactories.Add(new ChatCommandActionNodeFactoryInternal()); @@ -199,15 +161,15 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("rtsc"); supported.push_back("drink"); supported.push_back("calc"); + supported.push_back("roll"); supported.push_back("open items"); - supported.push_back("qi"); supported.push_back("unlock items"); supported.push_back("unlock traded item"); + supported.push_back("wipe"); supported.push_back("tame"); supported.push_back("glyphs"); supported.push_back("glyph equip"); supported.push_back("pet"); supported.push_back("pet attack"); supported.push_back("wait for attack time"); - supported.push_back("focus heal"); -} +} \ No newline at end of file From f1a2736a31db2e064db39a4ef27fc53f77cb81fa Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 16 May 2026 08:26:03 +0200 Subject: [PATCH 22/63] Polymorph spam fix (#2373) ## Pull Request Description Fix for polymorph spam in combat ## How to Test the Changes 1. Invite mage bot which have polymorph (check `spells polymorph`) 2. Enter combat and mage should not spam polymorph 3. Enter another combat best with multiple mobs and one of this mobs mark moon 4. Mage should cast polymorph on marked mob ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Class/Mage/Action/MageActions.cpp | 2 -- src/Ai/Class/Mage/Action/MageActions.h | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Ai/Class/Mage/Action/MageActions.cpp b/src/Ai/Class/Mage/Action/MageActions.cpp index 5eb63ea8667..63ba0d9d596 100644 --- a/src/Ai/Class/Mage/Action/MageActions.cpp +++ b/src/Ai/Class/Mage/Action/MageActions.cpp @@ -11,8 +11,6 @@ #include "ServerFacade.h" #include "SharedDefines.h" -Value* CastPolymorphAction::GetTargetValue() { return context->GetValue("cc target", getName()); } - bool UseManaSapphireAction::isUseful() { Player* bot = botAI->GetBot(); diff --git a/src/Ai/Class/Mage/Action/MageActions.h b/src/Ai/Class/Mage/Action/MageActions.h index 39d1531a700..abd3020526a 100644 --- a/src/Ai/Class/Mage/Action/MageActions.h +++ b/src/Ai/Class/Mage/Action/MageActions.h @@ -215,11 +215,10 @@ class UseManaAgateAction : public UseItemAction // CC, Interrupt, and Dispel Actions -class CastPolymorphAction : public CastBuffSpellAction +class CastPolymorphAction : public CastCrowdControlSpellAction { public: - CastPolymorphAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "polymorph") {} - Value* GetTargetValue() override; + CastPolymorphAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "polymorph") {} }; class CastSpellstealAction : public CastSpellAction From 05aebfea465bd043dc5ab344b15968a50bebc8ca Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 16 May 2026 08:26:22 +0200 Subject: [PATCH 23/63] Outfit database persist (#2378) ## Pull Request Description Added storing outfit in database. Related with: #2239 ## How to Test the Changes 1. Invite altbot 2. Use command `outfit testcollection +[itemlink]` to add outfit collection and item 3. Check that is added `outfit ?` 4. Logout bot for example via multibot 5. Login bot 6. Check that is still added `outfit ?` ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) To identify places to change. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers Result after bot relogin: obraz --- src/Ai/Base/Actions/OutfitAction.cpp | 5 +++++ src/Db/PlayerbotRepository.cpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/Ai/Base/Actions/OutfitAction.cpp b/src/Ai/Base/Actions/OutfitAction.cpp index 78f7f9bc45e..cbb41219466 100644 --- a/src/Ai/Base/Actions/OutfitAction.cpp +++ b/src/Ai/Base/Actions/OutfitAction.cpp @@ -7,6 +7,7 @@ #include "Event.h" #include "ItemVisitors.h" +#include "PlayerbotRepository.h" #include "Playerbots.h" #include "ItemPackets.h" @@ -28,6 +29,7 @@ bool OutfitAction::Execute(Event event) if (!name.empty()) { Save(name, items); + PlayerbotRepository::instance().Save(botAI); std::ostringstream out; out << "Setting outfit " << name << " as " << param; @@ -86,6 +88,7 @@ bool OutfitAction::Execute(Event event) botAI->TellMaster(out); Save(name, ItemIds()); + PlayerbotRepository::instance().Save(botAI); return true; } else if (command == "update") @@ -95,6 +98,7 @@ bool OutfitAction::Execute(Event event) botAI->TellMaster(out); Update(name); + PlayerbotRepository::instance().Save(botAI); return true; } @@ -124,6 +128,7 @@ bool OutfitAction::Execute(Event event) } Save(name, outfit); + PlayerbotRepository::instance().Save(botAI); } return true; diff --git a/src/Db/PlayerbotRepository.cpp b/src/Db/PlayerbotRepository.cpp index 115a4e95f17..53bd188f834 100644 --- a/src/Db/PlayerbotRepository.cpp +++ b/src/Db/PlayerbotRepository.cpp @@ -39,6 +39,8 @@ void PlayerbotRepository::Load(PlayerbotAI* botAI) botAI->ChangeStrategy(value, BOT_STATE_DEAD); } while (result->NextRow()); + botAI->GetAiObjectContext()->GetUntypedValue("outfit list"); + botAI->GetAiObjectContext()->Load(values); } } From 61dbae19dc63b34339182fd3779bef42ec9c5aa2 Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 16 May 2026 08:26:34 +0200 Subject: [PATCH 24/63] Fix for lfg command (#2379) ## Pull Request Description Fix for `lfg` to include healers in invitations and requester role. ## How to Test the Changes Use command `/1 lfg 40` on server which contains correct amount of bots possible to be invited. Such group should be create. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) To identify reason. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Actions/InviteToGroupAction.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Ai/Base/Actions/InviteToGroupAction.cpp b/src/Ai/Base/Actions/InviteToGroupAction.cpp index 8b71eadef98..e7bcb4da85d 100644 --- a/src/Ai/Base/Actions/InviteToGroupAction.cpp +++ b/src/Ai/Base/Actions/InviteToGroupAction.cpp @@ -308,9 +308,8 @@ bool LfgAction::Execute(Event event) allowedRoles[BOT_ROLE_HEALER] = 1; allowedRoles[BOT_ROLE_DPS] = 3; - BotRoles role = botAI->IsTank(requester, false) - ? BOT_ROLE_TANK - : (botAI->IsHeal(requester, false) ? BOT_ROLE_HEALER : BOT_ROLE_DPS); + BotRoles role = botAI->IsTank(requester, true) ? BOT_ROLE_TANK + : (botAI->IsHeal(requester, true) ? BOT_ROLE_HEALER : BOT_ROLE_DPS); Classes cls = (Classes)requester->getClass(); if (group) @@ -383,8 +382,8 @@ bool LfgAction::Execute(Event event) if (!botAI->IsSafe(player)) return false; - role = botAI->IsTank(player, false) ? BOT_ROLE_TANK - : (botAI->IsHeal(player, false) ? BOT_ROLE_HEALER : BOT_ROLE_DPS); + role = botAI->IsTank(player, true) ? BOT_ROLE_TANK + : (botAI->IsHeal(player, true) ? BOT_ROLE_HEALER : BOT_ROLE_DPS); cls = (Classes)player->getClass(); if (allowedRoles[role] > 0) @@ -403,7 +402,7 @@ bool LfgAction::Execute(Event event) allowedClassNr[cls][role]--; } - role = botAI->IsTank(bot, false) ? BOT_ROLE_TANK : (botAI->IsHeal(bot, false) ? BOT_ROLE_HEALER : BOT_ROLE_DPS); + role = botAI->IsTank(bot, true) ? BOT_ROLE_TANK : (botAI->IsHeal(bot, true) ? BOT_ROLE_HEALER : BOT_ROLE_DPS); cls = (Classes)bot->getClass(); if (allowedRoles[role] == 0) From 05e8f4d82c429ecf4f144b4321ad785bfeb20cce Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 16 May 2026 01:26:55 -0500 Subject: [PATCH 25/63] Implement Black Temple Strategies (#2381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Pull Request Description This PR implements strategies for all bosses in the Black Temple. As always, I’ve written these with the intent that all bosses be completable with appropriate gear and 50/50 IP nerfs and boss HP returned to TBC levels. Illidan is difficult at those parameters, but he is certainly doable. You just won’t be able to roll a bunch of meme specs or shitty compositions. Probably. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. My goal with raid strategies is the same—I’m not just trying to make encounters doable but am trying to allow people to experience them in a way that feels like they are part of a coordinated raid of real players. To me, being able to achieve that is the minimum, and that requires getting bots to respond to all major mechanics, even if they can technically just be powered through. ## How to Test the Changes Run the Black Temple raid and see if my descriptions of strategies in the next post check out. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [x] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) The performance impact exists only when the “blacktemple” strategy is on. I’ve tested with pmon and done my best to properly gate checks to limit the performance impact even during the instance and boss encounters. - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) The triggers and multipliers will be evaluated as long as the "blacktemple" strategy is active, and it will be applied automatically in the instance (and removed automatically when changing maps outside the instance). - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) GPT-5.4, mainly for calculations and things that are more intermediate concepts that I’m not proficient with like lambdas. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- conf/playerbots.conf.dist | 2 +- .../Action/RaidBlackTempleActions.cpp | 3041 +++++++++++++++++ .../Action/RaidBlackTempleActions.h | 565 +++ .../Multiplier/RaidBlackTempleMultipliers.cpp | 785 +++++ .../Multiplier/RaidBlackTempleMultipliers.h | 267 ++ .../RaidBlackTempleActionContext.h | 406 +++ .../RaidBlackTempleTriggerContext.h | 406 +++ .../Strategy/RaidBlackTempleStrategy.cpp | 253 ++ .../Strategy/RaidBlackTempleStrategy.h | 22 + .../Trigger/RaidBlackTempleTriggers.cpp | 863 +++++ .../Trigger/RaidBlackTempleTriggers.h | 518 +++ .../Util/RaidBlackTempleHelpers.cpp | 468 +++ .../BlackTemple/Util/RaidBlackTempleHelpers.h | 216 ++ src/Ai/Raid/RaidStrategyContext.h | 3 + src/Bot/Engine/BuildSharedActionContexts.cpp | 2 + src/Bot/Engine/BuildSharedTriggerContexts.cpp | 2 + src/Bot/PlayerbotAI.cpp | 9 +- 17 files changed, 7824 insertions(+), 4 deletions(-) create mode 100644 src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp create mode 100644 src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.h create mode 100644 src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.cpp create mode 100644 src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.h create mode 100644 src/Ai/Raid/BlackTemple/RaidBlackTempleActionContext.h create mode 100644 src/Ai/Raid/BlackTemple/RaidBlackTempleTriggerContext.h create mode 100644 src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.cpp create mode 100644 src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.h create mode 100644 src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.cpp create mode 100644 src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.h create mode 100644 src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.cpp create mode 100644 src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.h diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index ec5ec4a155d..01a6a4600f6 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -576,7 +576,7 @@ AiPlayerbot.AutoGearScoreLimit = 0 # "mana" (bots have infinite mana) # "power" (bots have infinite energy, rage, and runic power) # "taxi" (bots may use all flight paths, though they will not actually learn them) -# "raid" (bots use cheats implemented into raid strategies (currently only for SSC and Ulduar)) +# "raid" (bots use certain 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: food, taxi, and raid are enabled AiPlayerbot.BotCheats = "food,taxi,raid" diff --git a/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp b/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp new file mode 100644 index 00000000000..0d47dbf0720 --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp @@ -0,0 +1,3041 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidBlackTempleActions.h" + +#include + +#include "CreatureAI.h" +#include "Playerbots.h" +#include "RaidBlackTempleHelpers.h" +#include "RaidBossHelpers.h" + +using namespace BlackTempleHelpers; + +// General + +bool BlackTempleEraseTimersAndTrackersAction::Execute(Event /*event*/) +{ + const ObjectGuid guid = bot->GetGUID(); + const uint32 instanceId = bot->GetMap()->GetInstanceId(); + + if (botAI->IsTank(bot)) + { + bool erased = false; + if (!AI_VALUE2(Unit*, "find target", "illidan stormrage") && + !AI_VALUE2(Unit*, "find target", "flame of azzinoth")) + { + if (illidanBossDpsWaitTimer.erase(instanceId) > 0) + erased = true; + if (illidanFlameDpsWaitTimer.erase(instanceId) > 0) + erased = true; + if (illidanLastPhase.erase(instanceId) > 0) + erased = true; + if (illidanShadowTrapGuid.erase(guid) > 0) + erased = true; + if (illidanShadowTrapDestination.erase(guid) > 0) + erased = true; + if (flameTankWaypointIndex.erase(guid) > 0) + erased = true; + if (westFlameGuid.erase(instanceId) > 0) + erased = true; + if (eastFlameGuid.erase(instanceId) > 0) + erased = true; + } + if (!AI_VALUE2(Unit*, "find target", "gathios the shatterer")) + { + if (councilDpsWaitTimer.erase(instanceId) > 0) + erased = true; + if (gathiosTankStep.erase(guid) > 0) + erased = true; + } + if (!AI_VALUE2(Unit*, "find target", "mother shahraz") && + shahrazTankStep.erase(guid) > 0) + { + erased = true; + } + return erased; + } + else if (botAI->IsHeal(bot)) + { + if (zerevorHealStep.erase(guid) > 0) + return true; + else + return false; + } + else + { + bool erased = false; + if (!AI_VALUE2(Unit*, "find target", "supremus") && + supremusPhaseTimer.erase(instanceId) > 0) + { + erased = true; + } + if (!AI_VALUE2(Unit*, "find target", "ashtongue channeler") && + hasReachedAkamaChannelerPosition.erase(guid) > 0) + { + erased = true; + } + if (!AI_VALUE2(Unit*, "find target", "gurtogg bloodboil") && + gurtoggPhaseTimer.erase(instanceId) > 0) + { + erased = true; + } + return erased; + } +} + +// High Warlord Naj'entus + +bool HighWarlordNajentusMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* najentus = AI_VALUE2(Unit*, "find target", "high warlord naj'entus"); + if (!najentus) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", najentus)) + { + return botAI->CastSpell("steady shot", najentus); + } + + return false; +} + +bool HighWarlordNajentusTanksPositionBossAction::Execute(Event /*event*/) +{ + Unit* najentus = AI_VALUE2(Unit*, "find target", "high warlord naj'entus"); + if (!najentus) + return false; + + if (AI_VALUE(Unit*, "current target") != najentus) + return Attack(najentus); + + if (najentus->GetVictim() == bot && bot->IsWithinMeleeRange(najentus)) + { + const Position& position = NAJENTUS_TANK_POSITION; + const float distToPosition = bot->GetExactDist2d(position.GetPositionX(), + position.GetPositionY()); + if (distToPosition > 3.0f) + { + const float dX = position.GetPositionX() - bot->GetPositionX(); + const float dY = position.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(5.0f, distToPosition); + const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + + return false; +} + +bool HighWarlordNajentusDisperseRangedAction::Execute(Event /*event*/) +{ + Unit* najentus = AI_VALUE2(Unit*, "find target", "high warlord naj'entus"); + if (!najentus) + return false; + + constexpr uint32 minInterval = 1000; + + constexpr float safeDistFromBoss = 10.0f; + if (bot->GetExactDist2d(najentus) < safeDistFromBoss && + FleePosition(najentus->GetPosition(), safeDistFromBoss, minInterval)) + { + return true; + } + + constexpr float safeDistFromPlayer = 7.0f; + if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistFromPlayer)) + return FleePosition(nearestPlayer->GetPosition(), safeDistFromPlayer, minInterval); + + return false; +} + +bool HighWarlordNajentusRemoveImpalingSpineAction::Execute(Event /*event*/) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* impaledPlayer = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (member->HasAura(static_cast(BlackTempleSpells::SPELL_IMPALING_SPINE))) + { + impaledPlayer = member; + break; + } + } + if (!impaledPlayer) + return false; + + constexpr float searchRadius = 30.0f; + GameObject* spineGo = bot->FindNearestGameObject( + static_cast(BlackTempleObjects::GO_NAJENTUS_SPINE), searchRadius, true); + if (!spineGo) + return false; + + if (bot->GetExactDist2d(spineGo) > 3.0f) + { + const uint32 delay = urand(2000, 3000); + const ObjectGuid spineGuid = spineGo->GetGUID(); + + botAI->AddTimedEvent( + [this, spineGuid]() + { + if (GameObject* targetSpine = botAI->GetGameObject(spineGuid)) + { + MoveTo(BLACK_TEMPLE_MAP_ID, targetSpine->GetPositionX(), + targetSpine->GetPositionY(), bot->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, + true, false); + } + }, + delay); + + return true; + } + else + { + const uint32 delay = urand(1000, 2000); + const ObjectGuid spineGuid = spineGo->GetGUID(); + + botAI->AddTimedEvent( + [this, spineGuid]() + { + if (GameObject* targetSpine = botAI->GetGameObject(spineGuid)) + targetSpine->Use(bot); + }, + delay); + + return true; + } + + return false; +} + +bool HighWarlordNajentusThrowImpalingSpineAction::Execute(Event /*event*/) +{ + Unit* najentus = AI_VALUE2(Unit*, "find target", "high warlord naj'entus"); + if (!najentus) + return false; + + if (bot->GetExactDist2d(najentus) > 24.0f) + { + const float angle = atan2(bot->GetPositionY() - najentus->GetPositionY(), + bot->GetPositionX() - najentus->GetPositionX()); + const float targetX = najentus->GetPositionX() + 23.0f * std::cos(angle); + const float targetY = najentus->GetPositionY() + 23.0f * std::sin(angle); + + return MoveTo(BLACK_TEMPLE_MAP_ID, targetX, targetY, bot->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, + true, false); + } + + if (bot->GetItemByEntry(static_cast(BlackTempleItems::ITEM_NAJENTUS_SPINE))) + { + const uint32 delay = urand(500, 1500); + const ObjectGuid najentusGuid = najentus->GetGUID(); + + botAI->AddTimedEvent( + [this, najentusGuid]() + { + Item* targetSpine = bot->GetItemByEntry( + static_cast(BlackTempleItems::ITEM_NAJENTUS_SPINE)); + Unit* targetNajentus = botAI->GetUnit(najentusGuid); + if (targetSpine && targetNajentus) + botAI->ImbueItem(targetSpine, targetNajentus); + }, + delay); + + return true; + } + + return false; +} + +// Supremus + +bool SupremusMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* supremus = AI_VALUE2(Unit*, "find target", "supremus"); + if (!supremus) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector hunters; + for (GroupReference* ref = group->GetFirstMember(); ref && hunters.size() < 3; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER) + hunters.push_back(member); + } + + if (hunters.empty()) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); + Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1); + + Player* misdirectTarget = nullptr; + if (bot == hunters[0] && mainTank) + misdirectTarget = mainTank; + else if (hunters.size() > 1 && bot == hunters[1] && firstAssistTank) + misdirectTarget = firstAssistTank; + else if (hunters.size() > 2 && bot == hunters[2] && secondAssistTank) + misdirectTarget = secondAssistTank; + + if (!misdirectTarget) + return false; + + if (botAI->CanCastSpell("misdirection", misdirectTarget)) + return botAI->CastSpell("misdirection", misdirectTarget); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", supremus)) + { + return botAI->CastSpell("steady shot", supremus); + } + + return false; +} + +bool SupremusDisperseRangedAction::Execute(Event /*event*/) +{ + constexpr float safeDistance = 8.0f; + constexpr uint32 minInterval = 1000; + if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistance)) + return FleePosition(nearestPlayer->GetPosition(), safeDistance, minInterval); + + return false; +} + +bool SupremusKiteBossAction::Execute(Event /*event*/) +{ + Unit* supremus = AI_VALUE2(Unit*, "find target", "supremus"); + if (!supremus) + return false; + + constexpr float safeDistance = 25.0f; + const float currentDistance = bot->GetDistance2d(supremus); + if (currentDistance < safeDistance) + return MoveAway(supremus, safeDistance - currentDistance); + + return false; +} + +bool SupremusMoveAwayFromVolcanosAction::Execute(Event /*event*/) +{ + auto const& volcanos = GetAllSupremusVolcanos(); + if (volcanos.empty()) + return false; + + constexpr float hazardRadius = 16.0f; + bool inDanger = false; + for (Unit* volcano : volcanos) + { + if (bot->GetDistance2d(volcano) < hazardRadius) + { + inDanger = true; + break; + } + } + + if (!inDanger) + return false; + + constexpr float maxRadius = 40.0f; + Position safestPos = FindSafestNearbyPosition(volcanos, maxRadius, hazardRadius); + + return MoveTo(BLACK_TEMPLE_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), + bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); +} + +Position SupremusMoveAwayFromVolcanosAction::FindSafestNearbyPosition( + const std::vector& volcanos, float maxRadius, float hazardRadius) +{ + constexpr float searchStep = M_PI / 8.0f; + constexpr float distanceStep = 1.0f; + + Position bestPos; + float minMoveDistance = std::numeric_limits::max(); + bool foundSafe = false; + + for (float distance = 0.0f; + distance <= maxRadius; distance += distanceStep) + { + for (float angle = 0.0f; angle < 2 * M_PI; angle += searchStep) + { + float x = bot->GetPositionX() + distance * std::cos(angle); + float y = bot->GetPositionY() + distance * std::sin(angle); + + bool isSafe = true; + for (Unit* volcano : volcanos) + { + if (volcano->GetDistance2d(x, y) < hazardRadius) + { + isSafe = false; + break; + } + } + + if (!isSafe) + continue; + + Position testPos(x, y, bot->GetPositionZ()); + + bool pathSafe = + IsPathSafeFromVolcanos(bot->GetPosition(), testPos, volcanos, hazardRadius); + if (pathSafe || !foundSafe) + { + float moveDistance = bot->GetExactDist2d(x, y); + + if (pathSafe && (!foundSafe || moveDistance < minMoveDistance)) + { + bestPos = testPos; + minMoveDistance = moveDistance; + foundSafe = true; + } + else if (!foundSafe && moveDistance < minMoveDistance) + { + bestPos = testPos; + minMoveDistance = moveDistance; + } + } + } + + if (foundSafe) + break; + } + + return bestPos; +} + +bool SupremusMoveAwayFromVolcanosAction::IsPathSafeFromVolcanos(const Position& start, + const Position& end, const std::vector& volcanos, float hazardRadius) +{ + constexpr uint8 numChecks = 10; + float dx = end.GetPositionX() - start.GetPositionX(); + float dy = end.GetPositionY() - start.GetPositionY(); + + for (uint8 i = 1; i <= numChecks; ++i) + { + float ratio = static_cast(i) / numChecks; + float checkX = start.GetPositionX() + dx * ratio; + float checkY = start.GetPositionY() + dy * ratio; + + for (Unit* volcano : volcanos) + { + float distToVol = volcano->GetDistance2d(checkX, checkY); + if (distToVol < hazardRadius) + return false; + } + } + + return true; +} + +std::vector SupremusMoveAwayFromVolcanosAction::GetAllSupremusVolcanos() +{ + std::vector volcanos; + constexpr float searchRadius = 40.0f; + + std::list creatureList; + bot->GetCreatureListWithEntryInGrid(creatureList, static_cast( + BlackTempleNpcs::NPC_SUPREMUS_VOLCANO), searchRadius); + + for (Creature* creature : creatureList) + { + if (creature && creature->IsAlive()) + volcanos.push_back(creature); + } + + return volcanos; +} + +bool SupremusManagePhaseTimerAction::Execute(Event /*event*/) +{ + Unit* supremus = AI_VALUE2(Unit*, "find target", "supremus"); + if (!supremus) + return false; + + supremusPhaseTimer.try_emplace( + supremus->GetMap()->GetInstanceId(), std::time(nullptr)); + + return false; +} + +// Shade of Akama + +bool ShadeOfAkamaMeleeDpsPrioritizeChannelersAction::Execute(Event /*event*/) +{ + if (!hasReachedAkamaChannelerPosition.count(bot->GetGUID())) + { + const Position &position = AKAMA_CHANNELER_POSITION; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, position.GetPositionX(), position.GetPositionY(), + bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + else + { + hasReachedAkamaChannelerPosition.insert(bot->GetGUID()); + } + } + + constexpr float searchRadius = 30.0f; + std::list creatureList; + bot->GetCreatureListWithEntryInGrid( + creatureList, static_cast(BlackTempleNpcs::NPC_ASHTONGUE_CHANNELER), searchRadius); + + std::vector channelers; + for (Creature* creature : creatureList) + { + if (creature && creature->IsAlive()) + channelers.push_back(creature); + } + + if (channelers.empty()) + return false; + + std::sort(channelers.begin(), channelers.end(), + [](Creature* first, Creature* second) { return first->GetGUID() < second->GetGUID(); }); + + Creature* const channeler = channelers.front(); + + MarkTargetWithSkull(bot, channeler); + + if (AI_VALUE(Unit*, "current target") != channeler) + return Attack(channeler); + + return false; +} + +// Teron Gorefiend + +bool TeronGorefiendMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* gorefiend = AI_VALUE2(Unit*, "find target", "teron gorefiend"); + if (!gorefiend) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", gorefiend)) + { + return botAI->CastSpell("steady shot", gorefiend); + } + + return false; +} + +bool TeronGorefiendTanksPositionBossAction::Execute(Event /*event*/) +{ + Unit* gorefiend = AI_VALUE2(Unit*, "find target", "teron gorefiend"); + if (!gorefiend) + return false; + + MarkTargetWithSkull(bot, gorefiend); + + if (AI_VALUE(Unit*, "current target") != gorefiend) + return Attack(gorefiend); + + if (gorefiend->GetVictim() == bot && bot->IsWithinMeleeRange(gorefiend)) + { + const Position& position = GOREFIEND_TANK_POSITION; + const float distToPosition = bot->GetExactDist2d(position.GetPositionX(), + position.GetPositionY()); + if (distToPosition > 3.0f) + { + const float dX = position.GetPositionX() - bot->GetPositionX(); + const float dY = position.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(5.0f, distToPosition); + const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + + return false; +} + +// Assume positions in arc at the edge of the balcony (farthest from Constructs) +bool TeronGorefiendPositionRangedOnBalconyAction::Execute(Event /*event*/) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector rangedMembers; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !botAI->IsRanged(member)) + continue; + + rangedMembers.push_back(member); + } + + if (rangedMembers.empty()) + return false; + + size_t count = rangedMembers.size(); + auto findIt = std::find(rangedMembers.begin(), rangedMembers.end(), bot); + size_t botIndex = (findIt != rangedMembers.end()) ? + std::distance(rangedMembers.begin(), findIt) : 0; + + constexpr float arcSpan = 2.0f * M_PI / 5.0f; + constexpr float arcCenter = 6.279f; + constexpr float arcStart = arcCenter - arcSpan / 2.0f; + + constexpr float radius = 12.0f; + const float angle = (count == 1) ? arcCenter : + (arcStart + arcSpan * static_cast(botIndex) / static_cast(count - 1)); + + const float targetX = GOREFIEND_TANK_POSITION.GetPositionX() + radius * std::cos(angle); + const float targetY = GOREFIEND_TANK_POSITION.GetPositionY() + radius * std::sin(angle); + + if (bot->GetExactDist2d(targetX, targetY) > 1.0f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool TeronGorefiendAvoidShadowOfDeathAction::Execute(Event /*event*/) +{ + switch (bot->getClass()) + { + case CLASS_HUNTER: + return botAI->CanCastSpell("feign death", bot) && + botAI->CastSpell("feign death", bot); + + case CLASS_MAGE: + return botAI->CanCastSpell("ice block", bot) && + botAI->CastSpell("ice block", bot); + + case CLASS_PALADIN: + return botAI->CanCastSpell("divine shield", bot) && + botAI->CastSpell("divine shield", bot); + + case CLASS_ROGUE: + return botAI->CanCastSpell("vanish", bot) && + botAI->CastSpell("vanish", bot); + + default: + return false; + } +} + +bool TeronGorefiendMoveToCornerToDieAction::Execute(Event /*event*/) +{ + const Position& position = GOREFIEND_DIE_POSITION; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, position.GetPositionX(), position.GetPositionY(), + bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool TeronGorefiendControlAndDestroyShadowyConstructsAction::Execute(Event /*event*/) +{ + Unit* gorefiend = AI_VALUE2(Unit*, "find target", "teron gorefiend"); + if (!gorefiend) + return false; + + Unit* spirit = bot->GetCharm(); + if (!spirit) + return false; + + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("possible targets no los")->Get(); + Unit* priorityTarget = nullptr; + uint32 highestHp = std::numeric_limits::min(); + + float closestToGorefiend = std::numeric_limits::max(); + + for (auto guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || + unit->GetEntry() != static_cast(BlackTempleNpcs::NPC_SHADOWY_CONSTRUCT)) + continue; + + uint32 hp = unit->GetHealth(); + float distToGorefiend = gorefiend->GetExactDist2d(unit); + + if (hp > highestHp) + { + highestHp = hp; + priorityTarget = unit; + closestToGorefiend = distToGorefiend; + } + else if ((hp == highestHp) && (distToGorefiend < closestToGorefiend)) + { + priorityTarget = unit; + closestToGorefiend = distToGorefiend; + } + } + + if (priorityTarget) + { + const float distToTarget = spirit->GetExactDist2d(priorityTarget); + constexpr float desiredDist = 10.0f; + if (distToTarget > desiredDist) + { + const float moveDist = distToTarget - desiredDist + 2.0f; + const float dX = priorityTarget->GetPositionX() - spirit->GetPositionX(); + const float dY = priorityTarget->GetPositionY() - spirit->GetPositionY(); + const float moveX = spirit->GetPositionX() + (dX / distToTarget) * moveDist; + const float moveY = spirit->GetPositionY() + (dY / distToTarget) * moveDist; + + spirit->GetMotionMaster()->MovePoint(0, moveX, moveY, spirit->GetPositionZ()); + return true; + } + + // Adding cooldowns manually is needed due to the charmed creature not observing cooldowns, + // including the GCD. The ordering, including repeating some spells, is the product of testing + // to try to keep the bot from breaking chains with volley, which tends to happen when volley + // is cast before chains (maybe due to projectile travel time?) + if (!spirit->HasSpellCooldown(static_cast(BlackTempleSpells::SPELL_SPIRIT_CHAINS)) && + priorityTarget->GetHealthPct() == 100.0f) + { + spirit->CastSpell( + priorityTarget, static_cast(BlackTempleSpells::SPELL_SPIRIT_CHAINS), true); + spirit->AddSpellCooldown( + static_cast(BlackTempleSpells::SPELL_SPIRIT_CHAINS), 0, 15000); + return true; + } + else if (!spirit->HasSpellCooldown(static_cast(BlackTempleSpells::SPELL_SPIRIT_LANCE))) + { + spirit->CastSpell( + priorityTarget, static_cast(BlackTempleSpells::SPELL_SPIRIT_LANCE), true); + spirit->AddSpellCooldown( + static_cast(BlackTempleSpells::SPELL_SPIRIT_LANCE), 0, 1000); + return true; + } + else if (!spirit->HasSpellCooldown(static_cast(BlackTempleSpells::SPELL_SPIRIT_CHAINS))) + { + spirit->CastSpell( + priorityTarget, static_cast(BlackTempleSpells::SPELL_SPIRIT_CHAINS), true); + spirit->AddSpellCooldown( + static_cast(BlackTempleSpells::SPELL_SPIRIT_CHAINS), 0, 15000); + return true; + } + else if (!spirit->HasSpellCooldown(static_cast(BlackTempleSpells::SPELL_SPIRIT_LANCE))) + { + spirit->CastSpell + (priorityTarget, static_cast(BlackTempleSpells::SPELL_SPIRIT_LANCE), true); + spirit->AddSpellCooldown( + static_cast(BlackTempleSpells::SPELL_SPIRIT_LANCE), 0, 1000); + return true; + } + else if (!spirit->HasSpellCooldown(static_cast(BlackTempleSpells::SPELL_SPIRIT_VOLLEY)) && + !priorityTarget->HasAura(static_cast(BlackTempleSpells::SPELL_SPIRIT_CHAINS))) + { + spirit->CastSpell + (priorityTarget, static_cast(BlackTempleSpells::SPELL_SPIRIT_VOLLEY), true); + spirit->AddSpellCooldown( + static_cast(BlackTempleSpells::SPELL_SPIRIT_VOLLEY), 0, 15000); + return true; + } + } + else + { + const float distToGorefiend = spirit->GetExactDist2d(gorefiend); + constexpr float targetDist = 5.0f; + if (distToGorefiend > targetDist) + { + const float moveDist = distToGorefiend - targetDist; + const float dX = gorefiend->GetPositionX() - spirit->GetPositionX(); + const float dY = gorefiend->GetPositionY() - spirit->GetPositionY(); + const float moveX = spirit->GetPositionX() + (dX / distToGorefiend) * moveDist; + const float moveY = spirit->GetPositionY() + (dY / distToGorefiend) * moveDist; + + spirit->GetMotionMaster()->MovePoint(0, moveX, moveY, spirit->GetPositionZ()); + return true; + } + else if (!spirit->HasSpellCooldown(static_cast(BlackTempleSpells::SPELL_SPIRIT_STRIKE))) + { + spirit->CastSpell( + gorefiend, static_cast(BlackTempleSpells::SPELL_SPIRIT_STRIKE), true); + spirit->AddSpellCooldown( + static_cast(BlackTempleSpells::SPELL_SPIRIT_STRIKE), 0, 1000); + return true; + } + } + + return false; +} + +// Gurtogg Bloodboil + +bool GurtoggBloodboilMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* gurtogg = AI_VALUE2(Unit*, "find target", "gurtogg bloodboil"); + if (!gurtogg) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", gurtogg)) + { + return botAI->CastSpell("steady shot", gurtogg); + } + + return false; +} + +bool GurtoggBloodboilTanksPositionBossAction::Execute(Event /*event*/) +{ + Unit* gurtogg = AI_VALUE2(Unit*, "find target", "gurtogg bloodboil"); + if (!gurtogg) + return false; + + if (AI_VALUE(Unit*, "current target") != gurtogg) + return Attack(gurtogg); + + Unit* victim = gurtogg->GetVictim(); + Player* playerVictim = victim ? victim->ToPlayer() : nullptr; + if (playerVictim && botAI->IsTank(playerVictim) && bot->IsWithinMeleeRange(gurtogg)) + { + const Position& position = GURTOGG_TANK_POSITION; + const float distToPosition = bot->GetExactDist2d(position.GetPositionX(), + position.GetPositionY()); + if (distToPosition > 2.0f) + { + const float dX = position.GetPositionX() - bot->GetPositionX(); + const float dY = position.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(5.0f, distToPosition); + const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + + return false; +} + +bool GurtoggBloodboilRotateRangedGroupsAction::Execute(Event /*event*/) +{ + Unit* gurtogg = AI_VALUE2(Unit*, "find target", "gurtogg bloodboil"); + if (!gurtogg) + return false; + + auto groups = GetGurtoggRangedRotationGroups(bot); + int activeGroup = GetGurtoggActiveRotationGroup(gurtogg); + + bool inActiveGroup = false; + if (activeGroup >= 0 && activeGroup < groups.size()) + { + auto const& group = groups[activeGroup]; + inActiveGroup = std::find(group.begin(), group.end(), bot) != group.end(); + } + + const Position& nearPosition = GURTOGG_RANGED_POSITION; + const Position& farPosition = GURTOGG_SOAKER_POSITION; + constexpr float distFromPos = 2.0f; + + if (inActiveGroup && bot->GetExactDist2d(farPosition) > distFromPos) + { + return MoveInside(BLACK_TEMPLE_MAP_ID, farPosition.GetPositionX(), + farPosition.GetPositionY(), bot->GetPositionZ(), + distFromPos, MovementPriority::MOVEMENT_FORCED); + } + else if (!inActiveGroup && bot->GetExactDist2d(nearPosition) > distFromPos) + { + return MoveInside(BLACK_TEMPLE_MAP_ID, nearPosition.GetPositionX(), + nearPosition.GetPositionY(), bot->GetPositionZ(), + distFromPos, MovementPriority::MOVEMENT_FORCED); + } + + return false; +} + +bool GurtoggBloodboilRangedMoveAwayFromEnragedPlayerAction::Execute(Event /*event*/) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* enragedPlayer = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->HasAura( + static_cast(BlackTempleSpells::SPELL_PLAYER_FEL_RAGE))) + { + enragedPlayer = member; + break; + } + } + + constexpr float safeDistance = 20.0f; + constexpr uint32 minInterval = 0; + if (enragedPlayer && bot->GetExactDist2d(enragedPlayer) < safeDistance) + return FleePosition(enragedPlayer->GetPosition(), safeDistance, minInterval); + + return false; +} + +bool GurtoggBloodboilManagePhaseTimerAction::Execute(Event /*event*/) +{ + Unit* gurtogg = AI_VALUE2(Unit*, "find target", "gurtogg bloodboil"); + if (!gurtogg) + return false; + + const time_t now = std::time(nullptr); + const uint32 instanceId = gurtogg->GetMap()->GetInstanceId(); + + if (gurtogg->HasAura(static_cast(BlackTempleSpells::SPELL_BOSS_FEL_RAGE))) + { + return gurtoggPhaseTimer.erase(instanceId) > 0; + } + else + { + auto [it, inserted] = gurtoggPhaseTimer.try_emplace(instanceId, now); + return inserted; + } +} + +// Reliquary of Souls + +bool ReliquaryOfSoulsMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* desire = AI_VALUE2(Unit*, "find target", "essence of desire"); + Unit* anger = AI_VALUE2(Unit*, "find target", "essence of anger"); + + if (!desire && !anger) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + Unit* target = desire ? desire : anger; + + if (target->GetHealthPct() > 95.0f) + { + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", target)) + { + return botAI->CastSpell("steady shot", target); + } + } + + return false; +} + +bool ReliquaryOfSoulsAdjustDistanceFromSufferingAction::Execute(Event /*event*/) +{ + Unit* suffering = AI_VALUE2(Unit*, "find target", "essence of suffering"); + if (!suffering) + return false; + + if (botAI->IsTank(bot) && bot->GetHealthPct() > 25.0f) + return TanksMoveToMinimumRange(suffering); + else if (botAI->IsMelee(bot) && bot->GetVictim() != suffering) + return MeleeDpsStayAtMaximumRange(suffering); + else if (botAI->IsRanged(bot)) + return RangedMoveAwayFromBoss(suffering); + + return false; +} + +bool ReliquaryOfSoulsAdjustDistanceFromSufferingAction::TanksMoveToMinimumRange(Unit* suffering) +{ + const float distanceToBoss = bot->GetExactDist2d(suffering); + if (distanceToBoss > 2.0f) + { + const float dX = suffering->GetPositionX() - bot->GetPositionX(); + const float dY = suffering->GetPositionY() - bot->GetPositionY(); + const float targetX = bot->GetPositionX() + (dX / distanceToBoss); + const float targetY = bot->GetPositionY() + (dY / distanceToBoss); + + return MoveTo(BLACK_TEMPLE_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool ReliquaryOfSoulsAdjustDistanceFromSufferingAction::MeleeDpsStayAtMaximumRange(Unit* suffering) +{ + const float desiredDist = bot->GetMeleeRange(suffering); + const float behindAngle = Position::NormalizeOrientation(suffering->GetOrientation() + M_PI); + const float targetX = suffering->GetPositionX() + desiredDist * std::cos(behindAngle); + const float targetY = suffering->GetPositionY() + desiredDist * std::sin(behindAngle); + + if (bot->GetExactDist2d(targetX, targetY) > 0.25f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool ReliquaryOfSoulsAdjustDistanceFromSufferingAction::RangedMoveAwayFromBoss(Unit* suffering) +{ + constexpr float safeDistance = 15.0f; + constexpr uint32 minInterval = 1000; + if (bot->GetExactDist2d(suffering) < safeDistance) + return FleePosition(suffering->GetPosition(), safeDistance, minInterval); + + return false; +} + +bool ReliquaryOfSoulsHealersDpsSufferingAction::Execute(Event /*event*/) +{ + Unit* suffering = AI_VALUE2(Unit*, "find target", "essence of suffering"); + if (!suffering) + return false; + + if (bot->getClass() == CLASS_DRUID) + { + if (botAI->HasAura("tree of life", bot)) + botAI->RemoveAura("tree of life"); + + bool casted = false; + + if (botAI->CanCastSpell("barkskin", bot) && + botAI->CastSpell("barkskin", bot)) + casted = true; + + if (botAI->CanCastSpell("wrath", suffering) && + botAI->CastSpell("wrath", suffering)) + casted = true; + + return casted; + } + else if (bot->getClass() == CLASS_PALADIN) + { + bool casted = false; + + if (botAI->CanCastSpell("avenging wrath", bot) && + botAI->CastSpell("avenging wrath", bot)) + casted = true; + + if (botAI->CanCastSpell("consecration", bot) && + botAI->CastSpell("consecration", bot)) + casted = true; + + if (botAI->CanCastSpell("exorcism", suffering) && + botAI->CastSpell("exorcism", suffering)) + casted = true; + + if (botAI->CanCastSpell("hammer of wrath", suffering) && + botAI->CastSpell("hammer of wrath", suffering)) + casted = true; + + if (botAI->CanCastSpell("holy shock", suffering) && + botAI->CastSpell("holy shock", suffering)) + casted = true; + + if (botAI->CanCastSpell("judgement of light", suffering) && + botAI->CastSpell("judgement of light", suffering)) + casted = true; + + return casted; + } + else if (bot->getClass() == CLASS_PRIEST) + { + if (botAI->CanCastSpell("smite", suffering)) + return botAI->CastSpell("smite", suffering); + } + else if (bot->getClass() == CLASS_SHAMAN) + { + bool casted = false; + + if (botAI->CanCastSpell("earth shock", suffering) && + botAI->CastSpell("earth shock", suffering)) + casted = true; + + if (botAI->CanCastSpell("chain lightning", suffering) && + botAI->CastSpell("chain lightning", suffering)) + casted = true; + + if (botAI->CanCastSpell("lightning bolt", suffering) && + botAI->CastSpell("lightning bolt", suffering)) + casted = true; + + return casted; + } + + return false; +} + +bool ReliquaryOfSoulsSpellstealRuneShieldAction::Execute(Event /*event*/) +{ + if (Unit* desire = AI_VALUE2(Unit*, "find target", "essence of desire"); + desire && botAI->CanCastSpell("spellsteal", desire)) + { + return botAI->CastSpell("spellsteal", desire); + } + + return false; +} + +bool ReliquaryOfSoulsSpellReflectDeadenAction::Execute(Event /*event*/) +{ + if (botAI->CanCastSpell("spell reflection", bot)) + return botAI->CastSpell("spell reflection", bot); + + return false; +} + +// Mother Shahraz + +bool MotherShahrazMisdirectBossToMainTankAction::Execute(Event /*event*/) +{ + Unit* shahraz = AI_VALUE2(Unit*, "find target", "mother shahraz"); + if (!shahraz) + return false; + + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", shahraz)) + { + return botAI->CastSpell("steady shot", shahraz); + } + + return false; +} + +bool MotherShahrazTanksPositionBossUnderPillarAction::Execute(Event /*event*/) +{ + Unit* shahraz = AI_VALUE2(Unit*, "find target", "mother shahraz"); + if (!shahraz) + return false; + + if (AI_VALUE(Unit*, "current target") != shahraz) + return Attack(shahraz); + + Unit* victim = shahraz->GetVictim(); + Player* playerVictim = victim ? victim->ToPlayer() : nullptr; + if (playerVictim && botAI->IsTank(playerVictim)) + { + const ObjectGuid guid = bot->GetGUID(); + auto it = shahrazTankStep.try_emplace( + guid, TankPositionState::MovingToTransition).first; + TankPositionState state = it->second; + + constexpr float maxDistance = 0.5f; + const Position& position = state == TankPositionState::MovingToTransition ? + SHAHRAZ_TRANSITION_POSITION : SHAHRAZ_TANK_POSITION; + const float distToPosition = bot->GetExactDist2d(position); + + if (distToPosition > maxDistance && bot->IsWithinMeleeRange(shahraz)) + { + const bool backwards = (shahraz->GetVictim() == bot); + return MoveTo(BLACK_TEMPLE_MAP_ID, position.GetPositionX(), position.GetPositionY(), + bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, backwards); + } + + if (state == TankPositionState::MovingToTransition && distToPosition <= maxDistance) + shahrazTankStep[guid] = TankPositionState::MovingToFinal; + + if (state != TankPositionState::MovingToTransition && distToPosition <= maxDistance) + { + const float orientation = atan2(shahraz->GetPositionY() - bot->GetPositionY(), + shahraz->GetPositionX() - bot->GetPositionX()); + bot->SetFacingTo(orientation); + shahrazTankStep[guid] = TankPositionState::Positioned; + } + } + + return false; +} + +bool MotherShahrazMeleeDpsWaitAtSafePositionAction::Execute(Event /*event*/) +{ + return MoveTo(BLACK_TEMPLE_MAP_ID, SHAHRAZ_RANGED_POSITION.GetPositionX(), + SHAHRAZ_RANGED_POSITION.GetPositionY(), bot->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); +} + +// This doesn't matter for bots since they don't take fall damage, and it's actually easier +// to tank her closer to her starting position, but I want to simulate a player strategy +bool MotherShahrazPositionRangedUnderPillarAction::Execute(Event /*event*/) +{ + const Position& position = SHAHRAZ_RANGED_POSITION; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 1.0f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool MotherShahrazRunAwayToBreakFatalAttractionAction::Execute(Event /*event*/) +{ + std::vector attractedPlayers = GetAttractedPlayers(); + if (attractedPlayers.size() < 2) + return false; + + float centerX = 0.0f, centerY = 0.0f; + for (Player* member : attractedPlayers) + { + centerX += member->GetPositionX(); + centerY += member->GetPositionY(); + } + centerX /= attractedPlayers.size(); + centerY /= attractedPlayers.size(); + + auto botIt = std::find(attractedPlayers.begin(), attractedPlayers.end(), bot); + if (botIt == attractedPlayers.end()) + return false; + + const float spreadAngle = + 2.0f * M_PI * std::distance(attractedPlayers.begin(), botIt) / attractedPlayers.size(); + + constexpr float maxSpreadDistance = 35.0f; + constexpr float distanceStep = 1.0f; + float lastValidX = bot->GetPositionX(); + float lastValidY = bot->GetPositionY(); + float lastValidZ = bot->GetPositionZ(); + + for (float currentDistance = distanceStep; + currentDistance <= maxSpreadDistance; + currentDistance += distanceStep) + { + float testX = centerX + std::cos(spreadAngle) * currentDistance; + float testY = centerY + std::sin(spreadAngle) * currentDistance; + float testZ = lastValidZ; + + if (!bot->GetMap()->CheckCollisionAndGetValidCoords( + bot, bot->GetPositionX(), bot->GetPositionY(), + bot->GetPositionZ(), testX, testY, testZ)) + { + break; + } + + lastValidX = testX; + lastValidY = testY; + lastValidZ = testZ; + } + + if (MoveTo(BLACK_TEMPLE_MAP_ID, lastValidX, lastValidY, lastValidZ, false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false)) + { + return true; + } + else + { + // In case bots get stuck, try a 5-yard random move + const float angle = frand(0.0f, 2.0f * M_PI); + constexpr float dist = 5.0f; + float randX = bot->GetPositionX() + std::cos(angle) * dist; + float randY = bot->GetPositionY() + std::sin(angle) * dist; + float randZ = lastValidZ; + bot->GetMap()->CheckCollisionAndGetValidCoords( + bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), + randX, randY, randZ); + + return MoveTo(BLACK_TEMPLE_MAP_ID, randX, randY, randZ, false, false, false, + false, MovementPriority::MOVEMENT_FORCED, true, false); + } +} + +std::vector MotherShahrazRunAwayToBreakFatalAttractionAction::GetAttractedPlayers() +{ + std::vector attractedPlayers; + Group* group = bot->GetGroup(); + if (!group) + return attractedPlayers; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->HasAura(static_cast( + BlackTempleSpells::SPELL_FATAL_ATTRACTION))) + { + attractedPlayers.push_back(member); + } + } + + std::sort(attractedPlayers.begin(), attractedPlayers.end(), + [](Player* a, Player* b) { + return a->GetGUID().GetCounter() < b->GetGUID().GetCounter(); + }); + + return attractedPlayers; +} + +// Illidari Council + +bool IllidariCouncilMisdirectBossesToTanksAction::Execute(Event /*event*/) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector hunters; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && + GET_PLAYERBOT_AI(member)) + { + hunters.push_back(member); + } + + if (hunters.size() >= 4) + break; + } + + int8 hunterIndex = -1; + for (size_t i = 0; i < hunters.size(); ++i) + { + if (hunters[i] == bot) + { + hunterIndex = static_cast(i); + break; + } + } + if (hunterIndex == -1) + return false; + + Unit* councilTarget = nullptr; + Player* tankTarget = nullptr; + if (hunterIndex == 0) + { + councilTarget = AI_VALUE2(Unit*, "find target", "high nethermancer zerevor"); + tankTarget = GetZerevorMageTank(bot); + } + else if (hunterIndex == 1) + { + councilTarget = AI_VALUE2(Unit*, "find target", "lady malande"); + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + if (Player* member = GetGroupAssistTank(botAI, bot, 0)) + { + tankTarget = member; + break; + } + } + } + else if (hunterIndex == 2) + { + councilTarget = AI_VALUE2(Unit*, "find target", "gathios the shatterer"); + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + if (Player* member = GetGroupMainTank(botAI, bot)) + { + tankTarget = member; + break; + } + } + } + else if (hunterIndex == 3) + { + councilTarget = AI_VALUE2(Unit*, "find target", "veras darkshadow"); + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + if (Player* member = GetGroupAssistTank(botAI, bot, 1)) + { + tankTarget = member; + break; + } + } + } + + if (!councilTarget || !tankTarget || !tankTarget->IsAlive()) + return false; + + if (botAI->CanCastSpell("misdirection", tankTarget)) + return botAI->CastSpell("misdirection", tankTarget); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", councilTarget)) + { + return botAI->CastSpell("steady shot", councilTarget); + } + + return false; +} + +bool IllidariCouncilMainTankPositionGathiosAction::Execute(Event /*event*/) +{ + Unit* gathios = AI_VALUE2(Unit*, "find target", "gathios the shatterer"); + if (!gathios) + return false; + + // Failsafe for if bot falls through the floor, which tends to happen upon the pull + if (bot->GetPositionZ() < COUNCIL_FLOOR_Z_THRESHOLD) + { + bot->TeleportTo(BLACK_TEMPLE_MAP_ID, gathios->GetPositionX(), gathios->GetPositionY(), + gathios->GetPositionZ(), bot->GetOrientation()); + } + + MarkTargetWithSquare(bot, gathios); + SetRtiTarget(botAI, "square", gathios); + + if (AI_VALUE(Unit*, "current target") != gathios) + return Attack(gathios); + + const ObjectGuid guid = bot->GetGUID(); + uint8 index = gathiosTankStep.count(guid) ? gathiosTankStep[guid] : 0; + + const Position& position = GATHIOS_TANK_POSITIONS[index]; + + constexpr float maxDistance = 2.0f; + float distToPosition = bot->GetExactDist2d(position); + + if (gathios->GetVictim() == bot && bot->IsWithinMeleeRange(gathios)) + { + if (distToPosition <= maxDistance && HasDangerousCouncilAura(bot)) + { + index = (index + 1) % 4; + gathiosTankStep[guid] = index; + const Position& newPosition = GATHIOS_TANK_POSITIONS[index]; + const float newDistToPosition = bot->GetExactDist2d(newPosition); + if (newDistToPosition > maxDistance) + { + const float dX = newPosition.GetPositionX() - bot->GetPositionX(); + const float dY = newPosition.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(5.0f, newDistToPosition); + const float moveX = bot->GetPositionX() + (dX / newDistToPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / newDistToPosition) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + else if (distToPosition > maxDistance) + { + const float dX = position.GetPositionX() - bot->GetPositionX(); + const float dY = position.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(5.0f, distToPosition); + const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + + return false; +} + +bool IllidariCouncilMainTankReflectJudgementOfCommandAction::Execute(Event /*event*/) +{ + if (botAI->CanCastSpell("spell reflection", bot)) + return botAI->CastSpell("spell reflection", bot); + + return false; +} + +bool IllidariCouncilFirstAssistTankFocusMalandeAction::Execute(Event /*event*/) +{ + Unit* malande = AI_VALUE2(Unit*, "find target", "lady malande"); + if (!malande) + return false; + + // Failsafe for if bot falls through the floor, which tends to happen upon the pull + if (bot->GetPositionZ() < COUNCIL_FLOOR_Z_THRESHOLD) + { + bot->TeleportTo(BLACK_TEMPLE_MAP_ID, malande->GetPositionX(), malande->GetPositionY(), + malande->GetPositionZ(), bot->GetOrientation()); + } + + MarkTargetWithStar(bot, malande); + SetRtiTarget(botAI, "star", malande); + + if (AI_VALUE(Unit*, "current target") != malande) + return Attack(malande); + + return false; +} + +bool IllidariCouncilSecondAssistTankPositionDarkshadowAction::Execute(Event /*event*/) +{ + Unit* darkshadow = AI_VALUE2(Unit*, "find target", "veras darkshadow"); + if (!darkshadow) + return false; + + // Failsafe for if bot falls through the floor, which tends to happen upon the pull + if (bot->GetPositionZ() < COUNCIL_FLOOR_Z_THRESHOLD) + { + bot->TeleportTo(BLACK_TEMPLE_MAP_ID, darkshadow->GetPositionX(), darkshadow->GetPositionY(), + darkshadow->GetPositionZ(), bot->GetOrientation()); + } + + MarkTargetWithCircle(bot, darkshadow); + SetRtiTarget(botAI, "circle", darkshadow); + + if (AI_VALUE(Unit*, "current target") != darkshadow) + return Attack(darkshadow); + + if (darkshadow->GetVictim() == bot) + { + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return false; + + const float distToPosition = bot->GetExactDist2d(mainTank->GetPositionX(), + mainTank->GetPositionY()); + if (distToPosition > 2.0f) + { + const float dX = mainTank->GetPositionX() - bot->GetPositionX(); + const float dY = mainTank->GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(5.0f, distToPosition); + const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; + + const bool backwards = bot->GetExactDist2d(mainTank) < 10.0f; + return MoveTo(BLACK_TEMPLE_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, backwards); + } + } + + return false; +} + +bool IllidariCouncilMageTankPositionZerevorAction::Execute(Event /*event*/) +{ + Unit* zerevor = AI_VALUE2(Unit*, "find target", "high nethermancer zerevor"); + if (!zerevor) + return false; + + if (zerevor->HasAura(static_cast(BlackTempleSpells::SPELL_DAMPEN_MAGIC)) && + botAI->CanCastSpell("spellsteal", zerevor)) + { + return botAI->CastSpell("spellsteal", zerevor); + } + + MarkTargetWithTriangle(bot, zerevor); + SetRtiTarget(botAI, "triangle", zerevor); + + if (AI_VALUE(Unit*, "current target") != zerevor) + return Attack(zerevor); + + if (zerevor->GetVictim() == bot) + { + const Position& position = ZEREVOR_TANK_POSITION; + const float distToPosition = bot->GetExactDist2d(position.GetPositionX(), + position.GetPositionY()); + if (distToPosition > 2.0f) + { + const float dX = position.GetPositionX() - bot->GetPositionX(); + const float dY = position.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(10.0f, distToPosition); + const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + + return false; +} + +bool IllidariCouncilPositionMageTankHealerAction::Execute(Event /*event*/) +{ + Player* mageTank = GetZerevorMageTank(bot); + if (!mageTank) + return false; + + Unit* zerevor = AI_VALUE2(Unit*, "find target", "high nethermancer zerevor"); + if (!zerevor || zerevor->GetVictim() != mageTank) + return false; + + const ObjectGuid guid = bot->GetGUID(); + uint8 index = zerevorHealStep.count(guid) ? zerevorHealStep[guid] : 0; + + const Position& position = ZEREVOR_HEALER_POSITIONS[index]; + + constexpr float maxDistance = 1.0f; + const float distToPosition = bot->GetExactDist2d(position); + + if (distToPosition <= maxDistance && HasDangerousCouncilAura(bot)) + { + index = (index + 1) % 2; + zerevorHealStep[guid] = index; + const Position& newPosition = ZEREVOR_HEALER_POSITIONS[index]; + const float newDistToPosition = bot->GetExactDist2d(newPosition); + if (newDistToPosition > maxDistance) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, newPosition.GetPositionX(), + newPosition.GetPositionY(), bot->GetPositionZ(), + false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + else if (distToPosition > maxDistance) + { + const float dX = position.GetPositionX() - bot->GetPositionX(); + const float dY = position.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(10.0f, distToPosition); + const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +bool IllidariCouncilDisperseRangedAction::Execute(Event /*event*/) +{ + constexpr float safeDistance = 4.0f; + constexpr uint32 minInterval = 1000; + if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistance)) + return FleePosition(nearestPlayer->GetPosition(), safeDistance, minInterval); + + return false; +} + +bool IllidariCouncilCommandPetsToAttackGathiosAction::Execute(Event /*event*/) +{ + Unit* gathios = AI_VALUE2(Unit*, "find target", "gathios the shatterer"); + if (!gathios) + return false; + + Pet* pet = bot->GetPet(); + if (pet && pet->IsAlive() && pet->GetVictim() != gathios) + { + pet->ClearUnitState(UNIT_STATE_FOLLOW); + pet->AttackStop(); + pet->SetTarget(gathios->GetGUID()); + + if (pet->GetCharmInfo()) + { + pet->GetCharmInfo()->SetIsCommandAttack(true); + pet->GetCharmInfo()->SetIsAtStay(false); + pet->GetCharmInfo()->SetIsFollowing(false); + pet->GetCharmInfo()->SetIsCommandFollow(false); + pet->GetCharmInfo()->SetIsReturning(false); + + pet->AI()->AttackStart(gathios); + return true; + } + } + + return false; +} + +bool IllidariCouncilAssignDpsTargetsAction::Execute(Event /*event*/) +{ + Unit* malande = AI_VALUE2(Unit*, "find target", "lady malande"); + if (!malande) + return false; + + bool shouldAttackMalande = false; + Unit* zerevor = AI_VALUE2(Unit*, "find target", "high nethermancer zerevor"); + if (zerevor && zerevor->GetExactDist2d(malande) < 15.0f) + { + shouldAttackMalande = false; + } + else if (bot->getClass() == CLASS_ROGUE || + (bot->getClass() == CLASS_WARRIOR && botAI->IsDps(bot))) + { + shouldAttackMalande = !malande->HasAura( + static_cast(BlackTempleSpells::SPELL_BLESSING_OF_PROTECTION)); + } + else if (bot->getClass() == CLASS_SHAMAN && botAI->IsDps(bot)) + { + shouldAttackMalande = !malande->HasAura( + static_cast(BlackTempleSpells::SPELL_BLESSING_OF_SPELL_WARDING)); + } + + if (shouldAttackMalande) + { + SetRtiTarget(botAI, "star", malande); + + if (AI_VALUE(Unit*, "current target") != malande) + return Attack(malande); + } + else if (Unit* darkshadow = AI_VALUE2(Unit*, "find target", "veras darkshadow"); + darkshadow && !darkshadow->HasAura( + static_cast(BlackTempleSpells::SPELL_VANISH))) + { + SetRtiTarget(botAI, "circle", darkshadow); + + if (AI_VALUE(Unit*, "current target") != darkshadow) + return Attack(darkshadow); + } + else if (Unit* gathios = AI_VALUE2(Unit*, "find target", "gathios the shatterer")) + { + SetRtiTarget(botAI, "square", gathios); + + if (AI_VALUE(Unit*, "current target") != gathios) + return Attack(gathios); + } + + return false; +} + +bool IllidariCouncilManageDpsTimerAction::Execute(Event /*event*/) +{ + if (Unit* gathios = AI_VALUE2(Unit*, "find target", "gathios the shatterer")) + { + return councilDpsWaitTimer.try_emplace( + gathios->GetMap()->GetInstanceId(), std::time(nullptr)).second; + } + + return false; +} + +// Illidan Stormrage + +bool IllidanStormrageMisdirectToTankAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + int phase = GetIllidanPhase(illidan); + + if (phase == 2 && TryMisdirectToFlameTanks(group)) + return true; + + return phase == 4 && TryMisdirectToWarlockTank(illidan); +} + +bool IllidanStormrageMisdirectToTankAction::TryMisdirectToFlameTanks(Group* group) +{ + std::vector hunters; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && + GET_PLAYERBOT_AI(member)) + { + hunters.push_back(member); + } + + if (hunters.size() >= 2) + break; + } + + int8 hunterIndex = -1; + for (size_t i = 0; i < hunters.size(); ++i) + { + if (hunters[i] == bot) + { + hunterIndex = static_cast(i); + break; + } + } + if (hunterIndex == -1) + return false; + + auto [eastFlame, westFlame] = GetFlamesOfAzzinoth(bot); + if (!eastFlame || !westFlame || eastFlame == westFlame) + return false; + + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); + Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1); + if (!firstAssistTank || !secondAssistTank) + return false; + + if (hunters.size() == 1) + { + if (eastFlame->GetHealthPct() < 99.0f) + return false; + + if (botAI->CanCastSpell("misdirection", secondAssistTank)) + return botAI->CastSpell("misdirection", secondAssistTank); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", eastFlame)) + { + return botAI->CastSpell("steady shot", eastFlame); + } + + return false; + } + + Player* tankTarget = nullptr; + Unit* flame = nullptr; + + if (hunterIndex == 0) + { + tankTarget = secondAssistTank; + flame = eastFlame; + } + else if (hunterIndex == 1) + { + tankTarget = firstAssistTank; + flame = westFlame; + } + else + return false; + + if (!tankTarget || !tankTarget->IsAlive() || flame->GetHealthPct() < 90.0f) + return false; + + if (botAI->CanCastSpell("misdirection", tankTarget)) + return botAI->CastSpell("misdirection", tankTarget); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", flame)) + { + return botAI->CastSpell("steady shot", flame); + } + + return false; +} + +bool IllidanStormrageMisdirectToTankAction::TryMisdirectToWarlockTank(Unit* illidan) +{ + Player* warlockTank = GetIllidanWarlockTank(bot); + if (!warlockTank) + return false; + + if (botAI->CanCastSpell("misdirection", warlockTank)) + return botAI->CastSpell("misdirection", warlockTank); + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_MISDIRECTION)) && + botAI->CanCastSpell("steady shot", illidan)) + { + return botAI->CastSpell("steady shot", illidan); + } + + return false; +} + +bool IllidanStormrageMainTankRepositionBossAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + if (AI_VALUE(Unit*, "current target") != illidan) + return Attack(illidan); + + if (GetIllidanPhase(illidan) == 5) + { + GameObject* trap = FindNearestTrap(botAI, bot); + if (trap && bot->GetExactDist2d(trap) < 40.0f && illidan->GetVictim() == bot) + return MoveToShadowTrap(trap); + } + else + { + illidanShadowTrapGuid.erase(bot->GetGUID()); + illidanShadowTrapDestination.erase(bot->GetGUID()); + } + + if (illidan->GetVictim() != bot) + { + illidanShadowTrapGuid.erase(bot->GetGUID()); + illidanShadowTrapDestination.erase(bot->GetGUID()); + return false; + } + + auto const& flameCrashes = GetAllFlameCrashes(bot); + if (flameCrashes.empty()) + return false; + + constexpr float hazardRadius = 12.0f; + bool inDanger = false; + for (Unit* flameCrash : flameCrashes) + { + if (bot->GetDistance2d(flameCrash) < hazardRadius) + { + inDanger = true; + break; + } + } + + if (!inDanger) + return false; + + constexpr float maxRadius = 30.0f; + Position safestPos = FindSafestNearbyPosition(flameCrashes, maxRadius, hazardRadius); + + return MoveTo(BLACK_TEMPLE_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), + bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, true); +} + +bool IllidanStormrageMainTankRepositionBossAction::MoveToShadowTrap(GameObject* trap) +{ + if (!trap) + return false; + + ObjectGuid const botGuid = bot->GetGUID(); + ObjectGuid const trapGuid = trap->GetGUID(); + Position target; + + auto const cachedTrapIt = illidanShadowTrapGuid.find(botGuid); + auto const cachedDestinationIt = illidanShadowTrapDestination.find(botGuid); + if (cachedTrapIt != illidanShadowTrapGuid.end() && + cachedDestinationIt != illidanShadowTrapDestination.end() && + cachedTrapIt->second == trapGuid) + { + target = cachedDestinationIt->second; + } + else + { + const float trapX = trap->GetPositionX(); + const float trapY = trap->GetPositionY(); + + const float distToTrap = trap->GetExactDist2d(bot); + if (distToTrap <= 0.0f) + return false; + + constexpr float distBeyondTrap = 4.0f; + + const float dx = trapX - bot->GetPositionX(); + const float dy = trapY - bot->GetPositionY(); + const float targetX = trapX + (dx / distToTrap) * distBeyondTrap; + const float targetY = trapY + (dy / distToTrap) * distBeyondTrap; + + target = Position(targetX, targetY, trap->GetPositionZ()); + illidanShadowTrapGuid[botGuid] = trapGuid; + illidanShadowTrapDestination[botGuid] = target; + } + + const float targetDist = bot->GetExactDist2d(target); + + if (targetDist > 2.0f && bot->GetHealthPct() > 50.0f) + { + const float dX = target.GetPositionX() - bot->GetPositionX(); + const float dY = target.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(5.0f, targetDist); + const float moveX = bot->GetPositionX() + (dX / targetDist) * moveDist; + const float moveY = bot->GetPositionY() + (dY / targetDist) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, moveX, moveY, trap->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); + } + + return false; +} + +Position IllidanStormrageMainTankRepositionBossAction::FindSafestNearbyPosition( + const std::vector& flameCrashes, float maxRadius, float hazardRadius) +{ + constexpr float searchStep = M_PI / 16.0f; + constexpr float minDistance = 2.0f; + constexpr float distanceStep = 1.0f; + + float backwardsAngle = Position::NormalizeOrientation(bot->GetOrientation() + M_PI); + + Position bestPos; + float bestAngleDiff = M_PI * 2.0f; + float bestDistance = std::numeric_limits::max(); + bool foundSafe = false; + + for (float distance = minDistance; distance <= maxRadius; distance += distanceStep) + { + for (float angleOffset = 0.0f; angleOffset < 2 * M_PI; angleOffset += searchStep) + { + for (int sign = -1; sign <= 1; sign += 2) + { + const float testAngle = + Position::NormalizeOrientation(backwardsAngle + sign * angleOffset); + const float x = bot->GetPositionX() + distance * std::cos(testAngle); + const float y = bot->GetPositionY() + distance * std::sin(testAngle); + + Position testPos(x, y, bot->GetPositionZ()); + + bool isSafe = true; + for (Unit* flameCrash : flameCrashes) + { + if (flameCrash->GetDistance2d(x, y) < hazardRadius) + { + isSafe = false; + break; + } + } + if (!isSafe) + continue; + + bool pathSafe = IsPathSafeFromFlameCrashes(bot->GetPosition(), testPos, + flameCrashes, hazardRadius); + + float angleDiff = std::abs(Position::NormalizeOrientation( + testAngle - backwardsAngle)); + if (angleDiff > M_PI) + angleDiff = 2 * M_PI - angleDiff; + + if (pathSafe && (!foundSafe || angleDiff < bestAngleDiff || + (angleDiff == bestAngleDiff && distance < bestDistance))) + { + bestPos = testPos; + bestAngleDiff = angleDiff; + bestDistance = distance; + foundSafe = true; + } + else if (!foundSafe && angleDiff < bestAngleDiff) + { + bestPos = testPos; + bestAngleDiff = angleDiff; + bestDistance = distance; + } + } + if (foundSafe) + break; + } + if (foundSafe) + break; + } + + return bestPos; +} + +bool IllidanStormrageMainTankRepositionBossAction::IsPathSafeFromFlameCrashes( + const Position& start, const Position& end, const std::vector& flameCrashes, + float hazardRadius) +{ + constexpr uint8 numChecks = 10; + float dx = end.GetPositionX() - start.GetPositionX(); + float dy = end.GetPositionY() - start.GetPositionY(); + + for (uint8 i = 1; i <= numChecks; ++i) + { + float ratio = static_cast(i) / numChecks; + float checkX = start.GetPositionX() + dx * ratio; + float checkY = start.GetPositionY() + dy * ratio; + + for (Unit* flameCrash : flameCrashes) + { + float distToFlameCrash = flameCrash->GetDistance2d(checkX, checkY); + if (distToFlameCrash < hazardRadius) + return false; + } + } + + return true; +} + +bool IllidanStormrageIsolateBotWithParasiteAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + int phase = GetIllidanPhase(illidan); + + if (phase == 1) + { + constexpr float safeDistance = 15.0f; + if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistance)) + { + const float currentDistance = bot->GetExactDist2d(nearestPlayer); + if (currentDistance < safeDistance) + return MoveAway(nearestPlayer, safeDistance - currentDistance); + } + } + else + { + const float angle = illidan->GetOrientation() + M_PI; + constexpr float distBehindIllidan = 35.0f; + + const float targetX = illidan->GetPositionX() + std::cos(angle) * distBehindIllidan; + const float targetY = illidan->GetPositionY() + std::sin(angle) * distBehindIllidan; + const Position target(targetX, targetY, bot->GetPositionZ()); + + if (HasParasiticShadowfiend(bot)) + return InfectedBotMoveFromGroup(illidan, target); + + if (GetIllidanTrapperHunter(bot) == bot) + return FreezeTrapShadowfiend(bot, illidan, target); + } + + return false; +} + +bool IllidanStormrageIsolateBotWithParasiteAction::InfectedBotMoveFromGroup( + Unit* illidan, const Position& target) +{ + if (bot->GetExactDist2d(target) < 1.0f) + return false; + + return MoveTo(BLACK_TEMPLE_MAP_ID, target.GetPositionX(), target.GetPositionY(), + target.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); +} + +bool IllidanStormrageIsolateBotWithParasiteAction::FreezeTrapShadowfiend( + Player* bot, Unit* illidan, const Position& target) +{ + if (bot->HasSpellCooldown(static_cast(BlackTempleSpells::SPELL_FROST_TRAP))) + return false; + + Player* infected = GetBotWithParasiticShadowfiend(bot); + if (!infected) + return false; + + if (bot->GetExactDist2d(target) > 2.0f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, target.GetPositionX(), target.GetPositionY(), + target.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + else if (bot->GetExactDist2d(infected) < 2.0f && + botAI->CanCastSpell(static_cast(BlackTempleSpells::SPELL_FROST_TRAP), bot)) + { + return botAI->CastSpell(static_cast(BlackTempleSpells::SPELL_FROST_TRAP), bot); + } + + return false; +} + +bool IllidanStormrageSetEarthbindTotemAction::Execute(Event /*event*/) +{ + return botAI->CanCastSpell("earthbind totem", bot) && + botAI->CastSpell("earthbind totem", bot); +} + +bool IllidanStormrageAssistTanksHandleFlamesOfAzzinothAction::Execute(Event /*event*/) +{ + auto [eastFlame, westFlame] = GetFlamesOfAzzinoth(bot); + // The second assist tank's flame is killed first; this is so that if the tank + // for the second flame dies after the first flame is down, the dead flame's + // tank will become the first assist tank and take over the remaining flame + if (botAI->IsAssistTankOfIndex(bot, 1, true)) + { + if (eastFlame && westFlame) + { + if (AI_VALUE(Unit*, "current target") != eastFlame) + return Attack(eastFlame); + + if (eastFlame->GetVictim() != bot) + { + if (!bot->IsWithinMeleeRange(eastFlame)) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, eastFlame->GetPositionX(), + eastFlame->GetPositionY(), eastFlame->GetPositionZ(), + false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + return false; + } + } + else if (!eastFlame && !westFlame) + { + // (1) Before flames spawn, go to the waiting position + // (2) If both flames are dead and the waiting position is too close to hazards, + // move to a grate position + std::list demonFires; + constexpr float searchRadius = 40.0f; + bot->GetCreatureListWithEntryInGrid( + demonFires, static_cast(BlackTempleNpcs::NPC_DEMON_FIRE), searchRadius); + + const Position& pos = demonFires.empty() ? + ILLIDAN_E_GLAIVE_WAITING_POSITION : ILLIDAN_E_GRATE_POSITION; + + if (bot->GetExactDist2d(pos.GetPositionX(), pos.GetPositionY()) > 0.5f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, pos.GetPositionX(), pos.GetPositionY(), + pos.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + // After the first flame dies, its tank waits with other bots + else if (!eastFlame && westFlame) + { + const Position& pos = ILLIDAN_E_GRATE_POSITION; + if (bot->GetExactDist2d(pos.GetPositionX(), pos.GetPositionY()) > 0.5f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, pos.GetPositionX(), pos.GetPositionY(), + pos.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + } + else if (botAI->IsAssistTankOfIndex(bot, 0, true)) + { + if (westFlame) + { + if (AI_VALUE(Unit*, "current target") != westFlame) + return Attack(westFlame); + + if (westFlame->GetVictim() != bot) + { + if (!bot->IsWithinMeleeRange(westFlame)) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, westFlame->GetPositionX(), + westFlame->GetPositionY(), westFlame->GetPositionZ(), + false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + return false; + } + } + else + { + // (1) Before flames spawn, go to the waiting position + // (2) If both flames are dead and the waiting position is too close to hazards, + // move to a grate position + std::list demonFires; + constexpr float searchRadius = 40.0f; + bot->GetCreatureListWithEntryInGrid( + demonFires, static_cast(BlackTempleNpcs::NPC_DEMON_FIRE), searchRadius); + + const Position& pos = demonFires.empty() ? + ILLIDAN_W_GLAIVE_WAITING_POSITION : ILLIDAN_W_GRATE_POSITION; + + if (bot->GetExactDist2d(pos.GetPositionX(), pos.GetPositionY()) > 0.5f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, pos.GetPositionX(), pos.GetPositionY(), + pos.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + } + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + EyeBlastDangerArea dangerArea = GetEyeBlastDangerArea(bot); + + // Only consider the eye blast if its trigger NPC is within 30 yards of the tank + constexpr float eyeBlastTriggerRadius = 30.0f; + if (dangerArea.width > 0.0f && + bot->GetExactDist2d(dangerArea.start) <= eyeBlastTriggerRadius) + { + return RepositionToAvoidEyeBlast(illidan, dangerArea); + } + else + { + return RepositionToAvoidBlaze(eastFlame, westFlame); + } + + return false; +} + +bool IllidanStormrageAssistTanksHandleFlamesOfAzzinothAction::RepositionToAvoidEyeBlast( + Unit* illidan, const EyeBlastDangerArea& dangerArea) +{ + if (!IsPositionInEyeBlastDangerArea(bot->GetPosition(), dangerArea)) + return false; + + const float dx = dangerArea.end.GetPositionX() - dangerArea.start.GetPositionX(); + const float dy = dangerArea.end.GetPositionY() - dangerArea.start.GetPositionY(); + const float length = dangerArea.start.GetExactDist2d(dangerArea.end); + + const float px = bot->GetPositionX(); + const float py = bot->GetPositionY(); + const float sx = dangerArea.start.GetPositionX(); + const float sy = dangerArea.start.GetPositionY(); + + const float projection = std::clamp( + ((px - sx) * dx + (py - sy) * dy) / (length * length), 0.0f, 1.0f); + const float closestX = sx + projection * dx; + const float closestY = sy + projection * dy; + + const float distToLine = bot->GetExactDist2d(closestX, closestY); + const float moveDist = (dangerArea.width - distToLine) + 0.5f; + if (moveDist <= 0.0f) + return false; + + const float rawDirX = px - closestX; + const float rawDirY = py - closestY; + const float rawDirLength = std::sqrt(rawDirX * rawDirX + rawDirY * rawDirY); + const float dirX = rawDirLength == 0.0f ? -(dy / length) : rawDirX / rawDirLength; + const float dirY = rawDirLength == 0.0f ? dx / length : rawDirY / rawDirLength; + + const float safeX = px + dirX * moveDist; + const float safeY = py + dirY * moveDist; + const float safeZ = bot->GetPositionZ(); + const Position safePosition(safeX, safeY, safeZ); + + constexpr float minGrateDistance = 10.0f; + const bool tooCloseToNorthGrate = + safePosition.GetExactDist2d(ILLIDAN_N_GRATE_POSITION) < minGrateDistance; + const bool tooCloseToEastGrate = + safePosition.GetExactDist2d(ILLIDAN_E_GRATE_POSITION) < minGrateDistance; + const bool tooCloseToWestGrate = + safePosition.GetExactDist2d(ILLIDAN_W_GRATE_POSITION) < minGrateDistance; + + if (tooCloseToNorthGrate || tooCloseToEastGrate || tooCloseToWestGrate) + return false; + + return MoveTo(BLACK_TEMPLE_MAP_ID, safeX, safeY, safeZ, false, false, false, + false, MovementPriority::MOVEMENT_FORCED, true, false); +} + +bool IllidanStormrageAssistTanksHandleFlamesOfAzzinothAction::RepositionToAvoidBlaze( + Unit* eastFlame, Unit* westFlame) +{ + const std::array* waypoints = nullptr; + constexpr size_t numWaypoints = 7; + + if (botAI->IsAssistTankOfIndex(bot, 1, true)) + { + if (!eastFlame || eastFlame->GetVictim() != bot || + !bot->IsWithinMeleeRange(eastFlame)) + { + return false; + } + waypoints = &E_GLAIVE_TANK_POSITIONS; + } + else if (botAI->IsAssistTankOfIndex(bot, 0, true)) + { + if (!westFlame || westFlame->GetVictim() != bot || + !bot->IsWithinMeleeRange(westFlame)) + { + return false; + } + waypoints = &W_GLAIVE_TANK_POSITIONS; + } + + if (!waypoints) + return false; + + size_t& waypointIndex = flameTankWaypointIndex[bot->GetGUID()]; + const Position& target = (*waypoints)[waypointIndex]; + + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("possible triggers")->Get(); + + bool blazeNearby = false; + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->GetEntry() == static_cast(BlackTempleNpcs::NPC_BLAZE) && + bot->GetDistance2d(unit) <= 8.0f) + { + blazeNearby = true; + break; + } + } + + float distToPosition = + bot->GetExactDist2d(target.GetPositionX(), target.GetPositionY()); + if (blazeNearby && distToPosition <= 0.2f) + { + waypointIndex = (waypointIndex + 1) % numWaypoints; + const Position& newTarget = (*waypoints)[waypointIndex]; + const float distToNewPosition = + bot->GetExactDist2d(newTarget.GetPositionX(), newTarget.GetPositionY()); + + if (distToNewPosition > 0.2f) + { + const float dX = newTarget.GetPositionX() - bot->GetPositionX(); + const float dY = newTarget.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(5.0f, distToNewPosition); + const float moveX = bot->GetPositionX() + (dX / distToNewPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / distToNewPosition) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, newTarget.GetPositionX(), newTarget.GetPositionY(), + bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + else if (distToPosition > 0.2f) + { + const float dX = target.GetPositionX() - bot->GetPositionX(); + const float dY = target.GetPositionY() - bot->GetPositionY(); + const float moveDist = std::min(3.0f, distToPosition); + const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; + + return MoveTo(BLACK_TEMPLE_MAP_ID, target.GetPositionX(), target.GetPositionY(), + bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + + return false; +} + +// Pets grab aggro right away during Phase 2 and wipe the raid if not put on passive +// Just like players, pets cannot melee Illidan during Phase 4 +bool IllidanStormrageControlPetAggressionAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + Pet* pet = bot->GetPet(); + if (!pet) + return false; + + int phase = GetIllidanPhase(illidan); + + if ((phase == 2 || phase == 4) && + pet->GetReactState() != REACT_PASSIVE) + { + pet->AttackStop(); + pet->SetReactState(REACT_PASSIVE); + } + else if (pet->GetReactState() == REACT_PASSIVE) + { + pet->SetReactState(REACT_DEFENSIVE); + } + + return false; +} + +bool IllidanStormragePositionAboveGrateAction::Execute(Event /*event*/) +{ + const std::array& gratePositions = GRATE_POSITIONS; + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector bots; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && !botAI->IsAssistTankOfIndex(member, 0, true) && + !botAI->IsAssistTankOfIndex(member, 1, true)) + { + bots.push_back(member); + } + } + + if (bots.empty()) + return false; + + std::sort(bots.begin(), bots.end(), + [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); + + auto it = std::find(bots.begin(), bots.end(), bot); + if (it == bots.end()) + return false; + + const size_t botIndex = std::distance(bots.begin(), it); + const uint8 index = botIndex % gratePositions.size(); + + const Position& position = gratePositions[index]; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 0.2f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool IllidanStormrageRemoveDarkBarrageAction::Execute(Event /*event*/) +{ + switch (bot->getClass()) + { + case CLASS_MAGE: + return botAI->CanCastSpell("ice block", bot) && + botAI->CastSpell("ice block", bot); + + case CLASS_PALADIN: + return botAI->CanCastSpell("divine shield", bot) && + botAI->CastSpell("divine shield", bot); + + case CLASS_ROGUE: + return botAI->CanCastSpell("cloak of shadows", bot) && + botAI->CastSpell("cloak of shadows", bot); + + default: + return false; + } +} + +bool IllidanStormrageMoveAwayFromLandingPointAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + constexpr float safeDistance = 20.0f; + const float currentDistance = bot->GetExactDist2d(illidan); + if (currentDistance < safeDistance) + return MoveAway(illidan, safeDistance - currentDistance); + + return false; +} + +// NOTE: Illidan's bounding radius is 0.459f, and combatreach is 7.5f +bool IllidanStormrageDisperseRangedAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + int phase = GetIllidanPhase(illidan); + + if (phase == 4) + { + return SpreadInCircleInDemonPhase(illidan, group); + } + else if (GetBotWithParasiticShadowfiend(bot) == bot || + (GetIllidanTrapperHunter(bot) == bot && + GetBotWithParasiticShadowfiend(bot))) + { + return false; + } + else + { + return FanOutBehindInHumanPhase(illidan, group); + } +} + +bool IllidanStormrageDisperseRangedAction::FanOutBehindInHumanPhase( + Unit* illidan, Group* group) +{ + auto const& flameCrashes = GetAllFlameCrashes(bot); + + std::vector healers; + std::vector rangedDps; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !botAI->IsRanged(member)) + continue; + + if (botAI->IsHeal(member)) + healers.push_back(member); + else + rangedDps.push_back(member); + } + + constexpr float arcSpan = M_PI; + const float arcCenter = illidan->GetOrientation() + M_PI; + const float arcStart = arcCenter - arcSpan / 2.0f; + + const float radius = botAI->IsHeal(bot) ? 18.0f : 25.0f; + auto& bots = botAI->IsHeal(bot) ? healers : rangedDps; + const size_t count = bots.size(); + auto findIt = std::find(bots.begin(), bots.end(), bot); + const size_t botIndex = (findIt != bots.end()) ? + std::distance(bots.begin(), findIt) : 0; + + const float angle = (count == 1) ? arcCenter : + (arcStart + arcSpan * static_cast(botIndex) / + static_cast(count - 1)); + + const float targetX = illidan->GetPositionX() + radius * std::cos(angle); + const float targetY = illidan->GetPositionY() + radius * std::sin(angle); + + constexpr float hazardRadius = 12.0f; + bool safe = true; + for (Unit* flameCrash : flameCrashes) + { + if (flameCrash->GetDistance2d(targetX, targetY) < hazardRadius) + { + safe = false; + break; + } + } + + if (!safe) + return false; + + if (bot->GetExactDist2d(targetX, targetY) > 1.0f) + { + return MoveTo(BLACK_TEMPLE_MAP_ID, targetX, targetY, bot->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, + true, false); + } + + return false; +} + +bool IllidanStormrageDisperseRangedAction::SpreadInCircleInDemonPhase( + Unit* illidan, Group* group) +{ + Player* warlockTank = GetIllidanWarlockTank(bot); + if (!warlockTank) + { + constexpr float safeDistFromBoss = 24.0f; + if (bot->GetExactDist2d(illidan) < safeDistFromBoss) + { + constexpr uint32 minInterval = 0; + if (FleePosition(illidan->GetPosition(), safeDistFromBoss, minInterval)) + return true; + } + + constexpr float safeDistFromPlayer = 6.0f; + constexpr uint32 minInterval = 1000; + if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistFromPlayer)) + return FleePosition(nearestPlayer->GetPosition(), safeDistFromPlayer, minInterval); + + return false; + } + + std::vector rangedBots; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !botAI->IsRanged(member)) + continue; + + rangedBots.push_back(member); + } + + if (rangedBots.empty()) + return false; + + const size_t count = rangedBots.size(); + auto findIt = std::find(rangedBots.begin(), rangedBots.end(), bot); + const size_t botIndex = (findIt != rangedBots.end()) ? + std::distance(rangedBots.begin(), findIt) : 0; + + const float dx = warlockTank->GetPositionX() - illidan->GetPositionX(); + const float dy = warlockTank->GetPositionY() - illidan->GetPositionY(); + const float warlockAngle = std::atan2(dy, dx); + + constexpr float forbiddenArc = (2.0f / 3.0f) * M_PI; + constexpr float allowedArc = (4.0f / 3.0f) * M_PI; + + const float arcStart = Position::NormalizeOrientation(warlockAngle + forbiddenArc / 2.0f); + constexpr float radius = 25.0f; + + const float angle = (count == 1) ? + Position::NormalizeOrientation(arcStart + allowedArc / 2.0f) : + Position::NormalizeOrientation( + arcStart + allowedArc * static_cast(botIndex) / + static_cast(count - 1)); + + const float targetX = illidan->GetPositionX() + radius * std::cos(angle); + const float targetY = illidan->GetPositionY() + radius * std::sin(angle); + + if (bot->GetExactDist2d(targetX, targetY) > 1.0f) + { + if (MoveTo(BLACK_TEMPLE_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, false)) + { + return true; + } + else + { + constexpr float safeDistFromTank = 25.0f; + const float currentDistFromTank = bot->GetExactDist2d(warlockTank); + if (currentDistFromTank < safeDistFromTank) + return MoveAway(warlockTank, safeDistFromTank - currentDistFromTank); + } + } + + return false; +} + +// Melee cannot attack Demon Form Illidan +bool IllidanStormrageMeleeGoSomewhereToNotDieAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + constexpr float demonSearchRadius = 25.0f; + constexpr float shadowfiendSearchRadius = 15.0f; + + Unit* illidanVictim = illidan->GetVictim(); + // But they can attack Shadow Demons and Shadowfiends, if far enough from Illidan + Unit* shadowDemon = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_SHADOW_DEMON), demonSearchRadius, true); + + if (shadowDemon && shadowDemon->GetDistance2d(illidan) > 15.0f && + (!illidanVictim || shadowDemon->GetDistance2d(illidanVictim) > 24.0f)) + { + return false; + } + else + { + Unit* shadowfiend = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_PARASITIC_SHADOWFIEND), + shadowfiendSearchRadius, true); + + if (shadowfiend && shadowfiend->GetDistance2d(illidan) > 15.0f && + shadowfiend->GetHealthPct() < 30.0f && + (!illidanVictim || shadowfiend->GetDistance2d(illidanVictim) > 24.0f)) + { + return false; + } + } + + // 30y is closer than ideal but is a compromise to allow melee to reach targets in time + constexpr float safeDistFromBoss = 30.0f; + const float currentDistFromBoss = bot->GetExactDist2d(illidan); + if (currentDistFromBoss < safeDistFromBoss) + MoveAway(illidan, safeDistFromBoss - currentDistFromBoss); + + if (Player* warlockTank = GetIllidanWarlockTank(bot)) + { + constexpr float safeDistFromTank = 25.0f; + const float currentDistFromTank = bot->GetExactDist2d(warlockTank); + if (currentDistFromTank < safeDistFromTank) + MoveAway(warlockTank, safeDistFromTank - currentDistFromTank); + } + + constexpr float safeDistFromPlayer = 6.0f; + if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistFromPlayer)) + MoveAway(nearestPlayer, safeDistFromPlayer - bot->GetDistance2d(nearestPlayer)); + + return true; +} + +bool IllidanStormrageWarlockTankHandleDemonBossAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + constexpr float safeDistance = 24.0f; + const float currentDistance = bot->GetExactDist2d(illidan); + if (currentDistance < safeDistance && + MoveAway(illidan, safeDistance - currentDistance)) + { + return true; + } + + if (botAI->CanCastSpell("shadow ward", bot) && + botAI->CastSpell("shadow ward", bot)) + { + return true; + } + + if (botAI->CanCastSpell("searing pain", illidan)) + return botAI->CastSpell("searing pain", illidan); + + return false; +} + +bool IllidanStormrageDpsPrioritizeAddsAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + int phase = GetIllidanPhase(illidan); + + std::vector targets; + + if (phase == 4) + { + constexpr float searchRadius = 35.0f; + + Unit* shadowDemon = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_SHADOW_DEMON), searchRadius, true); + + if (GetIllidanWarlockTank(bot) == bot) + { + targets = { shadowDemon, illidan }; + } + else + { + Unit* shadowfiend = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_PARASITIC_SHADOWFIEND), + searchRadius, true); + + if (botAI->IsRanged(bot)) + { + if (shadowDemon) + targets = { shadowDemon }; + else if (shadowfiend && bot->GetDistance2d(shadowfiend) > 10.0f) + targets = { shadowfiend }; + else + targets = { illidan }; + } + else if (botAI->IsMelee(bot)) + { + targets = { shadowDemon, shadowfiend }; + } + } + } + else if (botAI->IsRanged(bot)) + { + if (phase == 1 || phase == 3 || phase == 5) + { + constexpr float searchRadius = 35.0f; + Unit* shadowfiend = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_PARASITIC_SHADOWFIEND), + searchRadius, true); + + if (shadowfiend && bot->GetDistance2d(shadowfiend) > 10.0f) + targets = { shadowfiend }; + else + targets = { illidan }; + } + else if (phase == 2) + { + constexpr float searchRadius = 20.0f; + Unit* shadowfiend = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_PARASITIC_SHADOWFIEND), + searchRadius, true); + + if (shadowfiend && bot->GetDistance2d(shadowfiend) > 5.0f) + { + targets = { shadowfiend }; + } + else + { + auto [eastFlame, westFlame] = GetFlamesOfAzzinoth(bot); + targets = { eastFlame, westFlame }; + } + } + } + + for (Unit* candidate : targets) + { + if (candidate && candidate->IsAlive()) + { + if (AI_VALUE(Unit*, "current target") != candidate) + return Attack(candidate); + + return false; + } + } + + return false; +} + +bool IllidanStormrageUseShadowTrapAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + GameObject* trap = FindNearestTrap(botAI, bot); + if (!trap || illidan->GetExactDist2d(trap) >= 4.0f) + return false; + + if (bot->GetExactDist2d(trap) < 3.0f) + { + trap->Use(bot); + return true; + } + else + { + return MoveTo(BLACK_TEMPLE_MAP_ID, trap->GetPositionX(), trap->GetPositionY(), + trap->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool IllidanStormrageManageDpsTimerAndRtiAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + const time_t now = std::time(nullptr); + const uint32 instanceId = illidan->GetMap()->GetInstanceId(); + + bool updated = false; + const int phase = GetIllidanPhase(illidan); + int lastPhase = -1; + if (auto const it = illidanLastPhase.find(instanceId); it != illidanLastPhase.end()) + lastPhase = it->second; + const bool phaseChanged = lastPhase != phase; + illidanLastPhase[instanceId] = phase; + + if (phaseChanged) + { + if (phase == 1 || phase == 3 || phase == 4 || phase == 5) + { + illidanBossDpsWaitTimer[instanceId] = now; + updated = true; + } + else if (phase == 2) + { + if (illidanBossDpsWaitTimer.erase(instanceId) > 0) + updated = true; + } + + if (phase != 2 && illidanFlameDpsWaitTimer.erase(instanceId) > 0) + updated = true; + } + + if (phase == 2) + { + if (eastFlameGuid.find(instanceId) == eastFlameGuid.end() && + westFlameGuid.find(instanceId) == westFlameGuid.end()) + { + std::list creatureList; + constexpr float searchRadius = 50.0f; + illidan->GetCreatureListWithEntryInGrid(creatureList, static_cast( + BlackTempleNpcs::NPC_FLAME_OF_AZZINOTH), searchRadius); + + std::vector flames; + for (Creature* creature : creatureList) + { + if (creature && creature->IsAlive()) + flames.push_back(creature); + } + + if (flames.size() == 2) + { + const float eastDist0 = + flames[0]->GetExactDist2d(ILLIDAN_E_GLAIVE_WAITING_POSITION); + const float eastDist1 = + flames[1]->GetExactDist2d(ILLIDAN_E_GLAIVE_WAITING_POSITION); + + if (eastDist0 < eastDist1) + { + eastFlameGuid[instanceId] = flames[0]->GetGUID(); + westFlameGuid[instanceId] = flames[1]->GetGUID(); + } + else + { + eastFlameGuid[instanceId] = flames[1]->GetGUID(); + westFlameGuid[instanceId] = flames[0]->GetGUID(); + } + + illidanFlameDpsWaitTimer[instanceId] = now; + + updated = true; + } + } + } + else + { + if (eastFlameGuid.erase(instanceId) > 0) + updated = true; + if (westFlameGuid.erase(instanceId) > 0) + updated = true; + } + + return updated; +} + +bool IllidanStormrageDestroyHazardsAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + int phase = GetIllidanPhase(illidan); + constexpr float searchRadius = 50.0f; + std::list hazards; + std::vector entries; + + if (phase == 2) + { + entries = { + static_cast(BlackTempleNpcs::NPC_FLAME_CRASH) + }; + } + else if (phase == 4) + { + entries = { + static_cast(BlackTempleNpcs::NPC_FLAME_CRASH) + }; + } + else if (phase == 0) + { + entries = { + static_cast(BlackTempleNpcs::NPC_DEMON_FIRE), + static_cast(BlackTempleNpcs::NPC_BLAZE) + }; + } + + if (!entries.empty()) + bot->GetCreatureListWithEntryInGrid(hazards, entries, searchRadius); + + for (Creature* creature : hazards) + { + if (creature && creature->IsAlive()) + { + creature->Kill(bot, creature); + return true; + } + } + + return false; +} + +// Reduce Shadow Demon to 25% health and kill residual Shadowfiends in Phase 2 +bool IllidanStormrageHandleAddsCheatAction::Execute(Event /*event*/) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + if (GetIllidanPhase(illidan) == 2) + { + constexpr float searchRadius = 20.0f; + if (Unit* shadowfiend = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_PARASITIC_SHADOWFIEND), + searchRadius, true)) + { + shadowfiend->Kill(bot, shadowfiend); + return true; + } + } + else + { + constexpr float searchRadius = 75.0f; + Unit* shadowDemon = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_SHADOW_DEMON), searchRadius, true); + + if (shadowDemon && shadowDemon->GetHealthPct() > 25.0f) + { + uint32 desiredDamage = 0; + const uint32 quarterHealth = shadowDemon->GetMaxHealth() / 4; + if (shadowDemon->GetHealth() > quarterHealth) + desiredDamage = shadowDemon->GetHealth() - quarterHealth; + + Unit::DealDamage(bot, shadowDemon, desiredDamage, nullptr, DIRECT_DAMAGE, + SPELL_SCHOOL_MASK_NORMAL, nullptr, false, false, nullptr); + return true; + } + } + + return false; +} diff --git a/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.h b/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.h new file mode 100644 index 00000000000..d4bc41d2381 --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.h @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDBLACKTEMPLEACTIONS_H +#define _PLAYERBOT_RAIDBLACKTEMPLEACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "MovementActions.h" +#include "RaidBlackTempleHelpers.h" + +namespace BlackTempleHelpers +{ + struct EyeBlastDangerArea; +} + +// General + +class BlackTempleEraseTimersAndTrackersAction : public Action +{ +public: + BlackTempleEraseTimersAndTrackersAction( + PlayerbotAI* botAI) : Action(botAI, "black temple erase timers and trackers") {} + bool Execute(Event event) override; +}; + +// High Warlord Naj'entus + +class HighWarlordNajentusMisdirectBossToMainTankAction : public AttackAction +{ +public: + HighWarlordNajentusMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "high warlord naj'entus misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class HighWarlordNajentusTanksPositionBossAction : public AttackAction +{ +public: + HighWarlordNajentusTanksPositionBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "high warlord naj'entus tanks position boss") {} + bool Execute(Event event) override; +}; + +class HighWarlordNajentusDisperseRangedAction : public MovementAction +{ +public: + HighWarlordNajentusDisperseRangedAction( + PlayerbotAI* botAI) : MovementAction(botAI, "naj'entus disperse ranged") {} + bool Execute(Event event) override; +}; + +class HighWarlordNajentusRemoveImpalingSpineAction : public MovementAction +{ +public: + HighWarlordNajentusRemoveImpalingSpineAction( + PlayerbotAI* botAI) : MovementAction(botAI, "high warlord naj'entus remove impaling spine") {} + bool Execute(Event event) override; +}; + +class HighWarlordNajentusThrowImpalingSpineAction : public MovementAction +{ +public: + HighWarlordNajentusThrowImpalingSpineAction( + PlayerbotAI* botAI) : MovementAction(botAI, "high warlord naj'entus throw impaling spine") {} + bool Execute(Event event) override; +}; + +// Supremus + +class SupremusMisdirectBossToMainTankAction : public AttackAction +{ +public: + SupremusMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "supremus misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class SupremusDisperseRangedAction : public MovementAction +{ +public: + SupremusDisperseRangedAction( + PlayerbotAI* botAI) : MovementAction(botAI, "supremus disperse ranged") {} + bool Execute(Event event) override; +}; + +class SupremusKiteBossAction : public MovementAction +{ +public: + SupremusKiteBossAction( + PlayerbotAI* botAI) : MovementAction(botAI, "supremus kite boss") {} + bool Execute(Event event) override; +}; + +class SupremusMoveAwayFromVolcanosAction : public MovementAction +{ +public: + SupremusMoveAwayFromVolcanosAction( + PlayerbotAI* botAI) : MovementAction(botAI, "supremus move away from volcanos") {} + bool Execute(Event event) override; + +private: + Position FindSafestNearbyPosition( + const std::vector& volcanos, float maxRadius, float hazardRadius); + bool IsPathSafeFromVolcanos(const Position& start, + const Position& end, const std::vector& volcanos, float hazardRadius); + std::vector GetAllSupremusVolcanos(); +}; + +class SupremusManagePhaseTimerAction : public Action +{ +public: + SupremusManagePhaseTimerAction( + PlayerbotAI* botAI) : Action(botAI, "supremus manage phase timer") {} + bool Execute(Event event) override; +}; + +// Shade of Akama + +class ShadeOfAkamaMeleeDpsPrioritizeChannelersAction : public AttackAction +{ +public: + ShadeOfAkamaMeleeDpsPrioritizeChannelersAction( + PlayerbotAI* botAI) : AttackAction(botAI, "shade of akama melee dps prioritize channelers") {} + bool Execute(Event event) override; +}; + +// Teron Gorefiend + +class TeronGorefiendMisdirectBossToMainTankAction : public AttackAction +{ +public: + TeronGorefiendMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "teron gorefiend misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class TeronGorefiendTanksPositionBossAction : public AttackAction +{ +public: + TeronGorefiendTanksPositionBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "teron gorefiend tanks position boss") {} + bool Execute(Event event) override; +}; + +class TeronGorefiendPositionRangedOnBalconyAction : public MovementAction +{ +public: + TeronGorefiendPositionRangedOnBalconyAction( + PlayerbotAI* botAI) : MovementAction(botAI, "teron gorefiend position ranged on balcony") {} + bool Execute(Event event) override; +}; + +class TeronGorefiendAvoidShadowOfDeathAction : public Action +{ +public: + TeronGorefiendAvoidShadowOfDeathAction( + PlayerbotAI* botAI) : Action(botAI, "teron gorefiend avoid shadow of death") {} + bool Execute(Event event) override; +}; + +class TeronGorefiendMoveToCornerToDieAction : public MovementAction +{ +public: + TeronGorefiendMoveToCornerToDieAction( + PlayerbotAI* botAI) : MovementAction(botAI, "teron gorefiend move to corner to die") {} + bool Execute(Event event) override; +}; + +class TeronGorefiendControlAndDestroyShadowyConstructsAction : public MovementAction +{ +public: + TeronGorefiendControlAndDestroyShadowyConstructsAction( + PlayerbotAI* botAI) : MovementAction(botAI, "teron gorefiend control and destroy shadowy constructs") {} + bool Execute(Event event) override; +}; + +// Gurtogg Bloodboil + +class GurtoggBloodboilMisdirectBossToMainTankAction : public AttackAction +{ +public: + GurtoggBloodboilMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "gurtogg bloodboil misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class GurtoggBloodboilTanksPositionBossAction : public AttackAction +{ +public: + GurtoggBloodboilTanksPositionBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "gurtogg bloodboil tanks position boss") {} + bool Execute(Event event) override; +}; + +class GurtoggBloodboilRotateRangedGroupsAction : public MovementAction +{ +public: + GurtoggBloodboilRotateRangedGroupsAction( + PlayerbotAI* botAI) : MovementAction(botAI, "gurtogg bloodboil rotate ranged groups") {} + bool Execute(Event event) override; +}; + +class GurtoggBloodboilRangedMoveAwayFromEnragedPlayerAction : public MovementAction +{ +public: + GurtoggBloodboilRangedMoveAwayFromEnragedPlayerAction( + PlayerbotAI* botAI) : MovementAction(botAI, "gurtogg bloodboil ranged move away from enraged player") {} + bool Execute(Event event) override; +}; + +class GurtoggBloodboilManagePhaseTimerAction : public Action +{ +public: + GurtoggBloodboilManagePhaseTimerAction( + PlayerbotAI* botAI) : Action(botAI, "gurtogg bloodboil manage phase timer") {} + bool Execute(Event event) override; +}; + +// Reliquary of Souls + +class ReliquaryOfSoulsMisdirectBossToMainTankAction : public AttackAction +{ +public: + ReliquaryOfSoulsMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "reliquary of souls misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class ReliquaryOfSoulsAdjustDistanceFromSufferingAction : public MovementAction +{ +public: + ReliquaryOfSoulsAdjustDistanceFromSufferingAction( + PlayerbotAI* botAI) : MovementAction(botAI, "reliquary of souls adjust distance from suffering") {} + bool Execute(Event event) override; + +private: + bool TanksMoveToMinimumRange(Unit* suffering); + bool MeleeDpsStayAtMaximumRange(Unit* suffering); + bool RangedMoveAwayFromBoss(Unit* suffering); +}; + +class ReliquaryOfSoulsHealersDpsSufferingAction : public Action +{ +public: + ReliquaryOfSoulsHealersDpsSufferingAction( + PlayerbotAI* botAI) : Action(botAI, "reliquary of souls healers dps suffering") {} + bool Execute(Event event) override; +}; + +class ReliquaryOfSoulsSpellstealRuneShieldAction : public Action +{ +public: + ReliquaryOfSoulsSpellstealRuneShieldAction( + PlayerbotAI* botAI) : Action(botAI, "reliquary of souls spellsteal rune shield") {} + bool Execute(Event event) override; +}; + +class ReliquaryOfSoulsSpellReflectDeadenAction : public Action +{ +public: + ReliquaryOfSoulsSpellReflectDeadenAction( + PlayerbotAI* botAI) : Action(botAI, "reliquary of souls spell reflect deaden") {} + bool Execute(Event event) override; +}; + +// Mother Shahraz + +class MotherShahrazMisdirectBossToMainTankAction : public AttackAction +{ +public: + MotherShahrazMisdirectBossToMainTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "mother shahraz misdirect boss to main tank") {} + bool Execute(Event event) override; +}; + +class MotherShahrazTanksPositionBossUnderPillarAction : public AttackAction +{ +public: + MotherShahrazTanksPositionBossUnderPillarAction( + PlayerbotAI* botAI) : AttackAction(botAI, "mother shahraz tanks position boss under pillar") {} + bool Execute(Event event) override; +}; + +class MotherShahrazMeleeDpsWaitAtSafePositionAction : public MovementAction +{ +public: + MotherShahrazMeleeDpsWaitAtSafePositionAction( + PlayerbotAI* botAI) : MovementAction(botAI, "mother shahraz melee dps wait at safe position") {} + bool Execute(Event event) override; +}; + +class MotherShahrazPositionRangedUnderPillarAction : public MovementAction +{ +public: + MotherShahrazPositionRangedUnderPillarAction( + PlayerbotAI* botAI) : MovementAction(botAI, "mother shahraz position ranged under pillar") {} + bool Execute(Event event) override; +}; + +class MotherShahrazRunAwayToBreakFatalAttractionAction : public MovementAction +{ +public: + MotherShahrazRunAwayToBreakFatalAttractionAction( + PlayerbotAI* botAI) : MovementAction(botAI, "mother shahraz run away to break fatal attraction") {} + bool Execute(Event event) override; + +private: + std::vector GetAttractedPlayers(); +}; + +// Illidari Council + +class IllidariCouncilMisdirectBossesToTanksAction : public AttackAction +{ +public: + IllidariCouncilMisdirectBossesToTanksAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidari council misdirect bosses to tanks") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilMainTankPositionGathiosAction : public AttackAction +{ +public: + IllidariCouncilMainTankPositionGathiosAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidari council main tank position gathios") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilMainTankReflectJudgementOfCommandAction : public Action +{ +public: + IllidariCouncilMainTankReflectJudgementOfCommandAction( + PlayerbotAI* botAI) : Action(botAI, "illidari council main tank reflect judgement of command") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilFirstAssistTankFocusMalandeAction : public AttackAction +{ +public: + IllidariCouncilFirstAssistTankFocusMalandeAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidari council first assist tank focus malande") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilSecondAssistTankPositionDarkshadowAction : public AttackAction +{ +public: + IllidariCouncilSecondAssistTankPositionDarkshadowAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidari council second assist tank position darkshadow") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilMageTankPositionZerevorAction : public AttackAction +{ +public: + IllidariCouncilMageTankPositionZerevorAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidari council mage tank position zerevor") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilPositionMageTankHealerAction : public AttackAction +{ +public: + IllidariCouncilPositionMageTankHealerAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidari council position mage tank healer") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilAssignDpsTargetsAction : public AttackAction +{ +public: + IllidariCouncilAssignDpsTargetsAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidari council assign dps targets") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilDisperseRangedAction : public MovementAction +{ +public: + IllidariCouncilDisperseRangedAction( + PlayerbotAI* botAI) : MovementAction(botAI, "illidari council disperse ranged") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilCommandPetsToAttackGathiosAction : public AttackAction +{ +public: + IllidariCouncilCommandPetsToAttackGathiosAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidari council command pets to attack gathios") {} + bool Execute(Event event) override; +}; + +class IllidariCouncilManageDpsTimerAction : public Action +{ +public: + IllidariCouncilManageDpsTimerAction( + PlayerbotAI* botAI) : Action(botAI, "illidari council manage dps timer") {} + bool Execute(Event event) override; +}; + +// Illidan Stormrage + +class IllidanStormrageMisdirectToTankAction : public AttackAction +{ +public: + IllidanStormrageMisdirectToTankAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidan stormrage misdirect to tank") {} + bool Execute(Event event) override; + +private: + bool TryMisdirectToFlameTanks(Group* group); + bool TryMisdirectToWarlockTank(Unit* illidan); +}; + +class IllidanStormrageMainTankRepositionBossAction : public AttackAction +{ +public: + IllidanStormrageMainTankRepositionBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidan stormrage main tank reposition boss") {} + bool Execute(Event event) override; + +private: + bool MoveToShadowTrap(GameObject* trap); + Position FindSafestNearbyPosition( + const std::vector& flameCrashes, float maxRadius, float hazardRadius); + bool IsPathSafeFromFlameCrashes(const Position& start, + const Position& end, const std::vector& flameCrashes, float hazardRadius); +}; + +class IllidanStormrageIsolateBotWithParasiteAction : public MovementAction +{ +public: + IllidanStormrageIsolateBotWithParasiteAction( + PlayerbotAI* botAI) : MovementAction(botAI, "illidan stormrage isolate bot with parasite") {} + bool Execute(Event event) override; + +private: + bool InfectedBotMoveFromGroup(Unit* illidan, const Position& targetPos); + bool FreezeTrapShadowfiend(Player* bot, Unit* illidan, const Position& targetPos); +}; + +class IllidanStormrageSetEarthbindTotemAction : public Action +{ +public: + IllidanStormrageSetEarthbindTotemAction( + PlayerbotAI* botAI) : Action(botAI, "illidan stormrage set earthbind totem") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageAssistTanksHandleFlamesOfAzzinothAction : public AttackAction +{ +public: + IllidanStormrageAssistTanksHandleFlamesOfAzzinothAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidan stormrage assist tanks handle flames of azzinoth") {} + bool Execute(Event event) override; + +private: + bool RepositionToAvoidEyeBlast(Unit* illidan, const BlackTempleHelpers::EyeBlastDangerArea& dangerArea); + bool RepositionToAvoidBlaze(Unit* eastFlame, Unit* westFlame); +}; + +class IllidanStormrageControlPetAggressionAction : public Action +{ +public: + IllidanStormrageControlPetAggressionAction( + PlayerbotAI* botAI) : Action(botAI, "illidan stormrage control pet aggression") {} + bool Execute(Event event) override; +}; + +class IllidanStormragePositionAboveGrateAction : public MovementAction +{ +public: + IllidanStormragePositionAboveGrateAction( + PlayerbotAI* botAI) : MovementAction(botAI, "illidan stormrage position above grate") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageRemoveDarkBarrageAction : public Action +{ +public: + IllidanStormrageRemoveDarkBarrageAction( + PlayerbotAI* botAI) : Action(botAI, "illidan stormrage remove dark barrage") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageMoveAwayFromLandingPointAction : public MovementAction +{ +public: + IllidanStormrageMoveAwayFromLandingPointAction( + PlayerbotAI* botAI) : MovementAction(botAI, "illidan stormrage move away from landing point") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageDisperseRangedAction : public MovementAction +{ +public: + IllidanStormrageDisperseRangedAction( + PlayerbotAI* botAI) : MovementAction(botAI, "illidan stormrage disperse ranged") {} + bool Execute(Event event) override; + +private: + bool FanOutBehindInHumanPhase(Unit* illidan, Group* group); + bool SpreadInCircleInDemonPhase(Unit* illidan, Group* group); +}; + +class IllidanStormrageMeleeGoSomewhereToNotDieAction : public MovementAction +{ +public: + IllidanStormrageMeleeGoSomewhereToNotDieAction( + PlayerbotAI* botAI) : MovementAction(botAI, "illidan stormrage melee go somewhere to not die") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageWarlockTankHandleDemonBossAction : public AttackAction +{ +public: + IllidanStormrageWarlockTankHandleDemonBossAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidan stormrage warlock tank handle demon boss") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageDpsPrioritizeAddsAction : public AttackAction +{ +public: + IllidanStormrageDpsPrioritizeAddsAction( + PlayerbotAI* botAI) : AttackAction(botAI, "illidan stormrage dps prioritize adds") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageUseShadowTrapAction : public MovementAction +{ +public: + IllidanStormrageUseShadowTrapAction( + PlayerbotAI* botAI) : MovementAction(botAI, "illidan stormrage use shadow trap") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageManageDpsTimerAndRtiAction : public Action +{ +public: + IllidanStormrageManageDpsTimerAndRtiAction( + PlayerbotAI* botAI) : Action(botAI, "illidan stormrage manage dps timer and rti") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageDestroyHazardsAction : public Action +{ +public: + IllidanStormrageDestroyHazardsAction( + PlayerbotAI* botAI) : Action(botAI, "illidan stormrage destroy hazards") {} + bool Execute(Event event) override; +}; + +class IllidanStormrageHandleAddsCheatAction : public Action +{ +public: + IllidanStormrageHandleAddsCheatAction( + PlayerbotAI* botAI) : Action(botAI, "illidan stormrage handle adds cheat") {} + bool Execute(Event event) override; +}; + +#endif diff --git a/src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.cpp b/src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.cpp new file mode 100644 index 00000000000..94e5143a6ed --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.cpp @@ -0,0 +1,785 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidBlackTempleMultipliers.h" + +#include "ChooseTargetActions.h" +#include "DKActions.h" +#include "DruidActions.h" +#include "DruidBearActions.h" +#include "DruidShapeshiftActions.h" +#include "FollowActions.h" +#include "HunterActions.h" +#include "MageActions.h" +#include "PaladinActions.h" +#include "PriestActions.h" +#include "RaidBlackTempleActions.h" +#include "RaidBlackTempleHelpers.h" +#include "ReachTargetActions.h" +#include "RogueActions.h" +#include "ShamanActions.h" +#include "WarlockActions.h" +#include "WarriorActions.h" +#include "WipeAction.h" + +using namespace BlackTempleHelpers; + +static bool IsDpsCooldownAction(Action* action) +{ + return dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action); +} + +// High Warlord Naj'entus + +float HighWarlordNajentusDelayDpsCooldownsMultiplier::GetValue(Action* action) +{ + Unit* najentus = AI_VALUE2(Unit*, "find target", "high warlord naj'entus"); + if (!najentus || najentus->GetHealthPct() < 95.0f) + return 1.0f; + + if (IsDpsCooldownAction(action) || + (botAI->IsDps(bot) && dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +float HighWarlordNajentusDisableCombatFormationMoveMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "high warlord naj'entus")) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +// Supremus + +float SupremusDelayDpsCooldownsMultiplier::GetValue(Action* action) +{ + Unit* supremus = AI_VALUE2(Unit*, "find target", "supremus"); + if (!supremus || supremus->GetHealthPct() < 95.0f) + return 1.0f; + + if (IsDpsCooldownAction(action) || + (botAI->IsDps(bot) && dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +float SupremusFocusOnAvoidanceInPhase2Multiplier::GetValue(Action* action) +{ + Unit* supremus = AI_VALUE2(Unit*, "find target", "supremus"); + if (!supremus || supremus->GetVictim() != bot || + !supremus->HasAura(static_cast(BlackTempleSpells::SPELL_SNARE_SELF))) + { + return 1.0f; + } + + if (dynamic_cast(action) && + !dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +float SupremusHitboxIsBuggedMultiplier::GetValue(Action* action) +{ + if (bot->getClass() != CLASS_ROGUE || + !AI_VALUE2(Unit*, "find target", "supremus")) + { + return 1.0f; + } + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// Teron Gorefiend + +float TeronGorefiendDelayDpsCooldownsMultiplier::GetValue(Action* action) +{ + Unit* gorefiend = AI_VALUE2(Unit*, "find target", "teron gorefiend"); + if (!gorefiend || gorefiend->GetHealthPct() < 95.0f) + return 1.0f; + + if (IsDpsCooldownAction(action) || + (botAI->IsDps(bot) && dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +float TeronGorefiendControlMovementMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "teron gorefiend")) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + + if (botAI->IsRanged(bot) && dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float TeronGorefiendMarkedBotOnlyMoveToDieMultiplier::GetValue(Action* action) +{ + Aura* aura = bot->GetAura( + static_cast(BlackTempleSpells::SPELL_SHADOW_OF_DEATH)); + if (!aura || aura->GetDuration() >= 15000) + return 1.0f; + + if (dynamic_cast(action)) + return 1.0f; + else if (!dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float TeronGorefiendSpiritsAttackOnlyShadowyConstructsMultiplier::GetValue(Action* action) +{ + if (!bot->HasAura(static_cast(BlackTempleSpells::SPELL_SPIRITUAL_VENGEANCE)) || + dynamic_cast(action)) + { + return 1.0f; + } + + if (!dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float TeronGorefiendDisableAttackingConstructsMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "teron gorefiend")) + return 1.0f; + + if (bot->GetVictim() != nullptr && + dynamic_cast(action)) + { + return 0.0f; + } + + if (!botAI->IsRangedDps(bot)) + return 1.0f; + + auto castSpellAction = dynamic_cast(action); + if (castSpellAction && + castSpellAction->getThreatType() == Action::ActionThreatType::Aoe) + { + return 0.0f; + } + + return 1.0f; +} + +// Gurtogg Bloodboil + +float GurtoggBloodboilDelayDpsCooldownsMultiplier::GetValue(Action* action) +{ + Unit* gurtogg = AI_VALUE2(Unit*, "find target", "gurtogg bloodboil"); + if (!gurtogg || gurtogg->GetHealthPct() < 95.0f) + return 1.0f; + + if (IsDpsCooldownAction(action) || + (botAI->IsDps(bot) && dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +float GurtoggBloodboilControlMovementMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "gurtogg bloodboil")) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + + if (bot->HasAura(static_cast(BlackTempleSpells::SPELL_PLAYER_FEL_RAGE)) && + (dynamic_cast(action) && + !dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +// Reliquary of Souls + +float ReliquaryOfSoulsDelayDpsCooldownsMultiplier::GetValue(Action* action) +{ + Unit* suffering = AI_VALUE2(Unit*, "find target", "essence of suffering"); + if (!suffering || suffering->GetHealthPct() < 95.0f) + return 1.0f; + + if (IsDpsCooldownAction(action) || + (botAI->IsDps(bot) && dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +float ReliquaryOfSoulsDontWasteHealingMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "essence of suffering")) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + { + return 1.0f; + } + + if (dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +// Mother Shahraz + +float MotherShahrazDelayDpsCooldownsMultiplier::GetValue(Action* action) +{ + Unit* shahraz = AI_VALUE2(Unit*, "find target", "mother shahraz"); + if (!shahraz || shahraz->GetHealthPct() < 90.0f) + return 1.0f; + + if (IsDpsCooldownAction(action) || + (botAI->IsDps(bot) && dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +float MotherShahrazControlMovementMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "mother shahraz")) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +float MotherShahrazBotsWithFatalAttractionOnlyRunAwayMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "mother shahraz") || + !bot->HasAura(static_cast(BlackTempleSpells::SPELL_FATAL_ATTRACTION))) + { + return 1.0f; + } + + if (dynamic_cast(action)) + return 1.0f; + + if (!dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// Illidari Council + +float IllidariCouncilDelayDpsCooldownsMultiplier::GetValue(Action* action) +{ + Unit* gathios = AI_VALUE2(Unit*, "find target", "gathios the shatterer"); + if (!gathios || gathios->GetHealthPct() < 90.0f) + return 1.0f; + + if (IsDpsCooldownAction(action) || + (botAI->IsDps(bot) && dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +float IllidariCouncilDisableTankActionsMultiplier::GetValue(Action* action) +{ + if (!botAI->IsTank(bot) || + !AI_VALUE2(Unit*, "find target", "gathios the shatterer")) + { + return 1.0f; + } + + if (bot->GetVictim() != nullptr && dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +float IllidariCouncilControlMovementMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "high nethermancer zerevor")) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + + if (botAI->IsAssistHealOfIndex(bot, 0, true) && + (dynamic_cast(action) && + !dynamic_cast(action))) + { + return 0.0f; + } + + if (!botAI->IsAssistTankOfIndex(bot, 0, false) && + dynamic_cast(action)) + { + return 0.0f; + } + + if ((botAI->IsMainTank(bot) || + botAI->IsAssistTankOfIndex(bot, 0, false) || + botAI->IsAssistTankOfIndex(bot, 1, false) || + GetZerevorMageTank(bot) == bot) && + dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +float IllidariCouncilControlMisdirectionMultiplier::GetValue(Action* action) +{ + if (bot->getClass() != CLASS_HUNTER || + !AI_VALUE2(Unit*, "find target", "high nethermancer zerevor")) + { + return 1.0f; + } + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float IllidariCouncilDisableIceBlockMultiplier::GetValue(Action* action) +{ + if (bot->getClass() != CLASS_MAGE || + !AI_VALUE2(Unit*, "find target", "high nethermancer zerevor")) + { + return 1.0f; + } + + if (GetZerevorMageTank(bot) != bot) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float IllidariCouncilDisableArcaneShotOnZerevorMultiplier::GetValue(Action* action) +{ + Unit* zerevor = AI_VALUE2(Unit*, "find target", "high nethermancer zerevor"); + if (!zerevor) + return 1.0f; + + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target || target->GetGUID() != zerevor->GetGUID()) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float IllidariCouncilWaitForDpsMultiplier::GetValue(Action* action) +{ + if (botAI->IsMainTank(bot) || + botAI->IsAssistTankOfIndex(bot, 0, false) || + botAI->IsAssistTankOfIndex(bot, 1, false) || + GetZerevorMageTank(bot) == bot) + { + return 1.0f; + } + + Unit* gathios = AI_VALUE2(Unit*, "find target", "gathios the shatterer"); + if (!gathios) + return 1.0f; + + if (dynamic_cast(action)) + return 1.0f; + + const time_t now = std::time(nullptr); + constexpr uint8 dpsWaitSeconds = 5; + + auto it = councilDpsWaitTimer.find(gathios->GetMap()->GetInstanceId()); + if (it == councilDpsWaitTimer.end() || (now - it->second) >= dpsWaitSeconds) + return 1.0f; + + if (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +// Illidan Stormrage + +float IllidanStormrageDelayDpsCooldownsMultiplier::GetValue(Action* action) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return 1.0f; + + if (illidan->GetHealthPct() > 62.0f && + (dynamic_cast(action) || + dynamic_cast(action))) + { + return 0.0f; + } + + if (illidan->GetHealthPct() <= 62.0f || illidan->GetHealthPct() > 95.0f) + return 1.0f; + + if (IsDpsCooldownAction(action) || + (botAI->IsDps(bot) && dynamic_cast(action))) + { + return 0.0f; + } + + return 1.0f; +} + +float IllidanStormrageControlTankActionsMultiplier::GetValue(Action* action) +{ + if (!botAI->IsTank(bot)) + return 1.0f; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (GetIllidanPhase(illidan) != 2) + return 1.0f; + + if (botAI->IsMainTank(bot)) + { + if (dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + if (dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + } + else if (botAI->IsAssistTankOfIndex(bot, 0, false) || + botAI->IsAssistTankOfIndex(bot, 1, false)) + { + if (dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +float IllidanStormrageDisableDefaultTargetingMultiplier::GetValue(Action* action) +{ + if (bot->GetVictim() == nullptr) + return 1.0f; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + int phase = GetIllidanPhase(illidan); + + if (phase == 4 && dynamic_cast(action)) + return 0.0f; + + if (botAI->IsRangedDps(bot)) + { + if (phase != 2) + context->GetValue("neglect threat")->Set(true); + + if (dynamic_cast(action)) + return 0.0f; + } + + constexpr float searchRadius = 40.0f; + Unit* shadowDemon = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_SHADOW_DEMON), searchRadius); + Unit* shadowfiend = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_PARASITIC_SHADOWFIEND), searchRadius); + + if (((shadowDemon && bot->GetTarget() == shadowDemon->GetGUID()) || + (shadowfiend && bot->GetTarget() == shadowfiend->GetGUID())) && + dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +float IllidanStormrageControlNonTankMovementMultiplier::GetValue(Action* action) +{ + if (botAI->IsTank(bot)) + return 1.0f; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + { + return 0.0f; + } + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + + int phase = GetIllidanPhase(illidan); + + if (phase == 2 && + (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action))) + { + return 0.0f; + } + + if (phase == 4 && botAI->IsHeal(bot) && + dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +float IllidanStormrageUseEarthbindTotemMultiplier::GetValue(Action* action) +{ + if (bot->getClass() != CLASS_SHAMAN) + return 1.0f; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || GetIllidanPhase(illidan) == 2) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +float IllidanStormrageWaitForDpsMultiplier::GetValue(Action* action) +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return 1.0f; + + if (dynamic_cast(action)) + return 1.0f; + + const time_t now = std::time(nullptr); + const uint32 instanceId = illidan->GetMap()->GetInstanceId(); + + int phase = GetIllidanPhase(illidan); + + if ((phase == 1 || phase == 3 || phase == 5) && + !botAI->IsMainTank(bot)) + { + constexpr uint8 humanoidPhaseDpsWaitSeconds = 3; + auto it = illidanBossDpsWaitTimer.find(instanceId); + + if ((it == illidanBossDpsWaitTimer.end() || + (now - it->second) < humanoidPhaseDpsWaitSeconds) && + (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action)))) + { + return 0.0f; + } + } + + if (phase == 4 && GetIllidanWarlockTank(bot) != bot) + { + constexpr uint8 demonPhaseDpsWaitSeconds = 8; + auto it = illidanBossDpsWaitTimer.find(instanceId); + + if ((it == illidanBossDpsWaitTimer.end() || + (now - it->second) < demonPhaseDpsWaitSeconds) && + (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action)))) + { + return 0.0f; + } + } + + if (AI_VALUE2(Unit*, "find target", "flame of azzinoth") && + !botAI->IsAssistTankOfIndex(bot, 0, true) && + !botAI->IsAssistTankOfIndex(bot, 1, true)) + { + constexpr uint8 flamePhaseDpsWaitSeconds = 6; + auto it = illidanFlameDpsWaitTimer.find(instanceId); + + if ((it == illidanFlameDpsWaitTimer.end() || + (now - it->second) < flamePhaseDpsWaitSeconds) && + (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action)))) + { + return 0.0f; + } + } + + return 1.0f; +} diff --git a/src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.h b/src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.h new file mode 100644 index 00000000000..ba82e0100b9 --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.h @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDBLACKTEMPLEMULTIPLIERS_H +#define _PLAYERBOT_RAIDBLACKTEMPLEMULTIPLIERS_H + +#include "Multiplier.h" + +// High Warlord Naj'entus + +class HighWarlordNajentusDelayDpsCooldownsMultiplier : public Multiplier +{ +public: + HighWarlordNajentusDelayDpsCooldownsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "high warlord naj'entus delay dps cooldowns multiplier") {} + virtual float GetValue(Action* action); +}; + +class HighWarlordNajentusDisableCombatFormationMoveMultiplier : public Multiplier +{ +public: + HighWarlordNajentusDisableCombatFormationMoveMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "high warlord naj'entus disable combat formation move multiplier") {} + virtual float GetValue(Action* action); +}; + +// Supremus + +class SupremusDelayDpsCooldownsMultiplier : public Multiplier +{ +public: + SupremusDelayDpsCooldownsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "supremus delay dps cooldowns multiplier") {} + virtual float GetValue(Action* action); +}; + +class SupremusFocusOnAvoidanceInPhase2Multiplier : public Multiplier +{ +public: + SupremusFocusOnAvoidanceInPhase2Multiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "supremus focus on avoidance in phase 2 multiplier") {} + virtual float GetValue(Action* action); +}; + +class SupremusHitboxIsBuggedMultiplier : public Multiplier +{ +public: + SupremusHitboxIsBuggedMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "supremus hitbox is bugged multiplier") {} + virtual float GetValue(Action* action); +}; + +// Teron Gorefiend + +class TeronGorefiendDelayDpsCooldownsMultiplier : public Multiplier +{ +public: + TeronGorefiendDelayDpsCooldownsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "teron gorefiend delay dps cooldowns multiplier") {} + virtual float GetValue(Action* action); +}; + +class TeronGorefiendControlMovementMultiplier : public Multiplier +{ +public: + TeronGorefiendControlMovementMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "teron gorefiend control movement multiplier") {} + virtual float GetValue(Action* action); +}; + +class TeronGorefiendMarkedBotOnlyMoveToDieMultiplier : public Multiplier +{ +public: + TeronGorefiendMarkedBotOnlyMoveToDieMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "teron gorefiend marked bot only move to die multiplier") {} + virtual float GetValue(Action* action); +}; + +class TeronGorefiendSpiritsAttackOnlyShadowyConstructsMultiplier : public Multiplier +{ +public: + TeronGorefiendSpiritsAttackOnlyShadowyConstructsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "teron gorefiend spirits attack only shadowy constructs multiplier") {} + virtual float GetValue(Action* action); +}; + +class TeronGorefiendDisableAttackingConstructsMultiplier : public Multiplier +{ +public: + TeronGorefiendDisableAttackingConstructsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "teron gorefiend disable attacking constructs multiplier") {} + virtual float GetValue(Action* action); +}; + +// Gurtogg Bloodboil + +class GurtoggBloodboilDelayDpsCooldownsMultiplier : public Multiplier +{ +public: + GurtoggBloodboilDelayDpsCooldownsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "gurtogg bloodboil delay dps cooldowns multiplier") {} + virtual float GetValue(Action* action); +}; + +class GurtoggBloodboilControlMovementMultiplier : public Multiplier +{ +public: + GurtoggBloodboilControlMovementMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "gurtogg bloodboil control movement multiplier") {} + virtual float GetValue(Action* action); +}; + +// Reliquary of Souls + +class ReliquaryOfSoulsDelayDpsCooldownsMultiplier : public Multiplier +{ +public: + ReliquaryOfSoulsDelayDpsCooldownsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "reliquary of souls delay dps cooldowns multiplier") {} + virtual float GetValue(Action* action); +}; + +class ReliquaryOfSoulsDontWasteHealingMultiplier : public Multiplier +{ +public: + ReliquaryOfSoulsDontWasteHealingMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "reliquary of souls don't waste healing multiplier") {} + virtual float GetValue(Action* action); +}; + +// Mother Shahraz + +class MotherShahrazDelayDpsCooldownsMultiplier : public Multiplier +{ +public: + MotherShahrazDelayDpsCooldownsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "mother shahraz delay dps cooldowns multiplier") {} + virtual float GetValue(Action* action); +}; + +class MotherShahrazControlMovementMultiplier : public Multiplier +{ +public: + MotherShahrazControlMovementMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "mother shahraz control movement multiplier") {} + virtual float GetValue(Action* action); +}; + +class MotherShahrazBotsWithFatalAttractionOnlyRunAwayMultiplier : public Multiplier +{ +public: + MotherShahrazBotsWithFatalAttractionOnlyRunAwayMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "mother shahraz bots with fatal attraction only run away multiplier") {} + virtual float GetValue(Action* action); +}; + +// Illidari Council + +class IllidariCouncilDelayDpsCooldownsMultiplier : public Multiplier +{ +public: + IllidariCouncilDelayDpsCooldownsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidari council delay dps cooldowns multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidariCouncilDisableTankActionsMultiplier : public Multiplier +{ +public: + IllidariCouncilDisableTankActionsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidari council disable tank actions multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidariCouncilControlMovementMultiplier : public Multiplier +{ +public: + IllidariCouncilControlMovementMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidari council control movement multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidariCouncilControlMisdirectionMultiplier : public Multiplier +{ +public: + IllidariCouncilControlMisdirectionMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidari council control misdirection multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidariCouncilDisableArcaneShotOnZerevorMultiplier : public Multiplier +{ +public: + IllidariCouncilDisableArcaneShotOnZerevorMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidari council disable arcane shot on zerevor multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidariCouncilDisableIceBlockMultiplier : public Multiplier +{ +public: + IllidariCouncilDisableIceBlockMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidari council disable ice block multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidariCouncilWaitForDpsMultiplier : public Multiplier +{ +public: + IllidariCouncilWaitForDpsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidari council wait for dps multiplier") {} + virtual float GetValue(Action* action); +}; + +// Illidan Stormrage + +class IllidanStormrageDelayDpsCooldownsMultiplier : public Multiplier +{ +public: + IllidanStormrageDelayDpsCooldownsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidan stormrage delay dps cooldowns multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidanStormrageControlTankActionsMultiplier : public Multiplier +{ +public: + IllidanStormrageControlTankActionsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidan stormrage control tank actions multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidanStormrageDisableDefaultTargetingMultiplier : public Multiplier +{ +public: + IllidanStormrageDisableDefaultTargetingMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidan stormrage disable default targeting multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidanStormrageControlNonTankMovementMultiplier : public Multiplier +{ +public: + IllidanStormrageControlNonTankMovementMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidan stormrage control non-tank movement multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidanStormrageUseEarthbindTotemMultiplier : public Multiplier +{ +public: + IllidanStormrageUseEarthbindTotemMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidan stormrage use earthbind totem multiplier") {} + virtual float GetValue(Action* action); +}; + +class IllidanStormrageWaitForDpsMultiplier : public Multiplier +{ +public: + IllidanStormrageWaitForDpsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "illidan stormrage wait for dps multiplier") {} + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/Ai/Raid/BlackTemple/RaidBlackTempleActionContext.h b/src/Ai/Raid/BlackTemple/RaidBlackTempleActionContext.h new file mode 100644 index 00000000000..8271449b581 --- /dev/null +++ b/src/Ai/Raid/BlackTemple/RaidBlackTempleActionContext.h @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDBLACKTEMPLEACTIONCONTEXT_H +#define _PLAYERBOT_RAIDBLACKTEMPLEACTIONCONTEXT_H + +#include "NamedObjectContext.h" +#include "RaidBlackTempleActions.h" + +class RaidBlackTempleActionContext : public NamedObjectContext +{ +public: + RaidBlackTempleActionContext() + { + // General + creators["black temple erase timers and trackers"] = + &RaidBlackTempleActionContext::black_temple_erase_timers_and_trackers; + + // High Warlord Naj'entus + creators["high warlord naj'entus misdirect boss to main tank"] = + &RaidBlackTempleActionContext::high_warlord_najentus_misdirect_boss_to_main_tank; + + creators["high warlord naj'entus tanks position boss"] = + &RaidBlackTempleActionContext::high_warlord_najentus_tanks_position_boss; + + creators["high warlord naj'entus disperse ranged"] = + &RaidBlackTempleActionContext::high_warlord_najentus_disperse_ranged; + + creators["high warlord naj'entus remove impaling spine"] = + &RaidBlackTempleActionContext::high_warlord_najentus_remove_impaling_spine; + + creators["high warlord naj'entus throw impaling spine"] = + &RaidBlackTempleActionContext::high_warlord_najentus_throw_impaling_spine; + + // Supremus + creators["supremus misdirect boss to main tank"] = + &RaidBlackTempleActionContext::supremus_misdirect_boss_to_main_tank; + + creators["supremus disperse ranged"] = + &RaidBlackTempleActionContext::supremus_disperse_ranged; + + creators["supremus kite boss"] = + &RaidBlackTempleActionContext::supremus_kite_boss; + + creators["supremus move away from volcanos"] = + &RaidBlackTempleActionContext::supremus_move_away_from_volcanos; + + creators["supremus manage phase timer"] = + &RaidBlackTempleActionContext::supremus_manage_phase_timer; + + // Shade of Akama + creators["shade of akama melee dps prioritize channelers"] = + &RaidBlackTempleActionContext::shade_of_akama_melee_dps_prioritize_channelers; + + // Teron Gorefiend + creators["teron gorefiend misdirect boss to main tank"] = + &RaidBlackTempleActionContext::teron_gorefiend_misdirect_boss_to_main_tank; + + creators["teron gorefiend tanks position boss"] = + &RaidBlackTempleActionContext::teron_gorefiend_tanks_position_boss; + + creators["teron gorefiend position ranged on balcony"] = + &RaidBlackTempleActionContext::teron_gorefiend_position_ranged_on_balcony; + + creators["teron gorefiend avoid shadow of death"] = + &RaidBlackTempleActionContext::teron_gorefiend_avoid_shadow_of_death; + + creators["teron gorefiend move to corner to die"] = + &RaidBlackTempleActionContext::teron_gorefiend_move_to_corner_to_die; + + creators["teron gorefiend control and destroy shadowy constructs"] = + &RaidBlackTempleActionContext::teron_gorefiend_control_and_destroy_shadowy_constructs; + + // Gurtogg Bloodboil + creators["gurtogg bloodboil misdirect boss to main tank"] = + &RaidBlackTempleActionContext::gurtogg_bloodboil_misdirect_boss_to_main_tank; + + creators["gurtogg bloodboil tanks position boss"] = + &RaidBlackTempleActionContext::gurtogg_bloodboil_tanks_position_boss; + + creators["gurtogg bloodboil rotate ranged groups"] = + &RaidBlackTempleActionContext::gurtogg_bloodboil_rotate_ranged_groups; + + creators["gurtogg bloodboil ranged move away from enraged player"] = + &RaidBlackTempleActionContext::gurtogg_bloodboil_ranged_move_away_from_enraged_player; + + creators["gurtogg bloodboil manage phase timer"] = + &RaidBlackTempleActionContext::gurtogg_bloodboil_manage_phase_timer; + + // Reliquary of Souls + creators["reliquary of souls misdirect boss to main tank"] = + &RaidBlackTempleActionContext::reliquary_of_souls_misdirect_boss_to_main_tank; + + creators["reliquary of souls adjust distance from suffering"] = + &RaidBlackTempleActionContext::reliquary_of_souls_adjust_distance_from_suffering; + + creators["reliquary of souls healers dps suffering"] = + &RaidBlackTempleActionContext::reliquary_of_souls_healers_dps_suffering; + + creators["reliquary of souls spellsteal rune shield"] = + &RaidBlackTempleActionContext::reliquary_of_souls_spellsteal_rune_shield; + + creators["reliquary of souls spell reflect deaden"] = + &RaidBlackTempleActionContext::reliquary_of_souls_spell_reflect_deaden; + + // Mother Shahraz + creators["mother shahraz misdirect boss to main tank"] = + &RaidBlackTempleActionContext::mother_shahraz_misdirect_boss_to_main_tank; + + creators["mother shahraz tanks position boss under pillar"] = + &RaidBlackTempleActionContext::mother_shahraz_tanks_position_boss_under_pillar; + + creators["mother shahraz melee dps wait at safe position"] = + &RaidBlackTempleActionContext::mother_shahraz_melee_dps_wait_at_safe_position; + + creators["mother shahraz position ranged under pillar"] = + &RaidBlackTempleActionContext::mother_shahraz_position_ranged_under_pillar; + + creators["mother shahraz run away to break fatal attraction"] = + &RaidBlackTempleActionContext::mother_shahraz_run_away_to_break_fatal_attraction; + + // Illidari Council + creators["illidari council misdirect bosses to tanks"] = + &RaidBlackTempleActionContext::illidari_council_misdirect_bosses_to_tanks; + + creators["illidari council main tank position gathios"] = + &RaidBlackTempleActionContext::illidari_council_main_tank_position_gathios; + + creators["illidari council main tank reflect judgement of command"] = + &RaidBlackTempleActionContext::illidari_council_main_tank_reflect_judgement_of_command; + + creators["illidari council first assist tank focus malande"] = + &RaidBlackTempleActionContext::illidari_council_first_assist_tank_focus_malande; + + creators["illidari council second assist tank position darkshadow"] = + &RaidBlackTempleActionContext::illidari_council_second_assist_tank_position_darkshadow; + + creators["illidari council mage tank position zerevor"] = + &RaidBlackTempleActionContext::illidari_council_mage_tank_position_zerevor; + + creators["illidari council position mage tank healer"] = + &RaidBlackTempleActionContext::illidari_council_position_mage_tank_healer; + + creators["illidari council disperse ranged"] = + &RaidBlackTempleActionContext::illidari_council_disperse_ranged; + + creators["illidari council command pets to attack gathios"] = + &RaidBlackTempleActionContext::illidari_council_command_pets_to_attack_gathios; + + creators["illidari council assign dps targets"] = + &RaidBlackTempleActionContext::illidari_council_assign_dps_targets; + + creators["illidari council manage dps timer"] = + &RaidBlackTempleActionContext::illidari_council_manage_dps_timer; + + // Illidan Stormrage + creators["illidan stormrage misdirect to tank"] = + &RaidBlackTempleActionContext::illidan_stormrage_misdirect_to_tank; + + creators["illidan stormrage main tank reposition boss"] = + &RaidBlackTempleActionContext::illidan_stormrage_main_tank_reposition_boss; + + creators["illidan stormrage isolate bot with parasite"] = + &RaidBlackTempleActionContext::illidan_stormrage_isolate_bot_with_parasite; + + creators["illidan stormrage set earthbind totem"] = + &RaidBlackTempleActionContext::illidan_stormrage_set_earthbind_totem; + + creators["illidan stormrage assist tanks handle flames of azzinoth"] = + &RaidBlackTempleActionContext::illidan_stormrage_assist_tanks_handle_flames_of_azzinoth; + + creators["illidan stormrage control pet aggression"] = + &RaidBlackTempleActionContext::illidan_stormrage_control_pet_aggression; + + creators["illidan stormrage position above grate"] = + &RaidBlackTempleActionContext::illidan_stormrage_position_above_grate; + + creators["illidan stormrage remove dark barrage"] = + &RaidBlackTempleActionContext::illidan_stormrage_remove_dark_barrage; + + creators["illidan stormrage move away from landing point"] = + &RaidBlackTempleActionContext::illidan_stormrage_move_away_from_landing_point; + + creators["illidan stormrage disperse ranged"] = + &RaidBlackTempleActionContext::illidan_stormrage_disperse_ranged; + + creators["illidan stormrage melee go somewhere to not die"] = + &RaidBlackTempleActionContext::illidan_stormrage_melee_go_somewhere_to_not_die; + + creators["illidan stormrage warlock tank handle demon boss"] = + &RaidBlackTempleActionContext::illidan_stormrage_warlock_tank_handle_demon_boss; + + creators["illidan stormrage dps prioritize adds"] = + &RaidBlackTempleActionContext::illidan_stormrage_dps_prioritize_adds; + + creators["illidan stormrage use shadow trap"] = + &RaidBlackTempleActionContext::illidan_stormrage_use_shadow_trap; + + creators["illidan stormrage manage dps timer and rti"] = + &RaidBlackTempleActionContext::illidan_stormrage_manage_dps_timer_and_rti; + + creators["illidan stormrage destroy hazards"] = + &RaidBlackTempleActionContext::illidan_stormrage_destroy_hazards; + + creators["illidan stormrage handle adds cheat"] = + &RaidBlackTempleActionContext::illidan_stormrage_handle_adds_cheat; + } + +private: + // General + static Action* black_temple_erase_timers_and_trackers( + PlayerbotAI* botAI) { return new BlackTempleEraseTimersAndTrackersAction(botAI); } + + // High Warlord Naj'entus + static Action* high_warlord_najentus_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new HighWarlordNajentusMisdirectBossToMainTankAction(botAI); } + + static Action* high_warlord_najentus_tanks_position_boss( + PlayerbotAI* botAI) { return new HighWarlordNajentusTanksPositionBossAction(botAI); } + + static Action* high_warlord_najentus_disperse_ranged( + PlayerbotAI* botAI) { return new HighWarlordNajentusDisperseRangedAction(botAI); } + + static Action* high_warlord_najentus_remove_impaling_spine( + PlayerbotAI* botAI) { return new HighWarlordNajentusRemoveImpalingSpineAction(botAI); } + + static Action* high_warlord_najentus_throw_impaling_spine( + PlayerbotAI* botAI) { return new HighWarlordNajentusThrowImpalingSpineAction(botAI); } + + // Supremus + static Action* supremus_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new SupremusMisdirectBossToMainTankAction(botAI); } + + static Action* supremus_disperse_ranged( + PlayerbotAI* botAI) { return new SupremusDisperseRangedAction(botAI); } + + static Action* supremus_kite_boss( + PlayerbotAI* botAI) { return new SupremusKiteBossAction(botAI); } + + static Action* supremus_move_away_from_volcanos( + PlayerbotAI* botAI) { return new SupremusMoveAwayFromVolcanosAction(botAI); } + + static Action* supremus_manage_phase_timer( + PlayerbotAI* botAI) { return new SupremusManagePhaseTimerAction(botAI); } + + // Shade of Akama + static Action* shade_of_akama_melee_dps_prioritize_channelers( + PlayerbotAI* botAI) { return new ShadeOfAkamaMeleeDpsPrioritizeChannelersAction(botAI); } + + // Teron Gorefiend + static Action* teron_gorefiend_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new TeronGorefiendMisdirectBossToMainTankAction(botAI); } + + static Action* teron_gorefiend_tanks_position_boss( + PlayerbotAI* botAI) { return new TeronGorefiendTanksPositionBossAction(botAI); } + + static Action* teron_gorefiend_position_ranged_on_balcony( + PlayerbotAI* botAI) { return new TeronGorefiendPositionRangedOnBalconyAction(botAI); } + + static Action* teron_gorefiend_avoid_shadow_of_death( + PlayerbotAI* botAI) { return new TeronGorefiendAvoidShadowOfDeathAction(botAI); } + + static Action* teron_gorefiend_move_to_corner_to_die( + PlayerbotAI* botAI) { return new TeronGorefiendMoveToCornerToDieAction(botAI); } + + static Action* teron_gorefiend_control_and_destroy_shadowy_constructs( + PlayerbotAI* botAI) { return new TeronGorefiendControlAndDestroyShadowyConstructsAction(botAI); } + + // Gurtogg Bloodboil + static Action* gurtogg_bloodboil_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new GurtoggBloodboilMisdirectBossToMainTankAction(botAI); } + + static Action* gurtogg_bloodboil_tanks_position_boss( + PlayerbotAI* botAI) { return new GurtoggBloodboilTanksPositionBossAction(botAI); } + + static Action* gurtogg_bloodboil_rotate_ranged_groups( + PlayerbotAI* botAI) { return new GurtoggBloodboilRotateRangedGroupsAction(botAI); } + + static Action* gurtogg_bloodboil_ranged_move_away_from_enraged_player( + PlayerbotAI* botAI) { return new GurtoggBloodboilRangedMoveAwayFromEnragedPlayerAction(botAI); } + + static Action* gurtogg_bloodboil_manage_phase_timer( + PlayerbotAI* botAI) { return new GurtoggBloodboilManagePhaseTimerAction(botAI); } + + // Reliquary of Souls + static Action* reliquary_of_souls_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsMisdirectBossToMainTankAction(botAI); } + + static Action* reliquary_of_souls_adjust_distance_from_suffering( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsAdjustDistanceFromSufferingAction(botAI); } + + static Action* reliquary_of_souls_healers_dps_suffering( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsHealersDpsSufferingAction(botAI); } + + static Action* reliquary_of_souls_spellsteal_rune_shield( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsSpellstealRuneShieldAction(botAI); } + + static Action* reliquary_of_souls_spell_reflect_deaden( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsSpellReflectDeadenAction(botAI); } + + // Mother Shahraz + static Action* mother_shahraz_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new MotherShahrazMisdirectBossToMainTankAction(botAI); } + + static Action* mother_shahraz_tanks_position_boss_under_pillar( + PlayerbotAI* botAI) { return new MotherShahrazTanksPositionBossUnderPillarAction(botAI); } + + static Action* mother_shahraz_melee_dps_wait_at_safe_position( + PlayerbotAI* botAI) { return new MotherShahrazMeleeDpsWaitAtSafePositionAction(botAI); } + + static Action* mother_shahraz_position_ranged_under_pillar( + PlayerbotAI* botAI) { return new MotherShahrazPositionRangedUnderPillarAction(botAI); } + + static Action* mother_shahraz_run_away_to_break_fatal_attraction( + PlayerbotAI* botAI) { return new MotherShahrazRunAwayToBreakFatalAttractionAction(botAI); } + + // Illidari Council + static Action* illidari_council_misdirect_bosses_to_tanks( + PlayerbotAI* botAI) { return new IllidariCouncilMisdirectBossesToTanksAction(botAI); } + + static Action* illidari_council_main_tank_position_gathios( + PlayerbotAI* botAI) { return new IllidariCouncilMainTankPositionGathiosAction(botAI); } + + static Action* illidari_council_main_tank_reflect_judgement_of_command( + PlayerbotAI* botAI) { return new IllidariCouncilMainTankReflectJudgementOfCommandAction(botAI); } + + static Action* illidari_council_first_assist_tank_focus_malande( + PlayerbotAI* botAI) { return new IllidariCouncilFirstAssistTankFocusMalandeAction(botAI); } + + static Action* illidari_council_second_assist_tank_position_darkshadow( + PlayerbotAI* botAI) { return new IllidariCouncilSecondAssistTankPositionDarkshadowAction(botAI); } + + static Action* illidari_council_mage_tank_position_zerevor( + PlayerbotAI* botAI) { return new IllidariCouncilMageTankPositionZerevorAction(botAI); } + + static Action* illidari_council_position_mage_tank_healer( + PlayerbotAI* botAI) { return new IllidariCouncilPositionMageTankHealerAction(botAI); } + + static Action* illidari_council_disperse_ranged( + PlayerbotAI* botAI) { return new IllidariCouncilDisperseRangedAction(botAI); } + + static Action* illidari_council_command_pets_to_attack_gathios( + PlayerbotAI* botAI) { return new IllidariCouncilCommandPetsToAttackGathiosAction(botAI); } + + static Action* illidari_council_assign_dps_targets( + PlayerbotAI* botAI) { return new IllidariCouncilAssignDpsTargetsAction(botAI); } + + static Action* illidari_council_manage_dps_timer( + PlayerbotAI* botAI) { return new IllidariCouncilManageDpsTimerAction(botAI); } + + // Illidan Stormrage + static Action* illidan_stormrage_misdirect_to_tank( + PlayerbotAI* botAI) { return new IllidanStormrageMisdirectToTankAction(botAI); } + + static Action* illidan_stormrage_main_tank_reposition_boss( + PlayerbotAI* botAI) { return new IllidanStormrageMainTankRepositionBossAction(botAI); } + + static Action* illidan_stormrage_isolate_bot_with_parasite( + PlayerbotAI* botAI) { return new IllidanStormrageIsolateBotWithParasiteAction(botAI); } + + static Action* illidan_stormrage_set_earthbind_totem( + PlayerbotAI* botAI) { return new IllidanStormrageSetEarthbindTotemAction(botAI); } + + static Action* illidan_stormrage_assist_tanks_handle_flames_of_azzinoth( + PlayerbotAI* botAI) { return new IllidanStormrageAssistTanksHandleFlamesOfAzzinothAction(botAI); } + + static Action* illidan_stormrage_control_pet_aggression( + PlayerbotAI* botAI) { return new IllidanStormrageControlPetAggressionAction(botAI); } + + static Action* illidan_stormrage_position_above_grate( + PlayerbotAI* botAI) { return new IllidanStormragePositionAboveGrateAction(botAI); } + + static Action* illidan_stormrage_remove_dark_barrage( + PlayerbotAI* botAI) { return new IllidanStormrageRemoveDarkBarrageAction(botAI); } + + static Action* illidan_stormrage_move_away_from_landing_point( + PlayerbotAI* botAI) { return new IllidanStormrageMoveAwayFromLandingPointAction(botAI); } + + static Action* illidan_stormrage_disperse_ranged( + PlayerbotAI* botAI) { return new IllidanStormrageDisperseRangedAction(botAI); } + + static Action* illidan_stormrage_melee_go_somewhere_to_not_die( + PlayerbotAI* botAI) { return new IllidanStormrageMeleeGoSomewhereToNotDieAction(botAI); } + + static Action* illidan_stormrage_warlock_tank_handle_demon_boss( + PlayerbotAI* botAI) { return new IllidanStormrageWarlockTankHandleDemonBossAction(botAI); } + + static Action* illidan_stormrage_dps_prioritize_adds( + PlayerbotAI* botAI) { return new IllidanStormrageDpsPrioritizeAddsAction(botAI); } + + static Action* illidan_stormrage_use_shadow_trap( + PlayerbotAI* botAI) { return new IllidanStormrageUseShadowTrapAction(botAI); } + + static Action* illidan_stormrage_manage_dps_timer_and_rti( + PlayerbotAI* botAI) { return new IllidanStormrageManageDpsTimerAndRtiAction(botAI); } + + static Action* illidan_stormrage_destroy_hazards( + PlayerbotAI* botAI) { return new IllidanStormrageDestroyHazardsAction(botAI); } + + static Action* illidan_stormrage_handle_adds_cheat( + PlayerbotAI* botAI) { return new IllidanStormrageHandleAddsCheatAction(botAI); } +}; + +#endif diff --git a/src/Ai/Raid/BlackTemple/RaidBlackTempleTriggerContext.h b/src/Ai/Raid/BlackTemple/RaidBlackTempleTriggerContext.h new file mode 100644 index 00000000000..d9cc09489f7 --- /dev/null +++ b/src/Ai/Raid/BlackTemple/RaidBlackTempleTriggerContext.h @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDBLACKTEMPLETRIGGERCONTEXT_H +#define _PLAYERBOT_RAIDBLACKTEMPLETRIGGERCONTEXT_H + +#include "NamedObjectContext.h" +#include "RaidBlackTempleTriggers.h" + +class RaidBlackTempleTriggerContext : public NamedObjectContext +{ +public: + RaidBlackTempleTriggerContext() + { + // General + creators["black temple bot is not in combat"] = + &RaidBlackTempleTriggerContext::black_temple_bot_is_not_in_combat; + + // High Warlord Naj'entus + creators["high warlord naj'entus pulling boss"] = + &RaidBlackTempleTriggerContext::high_warlord_najentus_pulling_boss; + + creators["high warlord naj'entus boss engaged by tanks"] = + &RaidBlackTempleTriggerContext::high_warlord_najentus_boss_engaged_by_tanks; + + creators["high warlord naj'entus casts needle spines"] = + &RaidBlackTempleTriggerContext::high_warlord_najentus_casts_needle_spines; + + creators["high warlord naj'entus player is impaled"] = + &RaidBlackTempleTriggerContext::high_warlord_najentus_player_is_impaled; + + creators["high warlord naj'entus boss has tidal shield"] = + &RaidBlackTempleTriggerContext::high_warlord_najentus_boss_has_tidal_shield; + + // Supremus + creators["supremus pulling boss or changing phase"] = + &RaidBlackTempleTriggerContext::supremus_pulling_boss_or_changing_phase; + + creators["supremus boss engaged by ranged"] = + &RaidBlackTempleTriggerContext::supremus_boss_engaged_by_ranged; + + creators["supremus boss is fixated on bot"] = + &RaidBlackTempleTriggerContext::supremus_boss_is_fixated_on_bot; + + creators["supremus volcano is nearby"] = + &RaidBlackTempleTriggerContext::supremus_volcano_is_nearby; + + creators["supremus need to manage phase timer"] = + &RaidBlackTempleTriggerContext::supremus_need_to_manage_phase_timer; + + // Shade of Akama + creators["shade of akama killing channelers starts phase 2"] = + &RaidBlackTempleTriggerContext::shade_of_akama_killing_channelers_starts_phase_2; + + // Teron Gorefiend + creators["teron gorefiend pulling boss"] = + &RaidBlackTempleTriggerContext::teron_gorefiend_pulling_boss; + + creators["teron gorefiend boss engaged by tanks"] = + &RaidBlackTempleTriggerContext::teron_gorefiend_boss_engaged_by_tanks; + + creators["teron gorefiend boss engaged by ranged"] = + &RaidBlackTempleTriggerContext::teron_gorefiend_boss_engaged_by_ranged; + + creators["teron gorefiend boss is casting shadow of death"] = + &RaidBlackTempleTriggerContext::teron_gorefiend_boss_is_casting_shadow_of_death; + + creators["teron gorefiend bot has shadow of death"] = + &RaidBlackTempleTriggerContext::teron_gorefiend_bot_has_shadow_of_death; + + creators["teron gorefiend bot transformed into vengeful spirit"] = + &RaidBlackTempleTriggerContext::teron_gorefiend_bot_transformed_into_vengeful_spirit; + + // Gurtogg Bloodboil + creators["gurtogg bloodboil pulling boss"] = + &RaidBlackTempleTriggerContext::gurtogg_bloodboil_pulling_boss; + + creators["gurtogg bloodboil boss engaged by tanks"] = + &RaidBlackTempleTriggerContext::gurtogg_bloodboil_boss_engaged_by_tanks; + + creators["gurtogg bloodboil boss casts bloodboil"] = + &RaidBlackTempleTriggerContext::gurtogg_bloodboil_boss_casts_bloodboil; + + creators["gurtogg bloodboil bot has fel rage"] = + &RaidBlackTempleTriggerContext::gurtogg_bloodboil_bot_has_fel_rage; + + creators["gurtogg bloodboil need to manage phase timer"] = + &RaidBlackTempleTriggerContext::gurtogg_bloodboil_need_to_manage_phase_timer; + + // Reliquary of Souls + creators["reliquary of souls aggro resets upon phase change"] = + &RaidBlackTempleTriggerContext::reliquary_of_souls_aggro_resets_upon_phase_change; + + creators["reliquary of souls essence of suffering fixates on closest target"] = + &RaidBlackTempleTriggerContext::reliquary_of_souls_essence_of_suffering_fixates_on_closest_target; + + creators["reliquary of souls essence of suffering disables healing"] = + &RaidBlackTempleTriggerContext::reliquary_of_souls_essence_of_suffering_disables_healing; + + creators["reliquary of souls essence of desire has rune shield"] = + &RaidBlackTempleTriggerContext::reliquary_of_souls_essence_of_desire_has_rune_shield; + + creators["reliquary of souls essence of desire casting deaden"] = + &RaidBlackTempleTriggerContext::reliquary_of_souls_essence_of_desire_casting_deaden; + + // Mother Shahraz + creators["mother shahraz pulling boss"] = + &RaidBlackTempleTriggerContext::mother_shahraz_pulling_boss; + + creators["mother shahraz boss engaged by tanks"] = + &RaidBlackTempleTriggerContext::mother_shahraz_boss_engaged_by_tanks; + + creators["mother shahraz tanks are positioning boss"] = + &RaidBlackTempleTriggerContext::mother_shahraz_tanks_are_positioning_boss; + + creators["mother shahraz sinister beam knocks back players"] = + &RaidBlackTempleTriggerContext::mother_shahraz_sinister_beam_knocks_back_players; + + creators["mother shahraz bots are linked by fatal attraction"] = + &RaidBlackTempleTriggerContext::mother_shahraz_bots_are_linked_by_fatal_attraction; + + // Illidari Council + creators["illidari council pulling bosses"] = + &RaidBlackTempleTriggerContext::illidari_council_pulling_bosses; + + creators["illidari council gathios engaged by main tank"] = + &RaidBlackTempleTriggerContext::illidari_council_gathios_engaged_by_main_tank; + + creators["illidari council gathios casting judgement of command"] = + &RaidBlackTempleTriggerContext::illidari_council_gathios_casting_judgement_of_command; + + creators["illidari council malande engaged by first assist tank"] = + &RaidBlackTempleTriggerContext::illidari_council_malande_engaged_by_first_assist_tank; + + creators["illidari council darkshadow engaged by second assist tank"] = + &RaidBlackTempleTriggerContext::illidari_council_darkshadow_engaged_by_second_assist_tank; + + creators["illidari council zerevor engaged by mage tank"] = + &RaidBlackTempleTriggerContext::illidari_council_zerevor_engaged_by_mage_tank; + + creators["illidari council mage tank needs dedicated healer"] = + &RaidBlackTempleTriggerContext::illidari_council_mage_tank_needs_dedicated_healer; + + creators["illidari council zerevor casts dangerous aoes"] = + &RaidBlackTempleTriggerContext::illidari_council_zerevor_casts_dangerous_aoes; + + creators["illidari council pets screw up the pull"] = + &RaidBlackTempleTriggerContext::illidari_council_pets_screw_up_the_pull; + + creators["illidari council determining dps assignments"] = + &RaidBlackTempleTriggerContext::illidari_council_determining_dps_assignments; + + creators["illidari council need to manage dps timer"] = + &RaidBlackTempleTriggerContext::illidari_council_need_to_manage_dps_timer; + + // Illidan Stormrage + creators["illidan stormrage tank needs aggro"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_tank_needs_aggro; + + creators["illidan stormrage boss casts flame crash in front of main tank"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_boss_casts_flame_crash_in_front_of_main_tank; + + creators["illidan stormrage bot has parasitic shadowfiend"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_bot_has_parasitic_shadowfiend; + + creators["illidan stormrage parasitic shadowfiends run wild"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_parasitic_shadowfiends_run_wild; + + creators["illidan stormrage boss summoned flames of azzinoth"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_boss_summoned_flames_of_azzinoth; + + creators["illidan stormrage pets die to fire"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_pets_die_to_fire; + + creators["illidan stormrage grate is safe from flames"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_grate_is_safe_from_flames; + + creators["illidan stormrage bot struck by dark barrage"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_bot_struck_by_dark_barrage; + + creators["illidan stormrage boss is preparing to land"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_boss_is_preparing_to_land; + + creators["illidan stormrage boss deals splash damage"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_boss_deals_splash_damage; + + creators["illidan stormrage this expansion hates melee"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_this_expansion_hates_melee; + + creators["illidan stormrage boss transforms into demon"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_boss_transforms_into_demon; + + creators["illidan stormrage boss spawns adds"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_boss_spawns_adds; + + creators["illidan stormrage maiev placed shadow trap"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_maiev_placed_shadow_trap; + + creators["illidan stormrage need to manage dps timer and rti"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_need_to_manage_dps_timer_and_rti; + + creators["illidan stormrage need to clear hazards between phases"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_need_to_clear_hazards_between_phases; + + creators["illidan stormrage cheat"] = + &RaidBlackTempleTriggerContext::illidan_stormrage_cheat; + } + +private: + // General + static Trigger* black_temple_bot_is_not_in_combat( + PlayerbotAI* botAI) { return new BlackTempleBotIsNotInCombatTrigger(botAI); } + + // High Warlord Naj'entus + static Trigger* high_warlord_najentus_pulling_boss( + PlayerbotAI* botAI) { return new HighWarlordNajentusPullingBossTrigger(botAI); } + + static Trigger* high_warlord_najentus_boss_engaged_by_tanks( + PlayerbotAI* botAI) { return new HighWarlordNajentusBossEngagedByTanksTrigger(botAI); } + + static Trigger* high_warlord_najentus_casts_needle_spines( + PlayerbotAI* botAI) { return new HighWarlordNajentusCastsNeedleSpinesTrigger(botAI); } + + static Trigger* high_warlord_najentus_player_is_impaled( + PlayerbotAI* botAI) { return new HighWarlordNajentusPlayerIsImpaledTrigger(botAI); } + + static Trigger* high_warlord_najentus_boss_has_tidal_shield( + PlayerbotAI* botAI) { return new HighWarlordNajentusBossHasTidalShieldTrigger(botAI); } + + // Supremus + static Trigger* supremus_pulling_boss_or_changing_phase( + PlayerbotAI* botAI) { return new SupremusPullingBossOrChangingPhaseTrigger(botAI); } + + static Trigger* supremus_boss_engaged_by_ranged( + PlayerbotAI* botAI) { return new SupremusBossEngagedByRangedTrigger(botAI); } + + static Trigger* supremus_boss_is_fixated_on_bot( + PlayerbotAI* botAI) { return new SupremusBossIsFixatedOnBotTrigger(botAI); } + + static Trigger* supremus_volcano_is_nearby( + PlayerbotAI* botAI) { return new SupremusVolcanoIsNearbyTrigger(botAI); } + + static Trigger* supremus_need_to_manage_phase_timer( + PlayerbotAI* botAI) { return new SupremusNeedToManagePhaseTimerTrigger(botAI); } + + // Shade of Akama + static Trigger* shade_of_akama_killing_channelers_starts_phase_2( + PlayerbotAI* botAI) { return new ShadeOfAkamaKillingChannelersStartsPhase2Trigger(botAI); } + + // Teron Gorefiend + static Trigger* teron_gorefiend_pulling_boss( + PlayerbotAI* botAI) { return new TeronGorefiendPullingBossTrigger(botAI); } + + static Trigger* teron_gorefiend_boss_engaged_by_tanks( + PlayerbotAI* botAI) { return new TeronGorefiendBossEngagedByTanksTrigger(botAI); } + + static Trigger* teron_gorefiend_boss_engaged_by_ranged( + PlayerbotAI* botAI) { return new TeronGorefiendBossEngagedByRangedTrigger(botAI); } + + static Trigger* teron_gorefiend_boss_is_casting_shadow_of_death( + PlayerbotAI* botAI) { return new TeronGorefiendBossIsCastingShadowOfDeathTrigger(botAI); } + + static Trigger* teron_gorefiend_bot_has_shadow_of_death( + PlayerbotAI* botAI) { return new TeronGorefiendBotHasShadowOfDeathTrigger(botAI); } + + static Trigger* teron_gorefiend_bot_transformed_into_vengeful_spirit( + PlayerbotAI* botAI) { return new TeronGorefiendBotTransformedIntoVengefulSpiritTrigger(botAI); } + + // Gurtogg Bloodboil + static Trigger* gurtogg_bloodboil_pulling_boss( + PlayerbotAI* botAI) { return new GurtoggBloodboilPullingBossTrigger(botAI); } + + static Trigger* gurtogg_bloodboil_boss_engaged_by_tanks( + PlayerbotAI* botAI) { return new GurtoggBloodboilBossEngagedByTanksTrigger(botAI); } + + static Trigger* gurtogg_bloodboil_boss_casts_bloodboil( + PlayerbotAI* botAI) { return new GurtoggBloodboilBossCastsBloodboilTrigger(botAI); } + + static Trigger* gurtogg_bloodboil_bot_has_fel_rage( + PlayerbotAI* botAI) { return new GurtoggBloodboilBotHasFelRageTrigger(botAI); } + + static Trigger* gurtogg_bloodboil_need_to_manage_phase_timer( + PlayerbotAI* botAI) { return new GurtoggBloodboilNeedToManagePhaseTimerTrigger(botAI); } + + // Reliquary of Souls + static Trigger* reliquary_of_souls_aggro_resets_upon_phase_change( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsAggroResetsUponPhaseChangeTrigger(botAI); } + + static Trigger* reliquary_of_souls_essence_of_suffering_fixates_on_closest_target( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsEssenceOfSufferingFixatesOnClosestTargetTrigger(botAI); } + + static Trigger* reliquary_of_souls_essence_of_suffering_disables_healing( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsEssenceOfSufferingDisablesHealingTrigger(botAI); } + + static Trigger* reliquary_of_souls_essence_of_desire_has_rune_shield( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsEssenceOfDesireHasRuneShieldTrigger(botAI); } + + static Trigger* reliquary_of_souls_essence_of_desire_casting_deaden( + PlayerbotAI* botAI) { return new ReliquaryOfSoulsEssenceOfDesireCastingDeadenTrigger(botAI); } + + // Mother Shahraz + static Trigger* mother_shahraz_pulling_boss( + PlayerbotAI* botAI) { return new MotherShahrazPullingBossTrigger(botAI); } + + static Trigger* mother_shahraz_boss_engaged_by_tanks( + PlayerbotAI* botAI) { return new MotherShahrazBossEngagedByTanksTrigger(botAI); } + + static Trigger* mother_shahraz_tanks_are_positioning_boss( + PlayerbotAI* botAI) { return new MotherShahrazTanksArePositioningBossTrigger(botAI); } + + static Trigger* mother_shahraz_sinister_beam_knocks_back_players( + PlayerbotAI* botAI) { return new MotherShahrazSinisterBeamKnocksBackPlayersTrigger(botAI); } + + static Trigger* mother_shahraz_bots_are_linked_by_fatal_attraction( + PlayerbotAI* botAI) { return new MotherShahrazBotsAreLinkedByFatalAttractionTrigger(botAI); } + + // Illidari Council + static Trigger* illidari_council_pulling_bosses( + PlayerbotAI* botAI) { return new IllidariCouncilPullingBossesTrigger(botAI); } + + static Trigger* illidari_council_gathios_engaged_by_main_tank( + PlayerbotAI* botAI) { return new IllidariCouncilGathiosEngagedByMainTankTrigger(botAI); } + + static Trigger* illidari_council_gathios_casting_judgement_of_command( + PlayerbotAI* botAI) { return new IllidariCouncilGathiosCastingJudgementOfCommandTrigger(botAI); } + + static Trigger* illidari_council_malande_engaged_by_first_assist_tank( + PlayerbotAI* botAI) { return new IllidariCouncilMalandeEngagedByFirstAssistTankTrigger(botAI); } + + static Trigger* illidari_council_darkshadow_engaged_by_second_assist_tank( + PlayerbotAI* botAI) { return new IllidariCouncilDarkshadowEngagedBySecondAssistTankTrigger(botAI); } + + static Trigger* illidari_council_zerevor_engaged_by_mage_tank( + PlayerbotAI* botAI) { return new IllidariCouncilZerevorEngagedByMageTankTrigger(botAI); } + + static Trigger* illidari_council_mage_tank_needs_dedicated_healer( + PlayerbotAI* botAI) { return new IllidariCouncilMageTankNeedsDedicatedHealerTrigger(botAI); } + + static Trigger* illidari_council_zerevor_casts_dangerous_aoes( + PlayerbotAI* botAI) { return new IllidariCouncilZerevorCastsDangerousAoesTrigger(botAI); } + + static Trigger* illidari_council_pets_screw_up_the_pull( + PlayerbotAI* botAI) { return new IllidariCouncilPetsScrewUpThePullTrigger(botAI); } + + static Trigger* illidari_council_determining_dps_assignments( + PlayerbotAI* botAI) { return new IllidariCouncilDeterminingDpsAssignmentsTrigger(botAI); } + + static Trigger* illidari_council_need_to_manage_dps_timer( + PlayerbotAI* botAI) { return new IllidariCouncilNeedToManageDpsTimerTrigger(botAI); } + + // Illidan Stormrage + static Trigger* illidan_stormrage_tank_needs_aggro( + PlayerbotAI* botAI) { return new IllidanStormrageTankNeedsAggroTrigger(botAI); } + + static Trigger* illidan_stormrage_boss_casts_flame_crash_in_front_of_main_tank( + PlayerbotAI* botAI) { return new IllidanStormrageBossCastsFlameCrashInFrontOfMainTankTrigger(botAI); } + + static Trigger* illidan_stormrage_bot_has_parasitic_shadowfiend( + PlayerbotAI* botAI) { return new IllidanStormrageBotHasParasiticShadowfiendTrigger(botAI); } + + static Trigger* illidan_stormrage_parasitic_shadowfiends_run_wild( + PlayerbotAI* botAI) { return new IllidanStormrageParasiticShadowfiendsRunWildTrigger(botAI); } + + static Trigger* illidan_stormrage_boss_summoned_flames_of_azzinoth( + PlayerbotAI* botAI) { return new IllidanStormrageBossSummonedFlamesOfAzzinothTrigger(botAI); } + + static Trigger* illidan_stormrage_pets_die_to_fire( + PlayerbotAI* botAI) { return new IllidanStormragePetsDieToFireTrigger(botAI); } + + static Trigger* illidan_stormrage_grate_is_safe_from_flames( + PlayerbotAI* botAI) { return new IllidanStormrageGrateIsSafeFromFlamesTrigger(botAI); } + + static Trigger* illidan_stormrage_bot_struck_by_dark_barrage( + PlayerbotAI* botAI) { return new IllidanStormrageBotStruckByDarkBarrageTrigger(botAI); } + + static Trigger* illidan_stormrage_boss_is_preparing_to_land( + PlayerbotAI* botAI) { return new IllidanStormrageBossIsPreparingToLandTrigger(botAI); } + + static Trigger* illidan_stormrage_boss_deals_splash_damage( + PlayerbotAI* botAI) { return new IllidanStormrageBossDealsSplashDamageTrigger(botAI); } + + static Trigger* illidan_stormrage_this_expansion_hates_melee( + PlayerbotAI* botAI) { return new IllidanStormrageThisExpansionHatesMeleeTrigger(botAI); } + + static Trigger* illidan_stormrage_boss_transforms_into_demon( + PlayerbotAI* botAI) { return new IllidanStormrageBossTransformsIntoDemonTrigger(botAI); } + + static Trigger* illidan_stormrage_boss_spawns_adds( + PlayerbotAI* botAI) { return new IllidanStormrageBossSpawnsAddsTrigger(botAI); } + + static Trigger* illidan_stormrage_maiev_placed_shadow_trap( + PlayerbotAI* botAI) { return new IllidanStormrageMaievPlacedShadowTrapTrigger(botAI); } + + static Trigger* illidan_stormrage_need_to_manage_dps_timer_and_rti( + PlayerbotAI* botAI) { return new IllidanStormrageNeedToManageDpsTimerAndRtiTrigger(botAI); } + + static Trigger* illidan_stormrage_need_to_clear_hazards_between_phases( + PlayerbotAI* botAI) { return new IllidanStormrageNeedToClearHazardsBetweenPhasesTrigger(botAI); } + + static Trigger* illidan_stormrage_cheat( + PlayerbotAI* botAI) { return new IllidanStormrageCheatTrigger(botAI); } +}; + +#endif diff --git a/src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.cpp b/src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.cpp new file mode 100644 index 00000000000..157469895a4 --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidBlackTempleStrategy.h" + +#include "RaidBlackTempleMultipliers.h" + +void RaidBlackTempleStrategy::InitTriggers(std::vector& triggers) +{ + // General + triggers.push_back(new TriggerNode("black temple bot is not in combat", { + NextAction("black temple erase timers and trackers", ACTION_EMERGENCY + 11) })); + + // High Warlord Naj'entus + triggers.push_back(new TriggerNode("high warlord naj'entus pulling boss", { + NextAction("high warlord naj'entus misdirect boss to main tank", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("high warlord naj'entus boss engaged by tanks", { + NextAction("high warlord naj'entus tanks position boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("high warlord naj'entus casts needle spines", { + NextAction("high warlord naj'entus disperse ranged", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("high warlord naj'entus player is impaled", { + NextAction("high warlord naj'entus remove impaling spine", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("high warlord naj'entus boss has tidal shield", { + NextAction("high warlord naj'entus throw impaling spine", ACTION_RAID + 2) })); + + // Supremus + triggers.push_back(new TriggerNode("supremus pulling boss or changing phase", { + NextAction("supremus misdirect boss to main tank", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("supremus boss engaged by ranged", { + NextAction("supremus disperse ranged", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("supremus boss is fixated on bot", { + NextAction("supremus kite boss", ACTION_EMERGENCY + 7) })); + + triggers.push_back(new TriggerNode("supremus volcano is nearby", { + NextAction("supremus move away from volcanos", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("supremus need to manage phase timer", { + NextAction("supremus manage phase timer", ACTION_EMERGENCY + 10) })); + + // Shade of Akama + triggers.push_back(new TriggerNode("shade of akama killing channelers starts phase 2", { + NextAction("shade of akama melee dps prioritize channelers", ACTION_RAID + 1) })); + + // Teron Gorefiend + triggers.push_back(new TriggerNode("teron gorefiend pulling boss", { + NextAction("teron gorefiend misdirect boss to main tank", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("teron gorefiend boss engaged by tanks", { + NextAction("teron gorefiend tanks position boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("teron gorefiend boss engaged by ranged", { + NextAction("teron gorefiend position ranged on balcony", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("teron gorefiend boss is casting shadow of death", { + NextAction("teron gorefiend avoid shadow of death", ACTION_EMERGENCY + 10) })); + + triggers.push_back(new TriggerNode("teron gorefiend bot has shadow of death", { + NextAction("teron gorefiend move to corner to die", ACTION_EMERGENCY + 10) })); + + triggers.push_back(new TriggerNode("teron gorefiend bot transformed into vengeful spirit", { + NextAction("teron gorefiend control and destroy shadowy constructs", ACTION_EMERGENCY + 10) })); + + // Gurtogg Bloodboil + triggers.push_back(new TriggerNode("gurtogg bloodboil pulling boss", { + NextAction("gurtogg bloodboil misdirect boss to main tank", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("gurtogg bloodboil boss engaged by tanks", { + NextAction("gurtogg bloodboil tanks position boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("gurtogg bloodboil boss casts bloodboil", { + NextAction("gurtogg bloodboil rotate ranged groups", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("gurtogg bloodboil bot has fel rage", { + NextAction("gurtogg bloodboil ranged move away from enraged player", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("gurtogg bloodboil need to manage phase timer", { + NextAction("gurtogg bloodboil manage phase timer", ACTION_EMERGENCY + 10) })); + + // Reliquary of Souls + triggers.push_back(new TriggerNode("reliquary of souls aggro resets upon phase change", { + NextAction("reliquary of souls misdirect boss to main tank", ACTION_RAID + 3) })); + + triggers.push_back(new TriggerNode("reliquary of souls essence of suffering fixates on closest target", { + NextAction("reliquary of souls adjust distance from suffering", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("reliquary of souls essence of suffering disables healing", { + NextAction("reliquary of souls healers dps suffering", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("reliquary of souls essence of desire has rune shield", { + NextAction("reliquary of souls spellsteal rune shield", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("reliquary of souls essence of desire casting deaden", { + NextAction("reliquary of souls spell reflect deaden", ACTION_EMERGENCY + 6) })); + + // Mother Shahraz + triggers.push_back(new TriggerNode("mother shahraz pulling boss", { + NextAction("mother shahraz misdirect boss to main tank", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("mother shahraz boss engaged by tanks", { + NextAction("mother shahraz tanks position boss under pillar", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("mother shahraz tanks are positioning boss", { + NextAction("mother shahraz melee dps wait at safe position", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("mother shahraz sinister beam knocks back players", { + NextAction("mother shahraz position ranged under pillar", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("mother shahraz bots are linked by fatal attraction", { + NextAction("mother shahraz run away to break fatal attraction", ACTION_EMERGENCY + 10) })); + + // Illidari Council + triggers.push_back(new TriggerNode("illidari council pulling bosses", { + NextAction("illidari council misdirect bosses to tanks", ACTION_RAID + 4) })); + + triggers.push_back(new TriggerNode("illidari council gathios engaged by main tank", { + NextAction("illidari council main tank position gathios", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("illidari council gathios casting judgement of command", { + NextAction("illidari council main tank reflect judgement of command", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("illidari council malande engaged by first assist tank", { + NextAction("illidari council first assist tank focus malande", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("illidari council darkshadow engaged by second assist tank", { + NextAction("illidari council second assist tank position darkshadow", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("illidari council zerevor engaged by mage tank", { + NextAction("illidari council mage tank position zerevor", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("illidari council mage tank needs dedicated healer", { + NextAction("illidari council position mage tank healer", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("illidari council zerevor casts dangerous aoes", { + NextAction("illidari council disperse ranged", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("illidari council pets screw up the pull", { + NextAction("illidari council command pets to attack gathios", ACTION_RAID + 3) })); + + triggers.push_back(new TriggerNode("illidari council determining dps assignments", { + NextAction("illidari council assign dps targets", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("illidari council need to manage dps timer", { + NextAction("illidari council manage dps timer", ACTION_EMERGENCY + 10) })); + + // Illidan Stormrage + triggers.push_back(new TriggerNode("illidan stormrage tank needs aggro", { + NextAction("illidan stormrage misdirect to tank", ACTION_RAID + 3) })); + + triggers.push_back(new TriggerNode("illidan stormrage boss casts flame crash in front of main tank", { + NextAction("illidan stormrage main tank reposition boss", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("illidan stormrage bot has parasitic shadowfiend", { + NextAction("illidan stormrage isolate bot with parasite", ACTION_RAID + 3) })); + + triggers.push_back(new TriggerNode("illidan stormrage parasitic shadowfiends run wild", { + NextAction("illidan stormrage set earthbind totem", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("illidan stormrage boss summoned flames of azzinoth", { + NextAction("illidan stormrage assist tanks handle flames of azzinoth", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("illidan stormrage pets die to fire", { + NextAction("illidan stormrage control pet aggression", ACTION_RAID + 4) })); + + triggers.push_back(new TriggerNode("illidan stormrage grate is safe from flames", { + NextAction("illidan stormrage position above grate", ACTION_EMERGENCY + 2) })); + + triggers.push_back(new TriggerNode("illidan stormrage bot struck by dark barrage", { + NextAction("illidan stormrage remove dark barrage", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("illidan stormrage boss is preparing to land", { + NextAction("illidan stormrage move away from landing point", ACTION_EMERGENCY + 3) })); + + triggers.push_back(new TriggerNode("illidan stormrage boss deals splash damage", { + NextAction("illidan stormrage disperse ranged", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("illidan stormrage this expansion hates melee", { + NextAction("illidan stormrage melee go somewhere to not die", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("illidan stormrage boss transforms into demon", { + NextAction("illidan stormrage warlock tank handle demon boss", ACTION_EMERGENCY + 9) })); + + triggers.push_back(new TriggerNode("illidan stormrage boss spawns adds", { + NextAction("illidan stormrage dps prioritize adds", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("illidan stormrage maiev placed shadow trap", { + NextAction("illidan stormrage use shadow trap", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("illidan stormrage need to manage dps timer and rti", { + NextAction("illidan stormrage manage dps timer and rti", ACTION_EMERGENCY + 11) })); + + triggers.push_back(new TriggerNode("illidan stormrage need to clear hazards between phases", { + NextAction("illidan stormrage destroy hazards", ACTION_EMERGENCY + 10) })); + + triggers.push_back(new TriggerNode("illidan stormrage cheat", { + NextAction("illidan stormrage handle adds cheat", ACTION_EMERGENCY + 10) })); +} + +void RaidBlackTempleStrategy::InitMultipliers(std::vector& multipliers) +{ + // High Warlord Naj'entus + multipliers.push_back(new HighWarlordNajentusDelayDpsCooldownsMultiplier(botAI)); + multipliers.push_back(new HighWarlordNajentusDisableCombatFormationMoveMultiplier(botAI)); + + // Supremus + multipliers.push_back(new SupremusDelayDpsCooldownsMultiplier(botAI)); + multipliers.push_back(new SupremusFocusOnAvoidanceInPhase2Multiplier(botAI)); + multipliers.push_back(new SupremusHitboxIsBuggedMultiplier(botAI)); + + // Teron Gorefiend + multipliers.push_back(new TeronGorefiendDelayDpsCooldownsMultiplier(botAI)); + multipliers.push_back(new TeronGorefiendControlMovementMultiplier(botAI)); + multipliers.push_back(new TeronGorefiendMarkedBotOnlyMoveToDieMultiplier(botAI)); + multipliers.push_back(new TeronGorefiendSpiritsAttackOnlyShadowyConstructsMultiplier(botAI)); + multipliers.push_back(new TeronGorefiendDisableAttackingConstructsMultiplier(botAI)); + + // Gurtogg Bloodboil + multipliers.push_back(new GurtoggBloodboilDelayDpsCooldownsMultiplier(botAI)); + multipliers.push_back(new GurtoggBloodboilControlMovementMultiplier(botAI)); + + // Reliquary of Souls + multipliers.push_back(new ReliquaryOfSoulsDelayDpsCooldownsMultiplier(botAI)); + multipliers.push_back(new ReliquaryOfSoulsDontWasteHealingMultiplier(botAI)); + + // Mother Shahraz + multipliers.push_back(new MotherShahrazDelayDpsCooldownsMultiplier(botAI)); + multipliers.push_back(new MotherShahrazControlMovementMultiplier(botAI)); + multipliers.push_back(new MotherShahrazBotsWithFatalAttractionOnlyRunAwayMultiplier(botAI)); + + // Illidari Council + multipliers.push_back(new IllidariCouncilDelayDpsCooldownsMultiplier(botAI)); + multipliers.push_back(new IllidariCouncilDisableTankActionsMultiplier(botAI)); + multipliers.push_back(new IllidariCouncilControlMovementMultiplier(botAI)); + multipliers.push_back(new IllidariCouncilControlMisdirectionMultiplier(botAI)); + multipliers.push_back(new IllidariCouncilDisableArcaneShotOnZerevorMultiplier(botAI)); + multipliers.push_back(new IllidariCouncilDisableIceBlockMultiplier(botAI)); + multipliers.push_back(new IllidariCouncilWaitForDpsMultiplier(botAI)); + + // Illidan Stormrage + multipliers.push_back(new IllidanStormrageDelayDpsCooldownsMultiplier(botAI)); + multipliers.push_back(new IllidanStormrageControlTankActionsMultiplier(botAI)); + multipliers.push_back(new IllidanStormrageDisableDefaultTargetingMultiplier(botAI)); + multipliers.push_back(new IllidanStormrageControlNonTankMovementMultiplier(botAI)); + multipliers.push_back(new IllidanStormrageUseEarthbindTotemMultiplier(botAI)); + multipliers.push_back(new IllidanStormrageWaitForDpsMultiplier(botAI)); +} diff --git a/src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.h b/src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.h new file mode 100644 index 00000000000..2f1cc4205af --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDBLACKTEMPLESTRATEGY_H_ +#define _PLAYERBOT_RAIDBLACKTEMPLESTRATEGY_H_ + +#include "Strategy.h" + +class RaidBlackTempleStrategy : public Strategy +{ +public: + RaidBlackTempleStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + std::string const getName() override { return "blacktemple"; } + + void InitTriggers(std::vector& triggers) override; + void InitMultipliers(std::vector& multipliers) override; +}; + +#endif diff --git a/src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.cpp b/src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.cpp new file mode 100644 index 00000000000..388e28d8b39 --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.cpp @@ -0,0 +1,863 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidBlackTempleTriggers.h" + +#include "AiFactory.h" +#include "Playerbots.h" +#include "RaidBlackTempleActions.h" +#include "RaidBlackTempleHelpers.h" +#include "RaidBossHelpers.h" +#include "SharedDefines.h" + +using namespace BlackTempleHelpers; + +// General + +bool BlackTempleBotIsNotInCombatTrigger::IsActive() +{ + return !bot->IsInCombat() && bot->GetMapId() == BLACK_TEMPLE_MAP_ID; +} + +// High Warlord Naj'entus + +bool HighWarlordNajentusPullingBossTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* najentus = AI_VALUE2(Unit*, "find target", "high warlord naj'entus"); + return najentus && najentus->GetHealthPct() > 95.0f; +} + +bool HighWarlordNajentusBossEngagedByTanksTrigger::IsActive() +{ + return botAI->IsTank(bot) && + AI_VALUE2(Unit*, "find target", "high warlord naj'entus"); +} + +bool HighWarlordNajentusCastsNeedleSpinesTrigger::IsActive() +{ + return botAI->IsRanged(bot) && + AI_VALUE2(Unit*, "find target", "high warlord naj'entus"); +} + +bool HighWarlordNajentusPlayerIsImpaledTrigger::IsActive() +{ + if (botAI->IsTank(bot)) + return false; + + if (!AI_VALUE2(Unit*, "find target", "high warlord naj'entus")) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* impaledPlayer = nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || member == bot) + continue; + + if (member->HasAura( + static_cast(BlackTempleSpells::SPELL_IMPALING_SPINE))) + { + impaledPlayer = member; + break; + } + } + + Player* closestBot = nullptr; + float closestDist = std::numeric_limits::max(); + + if (impaledPlayer) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == impaledPlayer || + !GET_PLAYERBOT_AI(member) || botAI->IsTank(member)) + { + continue; + } + + float dist = member->GetDistance(impaledPlayer); + if (dist < closestDist) + { + closestDist = dist; + closestBot = member; + } + } + } + + return closestBot == bot; +} + +bool HighWarlordNajentusBossHasTidalShieldTrigger::IsActive() +{ + Unit* najentus = AI_VALUE2(Unit*, "find target", "high warlord naj'entus"); + if (!najentus || !najentus->HasAura( + static_cast(BlackTempleSpells::SPELL_TIDAL_SHIELD))) + { + return false; + } + + return botAI->HasItemInInventory( + static_cast(BlackTempleItems::ITEM_NAJENTUS_SPINE)); +} + +// Supremus + +bool SupremusPullingBossOrChangingPhaseTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* supremus = AI_VALUE2(Unit*, "find target", "supremus"); + if (!supremus) + return false; + + auto it = supremusPhaseTimer.find(supremus->GetMap()->GetInstanceId()); + if (it == supremusPhaseTimer.end()) + return false; + + const time_t now = time(nullptr); + const time_t elapsed = now - it->second; + + // Active during first 10 seconds, or during 60-70, 120-130, etc. + return (elapsed < 10) || ((elapsed % 60) < 10 && elapsed >= 60); +} + +bool SupremusBossEngagedByRangedTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* supremus = AI_VALUE2(Unit*, "find target", "supremus"); + return supremus && !supremus->HasAura( + static_cast(BlackTempleSpells::SPELL_SNARE_SELF)); +} + +bool SupremusBossIsFixatedOnBotTrigger::IsActive() +{ + Unit* supremus = AI_VALUE2(Unit*, "find target", "supremus"); + return supremus && supremus->GetVictim() == bot && + supremus->HasAura(static_cast( + BlackTempleSpells::SPELL_SNARE_SELF)); +} + +bool SupremusVolcanoIsNearbyTrigger::IsActive() +{ + return AI_VALUE2(Unit*, "find target", "supremus") && + HasSupremusVolcanoNearby(botAI, bot); +} + +bool SupremusNeedToManagePhaseTimerTrigger::IsActive() +{ + if (!botAI->IsDps(bot)) + return false; + + if (!AI_VALUE2(Unit*, "find target", "supremus")) + return false; + + return IsMechanicTrackerBot(botAI, bot, BLACK_TEMPLE_MAP_ID); +} + +// Shade of Akama + +bool ShadeOfAkamaKillingChannelersStartsPhase2Trigger::IsActive() +{ + if (!botAI->IsDps(bot) || !botAI->IsMelee(bot)) + return false; + + constexpr float searchRadius = 30.0f; + Unit* channeler = bot->FindNearestCreature( + static_cast(BlackTempleNpcs::NPC_ASHTONGUE_CHANNELER), + searchRadius, true); + + return channeler && !channeler->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE); +} + +// Teron Gorefiend + +bool TeronGorefiendPullingBossTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* gorefiend = + AI_VALUE2(Unit*, "find target", "teron gorefiend"); + + return gorefiend && gorefiend->GetHealthPct() > 95.0f; +} + +bool TeronGorefiendBossEngagedByTanksTrigger::IsActive() +{ + return botAI->IsTank(bot) && + AI_VALUE2(Unit*, "find target", "teron gorefiend"); +} + +bool TeronGorefiendBossEngagedByRangedTrigger::IsActive() +{ + return botAI->IsRanged(bot) && + AI_VALUE2(Unit*, "find target", "teron gorefiend"); +} + +bool TeronGorefiendBossIsCastingShadowOfDeathTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER && bot->getClass() != CLASS_MAGE && + bot->getClass() != CLASS_PALADIN && bot->getClass() != CLASS_ROGUE) + { + return false; + } + + Unit* gorefiend = AI_VALUE2(Unit*, "find target", "teron gorefiend"); + if (!gorefiend) + return false; + + if (botAI->HasAura("feign death", bot)) + { + botAI->RemoveAura("feign death"); + return true; + } + else if (botAI->HasAura("ice block", bot)) + { + botAI->RemoveAura("ice block"); + return true; + } + else if (!botAI->IsHeal(bot) && botAI->HasAura("divine shield", bot)) + { + botAI->RemoveAura("divine shield"); + return true; + } + + if (!gorefiend->HasUnitState(UNIT_STATE_CASTING)) + return false; + + Spell* spell = gorefiend->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (!spell || spell->m_spellInfo->Id != + static_cast(BlackTempleSpells::SPELL_SHADOW_OF_DEATH)) + { + return false; + } + + Unit* target = spell->m_targets.GetUnitTarget(); + return target && target->GetGUID() == bot->GetGUID(); +} + +bool TeronGorefiendBotHasShadowOfDeathTrigger::IsActive() +{ + Aura* aura = bot->GetAura( + static_cast(BlackTempleSpells::SPELL_SHADOW_OF_DEATH)); + return aura && aura->GetDuration() < 12000; +} + +bool TeronGorefiendBotTransformedIntoVengefulSpiritTrigger::IsActive() +{ + return bot->HasAura( + static_cast(BlackTempleSpells::SPELL_SPIRITUAL_VENGEANCE)); +} + +// Gurtogg Bloodboil + +bool GurtoggBloodboilPullingBossTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* gurtogg = AI_VALUE2(Unit*, "find target", "gurtogg bloodboil"); + if (!gurtogg) + return false; + + auto it = gurtoggPhaseTimer.find(gurtogg->GetMap()->GetInstanceId()); + if (it == gurtoggPhaseTimer.end()) + return false; + + const time_t elapsed = std::time(nullptr) - it->second; + return elapsed < 10; +} + +bool GurtoggBloodboilBossEngagedByTanksTrigger::IsActive() +{ + if (!botAI->IsTank(bot)) + return false; + + Unit* gurtogg = AI_VALUE2(Unit*, "find target", "gurtogg bloodboil"); + return gurtogg && !gurtogg->HasAura( + static_cast(BlackTempleSpells::SPELL_BOSS_FEL_RAGE)); +} + +bool GurtoggBloodboilBossCastsBloodboilTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* gurtogg = AI_VALUE2(Unit*, "find target", "gurtogg bloodboil"); + return gurtogg && !gurtogg->HasAura( + static_cast(BlackTempleSpells::SPELL_BOSS_FEL_RAGE)); +} + +bool GurtoggBloodboilBotHasFelRageTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* gurtogg = AI_VALUE2(Unit*, "find target", "gurtogg bloodboil"); + if (!gurtogg || !gurtogg->HasAura( + static_cast(BlackTempleSpells::SPELL_BOSS_FEL_RAGE))) + { + return false; + } + + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->HasAura + (static_cast(BlackTempleSpells::SPELL_PLAYER_FEL_RAGE))) + { + return true; + } + } + } + + return false; +} + +bool GurtoggBloodboilNeedToManagePhaseTimerTrigger::IsActive() +{ + return AI_VALUE2(Unit*, "find target", "gurtogg bloodboil") && + IsMechanicTrackerBot(botAI, bot, BLACK_TEMPLE_MAP_ID); +} + +// Reliquary of Souls + +bool ReliquaryOfSoulsAggroResetsUponPhaseChangeTrigger::IsActive() +{ + return bot->getClass() == CLASS_HUNTER && + AI_VALUE2(Unit*, "find target", "reliquary of the lost"); +} + +bool ReliquaryOfSoulsEssenceOfSufferingFixatesOnClosestTargetTrigger::IsActive() +{ + return AI_VALUE2(Unit*, "find target", "essence of suffering"); +} + +bool ReliquaryOfSoulsEssenceOfSufferingDisablesHealingTrigger::IsActive() +{ + if (!botAI->IsHeal(bot)) + return false; + + if (bot->getClass() == CLASS_PRIEST && + AiFactory::GetPlayerSpecTab(bot) == PRIEST_TAB_DISCIPLINE) + { + return false; + } + + return AI_VALUE2(Unit*, "find target", "essence of suffering"); +} + +bool ReliquaryOfSoulsEssenceOfDesireHasRuneShieldTrigger::IsActive() +{ + if (bot->getClass() != CLASS_MAGE) + return false; + + Unit* desire = AI_VALUE2(Unit*, "find target", "essence of desire"); + return desire && desire->HasAura( + static_cast(BlackTempleSpells::SPELL_RUNE_SHIELD)); +} + +bool ReliquaryOfSoulsEssenceOfDesireCastingDeadenTrigger::IsActive() +{ + if (!botAI->IsTank(bot) || bot->getClass() != CLASS_WARRIOR) + return false; + + Unit* desire = AI_VALUE2(Unit*, "find target", "essence of desire"); + if (!desire || !desire->HasUnitState(UNIT_STATE_CASTING)) + return false; + + Spell* spell = desire->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (!spell || spell->m_spellInfo->Id != + static_cast(BlackTempleSpells::SPELL_DEADEN)) + { + return false; + } + + Unit* target = spell->m_targets.GetUnitTarget(); + return target && target->GetGUID() == bot->GetGUID(); +} + +// Mother Shahraz + +bool MotherShahrazPullingBossTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* shahraz = AI_VALUE2(Unit*, "find target", "mother shahraz"); + return shahraz && shahraz->GetHealthPct() > 95.0f; +} + +bool MotherShahrazBossEngagedByTanksTrigger::IsActive() +{ + if (!botAI->IsTank(bot)) + return false; + + if (!AI_VALUE2(Unit*, "find target", "mother shahraz")) + return false; + + return !bot->HasAura( + static_cast(BlackTempleSpells::SPELL_FATAL_ATTRACTION)); +} + +bool MotherShahrazTanksArePositioningBossTrigger::IsActive() +{ + if (!botAI->IsMelee(bot) || !botAI->IsDps(bot)) + return false; + + Unit* shahraz = AI_VALUE2(Unit*, "find target", "mother shahraz"); + if (!shahraz || shahraz->GetHealthPct() < 90.0f) + return false; + + TankPositionState tankState = GetShahrazTankPositionState(botAI, bot); + return tankState != TankPositionState::Positioned; +} + +bool MotherShahrazSinisterBeamKnocksBackPlayersTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + if (!AI_VALUE2(Unit*, "find target", "mother shahraz")) + return false; + + return !bot->HasAura( + static_cast(BlackTempleSpells::SPELL_FATAL_ATTRACTION)); +} + +bool MotherShahrazBotsAreLinkedByFatalAttractionTrigger::IsActive() +{ + return bot->HasAura( + static_cast(BlackTempleSpells::SPELL_FATAL_ATTRACTION)); +} + +// Illidari Council + +bool IllidariCouncilPullingBossesTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* gathios = AI_VALUE2(Unit*, "find target", "gathios the shatterer"); + return gathios && gathios->GetHealthPct() > 95.0f; +} + +bool IllidariCouncilGathiosEngagedByMainTankTrigger::IsActive() +{ + return botAI->IsMainTank(bot) && + AI_VALUE2(Unit*, "find target", "gathios the shatterer"); +} + +bool IllidariCouncilGathiosCastingJudgementOfCommandTrigger::IsActive() +{ + if (bot->getClass() != CLASS_WARRIOR || !botAI->IsMainTank(bot)) + return false; + + Unit* gathios = AI_VALUE2(Unit*, "find target", "gathios the shatterer"); + if (!gathios || !gathios->HasUnitState(UNIT_STATE_CASTING) || !gathios->HasAura( + static_cast(BlackTempleSpells::SPELL_SEAL_OF_COMMAND))) + { + return false; + } + + Spell* spell = gathios->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (!spell || spell->m_spellInfo->Id != + static_cast(BlackTempleSpells::SPELL_JUDGEMENT)) + { + return false; + } + + Unit* target = spell->m_targets.GetUnitTarget(); + return target && target->GetGUID() == bot->GetGUID(); +} + +bool IllidariCouncilMalandeEngagedByFirstAssistTankTrigger::IsActive() +{ + return botAI->IsAssistTankOfIndex(bot, 0, false) && + AI_VALUE2(Unit*, "find target", "lady malande"); +} + +bool IllidariCouncilDarkshadowEngagedBySecondAssistTankTrigger::IsActive() +{ + if (!botAI->IsAssistTankOfIndex(bot, 1, false)) + return false; + + Unit* darkshadow = AI_VALUE2(Unit*, "find target", "veras darkshadow"); + return darkshadow && !darkshadow->HasAura( + static_cast(BlackTempleSpells::SPELL_VANISH)); +} + +bool IllidariCouncilZerevorEngagedByMageTankTrigger::IsActive() +{ + if (bot->getClass() != CLASS_MAGE || GetZerevorMageTank(bot) != bot) + return false; + + return AI_VALUE2(Unit*, "find target", "high nethermancer zerevor"); +} + +bool IllidariCouncilMageTankNeedsDedicatedHealerTrigger::IsActive() +{ + return botAI->IsAssistHealOfIndex(bot, 0, true) && + AI_VALUE2(Unit*, "find target", "high nethermancer zerevor"); +} + +bool IllidariCouncilZerevorCastsDangerousAoesTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + if (!AI_VALUE2(Unit*, "find target", "high nethermancer zerevor")) + return false; + + return !HasDangerousCouncilAura(bot); +} + +bool IllidariCouncilPetsScrewUpThePullTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER && bot->getClass() != CLASS_WARLOCK) + return false; + + Pet* pet = bot->GetPet(); + if (!pet || !pet->IsAlive()) + return false; + + return AI_VALUE2(Unit*, "find target", "gathios the shatterer"); +} + +bool IllidariCouncilDeterminingDpsAssignmentsTrigger::IsActive() +{ + if (botAI->IsHeal(bot)) + return false; + + if (!AI_VALUE2(Unit*, "find target", "gathios the shatterer")) + return false; + + if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0, false) || + GetZerevorMageTank(bot) == bot) + { + return false; + } + + Unit* darkshadow = AI_VALUE2(Unit*, "find target", "veras darkshadow"); + if (botAI->IsTank(bot) && botAI->IsAssistTankOfIndex(bot, 1, false) && + darkshadow && !darkshadow->HasAura( + static_cast(BlackTempleSpells::SPELL_VANISH))) + { + return false; + } + + return true; +} + +bool IllidariCouncilNeedToManageDpsTimerTrigger::IsActive() +{ + if (!botAI->IsDps(bot)) + return false; + + if (!AI_VALUE2(Unit*, "find target", "gathios the shatterer")) + return false; + + return IsMechanicTrackerBot( + botAI, bot, BLACK_TEMPLE_MAP_ID, GetZerevorMageTank(bot)); +} + +// Illidan Stormrage + +bool IllidanStormrageTankNeedsAggroTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + return illidan && illidan->GetHealth() > 1; +} + +bool IllidanStormrageBossCastsFlameCrashInFrontOfMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + int phase = GetIllidanPhase(illidan); + return phase == 1 || phase == 3 || phase == 5; +} + +bool IllidanStormrageBotHasParasiticShadowfiendTrigger::IsActive() +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1 || + illidan->GetVictim() == bot) + { + return false; + } + + int phase = GetIllidanPhase(illidan); + if (phase == 2 || phase == 4) + return false; + + if (botAI->IsMainTank(bot)) + return false; + + if (phase == 5 && FindNearestTrap(botAI, bot)) + return false; + + Player* infected = GetBotWithParasiticShadowfiend(bot); + if (!infected) + return false; + + if (infected == bot || + (phase != 1 && bot->getClass() == CLASS_HUNTER)) + { + return true; + } + + return false; +} + +bool IllidanStormrageParasiticShadowfiendsRunWildTrigger::IsActive() +{ + if (bot->getClass() != CLASS_SHAMAN) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1 || GetIllidanPhase(illidan) == 2) + return false; + + ObjectGuid guid = bot->m_SummonSlot[SUMMON_SLOT_TOTEM_EARTH]; + if (guid.IsEmpty()) + return true; + + Creature* totem = bot->GetMap()->GetCreature(guid); + return !totem || totem->GetDistance(bot) > 20.0f || + totem->GetUInt32Value(UNIT_CREATED_BY_SPELL) != + static_cast(BlackTempleSpells::SPELL_EARTHBIND_TOTEM); +} + +bool IllidanStormrageBossSummonedFlamesOfAzzinothTrigger::IsActive() +{ + if (!botAI->IsAssistTankOfIndex(bot, 0, true) && + !botAI->IsAssistTankOfIndex(bot, 1, true)) + { + return false; + } + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + return illidan && GetIllidanPhase(illidan) == 2; +} + +bool IllidanStormragePetsDieToFireTrigger::IsActive() +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1) + return false; + + Pet* pet = bot->GetPet(); + return pet && pet->IsAlive(); +} + +bool IllidanStormrageGrateIsSafeFromFlamesTrigger::IsActive() +{ + if (botAI->IsAssistTankOfIndex(bot, 0, true) || + botAI->IsAssistTankOfIndex(bot, 1, true)) + { + return false; + } + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + return illidan && GetIllidanPhase(illidan) == 2; +} + +bool IllidanStormrageBotStruckByDarkBarrageTrigger::IsActive() +{ + if (bot->getClass() != CLASS_MAGE && bot->getClass() != CLASS_PALADIN && + bot->getClass() != CLASS_ROGUE) + { + return false; + } + + if (!AI_VALUE2(Unit*, "find target", "illidan stormrage")) + return false; + + if (botAI->HasAura("ice block", bot)) + { + botAI->RemoveAura("ice block"); + return true; + } + else if (!botAI->IsHeal(bot) && botAI->HasAura("divine shield", bot)) + { + botAI->RemoveAura("divine shield"); + return true; + } + + return bot->HasAura( + static_cast(BlackTempleSpells::SPELL_DARK_BARRAGE)); +} + +bool IllidanStormrageBossIsPreparingToLandTrigger::IsActive() +{ + if (botAI->IsMainTank(bot)) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + return illidan && GetIllidanPhase(illidan) == 0; +} + +bool IllidanStormrageBossDealsSplashDamageTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->HasAura + (static_cast(BlackTempleSpells::SPELL_CAGED))) + { + return false; + } + + int phase = GetIllidanPhase(illidan); + + if (phase == 4 && GetIllidanWarlockTank(bot) == bot) + return false; + + return phase == 3 || phase == 4 || phase == 5; +} + +bool IllidanStormrageThisExpansionHatesMeleeTrigger::IsActive() +{ + if (!botAI->IsMelee(bot)) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + return illidan && GetIllidanPhase(illidan) == 4; +} + +bool IllidanStormrageBossTransformsIntoDemonTrigger::IsActive() +{ + if (bot->getClass() != CLASS_WARLOCK) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || GetIllidanPhase(illidan) != 4) + return false; + + return GetIllidanWarlockTank(bot) == bot; +} + +bool IllidanStormrageBossSpawnsAddsTrigger::IsActive() +{ + if (botAI->IsHeal(bot)) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1) + return false; + + if (botAI->IsTank(bot) && GetIllidanPhase(illidan) != 4) + return false; + + return true; +} + +bool IllidanStormrageMaievPlacedShadowTrapTrigger::IsActive() +{ + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1 || + GetIllidanPhase(illidan) != 5) + { + return false; + } + + GameObject* trap = FindNearestTrap(botAI, bot); + if (!trap) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* closestBot = nullptr; + float closestDist = std::numeric_limits::max(); + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || illidan->GetVictim() == member || + !GET_PLAYERBOT_AI(member) || botAI->IsMainTank(member)) + { + continue; + } + + float dist = member->GetDistance(trap); + if (dist < closestDist) + { + closestDist = dist; + closestBot = member; + } + } + + return closestBot == bot; +} + +bool IllidanStormrageNeedToManageDpsTimerAndRtiTrigger::IsActive() +{ + if (!botAI->IsDps(bot)) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1) + return false; + + return IsMechanicTrackerBot( + botAI, bot, BLACK_TEMPLE_MAP_ID, GetIllidanWarlockTank(bot)); +} + +// Destroying hazards behind phases is not gated behind CheatMask +// The strategy simply cannot work without doing this +bool IllidanStormrageNeedToClearHazardsBetweenPhasesTrigger::IsActive() +{ + if (!botAI->IsDps(bot)) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan || illidan->GetHealth() == 1) + return false; + + int phase = GetIllidanPhase(illidan); + if (phase != 0 && phase != 2 && phase != 4) + return false; + + return IsMechanicTrackerBot( + botAI, bot, BLACK_TEMPLE_MAP_ID, GetIllidanWarlockTank(bot)); +} + +bool IllidanStormrageCheatTrigger::IsActive() +{ + if (!botAI->HasCheat(BotCheatMask::raid) || !botAI->IsDps(bot)) + return false; + + Unit* illidan = AI_VALUE2(Unit*, "find target", "illidan stormrage"); + if (!illidan) + return false; + + int phase = GetIllidanPhase(illidan); + return phase == 2 || phase == 4; +} diff --git a/src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.h b/src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.h new file mode 100644 index 00000000000..0113b4b149f --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.h @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDBLACKTEMPLETRIGGERS_H +#define _PLAYERBOT_RAIDBLACKTEMPLETRIGGERS_H + +#include "Trigger.h" + +// General + +class BlackTempleBotIsNotInCombatTrigger : public Trigger +{ +public: + BlackTempleBotIsNotInCombatTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "black temple bot is not in combat") {} + bool IsActive() override; +}; + +// High Warlord Naj'entus + +class HighWarlordNajentusPullingBossTrigger : public Trigger +{ +public: + HighWarlordNajentusPullingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "high warlord naj'entus pulling boss") {} + bool IsActive() override; +}; + +class HighWarlordNajentusBossEngagedByTanksTrigger : public Trigger +{ +public: + HighWarlordNajentusBossEngagedByTanksTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "high warlord naj'entus boss engaged by tanks") {} + bool IsActive() override; +}; + +class HighWarlordNajentusCastsNeedleSpinesTrigger : public Trigger +{ +public: + HighWarlordNajentusCastsNeedleSpinesTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "high warlord naj'entus casts needle spines") {} + bool IsActive() override; +}; + +class HighWarlordNajentusPlayerIsImpaledTrigger : public Trigger +{ +public: + HighWarlordNajentusPlayerIsImpaledTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "high warlord naj'entus player is impaled") {} + bool IsActive() override; +}; + +class HighWarlordNajentusBossHasTidalShieldTrigger : public Trigger +{ +public: + HighWarlordNajentusBossHasTidalShieldTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "high warlord naj'entus boss has tidal shield") {} + bool IsActive() override; +}; + +// Supremus + +class SupremusPullingBossOrChangingPhaseTrigger : public Trigger +{ +public: + SupremusPullingBossOrChangingPhaseTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "supremus pulling boss or changing phase") {} + bool IsActive() override; +}; + +class SupremusBossEngagedByRangedTrigger : public Trigger +{ +public: + SupremusBossEngagedByRangedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "supremus boss engaged by ranged") {} + bool IsActive() override; +}; + +class SupremusBossIsFixatedOnBotTrigger : public Trigger +{ +public: + SupremusBossIsFixatedOnBotTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "supremus boss is fixated on bot") {} + bool IsActive() override; +}; + +class SupremusVolcanoIsNearbyTrigger : public Trigger +{ +public: + SupremusVolcanoIsNearbyTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "supremus volcano is nearby") {} + bool IsActive() override; +}; + +class SupremusNeedToManagePhaseTimerTrigger : public Trigger +{ +public: + SupremusNeedToManagePhaseTimerTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "supremus need to manage phase timer") {} + bool IsActive() override; +}; + +// Shade of Akama + +class ShadeOfAkamaKillingChannelersStartsPhase2Trigger : public Trigger +{ +public: + ShadeOfAkamaKillingChannelersStartsPhase2Trigger( + PlayerbotAI* botAI) : Trigger(botAI, "shade of akama killing channelers starts phase 2") {} + bool IsActive() override; +}; + +// Teron Gorefiend +class TeronGorefiendPullingBossTrigger : public Trigger +{ +public: + TeronGorefiendPullingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "teron gorefiend pulling boss") {} + bool IsActive() override; +}; + +class TeronGorefiendBossEngagedByTanksTrigger : public Trigger +{ +public: + TeronGorefiendBossEngagedByTanksTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "teron gorefiend boss engaged by tanks") {} + bool IsActive() override; +}; + +class TeronGorefiendBossEngagedByRangedTrigger : public Trigger +{ +public: + TeronGorefiendBossEngagedByRangedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "teron gorefiend boss engaged by ranged") {} + bool IsActive() override; +}; + +class TeronGorefiendBossIsCastingShadowOfDeathTrigger : public Trigger +{ +public: + TeronGorefiendBossIsCastingShadowOfDeathTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "teron gorefiend boss is casting shadow of death") {} + bool IsActive() override; +}; + +class TeronGorefiendBotHasShadowOfDeathTrigger : public Trigger +{ +public: + TeronGorefiendBotHasShadowOfDeathTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "teron gorefiend bot has shadow of death") {} + bool IsActive() override; +}; + +class TeronGorefiendBotTransformedIntoVengefulSpiritTrigger : public Trigger +{ +public: + TeronGorefiendBotTransformedIntoVengefulSpiritTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "teron gorefiend bot transformed into vengeful spirit") {} + bool IsActive() override; +}; + +// Gurtogg Bloodboil + +class GurtoggBloodboilPullingBossTrigger : public Trigger +{ +public: + GurtoggBloodboilPullingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "gurtogg bloodboil pulling boss") {} + bool IsActive() override; +}; + +class GurtoggBloodboilBossEngagedByTanksTrigger : public Trigger +{ +public: + GurtoggBloodboilBossEngagedByTanksTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "gurtogg bloodboil boss engaged by tanks") {} + bool IsActive() override; +}; + +class GurtoggBloodboilBossCastsBloodboilTrigger : public Trigger +{ +public: + GurtoggBloodboilBossCastsBloodboilTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "gurtogg bloodboil boss casts bloodboil") {} + bool IsActive() override; +}; + +class GurtoggBloodboilBotHasFelRageTrigger : public Trigger +{ +public: + GurtoggBloodboilBotHasFelRageTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "gurtogg bloodboil bot has fel rage") {} + bool IsActive() override; +}; + +class GurtoggBloodboilNeedToManagePhaseTimerTrigger : public Trigger +{ +public: + GurtoggBloodboilNeedToManagePhaseTimerTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "gurtogg bloodboil need to manage phase timer") {} + bool IsActive() override; +}; + +// Reliquary of Souls + +class ReliquaryOfSoulsAggroResetsUponPhaseChangeTrigger : public Trigger +{ +public: + ReliquaryOfSoulsAggroResetsUponPhaseChangeTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "reliquary of souls aggro resets upon phase change") {} + bool IsActive() override; +}; + +class ReliquaryOfSoulsEssenceOfSufferingFixatesOnClosestTargetTrigger : public Trigger +{ +public: + ReliquaryOfSoulsEssenceOfSufferingFixatesOnClosestTargetTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "reliquary of souls essence of suffering fixates on closest target") {} + bool IsActive() override; +}; + +class ReliquaryOfSoulsEssenceOfSufferingDisablesHealingTrigger : public Trigger +{ +public: + ReliquaryOfSoulsEssenceOfSufferingDisablesHealingTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "reliquary of souls essence of suffering disables healing") {} + bool IsActive() override; +}; + +class ReliquaryOfSoulsEssenceOfDesireHasRuneShieldTrigger : public Trigger +{ +public: + ReliquaryOfSoulsEssenceOfDesireHasRuneShieldTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "reliquary of souls essence of desire has rune shield") {} + bool IsActive() override; +}; + +class ReliquaryOfSoulsEssenceOfDesireCastingDeadenTrigger : public Trigger +{ +public: + ReliquaryOfSoulsEssenceOfDesireCastingDeadenTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "reliquary of souls essence of desire casting deaden") {} + bool IsActive() override; +}; + +// Mother Shahraz + +class MotherShahrazPullingBossTrigger : public Trigger +{ +public: + MotherShahrazPullingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "mother shahraz pulling boss") {} + bool IsActive() override; +}; + +class MotherShahrazBossEngagedByTanksTrigger : public Trigger +{ +public: + MotherShahrazBossEngagedByTanksTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "mother shahraz boss engaged by tanks") {} + bool IsActive() override; +}; + +class MotherShahrazTanksArePositioningBossTrigger : public Trigger +{ +public: + MotherShahrazTanksArePositioningBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "mother shahraz tanks are positioning boss") {} + bool IsActive() override; +}; + +class MotherShahrazSinisterBeamKnocksBackPlayersTrigger : public Trigger +{ +public: + MotherShahrazSinisterBeamKnocksBackPlayersTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "mother shahraz sinister beam knocks back players") {} + bool IsActive() override; +}; + +class MotherShahrazBotsAreLinkedByFatalAttractionTrigger : public Trigger +{ +public: + MotherShahrazBotsAreLinkedByFatalAttractionTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "mother shahraz bots are linked by fatal attraction") {} + bool IsActive() override; +}; + +// Illidari Council + +class IllidariCouncilPullingBossesTrigger : public Trigger +{ +public: + IllidariCouncilPullingBossesTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council pulling bosses") {} + bool IsActive() override; +}; + +class IllidariCouncilGathiosEngagedByMainTankTrigger : public Trigger +{ +public: + IllidariCouncilGathiosEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council gathios engaged by main tank") {} + bool IsActive() override; +}; + +class IllidariCouncilGathiosCastingJudgementOfCommandTrigger : public Trigger +{ +public: + IllidariCouncilGathiosCastingJudgementOfCommandTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council gathios casting judgement of command") {} + bool IsActive() override; +}; + +class IllidariCouncilMalandeEngagedByFirstAssistTankTrigger : public Trigger +{ +public: + IllidariCouncilMalandeEngagedByFirstAssistTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council malande engaged by first assist tank") {} + bool IsActive() override; +}; + +class IllidariCouncilDarkshadowEngagedBySecondAssistTankTrigger : public Trigger +{ +public: + IllidariCouncilDarkshadowEngagedBySecondAssistTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council darkshadow engaged by second assist tank") {} + bool IsActive() override; +}; + +class IllidariCouncilZerevorEngagedByMageTankTrigger : public Trigger +{ +public: + IllidariCouncilZerevorEngagedByMageTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council zerevor engaged by mage tank") {} + bool IsActive() override; +}; + +class IllidariCouncilMageTankNeedsDedicatedHealerTrigger : public Trigger +{ +public: + IllidariCouncilMageTankNeedsDedicatedHealerTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council mage tank needs dedicated healer") {} + bool IsActive() override; +}; + +class IllidariCouncilZerevorCastsDangerousAoesTrigger : public Trigger +{ +public: + IllidariCouncilZerevorCastsDangerousAoesTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council zerevor casts dangerous aoes") {} + bool IsActive() override; +}; + +class IllidariCouncilPetsScrewUpThePullTrigger : public Trigger +{ +public: + IllidariCouncilPetsScrewUpThePullTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council pets screw up the pull") {} + bool IsActive() override; +}; + +class IllidariCouncilNeedToManageDpsTimerTrigger : public Trigger +{ +public: + IllidariCouncilNeedToManageDpsTimerTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council need to manage dps timer") {} + bool IsActive() override; +}; + +class IllidariCouncilDeterminingDpsAssignmentsTrigger : public Trigger +{ +public: + IllidariCouncilDeterminingDpsAssignmentsTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidari council determining dps assignments") {} + bool IsActive() override; +}; + +// Illidan Stormrage + +class IllidanStormrageTankNeedsAggroTrigger : public Trigger +{ +public: + IllidanStormrageTankNeedsAggroTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage tank needs aggro") {} + bool IsActive() override; +}; + +class IllidanStormrageBossCastsFlameCrashInFrontOfMainTankTrigger : public Trigger +{ +public: + IllidanStormrageBossCastsFlameCrashInFrontOfMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage boss casts flame crash in front of main tank") {} + bool IsActive() override; +}; + +class IllidanStormrageBotHasParasiticShadowfiendTrigger : public Trigger +{ +public: + IllidanStormrageBotHasParasiticShadowfiendTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage bot has parasitic shadowfiend") {} + bool IsActive() override; +}; + +class IllidanStormrageParasiticShadowfiendsRunWildTrigger : public Trigger +{ +public: + IllidanStormrageParasiticShadowfiendsRunWildTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage parasitic shadowfiends run wild") {} + bool IsActive() override; +}; + +class IllidanStormrageBossSummonedFlamesOfAzzinothTrigger : public Trigger +{ +public: + IllidanStormrageBossSummonedFlamesOfAzzinothTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage boss summoned flames of azzinoth") {} + bool IsActive() override; +}; + +class IllidanStormragePetsDieToFireTrigger : public Trigger +{ +public: + IllidanStormragePetsDieToFireTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage pets die to fire") {} + bool IsActive() override; +}; + +class IllidanStormrageGrateIsSafeFromFlamesTrigger : public Trigger +{ +public: + IllidanStormrageGrateIsSafeFromFlamesTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage grate is safe from flames") {} + bool IsActive() override; +}; + +class IllidanStormrageBotStruckByDarkBarrageTrigger : public Trigger +{ +public: + IllidanStormrageBotStruckByDarkBarrageTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage bot struck by dark barrage") {} + bool IsActive() override; +}; + +class IllidanStormrageBossIsPreparingToLandTrigger : public Trigger +{ +public: + IllidanStormrageBossIsPreparingToLandTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage boss is preparing to land") {} + bool IsActive() override; +}; + +class IllidanStormrageBossDealsSplashDamageTrigger : public Trigger +{ +public: + IllidanStormrageBossDealsSplashDamageTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage boss deals splash damage") {} + bool IsActive() override; +}; + +class IllidanStormrageThisExpansionHatesMeleeTrigger : public Trigger +{ +public: + IllidanStormrageThisExpansionHatesMeleeTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage this expansion hates melee") {} + bool IsActive() override; +}; + +class IllidanStormrageBossTransformsIntoDemonTrigger : public Trigger +{ +public: + IllidanStormrageBossTransformsIntoDemonTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage boss transforms into demon") {} + bool IsActive() override; +}; + +class IllidanStormrageBossSpawnsAddsTrigger : public Trigger +{ +public: + IllidanStormrageBossSpawnsAddsTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage boss spawns adds") {} + bool IsActive() override; +}; + +class IllidanStormrageMaievPlacedShadowTrapTrigger : public Trigger +{ +public: + IllidanStormrageMaievPlacedShadowTrapTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage maiev placed shadow trap") {} + bool IsActive() override; +}; + +class IllidanStormrageNeedToManageDpsTimerAndRtiTrigger : public Trigger +{ +public: + IllidanStormrageNeedToManageDpsTimerAndRtiTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage need to manage dps timer and rti") {} + bool IsActive() override; +}; + +class IllidanStormrageNeedToClearHazardsBetweenPhasesTrigger : public Trigger +{ +public: + IllidanStormrageNeedToClearHazardsBetweenPhasesTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage need to clear hazards between phases") {} + bool IsActive() override; +}; + +class IllidanStormrageCheatTrigger : public Trigger +{ +public: + IllidanStormrageCheatTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "illidan stormrage cheat") {} + bool IsActive() override; +}; + +#endif diff --git a/src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.cpp b/src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.cpp new file mode 100644 index 00000000000..75b0be0baea --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.cpp @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RaidBlackTempleHelpers.h" + +#include "Playerbots.h" +#include "RaidBossHelpers.h" + +namespace BlackTempleHelpers +{ + // High Warlord Naj'entus + const Position NAJENTUS_TANK_POSITION = { 438.515f, 772.436f, 11.931f }; + + // Supremus + std::unordered_map supremusPhaseTimer; + + bool HasSupremusVolcanoNearby(PlayerbotAI* botAI, Player* bot) + { + constexpr float searchRadius = 20.0f; + std::list creatureList; + bot->GetCreatureListWithEntryInGrid( + creatureList, static_cast( + BlackTempleNpcs::NPC_SUPREMUS_VOLCANO), searchRadius); + + for (Creature* creature : creatureList) + { + if (creature && creature->IsAlive()) + return true; + } + + return false; + } + + // Shade of Akama + const Position AKAMA_CHANNELER_POSITION = { 467.851f, 401.622f, 118.538f }; + + std::unordered_set hasReachedAkamaChannelerPosition; + + // Teron Gorefiend + const Position GOREFIEND_TANK_POSITION = { 597.653f, 402.284f, 187.090f }; + const Position GOREFIEND_DIE_POSITION = { 525.709f, 377.177f, 193.203f }; + + // Gurtogg Bloodboil + const Position GURTOGG_TANK_POSITION = { 735.987f, 272.451f, 063.554f }; + const Position GURTOGG_RANGED_POSITION = { 762.265f, 277.183f, 063.781f }; + const Position GURTOGG_SOAKER_POSITION = { 769.348f, 280.116f, 063.780f }; + + std::unordered_map gurtoggPhaseTimer; + + std::vector> GetGurtoggRangedRotationGroups(Player* bot) + { + Group* group = bot->GetGroup(); + std::vector rangedMembers; + std::vector> groups(3); + + if (!group) + return groups; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive()) + { + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (memberAI && memberAI->IsRanged(member)) + rangedMembers.push_back(member); + } + } + + for (size_t i = 0; i < rangedMembers.size(); ++i) + { + groups[i / 5].push_back(rangedMembers[i]); + if (groups[2].size() == 5) + break; + } + + return groups; + } + + int GetGurtoggActiveRotationGroup(Unit* gurtogg) + { + if (!gurtogg) + return -1; + + auto it = gurtoggPhaseTimer.find(gurtogg->GetMap()->GetInstanceId()); + if (it == gurtoggPhaseTimer.end()) + return -1; + + const time_t now = std::time(nullptr); + const time_t elapsed = now - it->second; + const int groupIndex = (elapsed % 30) / 10; // 3 groups, swapping every 10 seconds + + return groupIndex; + } + + // Mother Shahraz + const Position SHAHRAZ_TANK_POSITION = { 960.438f, 178.989f, 192.826f }; + const Position SHAHRAZ_TRANSITION_POSITION = { 951.327f, 179.550f, 192.550f }; + const Position SHAHRAZ_RANGED_POSITION = { 935.267f, 175.459f, 192.821f }; + std::unordered_map shahrazTankStep; + + TankPositionState GetShahrazTankPositionState(PlayerbotAI* botAI, Player* bot) + { + Player* mainTank = GetGroupMainTank(botAI, bot); + if (!mainTank) + return TankPositionState::Unknown; + + auto it = shahrazTankStep.find(mainTank->GetGUID()); + if (it != shahrazTankStep.end()) + return it->second; + + return TankPositionState::Unknown; + } + + // Illidari Council + const std::array GATHIOS_TANK_POSITIONS = {{ + { 662.977f, 296.246f, 271.688f }, + { 636.238f, 283.719f, 271.629f }, + { 655.571f, 261.377f, 271.687f }, + { 673.789f, 274.139f, 271.689f } + }}; + const Position ZEREVOR_TANK_POSITION = { 686.219f, 377.644f, 271.689f }; + const std::array ZEREVOR_HEALER_POSITIONS = {{ + { 661.385f, 351.219f, 271.690f }, + { 667.003f, 363.768f, 271.690f } + }}; + const Position MALANDE_TANK_POSITION = { 690.590f, 299.790f, 277.443f }; + + std::unordered_map councilDpsWaitTimer; + std::unordered_map gathiosTankStep; + std::unordered_map zerevorHealStep; + + // (1) First priority is an assistant Mage (real player or bot) + // (2) If no assistant Mage, then look for any Mage bot + Player* GetZerevorMageTank(Player* bot) + { + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + Player* fallbackMage = nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member->getClass() != CLASS_MAGE) + continue; + + if (group->IsAssistant(member->GetGUID())) + return member; + + if (!fallbackMage && GET_PLAYERBOT_AI(member)) + fallbackMage = member; + } + + return fallbackMage; + } + + bool HasDangerousCouncilAura(Unit* unit) + { + static const std::array dangerousAuras = + { + static_cast(BlackTempleSpells::SPELL_CONSECRATION), + static_cast(BlackTempleSpells::SPELL_BLIZZARD), + static_cast(BlackTempleSpells::SPELL_FLAMESTRIKE) + }; + + for (uint32 aura : dangerousAuras) + { + if (unit->HasAura(aura)) + return true; + } + + return false; + } + + // Illidan Stormrage + const Position ILLIDAN_LANDING_POSITION = { 676.648f, 304.761f, 354.189f }; + const Position ILLIDAN_N_GRATE_POSITION = { 682.100f, 306.000f, 353.192f }; + const Position ILLIDAN_E_GRATE_POSITION = { 673.500f, 298.500f, 353.192f }; + const Position ILLIDAN_W_GRATE_POSITION = { 672.400f, 312.500f, 353.192f }; + const std::array GRATE_POSITIONS = {{ + ILLIDAN_N_GRATE_POSITION, + ILLIDAN_E_GRATE_POSITION, + ILLIDAN_W_GRATE_POSITION + }}; + + const Position ILLIDAN_E_GLAIVE_WAITING_POSITION = { 677.656f, 294.066f, 353.192f }; + const std::array E_GLAIVE_TANK_POSITIONS = {{ + { 683.000f, 295.000f, 354.000f }, + { 696.969f, 300.982f, 354.302f }, + { 691.112f, 287.461f, 354.363f }, + { 676.674f, 280.797f, 354.268f }, + { 664.414f, 284.834f, 354.271f }, + { 656.826f, 295.113f, 354.165f }, + { 665.000f, 304.000f, 354.000f } + }}; + + const Position ILLIDAN_W_GLAIVE_WAITING_POSITION = { 676.102f, 316.305f, 353.192f }; + const std::array W_GLAIVE_TANK_POSITIONS = {{ + { 697.208f, 313.475f, 354.234f }, + { 681.000f, 318.000f, 354.000f }, + { 664.000f, 307.000f, 354.000f }, + { 656.161f, 314.132f, 354.092f }, + { 665.080f, 326.905f, 354.128f }, + { 678.809f, 329.968f, 354.387f }, + { 690.889f, 324.277f, 354.204f } + }}; + + std::unordered_map flameTankWaypointIndex; + std::unordered_map illidanShadowTrapGuid; + std::unordered_map illidanShadowTrapDestination; + std::unordered_map illidanLastPhase; + std::unordered_map illidanBossDpsWaitTimer; + std::unordered_map illidanFlameDpsWaitTimer; + std::unordered_map eastFlameGuid; + std::unordered_map westFlameGuid; + + int GetIllidanPhase(Unit* illidan) + { + if (!illidan || illidan->GetHealth() == 1 || illidan->HasAura( + static_cast(BlackTempleSpells::SPELL_SHADOW_PRISON))) + { + return -1; + } + + // Transitioning from Phase 2 to Phase 3 + float x, y, z; + illidan->GetMotionMaster()->GetDestination(x, y, z); + Position dest(x, y, z); + if ((dest.GetExactDist2d(ILLIDAN_LANDING_POSITION) < 0.2f || + illidan->GetExactDist2d(ILLIDAN_LANDING_POSITION) < 0.2f) && + illidan->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + { + return 0; + } + + // Phase 2: Flying + if (illidan->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + return 2; + + // Phase 1: Health > 65% + if (illidan->GetHealthPct() > 65.0f) + return 1; + + // Phase 4: Demon Form + if (!illidan->HasAura(static_cast(BlackTempleSpells::SPELL_CAGED)) && + (illidan->HasAura(static_cast(BlackTempleSpells::SPELL_DEMON_FORM)) || + illidan->HasAura(static_cast(BlackTempleSpells::SPELL_DEMON_TRANSFORM_1)) || + illidan->HasAura(static_cast(BlackTempleSpells::SPELL_DEMON_TRANSFORM_2)) || + illidan->HasAura(static_cast(BlackTempleSpells::SPELL_DEMON_TRANSFORM_3)))) + { + return 4; + } + + // Phase 3: Normal (ground, 65-30%, not demon) + if (illidan->GetHealthPct() > 30.0f) + return 3; + + // Phase 5: Health <= 30% + if (illidan->GetHealthPct() <= 30.0f) + return 5; + + return -1; + } + + std::vector GetAllFlameCrashes(Player* bot) + { + std::vector flameCrashes; + std::list creatureList; + constexpr float searchRadius = 30.0f; + bot->GetCreatureListWithEntryInGrid( + creatureList, static_cast(BlackTempleNpcs::NPC_FLAME_CRASH), searchRadius); + + for (Creature* creature : creatureList) + { + if (creature && creature->IsAlive()) + flameCrashes.push_back(creature); + } + + return flameCrashes; + } + + std::pair GetFlamesOfAzzinoth(Player* bot) + { + Unit* eastFlame = nullptr; + Unit* westFlame = nullptr; + + const uint32 instanceId = bot->GetMap()->GetInstanceId(); + + if (eastFlameGuid.find(instanceId) != eastFlameGuid.end()) + { + if (Unit* unit = ObjectAccessor::GetUnit(*bot, eastFlameGuid[instanceId])) + { + if (unit->IsAlive()) + eastFlame = unit; + } + } + + if (westFlameGuid.find(instanceId) != westFlameGuid.end()) + { + if (Unit* unit = ObjectAccessor::GetUnit(*bot, westFlameGuid[instanceId])) + { + if (unit->IsAlive()) + westFlame = unit; + } + } + + return { eastFlame, westFlame }; + } + + // (1) First priority is an assistant Warlock (real player or bot) + // (2) If no assistant Warlock, then look for any Warlock bot + Player* GetIllidanWarlockTank(Player* bot) + { + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + Player* fallbackWarlock = nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member->getClass() != CLASS_WARLOCK) + continue; + + if (group->IsAssistant(member->GetGUID())) + return member; + + if (!fallbackWarlock && GET_PLAYERBOT_AI(member)) + fallbackWarlock = member; + } + + return fallbackWarlock; + } + + bool HasParasiticShadowfiend(Player* member) + { + if (!member) + return false; + + constexpr uint32 shadowfiendAura1 = + static_cast(BlackTempleSpells::SPELL_PARASITIC_SHADOWFIEND_1); + constexpr uint32 shadowfiendAura2 = + static_cast(BlackTempleSpells::SPELL_PARASITIC_SHADOWFIEND_2); + + return member->HasAura(shadowfiendAura1) || member->HasAura(shadowfiendAura2); + } + + // Get the first bot hunter that doesn't have Parasitic Shadowfiend + Player* GetIllidanTrapperHunter(Player* bot) + { + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && + GET_PLAYERBOT_AI(member) && !HasParasiticShadowfiend(member)) + { + return member; + } + } + + return nullptr; + } + + Player* GetBotWithParasiticShadowfiend(Player* bot) + { + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && + HasParasiticShadowfiend(member)) + { + return member; + } + } + + return nullptr; + } + + EyeBlastDangerArea GetEyeBlastDangerArea(Player* bot) + { + constexpr float searchRadius = 100.0f; + std::list creatureList; + bot->GetCreatureListWithEntryInGrid( + creatureList, static_cast(BlackTempleNpcs::NPC_ILLIDAN_DB_TARGET), searchRadius); + + Creature* eyeBlastTrigger = nullptr; + for (Creature* creature : creatureList) + { + if (creature && creature->IsAlive()) + { + eyeBlastTrigger = creature; + break; + } + } + + if (!eyeBlastTrigger) + return {}; + + Position startPos = Position(eyeBlastTrigger->GetPositionX(), eyeBlastTrigger->GetPositionY(), + eyeBlastTrigger->GetPositionZ()); + + float destX, destY, destZ; + eyeBlastTrigger->GetMotionMaster()->GetDestination(destX, destY, destZ); + Position endPos(destX, destY, destZ); + + if (startPos.GetExactDist2d(endPos) < 0.1f) + return {}; + + constexpr float eyeBlastWidth = 9.0f; + return { startPos, endPos, eyeBlastWidth }; + } + + bool IsPositionInEyeBlastDangerArea(const Position& pos, const EyeBlastDangerArea& area) + { + const float dx = area.end.GetPositionX() - area.start.GetPositionX(); + const float dy = area.end.GetPositionY() - area.start.GetPositionY(); + const float length = area.start.GetExactDist2d(area.end.GetPositionX(), area.end.GetPositionY()); + + if (length < 0.1f) + return false; + + const float projectionFactor = ( + (pos.GetPositionX() - area.start.GetPositionX()) * dx + ( + pos.GetPositionY() - area.start.GetPositionY()) * dy) / (length * length); + + const float clampedProjectionFactor = std::clamp(projectionFactor, 0.0f, 1.0f); + + const float closestX = area.start.GetPositionX() + clampedProjectionFactor * dx; + const float closestY = area.start.GetPositionY() + clampedProjectionFactor * dy; + + const float distToLine = pos.GetExactDist2d(closestX, closestY); + + return distToLine < area.width; + } + + GameObject* FindNearestTrap(PlayerbotAI* botAI, Player* bot) + { + GuidVector const& gos = + botAI->GetAiObjectContext()->GetValue("nearest game objects")->Get(); + + GameObject* nearestTrap = nullptr; + for (ObjectGuid const& guid : gos) + { + GameObject* go = botAI->GetGameObject(guid); + if (go && go->isSpawned() && + go->GetEntry() == static_cast(BlackTempleObjects::GO_SHADOW_TRAP)) + { + nearestTrap = go; + break; + } + } + + return nearestTrap; + } +} diff --git a/src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.h b/src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.h new file mode 100644 index 00000000000..9ea6cb7dfb4 --- /dev/null +++ b/src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.h @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RAIDBLACKTEMPLEHELPERS_H_ +#define _PLAYERBOT_RAIDBLACKTEMPLEHELPERS_H_ + +#include +#include +#include +#include +#include + +#include "Common.h" +#include "ObjectGuid.h" +#include "Position.h" + +class GameObject; +class Player; +class PlayerbotAI; +class Unit; + +namespace BlackTempleHelpers +{ + +enum class BlackTempleSpells : uint32 +{ + // High Warlord Naj'entus + SPELL_IMPALING_SPINE = 39837, + SPELL_TIDAL_SHIELD = 39872, + + // Supremus + SPELL_SNARE_SELF = 41922, + + // Teron Gorefiend + SPELL_SHADOW_OF_DEATH = 40251, + SPELL_SPIRITUAL_VENGEANCE = 40268, + + SPELL_SPIRIT_LANCE = 40157, + SPELL_SPIRIT_CHAINS = 40175, + SPELL_SPIRIT_VOLLEY = 40314, + SPELL_SPIRIT_STRIKE = 40325, + + // Gurtogg Bloodboil + SPELL_BOSS_FEL_RAGE = 40594, + SPELL_PLAYER_FEL_RAGE = 40604, + SPELL_BLOODBOIL = 42005, + + // Reliquary of Souls + SPELL_DEADEN = 41410, + SPELL_RUNE_SHIELD = 41431, + + // Mother Shahraz + SPELL_FATAL_ATTRACTION = 41001, + + // Gathios the Shatterer + SPELL_BLESSING_OF_PROTECTION = 41450, + SPELL_BLESSING_OF_SPELL_WARDING = 41451, + SPELL_JUDGEMENT = 41467, + SPELL_SEAL_OF_COMMAND = 41469, + SPELL_CONSECRATION = 41541, + + // Veras Darkshadow + SPELL_VANISH = 41476, + + // High Nethermancer Zerevor + SPELL_DAMPEN_MAGIC = 41478, + SPELL_FLAMESTRIKE = 41481, + SPELL_BLIZZARD = 41482, + + // Illidan Stormrage + SPELL_DEMON_TRANSFORM_1 = 40511, + SPELL_DEMON_TRANSFORM_2 = 40398, + SPELL_DEMON_TRANSFORM_3 = 40510, + SPELL_DEMON_FORM = 40506, + SPELL_DARK_BARRAGE = 40585, + SPELL_SHADOW_PRISON = 40647, + SPELL_CAGED = 40695, + SPELL_PARASITIC_SHADOWFIEND_1 = 41917, // cast by Illidan (primary infection) + SPELL_PARASITIC_SHADOWFIEND_2 = 41914, // cast by Shadowfiend on contact (secondary infection) + + // Hunter + SPELL_FROST_TRAP = 13809, + SPELL_MISDIRECTION = 35079, + + // Shaman + SPELL_EARTHBIND_TOTEM = 2484, +}; + +enum class BlackTempleNpcs : uint32 +{ + // Supremus + NPC_SUPREMUS_VOLCANO = 23085, + + // Shade of Akama + NPC_ASHTONGUE_CHANNELER = 23421, + + // Teron Gorefiend + NPC_SHADOWY_CONSTRUCT = 23111, + + // Illidan Stormrage + NPC_FLAME_OF_AZZINOTH = 22997, + NPC_DEMON_FIRE = 23069, + NPC_ILLIDAN_DB_TARGET = 23070, + NPC_BLAZE = 23259, + NPC_FLAME_CRASH = 23336, + NPC_SHADOW_DEMON = 23375, + NPC_PARASITIC_SHADOWFIEND = 23498, +}; + +enum class BlackTempleItems : uint32 +{ + // High Warlord Naj'entus + ITEM_NAJENTUS_SPINE = 32408, +}; + +enum class BlackTempleObjects : uint32 +{ + // High Warlord Naj'entus + GO_NAJENTUS_SPINE = 185584, + + // Illidan Stormrage + GO_SHADOW_TRAP = 185916, +}; + +enum class TankPositionState : uint8 +{ + MovingToTransition = 0, + MovingToFinal = 1, + Positioned = 2, + Unknown = 255, +}; + +constexpr uint32 BLACK_TEMPLE_MAP_ID = 564; + +// High Warlord Naj'entus +extern const Position NAJENTUS_TANK_POSITION; + +// Supremus +extern std::unordered_map supremusPhaseTimer; +bool HasSupremusVolcanoNearby(PlayerbotAI* botAI, Player* bot); + +// Shade of Akama +extern const Position AKAMA_CHANNELER_POSITION; +extern std::unordered_set hasReachedAkamaChannelerPosition; + +// Teron Gorefiend +extern const Position GOREFIEND_TANK_POSITION; +extern const Position GOREFIEND_DIE_POSITION; + +// Gurtogg Bloodboil +extern const Position GURTOGG_TANK_POSITION; +extern const Position GURTOGG_RANGED_POSITION; +extern const Position GURTOGG_SOAKER_POSITION; +extern std::unordered_map gurtoggPhaseTimer; +std::vector> GetGurtoggRangedRotationGroups(Player* bot); +int GetGurtoggActiveRotationGroup(Unit* gurtogg); + +// Mother Shahraz +extern const Position SHAHRAZ_TANK_POSITION; +extern const Position SHAHRAZ_TRANSITION_POSITION; +extern const Position SHAHRAZ_RANGED_POSITION; +extern std::unordered_map shahrazTankStep; +TankPositionState GetShahrazTankPositionState(PlayerbotAI* botAI, Player* bot); + +// Illidari Council +constexpr float COUNCIL_FLOOR_Z_THRESHOLD = 270.000f; +extern const std::array GATHIOS_TANK_POSITIONS; +extern const Position MALANDE_TANK_POSITION; +extern const Position ZEREVOR_TANK_POSITION; +extern const std::array ZEREVOR_HEALER_POSITIONS; +extern std::unordered_map councilDpsWaitTimer; +extern std::unordered_map gathiosTankStep; +extern std::unordered_map zerevorHealStep; +Player* GetZerevorMageTank(Player* bot); +bool HasDangerousCouncilAura(Unit* unit); + +// Illidan Stormrage +extern const Position ILLIDAN_LANDING_POSITION; +extern const Position ILLIDAN_N_GRATE_POSITION; +extern const Position ILLIDAN_E_GRATE_POSITION; +extern const Position ILLIDAN_W_GRATE_POSITION; +extern const std::array GRATE_POSITIONS; +extern const Position ILLIDAN_E_GLAIVE_WAITING_POSITION; +extern const std::array E_GLAIVE_TANK_POSITIONS; +extern const Position ILLIDAN_W_GLAIVE_WAITING_POSITION; +extern const std::array W_GLAIVE_TANK_POSITIONS; +extern std::unordered_map flameTankWaypointIndex; +extern std::unordered_map illidanShadowTrapGuid; +extern std::unordered_map illidanShadowTrapDestination; +extern std::unordered_map illidanLastPhase; +extern std::unordered_map illidanBossDpsWaitTimer; +extern std::unordered_map illidanFlameDpsWaitTimer; +extern std::unordered_map eastFlameGuid; +extern std::unordered_map westFlameGuid; +int GetIllidanPhase(Unit* illidan); +std::vector GetAllFlameCrashes(Player* bot); +std::pair GetFlamesOfAzzinoth(Player* bot); +Player* GetIllidanWarlockTank(Player* bot); +bool HasParasiticShadowfiend(Player* member); +Player* GetIllidanTrapperHunter(Player* bot); +Player* GetBotWithParasiticShadowfiend(Player* bot); +struct EyeBlastDangerArea +{ + Position start; + Position end; + float width; +}; +EyeBlastDangerArea GetEyeBlastDangerArea(Player* bot); +bool IsPositionInEyeBlastDangerArea(const Position& pos, const EyeBlastDangerArea& area); +GameObject* FindNearestTrap(PlayerbotAI* botAI, Player* bot); + +} + +#endif diff --git a/src/Ai/Raid/RaidStrategyContext.h b/src/Ai/Raid/RaidStrategyContext.h index bdc76c4a731..f307c994fde 100644 --- a/src/Ai/Raid/RaidStrategyContext.h +++ b/src/Ai/Raid/RaidStrategyContext.h @@ -12,6 +12,7 @@ #include "RaidSSCStrategy.h" #include "RaidTempestKeepStrategy.h" #include "RaidHyjalSummitStrategy.h" +#include "RaidBlackTempleStrategy.h" #include "RaidZulAmanStrategy.h" #include "RaidOsStrategy.h" #include "RaidEoEStrategy.h" @@ -35,6 +36,7 @@ class RaidStrategyContext : public NamedObjectContext creators["ssc"] = &RaidStrategyContext::ssc; creators["tempestkeep"] = &RaidStrategyContext::tempestkeep; creators["hyjal"] = &RaidStrategyContext::hyjal; + creators["blacktemple"] = &RaidStrategyContext::blacktemple; creators["zulaman"] = &RaidStrategyContext::zulaman; creators["wotlk-os"] = &RaidStrategyContext::wotlk_os; creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe; @@ -55,6 +57,7 @@ class RaidStrategyContext : public NamedObjectContext static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); } static Strategy* tempestkeep(PlayerbotAI* botAI) { return new RaidTempestKeepStrategy(botAI); } static Strategy* hyjal(PlayerbotAI* botAI) { return new RaidHyjalSummitStrategy(botAI); } + static Strategy* blacktemple(PlayerbotAI* botAI) { return new RaidBlackTempleStrategy(botAI); } static Strategy* zulaman(PlayerbotAI* botAI) { return new RaidZulAmanStrategy(botAI); } static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); } static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); } diff --git a/src/Bot/Engine/BuildSharedActionContexts.cpp b/src/Bot/Engine/BuildSharedActionContexts.cpp index 4c2c8fad3cd..3cd68a20a50 100644 --- a/src/Bot/Engine/BuildSharedActionContexts.cpp +++ b/src/Bot/Engine/BuildSharedActionContexts.cpp @@ -12,6 +12,7 @@ #include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h" #include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h" #include "Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h" +#include "Ai/Raid/BlackTemple/RaidBlackTempleActionContext.h" #include "Ai/Raid/ZulAman/RaidZulAmanActionContext.h" #include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h" #include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h" @@ -36,6 +37,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList allInstanceStrategies = { - "aq20", "bwl", "karazhan", "gruulslair", "hyjal", "icc", "magtheridon", - "moltencore", "naxx", "onyxia", "ssc", "tbc-ac", "tempestkeep", "ulduar", - "voa", "wotlk-an", "wotlk-cos", "wotlk-dtk", "wotlk-eoe", "wotlk-fos", + "aq20", "blacktemple", "bwl", "gruulslair", "hyjal", "icc", "karazhan", + "magtheridon", "moltencore", "naxx", "onyxia", "ssc", "tbc-ac", "tempestkeep", + "ulduar", "voa", "wotlk-an", "wotlk-cos", "wotlk-dtk", "wotlk-eoe", "wotlk-fos", "wotlk-gd", "wotlk-hol", "wotlk-hor", "wotlk-hos", "wotlk-nex", "wotlk-occ", "wotlk-ok", "wotlk-os", "wotlk-pos", "wotlk-toc", "wotlk-uk", "wotlk-up", "wotlk-vh", "zulaman" @@ -1669,6 +1669,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) case 558: strategyName = "tbc-ac"; // Auchindoun: Auchenai Crypts break; + case 564: + strategyName = "blacktemple"; // Black Temple + break; case 565: strategyName = "gruulslair"; // Gruul's Lair break; From d4f86764bb10ced3d8848d8978b6ae1a3a6dbd01 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 16 May 2026 01:27:15 -0500 Subject: [PATCH 26/63] Fix raid group condition in SayToRaid (#2386) ## Pull Request Description PlayerbotAI::SayToRaid is not used currently but I may use it for Sunwell for bot speech during Kalecgos due to tank handoff mechanics. The function currently erroneously returns false for a bot in a raid. There's no impact on bot behavior or performance from this PR. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Bot/PlayerbotAI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 8cfbe39a386..9678e22617f 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -2877,7 +2877,7 @@ bool PlayerbotAI::SayToParty(const std::string& msg) bool PlayerbotAI::SayToRaid(const std::string& msg) { - if (!bot->GetGroup() || bot->GetGroup()->isRaidGroup()) + if (!bot->GetGroup() || !bot->GetGroup()->isRaidGroup()) return false; WorldPacket data; From 0e0d9fbde2dd47ee908ac3948a978c38f6d0f599 Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 16 May 2026 08:27:30 +0200 Subject: [PATCH 27/63] Hand of Freedom fix for Stealth (#2388) ## Pull Request Description Paladin no longer using Hand of Freedom on Rogue with Stealth Related with: #2385 ## How to Test the Changes 1. Invite paladin and rogue to group 2. Start fight 3. Rogue should use Stealth on fight beginning, Paladin should cast Hand of Freedom 4. Apply some snare effect to Rogue bot (for example .aura 1715) 5. Paladin bot should use hand of freedom ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Value/PartyMemberSnaredTargetValue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ai/Base/Value/PartyMemberSnaredTargetValue.cpp b/src/Ai/Base/Value/PartyMemberSnaredTargetValue.cpp index 02f726b0fa1..810090b5b82 100644 --- a/src/Ai/Base/Value/PartyMemberSnaredTargetValue.cpp +++ b/src/Ai/Base/Value/PartyMemberSnaredTargetValue.cpp @@ -29,7 +29,7 @@ class PartyMemberSnaredTargetPredicate : public FindPlayerPredicate, public Play if (!botAI->GetBot()->IsWithinLOSInMap(unit)) return false; - return botAI->IsMovementImpaired(unit); + return botAI->IsMovementImpaired(unit) && !botAI->HasAnyAuraOf(unit, "stealth", "prowl", nullptr); } }; From 9d787ca0b4850acbb712eef2c06b8137ff001a72 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 16 May 2026 01:27:42 -0500 Subject: [PATCH 28/63] Make Vashj Strategy Compatible with Acore Bug & Use AI Instance for Targeting (#2391) ## Pull Request Description Lady Vashj has been screwed up in Acore for a month or two. In Phase 2, only one generator activates (instead of four). As a result, disabling one generator brings Vashj into Phase 3. The strategy uses Vashj's HP + unit state to determine phase--with the Acore bug, Vashj enters Phase 3 at <65% HP (instead of the correct <50% HP, as deactivating a generator damages her by 5%) so under the current strategy, bots won't assume Phase 3 strategies at the right moment. I tried looking into the issue with Vashj in Acore and cannot figure it out, and I've had an issue open with Acore and don't know if/when it will be fixed. In the meantime, I'm modifying the strategy to use the shield barrier aura check on Vashj instead so it will work regardless of Vashj's HP at the time the generators are disabled. If/when Vashj is fixed, the strategy will still work. I also changed bot-side targeting checks throughout the strategies I've written from GetVictim() and GetTarget() to using the AI's target state via "current target." This approach I believe is the best way to actually check for the target because a GetVictim() check can lock up Elemental Shamans and Balance Druids due to them not being able to start an attack to pass that check when outside of spell range, and a GetTarget() check can occasionally not align with the target that the bot is actually attacking. - In effect, this should reduce cases where the bot AI thinks it is on the right raid target but encounter logic disagrees because the live victim or client selection is lagging or different. - Exception: multipliers for blocking TankAssistAction still use a GetVictim() check. This is because "current target" processes before issuing an attack, and the targeting check for TankAssistAction is not to suppress it if the bot hasn't actually issued an attack. There is a lot more than could be refactored with past strategies to make them better, but that's for another day. This PR is intended to be limited and simple to review. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. Logic is described above. There should be no relevant impact on processing cost. ## How to Test the Changes Attempt Lady Vashj with the current bugged version. After disabling the lone generator, bots should enter Phase 3 strategies and the encounter should be completable. Otherwise, generally run raids, and confirm there are no issues with bots switching targets or starting attacking. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- .../Action/RaidGruulsLairActions.cpp | 22 +++---- .../Multiplier/RaidGruulsLairMultipliers.cpp | 6 +- .../Action/RaidHyjalSummitActions.cpp | 28 ++++---- .../Karazhan/Action/RaidKarazhanActions.cpp | 28 ++++---- .../Action/RaidMagtheridonActions.cpp | 20 +++--- .../Action/RaidSSCActions.cpp | 50 ++++++++------- .../Multiplier/RaidSSCMultipliers.cpp | 4 +- .../Util/RaidSSCHelpers.cpp | 17 +---- .../SerpentshrineCavern/Util/RaidSSCHelpers.h | 1 + .../Action/RaidTempestKeepActions.cpp | 64 +++++++++---------- .../Multiplier/RaidTempestKeepMultipliers.cpp | 2 +- .../ZulAman/Action/RaidZulAmanActions.cpp | 22 +++---- .../Multiplier/RaidZulAmanMultipliers.cpp | 2 +- 13 files changed, 127 insertions(+), 139 deletions(-) diff --git a/src/Ai/Raid/GruulsLair/Action/RaidGruulsLairActions.cpp b/src/Ai/Raid/GruulsLair/Action/RaidGruulsLairActions.cpp index 8f8519f54d0..d39d8a5eda3 100644 --- a/src/Ai/Raid/GruulsLair/Action/RaidGruulsLairActions.cpp +++ b/src/Ai/Raid/GruulsLair/Action/RaidGruulsLairActions.cpp @@ -19,7 +19,7 @@ bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event /*event*/) MarkTargetWithSquare(bot, maulgar); SetRtiTarget(botAI, "square", maulgar); - if (bot->GetVictim() != maulgar) + if (AI_VALUE(Unit*, "current target") != maulgar) return Attack(maulgar); if (maulgar->GetVictim() == bot) @@ -53,7 +53,7 @@ bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event /*event*/) MarkTargetWithCircle(bot, olm); SetRtiTarget(botAI, "circle", olm); - if (bot->GetVictim() != olm) + if (AI_VALUE(Unit*, "current target") != olm) return Attack(olm); if (olm->GetVictim() == bot) @@ -88,7 +88,7 @@ bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event /*event* MarkTargetWithStar(bot, blindeye); SetRtiTarget(botAI, "star", blindeye); - if (bot->GetVictim() != blindeye) + if (AI_VALUE(Unit*, "current target") != blindeye) return Attack(blindeye); if (blindeye->GetVictim() == bot) @@ -128,7 +128,7 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event /*event*/) if (!bot->HasAura(SPELL_SPELL_SHIELD) && botAI->CanCastSpell("fire ward", bot)) return botAI->CastSpell("fire ward", bot); - if (bot->GetTarget() != krosh->GetGUID()) + if (AI_VALUE(Unit*, "current target") != krosh) return Attack(krosh); if (krosh->GetVictim() == bot) @@ -175,7 +175,7 @@ bool HighKingMaulgarMoonkinTankAttackKigglerAction::Execute(Event /*event*/) MarkTargetWithDiamond(bot, kiggler); SetRtiTarget(botAI, "diamond", kiggler); - if (bot->GetTarget() != kiggler->GetGUID()) + if (AI_VALUE(Unit*, "current target") != kiggler) return Attack(kiggler); Position safePos; @@ -205,7 +205,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/) SetRtiTarget(botAI, "star", blindeye); - if (bot->GetTarget() != blindeye->GetGUID()) + if (AI_VALUE(Unit*, "current target") != blindeye) return Attack(blindeye); return false; @@ -226,7 +226,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/) SetRtiTarget(botAI, "circle", olm); - if (bot->GetTarget() != olm->GetGUID()) + if (AI_VALUE(Unit*, "current target") != olm) return Attack(olm); return false; @@ -247,7 +247,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/) SetRtiTarget(botAI, "triangle", krosh); - if (bot->GetTarget() != krosh->GetGUID()) + if (AI_VALUE(Unit*, "current target") != krosh) return Attack(krosh); return false; @@ -268,7 +268,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/) SetRtiTarget(botAI, "diamond", kiggler); - if (bot->GetTarget() != kiggler->GetGUID()) + if (AI_VALUE(Unit*, "current target") != kiggler) return Attack(kiggler); return false; @@ -289,7 +289,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/) SetRtiTarget(botAI, "square", maulgar); - if (bot->GetTarget() != maulgar->GetGUID()) + if (AI_VALUE(Unit*, "current target") != maulgar) return Attack(maulgar); } @@ -499,7 +499,7 @@ bool GruulTheDragonkillerTanksPositionBossAction::Execute(Event /*event*/) if (!gruul) return false; - if (bot->GetVictim() != gruul) + if (AI_VALUE(Unit*, "current target") != gruul) return Attack(gruul); if (gruul->GetVictim() == bot) diff --git a/src/Ai/Raid/GruulsLair/Multiplier/RaidGruulsLairMultipliers.cpp b/src/Ai/Raid/GruulsLair/Multiplier/RaidGruulsLairMultipliers.cpp index a38f8a29181..7c8fb731e01 100644 --- a/src/Ai/Raid/GruulsLair/Multiplier/RaidGruulsLairMultipliers.cpp +++ b/src/Ai/Raid/GruulsLair/Multiplier/RaidGruulsLairMultipliers.cpp @@ -15,6 +15,9 @@ using namespace GruulsLairHelpers; float HighKingMaulgarDisableTankAssistMultiplier::GetValue(Action* action) { + if (bot->GetVictim() == nullptr) + return 1.0f; + if (IsAnyOgreBossAlive(botAI) && dynamic_cast(action)) return 0.0f; @@ -46,9 +49,8 @@ float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action) float HighKingMaulgarDisableArcaneShotOnKroshMultiplier::GetValue(Action* action) { Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); - Unit* target = AI_VALUE(Unit*, "current target"); - if (krosh && target && target->GetGUID() == krosh->GetGUID() && + if (krosh && AI_VALUE(Unit*, "current target") == krosh && dynamic_cast(action)) return 0.0f; diff --git a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp index 4a5f36d0214..557f3e6b042 100644 --- a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp +++ b/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp @@ -97,7 +97,7 @@ bool RageWinterchillMainTankPositionBossAction::Execute(Event /*event*/) if (!winterchill) return false; - if (bot->GetVictim() != winterchill) + if (AI_VALUE(Unit*, "current target") != winterchill) return Attack(winterchill); if (winterchill->GetVictim() == bot) @@ -265,7 +265,7 @@ bool AnetheronMainTankPositionBossAction::Execute(Event /*event*/) MarkTargetWithSquare(bot, anetheron); SetRtiTarget(botAI, "square", anetheron); - if (bot->GetVictim() != anetheron) + if (AI_VALUE(Unit*, "current target") != anetheron) return Attack(anetheron); if (anetheron->GetVictim() == bot) @@ -395,7 +395,7 @@ bool AnetheronFirstAssistTankPickUpInfernalsAction::Execute(Event /*event*/) MarkTargetWithDiamond(bot, infernal); SetRtiTarget(botAI, "diamond", infernal); - if (bot->GetVictim() != infernal) + if (AI_VALUE(Unit*, "current target") != infernal) return Attack(infernal); if ((infernoTarget && infernoTarget == bot) || @@ -432,7 +432,7 @@ bool AnetheronAssignDpsPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "square", anetheron); - if (bot->GetVictim() != anetheron) + if (AI_VALUE(Unit*, "current target") != anetheron) return Attack(anetheron); return false; @@ -455,7 +455,7 @@ bool AnetheronAssignDpsPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "diamond", infernal); - if (bot->GetTarget() != infernal->GetGUID()) + if (AI_VALUE(Unit*, "current target") != infernal) return Attack(infernal); } } @@ -464,7 +464,7 @@ bool AnetheronAssignDpsPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "square", anetheron); - if (bot->GetTarget() != anetheron->GetGUID()) + if (AI_VALUE(Unit*, "current target") != anetheron) return Attack(anetheron); } @@ -500,7 +500,7 @@ bool KazrogalMainTankPositionBossAction::Execute(Event /*event*/) if (!kazrogal) return false; - if (bot->GetVictim() != kazrogal) + if (AI_VALUE(Unit*, "current target") != kazrogal) return Attack(kazrogal); if (kazrogal->GetVictim() == bot && bot->IsWithinMeleeRange(kazrogal)) @@ -734,7 +734,7 @@ bool AzgalorMainTankPositionBossAction::Execute(Event /*event*/) MarkTargetWithStar(bot, azgalor); SetRtiTarget(botAI, "star", azgalor); - if (bot->GetVictim() != azgalor) + if (AI_VALUE(Unit*, "current target") != azgalor) return Attack(azgalor); if (azgalor->GetVictim() == bot && bot->IsWithinMeleeRange(azgalor)) @@ -801,7 +801,7 @@ bool AzgalorDisperseRangedAction::Execute(Event /*event*/) { return FleePosition(doomguard->GetPosition(), safeDistFromDoomguard); } - else if (!doomguard || bot->GetTarget() != doomguard->GetGUID()) + else if (!doomguard || AI_VALUE(Unit*, "current target") != doomguard) { Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistFromPlayer); if (nearestPlayer) @@ -848,7 +848,7 @@ bool AzgalorMeleeGetOutOfFireAndSwapTargetsAction::Execute(Event /*event*/) } } - if (bot->GetVictim() != desiredTarget || bot->GetTarget() != desiredTarget->GetGUID()) + if (AI_VALUE(Unit*, "current target") != desiredTarget) return Attack(desiredTarget); return false; @@ -912,7 +912,7 @@ bool AzgalorFirstAssistTankPositionDoomguardAction::Execute(Event /*event*/) MarkTargetWithCircle(bot, doomguard); SetRtiTarget(botAI, "circle", doomguard); - if (bot->GetVictim() != doomguard) + if (AI_VALUE(Unit*, "current target") != doomguard) return Attack(doomguard); if (doomguard->GetVictim() == bot && bot->IsWithinMeleeRange(doomguard) && @@ -963,7 +963,7 @@ bool AzgalorRangedDpsPrioritizeDoomguardsAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "circle", doomguard); - if (bot->GetTarget() != doomguard->GetGUID()) + if (AI_VALUE(Unit*, "current target") != doomguard) return Attack(doomguard); } } @@ -971,7 +971,7 @@ bool AzgalorRangedDpsPrioritizeDoomguardsAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "star", azgalor); - if (bot->GetTarget() != azgalor->GetGUID()) + if (AI_VALUE(Unit*, "current target") != azgalor) return Attack(azgalor); } @@ -1007,7 +1007,7 @@ bool ArchimondeMoveBossToInitialPositionAction::Execute(Event /*event*/) if (!archimonde) return false; - if (bot->GetVictim() != archimonde) + if (AI_VALUE(Unit*, "current target") != archimonde) return Attack(archimonde); if (archimonde->GetVictim() == bot && bot->IsWithinMeleeRange(archimonde) && diff --git a/src/Ai/Raid/Karazhan/Action/RaidKarazhanActions.cpp b/src/Ai/Raid/Karazhan/Action/RaidKarazhanActions.cpp index 69ef73987d4..9fc25827efd 100644 --- a/src/Ai/Raid/Karazhan/Action/RaidKarazhanActions.cpp +++ b/src/Ai/Raid/Karazhan/Action/RaidKarazhanActions.cpp @@ -50,11 +50,9 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event /*event*/) SetRtiTarget(botAI, "star", attumenMounted); - if (bot->GetTarget() != attumenMounted->GetGUID()) - { - bot->SetTarget(attumenMounted->GetGUID()); + if (AI_VALUE(Unit*, "current target") != attumenMounted) return Attack(attumenMounted); - } + } else if (Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight")) { @@ -65,11 +63,8 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "star", midnight); - if (bot->GetTarget() != midnight->GetGUID()) - { - bot->SetTarget(midnight->GetGUID()); + if (AI_VALUE(Unit*, "current target") != midnight) return Attack(midnight); - } } } @@ -90,7 +85,7 @@ bool AttumenTheHuntsmanSplitBossesAction::Execute(Event /*event*/) MarkTargetWithSquare(bot, attumen); SetRtiTarget(botAI, "square", attumen); - if (bot->GetVictim() != attumen) + if (AI_VALUE(Unit*, "current target") != attumen) return Attack(attumen); if (attumen->GetVictim() == bot && midnight->GetVictim() != bot) @@ -162,7 +157,7 @@ bool MoroesMainTankAttackBossAction::Execute(Event /*event*/) MarkTargetWithCircle(bot, moroes); SetRtiTarget(botAI, "circle", moroes); - if (bot->GetVictim() != moroes) + if (AI_VALUE(Unit*, "current target") != moroes) return Attack(moroes); return false; @@ -200,7 +195,7 @@ bool MaidenOfVirtueMoveBossToHealerAction::Execute(Event /*event*/) if (!maiden) return false; - if (bot->GetVictim() != maiden) + if (AI_VALUE(Unit*, "current target") != maiden) return Attack(maiden); Unit* healer = nullptr; @@ -294,7 +289,7 @@ bool BigBadWolfPositionBossAction::Execute(Event /*event*/) if (!wolf) return false; - if (bot->GetVictim() != wolf) + if (AI_VALUE(Unit*, "current target") != wolf) return Attack(wolf); if (wolf->GetVictim() == bot) @@ -425,7 +420,7 @@ bool TheCuratorPositionBossAction::Execute(Event /*event*/) MarkTargetWithCircle(bot, curator); SetRtiTarget(botAI, "circle", curator); - if (bot->GetVictim() != curator) + if (AI_VALUE(Unit*, "current target") != curator) return Attack(curator); if (curator->GetVictim() == bot) @@ -1193,7 +1188,7 @@ bool PrinceMalchezaarMainTankMovementAction::Execute(Event /*event*/) if (!malchezaar) return false; - if (bot->GetVictim() != malchezaar) + if (AI_VALUE(Unit*, "current target") != malchezaar) return Attack(malchezaar); std::vector infernals = GetSpawnedInfernals(botAI); @@ -1261,7 +1256,7 @@ bool NightbaneGroundPhasePositionBossAction::Execute(Event /*event*/) MarkTargetWithSkull(bot, nightbane); - if (bot->GetVictim() != nightbane) + if (AI_VALUE(Unit*, "current target") != nightbane) return Attack(nightbane); const ObjectGuid botGuid = bot->GetGUID(); @@ -1400,8 +1395,7 @@ bool NightbaneFlightPhaseMovementAction::Execute(Event /*event*/) MarkTargetWithMoon(bot, nightbane); - Unit* botTarget = botAI->GetUnit(bot->GetTarget()); - if (botTarget && botTarget == nightbane) + if (AI_VALUE(Unit*, "current target") == nightbane) { bot->AttackStop(); bot->InterruptNonMeleeSpells(true); diff --git a/src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.cpp b/src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.cpp index 024ffe4d617..a9e9bcfd747 100644 --- a/src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.cpp +++ b/src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.cpp @@ -61,7 +61,7 @@ bool MagtheridonMainTankAttackFirstThreeChannelersAction::Execute(Event /*event* SetRtiTarget(botAI, rtiName, currentTarget); - if (currentTarget && bot->GetVictim() != currentTarget) + if (currentTarget && AI_VALUE(Unit*, "current target") != currentTarget) return Attack(currentTarget); return false; @@ -76,7 +76,7 @@ bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event /*event*/) MarkTargetWithDiamond(bot, channelerDiamond); SetRtiTarget(botAI, "diamond", channelerDiamond); - if (bot->GetVictim() != channelerDiamond) + if (AI_VALUE(Unit*, "current target") != channelerDiamond) return Attack(channelerDiamond); if (channelerDiamond->GetVictim() == bot) @@ -109,7 +109,7 @@ bool MagtheridonSecondAssistTankAttackNEChannelerAction::Execute(Event /*event*/ MarkTargetWithTriangle(bot, channelerTriangle); SetRtiTarget(botAI, "triangle", channelerTriangle); - if (bot->GetVictim() != channelerTriangle) + if (AI_VALUE(Unit*, "current target") != channelerTriangle) return Attack(channelerTriangle); if (channelerTriangle->GetVictim() == bot) @@ -219,7 +219,7 @@ bool MagtheridonAssignDPSPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "square", channelerSquare); - if (bot->GetTarget() != channelerSquare->GetGUID()) + if (AI_VALUE(Unit*, "current target") != channelerSquare) return Attack(channelerSquare); return false; @@ -230,7 +230,7 @@ bool MagtheridonAssignDPSPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "star", channelerStar); - if (bot->GetTarget() != channelerStar->GetGUID()) + if (AI_VALUE(Unit*, "current target") != channelerStar) return Attack(channelerStar); return false; @@ -241,7 +241,7 @@ bool MagtheridonAssignDPSPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "circle", channelerCircle); - if (bot->GetTarget() != channelerCircle->GetGUID()) + if (AI_VALUE(Unit*, "current target") != channelerCircle) return Attack(channelerCircle); return false; @@ -252,7 +252,7 @@ bool MagtheridonAssignDPSPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "diamond", channelerDiamond); - if (bot->GetTarget() != channelerDiamond->GetGUID()) + if (AI_VALUE(Unit*, "current target") != channelerDiamond) return Attack(channelerDiamond); return false; @@ -263,7 +263,7 @@ bool MagtheridonAssignDPSPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "triangle", channelerTriangle); - if (bot->GetTarget() != channelerTriangle->GetGUID()) + if (AI_VALUE(Unit*, "current target") != channelerTriangle) return Attack(channelerTriangle); return false; @@ -276,7 +276,7 @@ bool MagtheridonAssignDPSPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "cross", magtheridon); - if (bot->GetTarget() != magtheridon->GetGUID()) + if (AI_VALUE(Unit*, "current target") != magtheridon) return Attack(magtheridon); } @@ -347,7 +347,7 @@ bool MagtheridonMainTankPositionBossAction::Execute(Event /*event*/) MarkTargetWithCross(bot, magtheridon); SetRtiTarget(botAI, "cross", magtheridon); - if (bot->GetVictim() != magtheridon) + if (AI_VALUE(Unit*, "current target") != magtheridon) return Attack(magtheridon); if (magtheridon->GetVictim() == bot) diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp index 68dbc8b621f..9ccd67d2b9b 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp @@ -153,7 +153,7 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event /*event*/) MarkTargetWithSquare(bot, hydross); SetRtiTarget(botAI, "square", hydross); - if (bot->GetTarget() != hydross->GetGUID()) + if (AI_VALUE(Unit*, "current target") != hydross) return Attack(hydross); if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) @@ -233,7 +233,7 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event /*event*/) MarkTargetWithTriangle(bot, hydross); SetRtiTarget(botAI, "triangle", hydross); - if (bot->GetTarget() != hydross->GetGUID()) + if (AI_VALUE(Unit*, "current target") != hydross) return Attack(hydross); if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) @@ -308,7 +308,7 @@ bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event /*event*/) SetRtiTarget(botAI, "skull", waterElemental); - if (bot->GetTarget() != waterElemental->GetGUID()) + if (AI_VALUE(Unit*, "current target") != waterElemental) return Attack(waterElemental); } else if (Unit* natureElemental = GetFirstAliveUnitByEntry(botAI, NPC_TAINTED_SPAWN_OF_HYDROSS)) @@ -318,7 +318,7 @@ bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event /*event*/) SetRtiTarget(botAI, "skull", natureElemental); - if (bot->GetTarget() != natureElemental->GetGUID()) + if (AI_VALUE(Unit*, "current target") != natureElemental) return Attack(natureElemental); } @@ -518,7 +518,7 @@ bool TheLurkerBelowPositionMainTankAction::Execute(Event /*event*/) if (!lurker) return false; - if (bot->GetTarget() != lurker->GetGUID()) + if (AI_VALUE(Unit*, "current target") != lurker) return Attack(lurker); const Position& position = LURKER_MAIN_TANK_POSITION; @@ -639,7 +639,7 @@ bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event /*event*/) MarkTargetWithIcon(bot, guardian, rtiIndices[i]); SetRtiTarget(botAI, rtiNames[i], guardian); - if (bot->GetVictim() != guardian) + if (AI_VALUE(Unit*, "current target") != guardian) return Attack(guardian); } } @@ -841,7 +841,7 @@ bool LeotherasTheBlindDestroyInnerDemonAction::Execute(Event /*event*/) // Roles without a strategy need to affirmatively attack their Inner Demons // Because DPS assist is disabled via multipliers - if (bot->GetTarget() != innerDemon->GetGUID()) + if (AI_VALUE(Unit*, "current target") != innerDemon) return Attack(innerDemon); } @@ -978,7 +978,7 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event /*event*/ MarkTargetWithStar(bot, leotherasHuman); SetRtiTarget(botAI, "star", leotherasHuman); - if (bot->GetTarget() != leotherasHuman->GetGUID()) + if (AI_VALUE(Unit*, "current target") != leotherasHuman) return Attack(leotherasHuman); Unit* leotherasDemon = GetPhase3LeotherasDemon(bot); @@ -1092,7 +1092,7 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event /*event*/) MarkTargetWithTriangle(bot, karathress); SetRtiTarget(botAI, "triangle", karathress); - if (bot->GetTarget() != karathress->GetGUID()) + if (AI_VALUE(Unit*, "current target") != karathress) return Attack(karathress); if (karathress->GetVictim() == bot && bot->IsWithinMeleeRange(karathress)) @@ -1128,7 +1128,7 @@ bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event /* MarkTargetWithDiamond(bot, caribdis); SetRtiTarget(botAI, "diamond", caribdis); - if (bot->GetTarget() != caribdis->GetGUID()) + if (AI_VALUE(Unit*, "current target") != caribdis) return Attack(caribdis); if (caribdis->GetVictim() == bot) @@ -1163,7 +1163,7 @@ bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event / MarkTargetWithStar(bot, sharkkis); SetRtiTarget(botAI, "star", sharkkis); - if (bot->GetTarget() != sharkkis->GetGUID()) + if (AI_VALUE(Unit*, "current target") != sharkkis) return Attack(sharkkis); if (sharkkis->GetVictim() == bot && bot->IsWithinMeleeRange(sharkkis)) @@ -1198,7 +1198,7 @@ bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event / MarkTargetWithCircle(bot, tidalvess); SetRtiTarget(botAI, "circle", tidalvess); - if (bot->GetTarget() != tidalvess->GetGUID()) + if (AI_VALUE(Unit*, "current target") != tidalvess) return Attack(tidalvess); if (tidalvess->GetVictim() == bot && bot->IsWithinMeleeRange(tidalvess)) @@ -1322,7 +1322,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) MarkTargetWithSkull(bot, totem); SetRtiTarget(botAI, "skull", totem); - if (bot->GetTarget() != totem->GetGUID()) + if (AI_VALUE(Unit*, "current target") != totem) return Attack(totem); // Direct movement order due to path between Sharkkis and totem sometimes being screwy @@ -1343,7 +1343,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) MarkTargetWithCircle(bot, tidalvess); SetRtiTarget(botAI, "circle", tidalvess); - if (bot->GetTarget() != tidalvess->GetGUID()) + if (AI_VALUE(Unit*, "current target") != tidalvess) return Attack(tidalvess); return false; @@ -1363,7 +1363,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) position.GetPositionZ(), 8.0f, MovementPriority::MOVEMENT_COMBAT); } - if (bot->GetTarget() != caribdis->GetGUID()) + if (AI_VALUE(Unit*, "current target") != caribdis) return Attack(caribdis); return false; @@ -1376,7 +1376,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) MarkTargetWithStar(bot, sharkkis); SetRtiTarget(botAI, "star", sharkkis); - if (bot->GetTarget() != sharkkis->GetGUID()) + if (AI_VALUE(Unit*, "current target") != sharkkis) return Attack(sharkkis); return false; @@ -1389,7 +1389,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) MarkTargetWithCross(bot, fathomSporebat); SetRtiTarget(botAI, "cross", fathomSporebat); - if (bot->GetTarget() != fathomSporebat->GetGUID()) + if (AI_VALUE(Unit*, "current target") != fathomSporebat) return Attack(fathomSporebat); return false; @@ -1401,7 +1401,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) MarkTargetWithSquare(bot, fathomLurker); SetRtiTarget(botAI, "square", fathomLurker); - if (bot->GetTarget() != fathomLurker->GetGUID()) + if (AI_VALUE(Unit*, "current target") != fathomLurker) return Attack(fathomLurker); return false; @@ -1414,7 +1414,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) MarkTargetWithTriangle(bot, karathress); SetRtiTarget(botAI, "triangle", karathress); - if (bot->GetTarget() != karathress->GetGUID()) + if (AI_VALUE(Unit*, "current target") != karathress) return Attack(karathress); } @@ -1460,7 +1460,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event /*event*/) if (!tidewalker) return false; - if (bot->GetTarget() != tidewalker->GetGUID()) + if (AI_VALUE(Unit*, "current target") != tidewalker) return Attack(tidewalker); if (tidewalker->GetVictim() == bot && bot->IsWithinMeleeRange(tidewalker)) @@ -1610,7 +1610,7 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event /*event*/) if (!vashj) return false; - if (bot->GetTarget() != vashj->GetGUID()) + if (AI_VALUE(Unit*, "current target") != vashj) return Attack(vashj); if (vashj->GetVictim() == bot && bot->IsWithinMeleeRange(vashj)) @@ -1929,7 +1929,8 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) currentTarget = nullptr; } - if (target && currentTarget != target && bot->GetTarget() != target->GetGUID()) + if (target && currentTarget != target && + AI_VALUE(Unit*, "current target") != target) return Attack(target); // If bots have wandered too far from the center, move them back @@ -1984,7 +1985,8 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event /*event*/) if (!bot->HasAura(SPELL_FEAR_WARD)) bot->AddAura(SPELL_FEAR_WARD, bot); - if (botAI->IsAssistTankOfIndex(bot, 0, true) && bot->GetTarget() != strider->GetGUID()) + if (botAI->IsAssistTankOfIndex(bot, 0, true) && + AI_VALUE(Unit*, "current target") != strider) return Attack(strider); float currentDistance = bot->GetExactDist2d(vashj); @@ -2040,7 +2042,7 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event /*event*/) tainted->GetPositionZ(), tainted->GetOrientation()); } - if (bot->GetTarget() != tainted->GetGUID()) + if (AI_VALUE(Unit*, "current target") != tainted) { MarkTargetWithStar(bot, tainted); SetRtiTarget(botAI, "star", tainted); diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index f43ef466704..85d2c51a5e8 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -752,7 +752,7 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac return 0.0f; Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); - if (enchanted && bot->GetVictim() == enchanted && + if (enchanted && AI_VALUE(Unit*, "current target") == enchanted && dynamic_cast(action)) return 0.0f; } @@ -772,7 +772,7 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac dynamic_cast(action)) return 0.0f; - if (enchanted && bot->GetVictim() == enchanted && + if (enchanted && AI_VALUE(Unit*, "current target") == enchanted && dynamic_cast(action)) return 0.0f; } diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp index 4ebeb6f885f..110481775af 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp @@ -209,35 +209,24 @@ namespace SerpentShrineCavernHelpers { Unit* vashj = botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); - if (!vashj) - return false; - Creature* vashjCreature = vashj->ToCreature(); - return vashjCreature && vashjCreature->GetHealthPct() > 70.0f && - vashjCreature->GetReactState() != REACT_PASSIVE; + return vashj && vashj->GetHealthPct() > 70.0f; } bool IsLadyVashjInPhase2(PlayerbotAI* botAI) { Unit* vashj = botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); - if (!vashj) - return false; - Creature* vashjCreature = vashj->ToCreature(); - return vashjCreature && vashjCreature->GetReactState() == REACT_PASSIVE; + return vashj && vashj->GetHealthPct() <= 70.0f && vashj->HasAura(SPELL_MAGIC_BARRIER); } bool IsLadyVashjInPhase3(PlayerbotAI* botAI) { Unit* vashj = botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); - if (!vashj) - return false; - Creature* vashjCreature = vashj->ToCreature(); - return vashjCreature && vashjCreature->GetHealthPct() <= 50.0f && - vashjCreature->GetReactState() != REACT_PASSIVE; + return vashj && vashj->GetHealthPct() <= 70.0f && !vashj->HasAura(SPELL_MAGIC_BARRIER); } bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI) diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h index 72ee52c8c1f..67b2623570b 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h @@ -48,6 +48,7 @@ namespace SerpentShrineCavernHelpers // Lady Vashj SPELL_FEAR_WARD = 6346, + SPELL_MAGIC_BARRIER = 38112, SPELL_POISON_BOLT = 38253, SPELL_STATIC_CHARGE = 38280, SPELL_ENTANGLE = 38316, diff --git a/src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.cpp b/src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.cpp index c31a5e7ad48..0255e192838 100644 --- a/src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.cpp +++ b/src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.cpp @@ -93,7 +93,7 @@ bool AlarBossTanksMoveBetweenPlatformsAction::PositionMainTank( MovementPriority::MOVEMENT_COMBAT, true, false); } else if ((locationIndex == PLATFORM_0_IDX || locationIndex == PLATFORM_2_IDX) && - bot->GetTarget() != alar->GetGUID()) + AI_VALUE(Unit*, "current target") != alar) return Attack(alar); } @@ -116,7 +116,7 @@ bool AlarBossTanksMoveBetweenPlatformsAction::PositionAssistTank( MovementPriority::MOVEMENT_COMBAT, true, false); } else if ((locationIndex == PLATFORM_1_IDX || locationIndex == PLATFORM_3_IDX) && - bot->GetTarget() != alar->GetGUID()) + AI_VALUE(Unit*, "current target") != alar) return Attack(alar); } @@ -152,7 +152,7 @@ bool AlarMeleeDpsMoveBetweenPlatformsAction::Execute(Event /*event*/) MovementPriority::MOVEMENT_COMBAT, true, false); } - if (bot->GetTarget() != alar->GetGUID()) + if (AI_VALUE(Unit*, "current target") != alar) return Attack(alar); } @@ -227,7 +227,7 @@ bool AlarAssistTanksPickUpEmbersAction::HandlePhase1Embers(Unit* alar) MarkTargetWithSquare(bot, ember); SetRtiTarget(botAI, "square", ember); - if (bot->GetTarget() != ember->GetGUID()) + if (AI_VALUE(Unit*, "current target") != ember) return Attack(ember); if (ember->GetVictim() == bot) @@ -286,7 +286,7 @@ bool AlarAssistTanksPickUpEmbersAction::HandlePhase2Embers() if (firstEmber->GetVictim() != bot) { - if (bot->GetTarget() != firstEmber->GetGUID()) + if (AI_VALUE(Unit*, "current target") != firstEmber) return Attack(firstEmber); return botAI->DoSpecificAction("taunt spell", Event(), true); @@ -305,7 +305,7 @@ bool AlarAssistTanksPickUpEmbersAction::HandlePhase2Embers() if (secondEmber->GetVictim() != bot) { - if (bot->GetTarget() != secondEmber->GetGUID()) + if (AI_VALUE(Unit*, "current target") != secondEmber) return Attack(secondEmber); return botAI->DoSpecificAction("taunt spell", Event(), true); @@ -336,7 +336,7 @@ bool AlarRangedDpsPrioritizeEmbersAction::Execute(Event /*event*/) } SetRtiTarget(botAI, "square", firstEmber); - if (bot->GetTarget() != firstEmber->GetGUID()) + if (AI_VALUE(Unit*, "current target") != firstEmber) return Attack(firstEmber); } else if (secondEmber) @@ -349,13 +349,13 @@ bool AlarRangedDpsPrioritizeEmbersAction::Execute(Event /*event*/) } SetRtiTarget(botAI, "circle", secondEmber); - if (bot->GetTarget() != secondEmber->GetGUID()) + if (AI_VALUE(Unit*, "current target") != secondEmber) return Attack(secondEmber); } else if (Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar")) { SetRtiTarget(botAI, "star", alar); - if (bot->GetTarget() != alar->GetGUID()) + if (AI_VALUE(Unit*, "current target") != alar) return Attack(alar); } @@ -469,7 +469,7 @@ bool AlarSwapTanksOnBossAction::Execute(Event /*event*/) if (alar->GetHealth() == alar->GetMaxHealth()) { SetRtiTarget(botAI, "star", alar); - if (bot->GetTarget() != alar->GetGUID()) + if (AI_VALUE(Unit*, "current target") != alar) return Attack(alar); } @@ -477,7 +477,7 @@ bool AlarSwapTanksOnBossAction::Execute(Event /*event*/) if (secondEmberTank && secondEmberTank != bot) { SetRtiTarget(botAI, "star", alar); - if (bot->GetTarget() != alar->GetGUID()) + if (AI_VALUE(Unit*, "current target") != alar) return Attack(alar); else if (alar->GetVictim() != bot) return botAI->DoSpecificAction("taunt spell", Event(), true); @@ -555,7 +555,7 @@ bool AlarReturnToRoomCenterAction::Execute(Event /*event*/) { constexpr float distFromCenter = 45.0f; const Position& center = ALAR_ROOM_CENTER; - if (bot->GetVictim() == nullptr && + if (AI_VALUE(Unit*, "current target") == nullptr && bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY()) > distFromCenter) { return MoveInside(TEMPEST_KEEP_MAP_ID, center.GetPositionX(), center.GetPositionY(), @@ -887,7 +887,7 @@ bool HighAstromancerSolarianTargetSolariumPriestsAction::Execute(Event /*event*/ SetRtiTarget(botAI, "star", targetPriest); } - if (bot->GetTarget() != targetPriest->GetGUID()) + if (AI_VALUE(Unit*, "current target") != targetPriest) return Attack(targetPriest); return false; @@ -1047,7 +1047,7 @@ bool KaelthasSunstriderMainTankPositionSanguinarAction::Execute(Event /*event*/) MarkTargetWithStar(bot, sanguinar); SetRtiTarget(botAI, "star", sanguinar); - if (bot->GetTarget() != sanguinar->GetGUID()) + if (AI_VALUE(Unit*, "current target") != sanguinar) return Attack(sanguinar); if (sanguinar->GetVictim() == bot && bot->IsWithinMeleeRange(sanguinar)) @@ -1090,7 +1090,7 @@ bool KaelthasSunstriderWarlockTankPositionCapernianAction::Execute(Event /*event MarkTargetWithCircle(bot, capernian); SetRtiTarget(botAI, "circle", capernian); - if (bot->GetTarget() != capernian->GetGUID() && + if (AI_VALUE(Unit*, "current target") != capernian && botAI->CanCastSpell("searing pain", capernian) && botAI->CastSpell("searing pain", capernian)) return true; @@ -1251,7 +1251,7 @@ bool KaelthasSunstriderFirstAssistTankPositionTelonicusAction::Execute(Event /*e MarkTargetWithTriangle(bot, telonicus); SetRtiTarget(botAI, "triangle", telonicus); - if (bot->GetTarget() != telonicus->GetGUID()) + if (AI_VALUE(Unit*, "current target") != telonicus) return Attack(telonicus); if (telonicus->GetVictim() == bot && bot->IsWithinMeleeRange(telonicus)) @@ -1331,7 +1331,7 @@ bool KaelthasSunstriderAssignAdvisorDpsPriorityAction::Execute(Event /*event*/) MarkTargetWithSquare(bot, thaladred); SetRtiTarget(botAI, "square", thaladred); - if (bot->GetTarget() != thaladred->GetGUID()) + if (AI_VALUE(Unit*, "current target") != thaladred) return Attack(thaladred); return false; @@ -1346,7 +1346,7 @@ bool KaelthasSunstriderAssignAdvisorDpsPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "circle", capernian); - if (bot->GetTarget() != capernian->GetGUID()) + if (AI_VALUE(Unit*, "current target") != capernian) return Attack(capernian); return false; @@ -1360,7 +1360,7 @@ bool KaelthasSunstriderAssignAdvisorDpsPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "star", sanguinar); - if (bot->GetTarget() != sanguinar->GetGUID()) + if (AI_VALUE(Unit*, "current target") != sanguinar) return Attack(sanguinar); return false; @@ -1373,7 +1373,7 @@ bool KaelthasSunstriderAssignAdvisorDpsPriorityAction::Execute(Event /*event*/) !telonicus->HasAura(SPELL_PERMANENT_FEIGN_DEATH)) { SetRtiTarget(botAI, "triangle", telonicus); - if (bot->GetTarget() != telonicus->GetGUID()) + if (AI_VALUE(Unit*, "current target") != telonicus) return Attack(telonicus); // Melee DPS need to stay at max-ish melee range behind Telonicus to avoid bombs @@ -1461,7 +1461,7 @@ bool KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction::Execute(Event /*e MarkTargetWithSkull(bot, staff); SetRtiTarget(botAI, "skull", staff); - if (bot->GetTarget() != staff->GetGUID()) + if (AI_VALUE(Unit*, "current target") != staff) return Attack(staff); } // Priority 2: Cosmic Infuser (Skull) @@ -1470,7 +1470,7 @@ bool KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction::Execute(Event /*e MarkTargetWithSkull(bot, mace); SetRtiTarget(botAI, "skull", mace); - if (bot->GetTarget() != mace->GetGUID()) + if (AI_VALUE(Unit*, "current target") != mace) return Attack(mace); } // Priority 3: Warp Slicer (Skull) @@ -1479,7 +1479,7 @@ bool KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction::Execute(Event /*e MarkTargetWithSkull(bot, sword); SetRtiTarget(botAI, "skull", sword); - if (bot->GetTarget() != sword->GetGUID()) + if (AI_VALUE(Unit*, "current target") != sword) return Attack(sword); } // Priority 4: Infinity Blades (Skull) @@ -1488,7 +1488,7 @@ bool KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction::Execute(Event /*e MarkTargetWithSkull(bot, dagger); SetRtiTarget(botAI, "skull", dagger); - if (bot->GetTarget() != dagger->GetGUID()) + if (AI_VALUE(Unit*, "current target") != dagger) return Attack(dagger); } // Priority 5: Devastation - ranged only (Diamond--marked in other method by main tank) @@ -1496,7 +1496,7 @@ bool KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction::Execute(Event /*e { SetRtiTarget(botAI, "diamond", axe); - if (bot->GetTarget() != axe->GetGUID()) + if (AI_VALUE(Unit*, "current target") != axe) return Attack(axe); } // Priority 6: Netherstrand Longbow (Skull) @@ -1505,7 +1505,7 @@ bool KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction::Execute(Event /*e MarkTargetWithSkull(bot, longbow); SetRtiTarget(botAI, "skull", longbow); - if (bot->GetTarget() != longbow->GetGUID()) + if (AI_VALUE(Unit*, "current target") != longbow) return Attack(longbow); } // Priority 7: Phaseshift Bulwark (Skull) @@ -1514,7 +1514,7 @@ bool KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction::Execute(Event /*e MarkTargetWithSkull(bot, shield); SetRtiTarget(botAI, "skull", shield); - if (bot->GetTarget() != shield->GetGUID()) + if (AI_VALUE(Unit*, "current target") != shield) return Attack(shield); } } @@ -1531,7 +1531,7 @@ bool KaelthasSunstriderMoveDevastationAwayAction::Execute(Event /*event*/) MarkTargetWithDiamond(bot, axe); SetRtiTarget(botAI, "diamond", axe); - if (bot->GetTarget() != axe->GetGUID()) + if (AI_VALUE(Unit*, "current target") != axe) return Attack(axe); constexpr float safeDistance = 13.0f; @@ -1764,7 +1764,7 @@ bool KaelthasSunstriderMainTankPositionBossAction::Execute(Event /*event*/) MarkTargetWithStar(bot, kaelthas); SetRtiTarget(botAI, "star", kaelthas); - if (bot->GetTarget() != kaelthas->GetGUID()) + if (AI_VALUE(Unit*, "current target") != kaelthas) return Attack(kaelthas); if (kaelthas->GetVictim() == bot && bot->IsWithinMeleeRange(kaelthas)) @@ -1862,7 +1862,7 @@ bool KaelthasSunstriderHandlePhoenixesAndEggsAction::AssistTanksPickUpPhoenixes( if (!targetPhoenix) return false; - if (bot->GetTarget() != targetPhoenix->GetGUID()) + if (AI_VALUE(Unit*, "current target") != targetPhoenix) return Attack(targetPhoenix); constexpr float safeDistance = 12.0f; @@ -1886,7 +1886,7 @@ bool KaelthasSunstriderHandlePhoenixesAndEggsAction::NonTanksDestroyEggsAndAvoid MarkTargetWithDiamond(bot, phoenixEgg); SetRtiTarget(botAI, "diamond", phoenixEgg); - if (bot->GetTarget() != phoenixEgg->GetGUID()) + if (AI_VALUE(Unit*, "current target") != phoenixEgg) return Attack(phoenixEgg); } } @@ -1991,7 +1991,7 @@ bool KaelthasSunstriderBreakThroughShockBarrierAction::Execute(Event /*event*/) return botAI->CastSpell(spell, kaelthas); } } - else if (bot->GetTarget() != kaelthas->GetGUID()) + else if (AI_VALUE(Unit*, "current target") != kaelthas) { SetRtiTarget(botAI, "star", kaelthas); return Attack(kaelthas); diff --git a/src/Ai/Raid/TempestKeep/Multiplier/RaidTempestKeepMultipliers.cpp b/src/Ai/Raid/TempestKeep/Multiplier/RaidTempestKeepMultipliers.cpp index 201bbc76f8c..3bd65aecbbe 100644 --- a/src/Ai/Raid/TempestKeep/Multiplier/RaidTempestKeepMultipliers.cpp +++ b/src/Ai/Raid/TempestKeep/Multiplier/RaidTempestKeepMultipliers.cpp @@ -100,7 +100,7 @@ float AlarPhase2NoTankingIfArmorMeltedMultiplier::GetValue(Action* action) return 1.0f; Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar"); - if (!alar || bot->GetTarget() != alar->GetGUID()) + if (!alar || AI_VALUE(Unit*, "current target") != alar) return 1.0f; if (dynamic_cast(action) || diff --git a/src/Ai/Raid/ZulAman/Action/RaidZulAmanActions.cpp b/src/Ai/Raid/ZulAman/Action/RaidZulAmanActions.cpp index 7ff3d64f270..7e621210497 100644 --- a/src/Ai/Raid/ZulAman/Action/RaidZulAmanActions.cpp +++ b/src/Ai/Raid/ZulAman/Action/RaidZulAmanActions.cpp @@ -56,7 +56,7 @@ bool AkilzonTanksPositionBossAction::Execute(Event /*event*/) if (!akilzon) return false; - if (bot->GetVictim() != akilzon) + if (AI_VALUE(Unit*, "current target") != akilzon) return Attack(akilzon); if (akilzon->GetVictim() == bot) @@ -168,7 +168,7 @@ bool NalorakkTanksPositionBossAction::MainTankPositionTrollForm(Unit* nalorakk) { if (!nalorakk->HasAura(static_cast(ZulAmanSpells::SPELL_BEARFORM))) { - if (bot->GetVictim() != nalorakk) + if (AI_VALUE(Unit*, "current target") != nalorakk) return Attack(nalorakk); if (nalorakk->GetVictim() != bot) @@ -198,7 +198,7 @@ bool NalorakkTanksPositionBossAction::FirstAssistTankPositionBearForm(Unit* nalo { if (nalorakk->HasAura(static_cast(ZulAmanSpells::SPELL_BEARFORM))) { - if (bot->GetVictim() != nalorakk) + if (AI_VALUE(Unit*, "current target") != nalorakk) return Attack(nalorakk); if (nalorakk->GetVictim() != bot) @@ -262,7 +262,7 @@ bool JanalaiTanksPositionBossAction::Execute(Event /*event*/) if (!janalai) return false; - if (bot->GetVictim() != janalai) + if (AI_VALUE(Unit*, "current target") != janalai) return Attack(janalai); if (janalai->GetVictim() == bot) @@ -409,7 +409,7 @@ bool HalazziMainTankPositionBossAction::Execute(Event /*event*/) MarkTargetWithStar(bot, halazzi); SetRtiTarget(botAI, "star", halazzi); - if (bot->GetVictim() != halazzi) + if (AI_VALUE(Unit*, "current target") != halazzi) return Attack(halazzi); if (halazzi->GetVictim() == bot) @@ -443,7 +443,7 @@ bool HalazziFirstAssistTankAttackSpiritLynxAction::Execute(Event /*event*/) MarkTargetWithCircle(bot, lynx); SetRtiTarget(botAI, "circle", lynx); - if (bot->GetVictim() != lynx) + if (AI_VALUE(Unit*, "current target") != lynx) return Attack(lynx); if (lynx->GetVictim() != bot) @@ -455,7 +455,7 @@ bool HalazziFirstAssistTankAttackSpiritLynxAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "star", halazzi); - if (bot->GetVictim() != halazzi) + if (AI_VALUE(Unit*, "current target") != halazzi) return Attack(halazzi); targetFound = true; @@ -492,7 +492,7 @@ bool HalazziAssignDpsPriorityAction::Execute(Event /*event*/) MarkTargetWithSkull(bot, totem); SetRtiTarget(botAI, "skull", totem); - if (bot->GetTarget() != totem->GetGUID()) + if (AI_VALUE(Unit*, "current target") != totem) return Attack(totem); return false; @@ -503,7 +503,7 @@ bool HalazziAssignDpsPriorityAction::Execute(Event /*event*/) { SetRtiTarget(botAI, "star", halazzi); - if (bot->GetTarget() != halazzi->GetGUID()) + if (AI_VALUE(Unit*, "current target") != halazzi) return Attack(halazzi); } @@ -602,7 +602,7 @@ bool HexLordMalacrassCastersStopAttackingAction::Execute(Event /*event*/) !malacrass->HasAura(static_cast(ZulAmanSpells::SPELL_HEX_LORD_SPELL_REFLECTION))) return false; - if (bot->GetVictim() == malacrass) + if (AI_VALUE(Unit*, "current target") == malacrass) { bot->AttackStop(); bot->InterruptNonMeleeSpells(true); @@ -657,7 +657,7 @@ bool ZuljinTanksPositionBossAction::Execute(Event /*event*/) if (!zuljin) return false; - if (bot->GetVictim() != zuljin) + if (AI_VALUE(Unit*, "current target") != zuljin) return Attack(zuljin); if (zuljin->GetVictim() == bot) diff --git a/src/Ai/Raid/ZulAman/Multiplier/RaidZulAmanMultipliers.cpp b/src/Ai/Raid/ZulAman/Multiplier/RaidZulAmanMultipliers.cpp index b727681a465..8a064fead3c 100644 --- a/src/Ai/Raid/ZulAman/Multiplier/RaidZulAmanMultipliers.cpp +++ b/src/Ai/Raid/ZulAman/Multiplier/RaidZulAmanMultipliers.cpp @@ -267,7 +267,7 @@ float HexLordMalacrassStopAttackingDuringSpellReflectionMultiplier::GetValue(Act return 1.0f; if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe || - (bot->GetVictim() == malacrass && + (AI_VALUE(Unit*, "current target") == malacrass && castSpellAction->getThreatType() == Action::ActionThreatType::Single)) return 0.0f; From 9c9c386af71ac0ff51e1427b0da6ce9d7e97c282 Mon Sep 17 00:00:00 2001 From: dillyns <49765217+dillyns@users.noreply.github.com> Date: Sat, 16 May 2026 02:28:04 -0400 Subject: [PATCH 29/63] Add ELEMENTAL_SHARPENING_STONE to prioritized weight stone IDs (#2395) ## Pull Request Description Adds elemental sharpening stone to the list of weightstones for weapon buff application. Elemental sharpening stones can be used on any melee weapon, not just sharp ones. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes Give a bot that has a blunt weapon equipped (staff or mace) an elemental sharpening stone (ID: 18262) They should now use the stone on a blunt weapon. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Bot/PlayerbotAI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 9678e22617f..958a5799ab1 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -5563,7 +5563,7 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE}; static const std::vector uPrioritizedWeightStoneIds = { - ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, + ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, ELEMENTAL_SHARPENING_STONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE}; Item* stone = nullptr; From d0ba99f381ee519698464670782821c438a4b8f3 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 16 May 2026 08:36:59 +0200 Subject: [PATCH 30/63] Updates the Windows CI workflow to build AzerothCore and mod-playerbots reliably with Ninja instead of the Visual Studio/MSBuild generator (#2383) Added a Windows CI fix to build with Ninja and avoid Windows command-line length failures. ## Pull Request Description This PR updates the Windows CI workflow to build AzerothCore + mod-playerbots reliably with Ninja instead of the Visual Studio/MSBuild generator. The previous Windows build could fail during the final `worldserver` build steps because MSBuild/RC generated command lines that exceeded Windows command-line limits. Enabling long-path support was not enough, because the failure was caused by tool command length rather than only filesystem path length. This workflow now: - installs and uses Ninja as the CMake generator; - creates an AzerothCore `conf/config.sh` for CI; - forces the compiler to MSVC `cl`; - enables CMake/Ninja response files to reduce command-line length; - moves the source tree to a short path (`C:\ac`) before configuring and building. This keeps the Windows build on MSVC while avoiding the MSBuild/RC command-line length issue. **Pros:** - Fixes Windows CI command-line length failures; - avoids unreliable `rc.exe` / `MSB6003` errors from the MSBuild generator; - keeps the build on MSVC; - makes the Windows CI build more predictable; - does not affect runtime code or bot behavior. **Cons:** - Visual Studio project files (`.vcxproj`) are no longer generated in CI; - the workflow now copies the source tree to a short path before building. ## Feature Evaluation - Minimum logic: CI-only workflow changes to use Ninja, force MSVC, enable response files, and build from a short path. - Processing cost: None at runtime. This only affects GitHub Actions build tooling. ## How to Test the Changes - Run the Windows CI workflow on GitHub Actions. - Confirm that CMake reports `-- Building for: Ninja`. - Confirm that CMake detects MSVC as the C and C++ compiler. - Confirm that the Windows build completes successfully. - Confirm that no MSBuild `MSB6003`, `rc.exe`, or Ninja `CreateProcess` command-line length errors occur. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - [x] No, not at all - Does this change modify default bot behavior? - [x] No - Does this change add new decision branches or increase maintenance complexity? - [x] No ## AI Assistance Was AI assistance used while working on this change? - [x] No Maybe if should so i'd made less commits... ## Final Checklist - [x] Stability is not compromised. - [x] Performance impact is understood, tested, and acceptable. - [x] Added logic complexity is justified and explained. - [x] Any new bot dialogue lines are translated. - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers This change is CI-only and does not affect gameplay, bot logic, database logic, or runtime performance. The Windows workflow was failing because generated build commands became too long for Windows process creation. Switching the CI build to Ninja, forcing MSVC, enabling response files, and building from `C:\ac` fixes the issue while keeping the compiler/toolchain consistent with the Windows target. --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- .github/workflows/windows_build.yml | 92 ++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 9 deletions(-) diff --git a/.github/workflows/windows_build.yml b/.github/workflows/windows_build.yml index 94d6af10879..17bd03f83b6 100644 --- a/.github/workflows/windows_build.yml +++ b/.github/workflows/windows_build.yml @@ -1,4 +1,5 @@ name: windows-build + on: push: branches: [ "master", "test-staging" ] @@ -6,7 +7,7 @@ on: branches: [ "master", "test-staging" ] concurrency: - group: "windows-build-${{ github.event.pull_request.number }}" + group: "windows-build-${{ github.event.pull_request.number || github.ref }}" cancel-in-progress: true jobs: @@ -15,35 +16,108 @@ jobs: fail-fast: false matrix: os: [windows-latest] + runs-on: ${{ matrix.os }} name: ${{ matrix.os }} + env: - BOOST_ROOT: C:\local\boost_1_82_0 + BOOST_ROOT: C:\local\boost_1_87_0 + CMAKE_GENERATOR: Ninja + CTOOLS_BUILD: all + steps: - name: Checkout AzerothCore - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'mod-playerbots/azerothcore-wotlk' ref: ${{ (github.base_ref || github.ref_name) == 'test-staging' && 'test-staging' || 'Playerbot' }} - path: 'ac' + path: a + - name: Checkout Playerbot Module - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'mod-playerbots/mod-playerbots' - #path: 'modules/mod-playerbots' - path: ac/modules/mod-playerbots + path: a/modules/mod-playerbots + + - name: Move source tree to short path + shell: powershell + run: | + if (Test-Path C:\ac) { + Remove-Item C:\ac -Recurse -Force + } + + New-Item -ItemType Directory -Path C:\ac | Out-Null + + robocopy "${{ github.workspace }}\a" "C:\ac" /MIR + + if ($LASTEXITCODE -le 7) { + exit 0 + } + + exit $LASTEXITCODE + + - name: Install Ninja + shell: powershell + run: | + choco install ninja -y + - name: ccache uses: hendrikmuhs/ccache-action@v1.2.13 + - name: Configure OS shell: bash - working-directory: ac + working-directory: C:\ac env: CONTINUOUS_INTEGRATION: true run: | ./acore.sh install-deps + + - name: Create AzerothCore CI config + shell: bash + working-directory: C:\ac + run: | + cat > conf/config.sh <<'EOF' + CCOMPILERC="cl" + CCOMPILERCXX="cl" + + CTYPE="Release" + CSCRIPTS="static" + CMODULES="static" + CTOOLS_BUILD="all" + + CCUSTOMOPTIONS="-DCMAKE_RC_COMPILER=rc -DCMAKE_NINJA_FORCE_RESPONSE_FILE=ON -DCMAKE_NINJA_CMCLDEPS_RC=OFF -DCMAKE_C_USE_RESPONSE_FILE_FOR_OBJECTS=ON -DCMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS=ON -DCMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES=ON -DCMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES=ON -DCMAKE_C_USE_RESPONSE_FILE_FOR_LIBRARIES=ON -DCMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES=ON" + EOF + + cat conf/config.sh + + - name: Setup MSVC + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 + with: + arch: x64 + - name: Build shell: bash - working-directory: ac + working-directory: C:\ac run: | export CTOOLS_BUILD=all + export CMAKE_GENERATOR=Ninja + + export CC=cl + export CXX=cl + export RC=rc + + cmake --version + ninja --version + + echo "CMAKE_GENERATOR=$CMAKE_GENERATOR" + echo "CC=$CC" + echo "CXX=$CXX" + + which cl || true + which rc || true + cl || true + rc || true + + rm -rf var/build/obj + ./acore.sh compiler build From 2973083ddaeae5dbf1d78b460dc0060447dab202 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Fri, 22 May 2026 19:23:18 -0700 Subject: [PATCH 31/63] RBAC sync (#2355) ## Pull Request Description Implement RBAC Permission system in checks. Claude flagged the following PlayerbotMgr.cpp:751 <= SEC_PLAYER SecurityCheckAction.cpp:27 == SEC_PLAYER In these two cases a moderator level account has access to these commands. This was preserved in PR. The question is whether mods should maintain the override. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Actions/BattleGroundTactics.cpp | 2 +- src/Ai/Base/Actions/ChangeStrategyAction.cpp | 2 +- src/Ai/Base/Actions/SecurityCheckAction.cpp | 2 +- src/Bot/PlayerbotAI.cpp | 7 ++++--- src/Bot/PlayerbotMgr.cpp | 20 ++++++++++---------- src/Mgr/Security/PlayerbotSecurity.cpp | 7 +++---- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Ai/Base/Actions/BattleGroundTactics.cpp b/src/Ai/Base/Actions/BattleGroundTactics.cpp index efb2fce7fea..238a2291e5d 100644 --- a/src/Ai/Base/Actions/BattleGroundTactics.cpp +++ b/src/Ai/Base/Actions/BattleGroundTactics.cpp @@ -1299,7 +1299,7 @@ std::string const BGTactics::HandleConsoleCommandPrivate(WorldSession* session, Player* player = session->GetPlayer(); if (!player) return "Error - session player not found"; - if (player->GetSession()->GetSecurity() < SEC_GAMEMASTER) + if (!player->CanBeGameMaster()) return "Command can only be used by a GM"; Battleground* bg = player->GetBattleground(); if (!bg) diff --git a/src/Ai/Base/Actions/ChangeStrategyAction.cpp b/src/Ai/Base/Actions/ChangeStrategyAction.cpp index e81096b2a5a..9c8c92552d6 100644 --- a/src/Ai/Base/Actions/ChangeStrategyAction.cpp +++ b/src/Ai/Base/Actions/ChangeStrategyAction.cpp @@ -49,7 +49,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event) uint32 account = bot->GetSession()->GetAccountId(); if (sPlayerbotAIConfig.IsInRandomAccountList(account) && botAI->GetMaster() && - botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER) + !botAI->GetMaster()->CanBeGameMaster()) { if (text.find("loot") != std::string::npos || text.find("gather") != std::string::npos) { diff --git a/src/Ai/Base/Actions/SecurityCheckAction.cpp b/src/Ai/Base/Actions/SecurityCheckAction.cpp index 3d1b4c6015c..d4daa367acd 100644 --- a/src/Ai/Base/Actions/SecurityCheckAction.cpp +++ b/src/Ai/Base/Actions/SecurityCheckAction.cpp @@ -12,7 +12,7 @@ bool SecurityCheckAction::isUseful() { return RandomPlayerbotMgr::instance().IsRandomBot(bot) && botAI->GetMaster() - && botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER + && !botAI->GetMaster()->CanBeGameMaster() && !GET_PLAYERBOT_AI(botAI->GetMaster()); } diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 958a5799ab1..a9eb598e5fa 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -43,6 +43,7 @@ #include "PlayerbotGuildMgr.h" #include "Playerbots.h" #include "PositionValue.h" +#include "RBAC.h" #include "RandomPlayerbotMgr.h" #include "SayAction.h" #include "ScriptMgr.h" @@ -507,7 +508,7 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal logout = true; if (bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || - botWorldSessionPtr->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT)) + botWorldSessionPtr->HasPermission(rbac::RBAC_PERM_INSTANT_LOGOUT)) { logout = true; } @@ -515,7 +516,7 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal if (master && (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || master->HasUnitState(UNIT_STATE_IN_FLIGHT) || (master->GetSession() && - master->GetSession()->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT)))) + master->GetSession()->HasPermission(rbac::RBAC_PERM_INSTANT_LOGOUT)))) { logout = true; } @@ -3003,7 +3004,7 @@ bool PlayerbotAI::IsTellAllowed(PlayerbotSecurityLevel securityLevel) return false; if (sPlayerbotAIConfig.whisperDistance && !bot->GetGroup() && sRandomPlayerbotMgr.IsRandomBot(bot) && - master->GetSession()->GetSecurity() < SEC_GAMEMASTER && + !master->CanBeGameMaster() && (bot->GetMapId() != master->GetMapId() || ServerFacade::instance().GetDistance2d(bot, master) > sPlayerbotAIConfig.whisperDistance)) return false; diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index 9d3d61ca335..7a8c311ce65 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -919,7 +919,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg if (!strcmp(cmd, "initself")) { - if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + if (master->CanBeGameMaster()) { // OnBotLogin(master); PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_EPIC); @@ -938,7 +938,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg { if (!strcmp(cmd, "initself=uncommon")) { - if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + if (master->CanBeGameMaster()) { // OnBotLogin(master); PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_UNCOMMON); @@ -954,7 +954,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg } if (!strcmp(cmd, "initself=rare")) { - if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + if (master->CanBeGameMaster()) { // OnBotLogin(master); PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_RARE); @@ -970,7 +970,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg } if (!strcmp(cmd, "initself=epic")) { - if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + if (master->CanBeGameMaster()) { // OnBotLogin(master); PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_EPIC); @@ -986,7 +986,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg } if (!strcmp(cmd, "initself=legendary")) { - if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + if (master->CanBeGameMaster()) { // OnBotLogin(master); PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY); @@ -1003,7 +1003,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg int32 gs; if (sscanf(cmd, "initself=%d", &gs) != -1) { - if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + if (master->CanBeGameMaster()) { // OnBotLogin(master); PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY, gs); @@ -1027,7 +1027,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg if (!strcmp(cmd, "reload")) { - if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + if (master->CanBeGameMaster()) { sPlayerbotAIConfig.Initialize(); messages.push_back("Config reloaded."); @@ -1059,7 +1059,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg } else if (sPlayerbotAIConfig.selfBotLevel == 0) messages.push_back("Self-bot is disabled"); - else if (sPlayerbotAIConfig.selfBotLevel == 1 && master->GetSession()->GetSecurity() < SEC_GAMEMASTER) + else if (sPlayerbotAIConfig.selfBotLevel == 1 && !master->CanBeGameMaster()) messages.push_back("You do not have permission to enable player botAI"); else { @@ -1079,7 +1079,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg if (!strcmp(cmd, "addclass")) { - if (sPlayerbotAIConfig.addClassCommand == 0 && master->GetSession()->GetSecurity() < SEC_GAMEMASTER) + if (sPlayerbotAIConfig.addClassCommand == 0 && !master->CanBeGameMaster()) { messages.push_back("You do not have permission to create bot by addclass command"); return messages; @@ -1304,7 +1304,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg else if (master && member != master->GetGUID()) { out << ProcessBotCommand(cmdStr, member, master->GetGUID(), - master->GetSession()->GetSecurity() >= SEC_GAMEMASTER, + master->CanBeGameMaster(), master->GetSession()->GetAccountId(), master->GetGuildId()); } else if (!master) diff --git a/src/Mgr/Security/PlayerbotSecurity.cpp b/src/Mgr/Security/PlayerbotSecurity.cpp index 4a3bd365535..f2a2863ac6d 100644 --- a/src/Mgr/Security/PlayerbotSecurity.cpp +++ b/src/Mgr/Security/PlayerbotSecurity.cpp @@ -27,7 +27,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea } // GMs always have full access - if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + if (from->CanBeGameMaster()) return PLAYERBOT_SECURITY_ALLOW_ALL; PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); @@ -188,9 +188,8 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* master = botAI->GetMaster(); if (master && botAI->IsOpposing(master)) - if (WorldSession* session = master->GetSession()) - if (session->GetSecurity() < SEC_GAMEMASTER) - return false; + if (master->GetSession() && !master->CanBeGameMaster()) + return false; std::ostringstream out; From c7b4b9aa8001e5c834de9e69d0443aed10a0d505 Mon Sep 17 00:00:00 2001 From: Mat Date: Sat, 23 May 2026 04:23:35 +0200 Subject: [PATCH 32/63] ICC V2, Autogear BiS cmd (#2363) ## Pull Request Description Big thanks to @kadeshar for providing the bis list for many raids and ilvls :D Video demo for ICC 25HC: https://studio.youtube.com/video/nACyjn817iQ/edit Video demo for autogear bis chat command: https://www.youtube.com/watch?v=2YqyVBaSb2g split main IccActions.cpp into sperate per boss .cpp files changed style to be more aligned with https://www.azerothcore.org/wiki/cpp-code-standards (WIP) added bisicc chat command for bots to gear with ICC bis gear if autogear and bisicc is enabled in cfg https://gist.github.com/metal0/0bb094bf65d27e17044308ad0646cae1 bis list used LM Added multiple spike marking and focus for faster spike clearing, each spike will get its own kill group, tank spike will never get melee bots (only assist tank and ranged dps) Added coldflame detection so that melee bots dont go for spikes that are in flames During bonestorm assist tank will go far away spot so that once bonestorm is fixed, LM will bounce back and forth from MT to AT (atm it targets randomly, it should always pick furthest target) Coldflame avoidance is handled by avoid AOE, important to keep it on in cfg Tested on ALL diffs LDW Improved skull marking of adds, add handling by tanks and dps Changed 1st position for ranged bots for easier adds handling in HC and NM Improved tanking logic for tanks, assist tank will focus on collecting adds and bring them near boss Real players will also get cyclone aura when mind controlled Improved ranged position during 2nd phase, they should not get stuck in corners/walls anymore Tanks will remove LDW ToI aura in HC (really hard to tank with it since many things are happening at once) Added Cheat for LDW fight to help tanks with agro in 2nd phase of heroic modes Changed tank position in phase 2 closer to pillars opposed to stairs (bots love to fall thru floor and run thru walls if near them) this fixed the issue Fixed edge case for escaping from shades, it could happen that multiple shades would target bot, and it was running from 1st one he found, now it will run form all that are targeting it Hunters will cast viper sting now, to increase shield draining speed Tested on ALL diffs Edit 19.5. : Tested 25hc with autogear bis gear, playebots cfg ICC cheats off, world cfg ICC buff on max (30%) In short cleared LDW without ICC cheats with bis gear but unoptimized enchants, talents, gems. I still recommend using ICC cheats for better and fun experience. GS Changed triggers and actions to enable cross faction play Assist tank will now actually tank adds on friendly ship Dps will properly jump to attack mage and go back to their ship, if stuck on enemy ship /p reset, /p summon or /p follow fixed trigger for cannons, if cannons are frozen bots wont try to mount them anymore which prevented them from attacking mage properly bots will use rocket packs to jump to and from enemy ship instead of teleporting Main tank will now jump 1st. tank enemy boss and wait until all bots have jumped back before he jumps back All bots will wait for main tank to engage enemy captain before jumping to enemy ship Cannons will focus rockets 1st, then other adds now (for when gs gets scripted) Rdps will focus nearby adds on enemy ship and mark with star rti icon when there is no deep freeze todo: remove tanking bypass when core fixes enemy ship boss threat reseting Tested crossfaction on horde with single ally bot, ally bot did everything right, need to test more. note horde side is heavily bugged due to threat issue of adds, tanks cant take threat, on ally its somewhat ok, on horde rip. Horde is doable, but annoying cus of threat issue. Tested on ALL diffs DBS Remade tank taunt logic, tanks should now properly taunt boss and let other tank taunt it if they get rune of blood Tanks will tank adds better now, no loose adds anymore Tested on ALL diffs Dogs Remade tank taunt logic, tanks should now properly taunt boss and let other tank taunt it if they get 8 mortal wounds stacks Tanks will tank adds better now, no loose adds anymore Tested on ALL diffs Festergut Hunters sometimes populated row 0 which would make them in melee range of the boss (bad for dps). They should pick correct rows now Healers will populate row 0 1st then other rows for optimal healing position Ranged bots should properly choose unique spots to avoid stacking when there is no spore present Remade tank taunt logic, tanks should now properly taunt boss and let other tank taunt it if they get 6 gastric bloat stacks Changed ranged spore position closer to boss Spore bots should be able to attack/do non movement action when they have spore and are in position Solved malleable goo detection via direct boss hooks to detect boss targets! Tested on ALL diffs Rotface Tanks should not fight over big ooze anymore Improved big ooze kiting Improved small ooze stacking logic (when no big ooze present, stack at small ooze position, when big ooze is present move to it) Fixed edge cases when main and assist tank get small ooze (they used to move to big ooze, that was really bad since main tank would start to tank big ooze and get hit by big ooze, assist tank would stop kiting and get hit by big ooze) stopped mutated plague from dispelling instantly (as fight goes on, rotface cast mutated plague more and more, thus making it impossible to pass due to sheer numbers of small oozes and big oozes on the map, this will delay their spawning and give enough time for bots to handle them properly) Fixed edge case of multiple small oozes and big oozes being alive at same time (bots would detect wrong oozes and wipe raid or get stuck) Improved flood avoidance Improved ranged positioning in heroic mode, instead of letting them choose positions (which is a nightmare on dynamic fight as rotface, they now will choose 1 spot of 22 premade ones and populate them based on guid and adopt spot based on flood position) Improved Explosion avoidance by making bots remember their starting position so that they can return to it after big ooze explode, their movement is not chaotic anymore, and improved timers, they will wait 2 sec at new position before returning to starting position so that they can avoid explosion projectiles properly, they should also avoid moving to other bots starting positions. These changes ensure minimal movements so that bot can do maximum dps possible. Tested on ALL diffs PP Fixed many logic conflicts that caused bots to freeze, do bad dps to ooze/clouds Fixed triggers and multipliers Improved Gas Cloud avoidance, bloated bot will now remember its previous position to avoid backtracking/getting stuck in corners Added boss hooks to finally detect malleable goo, it is not an npc, object or creature and PP doesn't target anyone, bots will flee from it now Boss stacking now only in last phase Added cheats for players also (if enabled in cfg) only bots used to get auras Fixed tank switching in last phase, atm PP doesn't apply aura, but it should work, since same logic works for dogs, festergut and dbs Assist tank will now become abo if there is no abo before first puddle appears Abo will during puddles, slow oozes, slash boss & oozes In last phase assist will return to normal Tested on ALL diffs Edit 19.5. : Tested 25hc with autogear bis gear, playebots cfg ICC cheats off, world cfg ICC buff on max (30%) Tanks switched in last phase flawlesly and shared stacks as they should (mutated plague got fixed in core) In short cleared PP without ICC cheats with bis gear but unoptimized enchants, talents, gems. I still recommend using ICC cheats for better and fun experience. BPC Added center position to prevent bots from pulling BQL or other adds when they glitch thru walls/floor and thus resetting raid back to icc entrance teleporter Added additional z axis resetting since bots like to "fly" up in the air when attacking kinetic bombs. using cheat bypass for ball of inferno flames (atm bugged, doesn't shrink), bots will simply kill them when they spawn. Improved tanking for main tanks, improved collection of dark nuclei for assist tank Improved kinetics bomb handling Improved shock vortex spreading Improved valaran spreading for ranged Added shock vortex (non empowered) detection to avoid it while moving into safe positions Fixed jittery movement Tested on ALL diffs BQL Removed center position block so that bots can spread our easier in 25 mode, not ideal but makes 25hc easier Replaced repulsion based spreading, now each bot will have its own spot and move if needed to new spot Improved air phase spreading Fixed assist tank taking 1st bite Tested on ALL diffs VDW Due to recent core changes bots got bugged in portals if no real player entered and changed Z axis, if there was no z axis change bots would chill under the cloud on the ground and do nothing. I could not figure out how to fix this (thus breaking immersion) without force teleporting them to the clouds. Bots that go into portals will now teleport at the same time to clouds instead of following leader bot. Added feature that if players enter the portal, player with lowest guid will become bot "leader" and they will follow that player so that there is at least a little bit of immersion left. Fixed cloud collection for Heroic Mode, bots will now time clouds more precisely to avoid loosing stack due to not picking them up Improved RTI marking Improved group splitting Improved zombie kiting and avoiding explosion Tested on ALL diffs Sindragosa Bots will mark tomb positions with red smoke bomb in air phase so that real player know where to go with when beacon on them in last phase they will mark with blue smoke tomb position Fixed tank positioning Fixed wrong tomb choice and positioning Fixed tomb marking In last phase healers will stack with melee to allow boss healing In last phase when waiting for mystic debuff to pass, bots will damage tomb like in air phase to speed up the kill todo: tank switch to reset mystic buffet stacks Tested on ALL diffs Edit 19.5. : Tested 25hc with autogear bis gear, playebots cfg ICC cheats off, world cfg ICC buff on max (30%) In short cleared LDW without ICC cheats with bis gear but unoptimized enchants, talents, gems. I still recommend using ICC cheats for better and fun experience. LK Changed add gathering logic for 1st phase and winter phase, instead of tank moving to shamblings, he will keep taunting until they agro him. necrotic plague is easy now, ditched complex timing logic for a simple logic ( move to shambling, wait until dispeled, go back. Healers dont dispel until defile ally is near shambling ) Fixed winter phase gathering logic, assist tank will now properly move to raging spirits asap and bring them to main tank, melee dps will no properly move behind/flank spirits and shamblings to avoid instant death. Rdps will now properly focus frost orbs and adds, Transition should also be smoother now, but still needs /p reset if they get stuck. Other phases are ok, LK fight is now even better than before, but player still need to know tactics and use multibot addon to help out bots when needed, especially during defile phase since its random and position matter for valkyrs and future defiles Non winter phase AT will collect raging spirits and move them to main tank, ranged bots will keep distance, melee bots will flank them to avoid aoe Defile, ditched complex spreading which was mostly gamble with boss hooks to detect defile victim. If bot, bot will move away from raid, if real player main tank will yell Player name move away defile. bots will stay in center now if safe from defile, raging spirits or vile spirits Vile spirits soaking by assist tank. Assist tank will stand between spirits and raid and chase spirits. healers are allowed to move from position to heal assist tank. one hunter if alive will be at center position to place traps to slow down spirits HC Real players will also get buffs if cheats are enabled now Assist tank will now never move towards the raid to gather adds, instead it will taunt them instead so that they come to it Assist tank will rotate shamblings at all times away from raid Assist tank will stun shamblings before transition to avoid shockwave wipe Winter phase ice sphere location changed, ranged will focus sphere faster and better now Fixed jittery movement and low dps during winter phase Fixed most of the bots getting stuck during winter phase Valkyrs will be properly marked now, one by one, in hc bots will now ignore low hp valkyrs and focus on grabbing valkyrs or boss After winter raging spirit will have top priority for killing After winter ranged bots will 1st handle ice spheres then skull targets Spirit bomb avoidance improved, main tank should not back track into unsafe positions anymore Since real player is leader its crucial that player know the tactics, bots can not handle edge cases during the fight alone, they need some of reset, follow, summon here and there since its a long fight and things can go wrong. Tested on ALL diffs NOTE: If server crash, bots will sometimes drop ICC strategy even though they are in ICC, simply re enter or write /p nc +ICC to re enable. NOTE: addons that mark icons during fight could break bots, since icons are used for RTI by bots NOTE: I did not use any raiding addons besides unbot and multibot to control bots NOTE: In theory everything should work wihout ICC buff from world cfg, and ICC cheats from playerbots cfg, didnt test it, didnt try, its too hard core for hc mode to go raw, but it should be possible good luck :) NOTE: For normal about 5k gs should be enough to do most bosses. For HC T10 set + ICC 25 nm or HC gear + gems + enchants + buffs from cfg for fun experience. NOTE: As player its good to know every strategy for Bosses, so that you can spot and help out with reset, follow, summon if bots seem stuck or are doing something strange, a lot of stuff is happening on most fights so expect some intervention with reset, summon, follow. 10 MAN 2-3 Healers, 2 Tanks, at least 1 hunter, at least one druid for bress (its not set in stone, but most success with this setup) 25 MAN 6-7 Healers, 2 tanks, at least 1 hunter, at least 3-4 druids for bress (its not set in stone, but most success with this setup) GL & HF, happy raiding :D Closes #1421 #2120 Fixes #1219 NOTE: Not all of them, I have updated affected changes in #1219. Trash, quest, cheats are still nice to haves, but I don't see working on that in near future. Before posting bugs check #1219 and write there. As I said, I dont plan to implement certain things in near future, but I am more than willing to fix bugs that crash server if they happen ASAP. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [X ] Minimal impact (**explain below**) In theory it should not impact, didnt test with hi bot count or large player count - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [X ] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [X ] Yes (**explain below**) Impacts in raid, new actions, triggers Impacts with new bisicc cmd that will gear bots Everything should make it easier for maintenance since each boss is in seperate file now ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x ] Yes (**explain below**) AI was used for analyzing code for ac code standard violations, edits were made by me. It was used for fixing bugs, brainstorming and code generation (for complex math problems, such as dynamicaly kiting oozes around, assiging positions during multiple complex situations in rotface encouter. Everything was checked and tested multiple times until it was polished (to my abilites and understanding). It helped me to solve Malleable goo detection, defile, by hooking directly to boss in order to detect it, since it was detectable only by split second since it was not npc, spell or object. ## Final Checklist - - [ x] Stability is not compromised. - - [x ] Performance impact is understood, tested, and acceptable. - - [x ] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x ] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers I have not tested with multiple players, or large servers or with 3k+ bots --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- conf/playerbots.conf.dist | 40 +- .../2026_04_28_00_playerbots_bis_gear.sql | 7973 ++++++++++++++ .../2026_04_28_01_ai_playerbot_bis_texts.sql | 183 + src/Ai/Base/Actions/TrainerAction.cpp | 280 + src/Ai/Base/Actions/TrainerAction.h | 10 + src/Ai/Base/ChatActionContext.h | 2 + src/Ai/Base/ChatTriggerContext.h | 2 + .../Strategy/ChatCommandHandlerStrategy.cpp | 1 + src/Ai/Raid/ICC/Action/ICCActions.h | 878 ++ src/Ai/Raid/ICC/Action/ICCActions_BPC.cpp | 821 ++ src/Ai/Raid/ICC/Action/ICCActions_BQL.cpp | 1318 +++ src/Ai/Raid/ICC/Action/ICCActions_DBS.cpp | 504 + src/Ai/Raid/ICC/Action/ICCActions_Dogs.cpp | 110 + src/Ai/Raid/ICC/Action/ICCActions_FG.cpp | 614 ++ src/Ai/Raid/ICC/Action/ICCActions_GSB.cpp | 1074 ++ src/Ai/Raid/ICC/Action/ICCActions_LDW.cpp | 1044 ++ src/Ai/Raid/ICC/Action/ICCActions_LK.cpp | 4568 ++++++++ src/Ai/Raid/ICC/Action/ICCActions_LM.cpp | 601 ++ src/Ai/Raid/ICC/Action/ICCActions_PP.cpp | 1733 +++ src/Ai/Raid/ICC/Action/ICCActions_RF.cpp | 1091 ++ src/Ai/Raid/ICC/Action/ICCActions_SG.cpp | 1530 +++ src/Ai/Raid/ICC/Action/ICCActions_SS.cpp | 61 + src/Ai/Raid/ICC/Action/ICCActions_VT.cpp | 1357 +++ .../ICCActionContext.h} | 32 +- src/Ai/Raid/ICC/ICCMultipliers.cpp | 1208 +++ .../ICCMultipliers.h} | 19 +- src/Ai/Raid/ICC/ICCScripts.cpp | 123 + src/Ai/Raid/ICC/ICCScripts.h | 68 + .../ICCStrategy.cpp} | 49 +- .../RaidIccStrategy.h => ICC/ICCStrategy.h} | 4 +- .../ICCTriggerContext.h} | 36 +- .../ICCTriggers.cpp} | 591 +- .../RaidIccTriggers.h => ICC/ICCTriggers.h} | 153 +- .../Raid/Icecrown/Action/RaidIccActions.cpp | 9258 ----------------- src/Ai/Raid/Icecrown/Action/RaidIccActions.h | 669 -- .../Multiplier/RaidIccMultipliers.cpp | 872 -- src/Ai/Raid/Icecrown/Util/RaidIccScripts.h | 6 - src/Ai/Raid/RaidStrategyContext.h | 2 +- src/Bot/Engine/BuildSharedActionContexts.cpp | 2 +- src/Bot/Engine/BuildSharedTriggerContexts.cpp | 2 +- src/Mgr/Item/BisListMgr.cpp | 92 + src/Mgr/Item/BisListMgr.h | 44 + src/PlayerbotAIConfig.cpp | 3 + src/PlayerbotAIConfig.h | 1 + src/Script/Playerbots.cpp | 2 + 45 files changed, 27964 insertions(+), 11067 deletions(-) create mode 100644 data/sql/playerbots/updates/2026_04_28_00_playerbots_bis_gear.sql create mode 100644 data/sql/playerbots/updates/2026_04_28_01_ai_playerbot_bis_texts.sql create mode 100644 src/Ai/Raid/ICC/Action/ICCActions.h create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_BPC.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_BQL.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_DBS.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_Dogs.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_FG.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_GSB.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_LDW.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_LK.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_LM.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_PP.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_RF.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_SG.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_SS.cpp create mode 100644 src/Ai/Raid/ICC/Action/ICCActions_VT.cpp rename src/Ai/Raid/{Icecrown/RaidIccActionContext.h => ICC/ICCActionContext.h} (81%) create mode 100644 src/Ai/Raid/ICC/ICCMultipliers.cpp rename src/Ai/Raid/{Icecrown/Multiplier/RaidIccMultipliers.h => ICC/ICCMultipliers.h} (84%) create mode 100644 src/Ai/Raid/ICC/ICCScripts.cpp create mode 100644 src/Ai/Raid/ICC/ICCScripts.h rename src/Ai/Raid/{Icecrown/Strategy/RaidIccStrategy.cpp => ICC/ICCStrategy.cpp} (81%) rename src/Ai/Raid/{Icecrown/Strategy/RaidIccStrategy.h => ICC/ICCStrategy.h} (83%) rename src/Ai/Raid/{Icecrown/RaidIccTriggerContext.h => ICC/ICCTriggerContext.h} (81%) rename src/Ai/Raid/{Icecrown/Trigger/RaidIccTriggers.cpp => ICC/ICCTriggers.cpp} (64%) rename src/Ai/Raid/{Icecrown/Trigger/RaidIccTriggers.h => ICC/ICCTriggers.h} (74%) delete mode 100644 src/Ai/Raid/Icecrown/Action/RaidIccActions.cpp delete mode 100644 src/Ai/Raid/Icecrown/Action/RaidIccActions.h delete mode 100644 src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.cpp delete mode 100644 src/Ai/Raid/Icecrown/Util/RaidIccScripts.h create mode 100644 src/Mgr/Item/BisListMgr.cpp create mode 100644 src/Mgr/Item/BisListMgr.h diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 01a6a4600f6..c1d7407745b 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -552,6 +552,17 @@ AiPlayerbot.AutoGearCommand = 1 # Default: 1 (enabled) AiPlayerbot.AutoGearCommandAltBots = 1 +# If 1 (enabled) chat command: autogear bis, gives bots BiS gear from the playerbots_bis_gear table +# whose item-level floor matches AutoGearScoreLimit (e.g. 290 = ICC, 245 = ToC, +# 125 = Kara, 78 = MC). See the AutoGearScoreLimit comment below for the full list. +# chat command: autogear bis x, (x must be positive integer) will give items based on value of x. +# If x is bigger than AutoGearScoreLimit, bis wont be given, if lower it will match x when giving items. +# Commands falls back to autogear when no tier matches or the table is empty for the bot's class/spec. +# Requires AutoGearQualityLimit = 4 and AutoGearCommand = 1. +# If AutoGearCommandAltBots = 1 it will be anbled for alt bots. +# Default: 0 (disabled) +AiPlayerbot.AutoGearBisCommand = 0 + # Equipment quality limitation for autogear command (1 = normal, 2 = uncommon, 3 = rare, 4 = epic, 5 = legendary) # Default: 3 (rare) AiPlayerbot.AutoGearQualityLimit = 3 @@ -2151,6 +2162,27 @@ AiPlayerbot.RandomClassSpecIndex.11.6 = 6 # #################################################################################################### +################################### +# # +# RAIDS # +# # +################################### + +#################################################################################################### +# +# +# + +# Enable buffs in ICC to make Heroic easier and more casual. Default is 1. +# 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for +# non tank bots, increased threat for tank bots. +# Buffs will be applied on LDW, PP, Sindragosa and Lich King. +AiPlayerbot.EnableICCBuffs = 1 + +# +# +# +#################################################################################################### ################################### # # @@ -2433,10 +2465,4 @@ AiPlayerbot.GuildTaskKillTaskDistance = 200 AiPlayerbot.TargetPosRecalcDistance = 0.1 # Allow bots to be summoned near innkeepers -AiPlayerbot.SummonAtInnkeepersEnabled = 1 - -# Enable buffs in ICC to make Heroic easier and more casual. -# 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for non tank bots, increased threat for tank bots. -# Buffs will be applied on PP, Sindragosa and Lich King - -AiPlayerbot.EnableICCBuffs = 1 +AiPlayerbot.SummonAtInnkeepersEnabled = 1 \ No newline at end of file diff --git a/data/sql/playerbots/updates/2026_04_28_00_playerbots_bis_gear.sql b/data/sql/playerbots/updates/2026_04_28_00_playerbots_bis_gear.sql new file mode 100644 index 00000000000..89c1c8cc0b0 --- /dev/null +++ b/data/sql/playerbots/updates/2026_04_28_00_playerbots_bis_gear.sql @@ -0,0 +1,7973 @@ +-- BiS gear table for the /p bis party-chat command . Unified across tiers. +-- Lookup keys: (class, tab, slot, faction, auto_gear_score_limit). +-- faction: 0=Both, 1=Alliance, 2=Horde. Faction-specific overrides Both. +-- auto_gear_score_limit: matched exactly against AiPlayerbot.AutoGearScoreLimit. +-- phase: free-text label for readability (e.g. 'Phase 5', 'Pre-Raid'). +-- Slot uses AzerothCore EquipmentSlots enum: +-- head=0, neck=1, shoulders=2, chest=4, waist=5, legs=6, feet=7, wrists=8, hands=9, +-- finger1=10, finger2=11, trinket1=12, trinket2=13, back=14, mainhand=15, offhand=16, ranged=17. +-- Druid Bear lives under sentinel tab=10 (resolved at runtime when bot has Tank strategy). + +DROP TABLE IF EXISTS `playerbots_bis`; +DROP TABLE IF EXISTS `playerbots_bis_gear`; +CREATE TABLE `playerbots_bis_gear` ( + `class` TINYINT UNSIGNED NOT NULL, + `tab` TINYINT UNSIGNED NOT NULL, + `slot` TINYINT UNSIGNED NOT NULL, + `faction` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `auto_gear_score_limit` SMALLINT UNSIGNED NOT NULL, + `item_id` INT UNSIGNED NOT NULL, + `phase` VARCHAR(32) NOT NULL DEFAULT '', + `class_name` VARCHAR(16) NOT NULL, + `spec_name` VARCHAR(32) NOT NULL, + `slot_name` VARCHAR(16) NOT NULL, + `faction_name` VARCHAR(8) NOT NULL DEFAULT 'Both', + `item_name` VARCHAR(96) NOT NULL, + PRIMARY KEY (`class`, `tab`, `slot`, `faction`, `auto_gear_score_limit`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + + +-- ============================================================ +-- Warrior (1) +-- ============================================================ +-- Arms (tab 0) +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 120, 32087, 'Pre-Raid', 'Warrior', 'Arms', 'Head', 'Both', 'Mask of the Deceiver'), +(1, 0, 1, 0, 120, 29349, 'Pre-Raid', 'Warrior', 'Arms', 'Neck', 'Both', 'Adamantine Chain of the Unbroken'), +(1, 0, 2, 0, 120, 33173, 'Pre-Raid', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Ragesteel Shoulders'), +(1, 0, 4, 0, 120, 23522, 'Pre-Raid', 'Warrior', 'Arms', 'Chest', 'Both', 'Ragesteel Breastplate'), +(1, 0, 5, 0, 120, 27985, 'Pre-Raid', 'Warrior', 'Arms', 'Waist', 'Both', 'Deathforge Girdle'), +(1, 0, 6, 0, 120, 30538, 'Pre-Raid', 'Warrior', 'Arms', 'Legs', 'Both', 'Midnight Legguards'), +(1, 0, 7, 0, 120, 25686, 'Pre-Raid', 'Warrior', 'Arms', 'Feet', 'Both', 'Fel Leather Boots'), +(1, 0, 8, 0, 120, 23537, 'Pre-Raid', 'Warrior', 'Arms', 'Wrists', 'Both', 'Black Felsteel Bracers'), +(1, 0, 9, 0, 120, 25685, 'Pre-Raid', 'Warrior', 'Arms', 'Hands', 'Both', 'Fel Leather Gloves'), +(1, 0, 10, 0, 120, 29379, 'Pre-Raid', 'Warrior', 'Arms', 'Finger1', 'Both', 'Ring of Arathi Warlords'), +(1, 0, 11, 0, 120, 30834, 'Pre-Raid', 'Warrior', 'Arms', 'Finger2', 'Both', 'Shapeshifter''s Signet'), +(1, 0, 12, 0, 120, 29383, 'Pre-Raid', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(1, 0, 13, 0, 120, 28034, 'Pre-Raid', 'Warrior', 'Arms', 'Trinket2', 'Both', 'Hourglass of the Unraveller'), +(1, 0, 14, 0, 120, 24259, 'Pre-Raid', 'Warrior', 'Arms', 'Back', 'Both', 'Vengeance Wrap'), +(1, 0, 15, 0, 120, 28438, 'Pre-Raid', 'Warrior', 'Arms', 'MainHand', 'Both', 'Dragonmaw'), +(1, 0, 16, 0, 120, 23542, 'Pre-Raid', 'Warrior', 'Arms', 'OffHand', 'Both', 'Fel Edged Battleaxe'), +(1, 0, 17, 0, 120, 30279, 'Pre-Raid', 'Warrior', 'Arms', 'Ranged', 'Both', 'Mama''s Insurance'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 125, 29021, 'Phase 1', 'Warrior', 'Arms', 'Head', 'Both', 'Warbringer Battle-Helm'), +(1, 0, 1, 0, 125, 29349, 'Phase 1', 'Warrior', 'Arms', 'Neck', 'Both', 'Adamantine Chain of the Unbroken'), +(1, 0, 2, 0, 125, 30740, 'Phase 1', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Ripfiend Shoulderplates'), +(1, 0, 4, 0, 125, 29019, 'Phase 1', 'Warrior', 'Arms', 'Chest', 'Both', 'Warbringer Breastplate'), +(1, 0, 5, 0, 125, 28779, 'Phase 1', 'Warrior', 'Arms', 'Waist', 'Both', 'Girdle of the Endless Pit'), +(1, 0, 6, 0, 125, 28741, 'Phase 1', 'Warrior', 'Arms', 'Legs', 'Both', 'Skulker''s Greaves'), +(1, 0, 7, 0, 125, 28608, 'Phase 1', 'Warrior', 'Arms', 'Feet', 'Both', 'Ironstriders of Urgency'), +(1, 0, 8, 0, 125, 28795, 'Phase 1', 'Warrior', 'Arms', 'Wrists', 'Both', 'Bladespire Warbands'), +(1, 0, 9, 0, 125, 28824, 'Phase 1', 'Warrior', 'Arms', 'Hands', 'Both', 'Gauntlets of Martial Perfection'), +(1, 0, 10, 0, 125, 28757, 'Phase 1', 'Warrior', 'Arms', 'Finger1', 'Both', 'Ring of a Thousand Marks'), +(1, 0, 11, 0, 125, 30834, 'Phase 1', 'Warrior', 'Arms', 'Finger2', 'Both', 'Shapeshifter''s Signet'), +(1, 0, 12, 0, 125, 29383, 'Phase 1', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(1, 0, 13, 0, 125, 28830, 'Phase 1', 'Warrior', 'Arms', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(1, 0, 14, 0, 125, 24259, 'Phase 1', 'Warrior', 'Arms', 'Back', 'Both', 'Vengeance Wrap'), +(1, 0, 15, 0, 125, 28438, 'Phase 1', 'Warrior', 'Arms', 'MainHand', 'Both', 'Dragonmaw'), +(1, 0, 16, 0, 125, 31332, 'Phase 1', 'Warrior', 'Arms', 'OffHand', 'Both', 'Blinkstrike'), +(1, 0, 17, 0, 125, 28772, 'Phase 1', 'Warrior', 'Arms', 'Ranged', 'Both', 'Sunfury Bow of the Phoenix'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 141, 30120, 'Phase 2', 'Warrior', 'Arms', 'Head', 'Both', 'Destroyer Battle-Helm'), +(1, 0, 1, 0, 141, 30022, 'Phase 2', 'Warrior', 'Arms', 'Neck', 'Both', 'Pendant of the Perilous'), +(1, 0, 2, 0, 141, 30122, 'Phase 2', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Destroyer Shoulderblades'), +(1, 0, 4, 0, 141, 30118, 'Phase 2', 'Warrior', 'Arms', 'Chest', 'Both', 'Destroyer Breastplate'), +(1, 0, 5, 0, 141, 30106, 'Phase 2', 'Warrior', 'Arms', 'Waist', 'Both', 'Belt of One-Hundred Deaths'), +(1, 0, 6, 0, 141, 29995, 'Phase 2', 'Warrior', 'Arms', 'Legs', 'Both', 'Leggings of Murderous Intent'), +(1, 0, 7, 0, 141, 30081, 'Phase 2', 'Warrior', 'Arms', 'Feet', 'Both', 'Warboots of Obliteration'), +(1, 0, 8, 0, 141, 30057, 'Phase 2', 'Warrior', 'Arms', 'Wrists', 'Both', 'Bracers of Eradication'), +(1, 0, 9, 0, 141, 30119, 'Phase 2', 'Warrior', 'Arms', 'Hands', 'Both', 'Destroyer Gauntlets'), +(1, 0, 10, 0, 141, 29997, 'Phase 2', 'Warrior', 'Arms', 'Finger1', 'Both', 'Band of the Ranger-General'), +(1, 0, 11, 0, 141, 30834, 'Phase 2', 'Warrior', 'Arms', 'Finger2', 'Both', 'Shapeshifter''s Signet'), +(1, 0, 12, 0, 141, 29383, 'Phase 2', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(1, 0, 13, 0, 141, 28830, 'Phase 2', 'Warrior', 'Arms', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(1, 0, 14, 0, 141, 24259, 'Phase 2', 'Warrior', 'Arms', 'Back', 'Both', 'Vengeance Wrap'), +(1, 0, 15, 0, 141, 28439, 'Phase 2', 'Warrior', 'Arms', 'MainHand', 'Both', 'Dragonstrike'), +(1, 0, 16, 0, 141, 30082, 'Phase 2', 'Warrior', 'Arms', 'OffHand', 'Both', 'Talon of Azshara'), +(1, 0, 17, 0, 141, 30105, 'Phase 2', 'Warrior', 'Arms', 'Ranged', 'Both', 'Serpent Spine Longbow'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 156, 32235, 'Phase 3', 'Warrior', 'Arms', 'Head', 'Both', 'Cursed Vision of Sargeras'), +(1, 0, 1, 0, 156, 32591, 'Phase 3', 'Warrior', 'Arms', 'Neck', 'Both', 'Choker of Serrated Blades'), +(1, 0, 2, 0, 156, 30979, 'Phase 3', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Onslaught Shoulderblades'), +(1, 0, 4, 0, 156, 30975, 'Phase 3', 'Warrior', 'Arms', 'Chest', 'Both', 'Onslaught Breastplate'), +(1, 0, 5, 0, 156, 30106, 'Phase 3', 'Warrior', 'Arms', 'Waist', 'Both', 'Belt of One-Hundred Deaths'), +(1, 0, 6, 0, 156, 32341, 'Phase 3', 'Warrior', 'Arms', 'Legs', 'Both', 'Leggings of Divine Retribution'), +(1, 0, 7, 0, 156, 32345, 'Phase 3', 'Warrior', 'Arms', 'Feet', 'Both', 'Dreadboots of the Legion'), +(1, 0, 8, 0, 156, 30863, 'Phase 3', 'Warrior', 'Arms', 'Wrists', 'Both', 'Deadly Cuffs'), +(1, 0, 9, 0, 156, 32278, 'Phase 3', 'Warrior', 'Arms', 'Hands', 'Both', 'Grips of Silent Justice'), +(1, 0, 10, 0, 156, 32497, 'Phase 3', 'Warrior', 'Arms', 'Finger1', 'Both', 'Stormrage Signet Ring'), +(1, 0, 11, 0, 156, 32335, 'Phase 3', 'Warrior', 'Arms', 'Finger2', 'Both', 'Unstoppable Aggressor''s Ring'), +(1, 0, 12, 0, 156, 28830, 'Phase 3', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Dragonspine Trophy'), +(1, 0, 13, 0, 156, 32505, 'Phase 3', 'Warrior', 'Arms', 'Trinket2', 'Both', 'Madness of the Betrayer'), +(1, 0, 14, 0, 156, 32323, 'Phase 3', 'Warrior', 'Arms', 'Back', 'Both', 'Shadowmoon Destroyer''s Drape'), +(1, 0, 15, 0, 156, 32837, 'Phase 3', 'Warrior', 'Arms', 'MainHand', 'Both', 'Warglaive of Azzinoth'), +(1, 0, 16, 0, 156, 32838, 'Phase 3', 'Warrior', 'Arms', 'OffHand', 'Both', 'Warglaive of Azzinoth'), +(1, 0, 17, 0, 156, 32326, 'Phase 3', 'Warrior', 'Arms', 'Ranged', 'Both', 'Twisted Blades of Zarak'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 164, 32235, 'Phase 4', 'Warrior', 'Arms', 'Head', 'Both', 'Cursed Vision of Sargeras'), +(1, 0, 1, 0, 164, 32591, 'Phase 4', 'Warrior', 'Arms', 'Neck', 'Both', 'Choker of Serrated Blades'), +(1, 0, 2, 0, 164, 30979, 'Phase 4', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Onslaught Shoulderblades'), +(1, 0, 4, 0, 164, 30975, 'Phase 4', 'Warrior', 'Arms', 'Chest', 'Both', 'Onslaught Breastplate'), +(1, 0, 5, 0, 164, 30106, 'Phase 4', 'Warrior', 'Arms', 'Waist', 'Both', 'Belt of One-Hundred Deaths'), +(1, 0, 6, 0, 164, 32341, 'Phase 4', 'Warrior', 'Arms', 'Legs', 'Both', 'Leggings of Divine Retribution'), +(1, 0, 7, 0, 164, 32345, 'Phase 4', 'Warrior', 'Arms', 'Feet', 'Both', 'Dreadboots of the Legion'), +(1, 0, 8, 0, 164, 30863, 'Phase 4', 'Warrior', 'Arms', 'Wrists', 'Both', 'Deadly Cuffs'), +(1, 0, 9, 0, 164, 32278, 'Phase 4', 'Warrior', 'Arms', 'Hands', 'Both', 'Grips of Silent Justice'), +(1, 0, 10, 0, 164, 33496, 'Phase 4', 'Warrior', 'Arms', 'Finger1', 'Both', 'Signet of Primal Wrath'), +(1, 0, 11, 0, 164, 32497, 'Phase 4', 'Warrior', 'Arms', 'Finger2', 'Both', 'Stormrage Signet Ring'), +(1, 0, 12, 0, 164, 28830, 'Phase 4', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Dragonspine Trophy'), +(1, 0, 13, 0, 164, 32505, 'Phase 4', 'Warrior', 'Arms', 'Trinket2', 'Both', 'Madness of the Betrayer'), +(1, 0, 14, 0, 164, 32323, 'Phase 4', 'Warrior', 'Arms', 'Back', 'Both', 'Shadowmoon Destroyer''s Drape'), +(1, 0, 15, 0, 164, 32837, 'Phase 4', 'Warrior', 'Arms', 'MainHand', 'Both', 'Warglaive of Azzinoth'), +(1, 0, 16, 0, 164, 32838, 'Phase 4', 'Warrior', 'Arms', 'OffHand', 'Both', 'Warglaive of Azzinoth'), +(1, 0, 17, 0, 164, 33474, 'Phase 4', 'Warrior', 'Arms', 'Ranged', 'Both', 'Ancient Amani Longbow'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 200, 41386, 'Pre-Raid', 'Warrior', 'Arms', 'Head', 'Both', 'Spiked Titansteel Helm'), +(1, 0, 1, 0, 200, 42645, 'Pre-Raid', 'Warrior', 'Arms', 'Neck', 'Both', 'Titanium Impact Choker'), +(1, 0, 2, 0, 200, 37627, 'Pre-Raid', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Snake Den Spaulders'), +(1, 0, 4, 0, 200, 37612, 'Pre-Raid', 'Warrior', 'Arms', 'Chest', 'Both', 'Bonegrinder Breastplate'), +(1, 0, 5, 0, 200, 37171, 'Pre-Raid', 'Warrior', 'Arms', 'Waist', 'Both', 'Flame-Bathed Steel Girdle'), +(1, 0, 6, 0, 200, 37193, 'Pre-Raid', 'Warrior', 'Arms', 'Legs', 'Both', 'Staggering Legplates'), +(1, 0, 7, 0, 200, 41391, 'Pre-Raid', 'Warrior', 'Arms', 'Feet', 'Both', 'Spiked Titansteel Treads'), +(1, 0, 8, 0, 200, 37668, 'Pre-Raid', 'Warrior', 'Arms', 'Wrists', 'Both', 'Bands of the Stoneforge'), +(1, 0, 9, 0, 200, 37363, 'Pre-Raid', 'Warrior', 'Arms', 'Hands', 'Both', 'Gauntlets of Dragon Wrath'), +(1, 0, 10, 0, 200, 37642, 'Pre-Raid', 'Warrior', 'Arms', 'Finger1', 'Both', 'Hemorrhaging Circle'), +(1, 0, 12, 0, 200, 40684, 'Pre-Raid', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Mirror of Truth'), +(1, 0, 14, 0, 200, 43566, 'Pre-Raid', 'Warrior', 'Arms', 'Back', 'Both', 'Ice Striker''s Cloak'), +(1, 0, 15, 0, 200, 41257, 'Pre-Raid', 'Warrior', 'Arms', 'MainHand', 'Both', 'Titansteel Destroyer'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 224, 40528, 'Phase 1', 'Warrior', 'Arms', 'Head', 'Both', 'Valorous Dreadnaught Helmet'), +(1, 0, 1, 0, 224, 44664, 'Phase 1', 'Warrior', 'Arms', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(1, 0, 2, 0, 224, 40530, 'Phase 1', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Valorous Dreadnaught Shoulderplates'), +(1, 0, 4, 0, 224, 40525, 'Phase 1', 'Warrior', 'Arms', 'Chest', 'Both', 'Valorous Dreadnaught Battleplate'), +(1, 0, 5, 0, 224, 40317, 'Phase 1', 'Warrior', 'Arms', 'Waist', 'Both', 'Girdle of Razuvious'), +(1, 0, 6, 0, 224, 40318, 'Phase 1', 'Warrior', 'Arms', 'Legs', 'Both', 'Legplates of Double Strikes'), +(1, 0, 7, 0, 224, 40206, 'Phase 1', 'Warrior', 'Arms', 'Feet', 'Both', 'Iron-Spring Jumpers'), +(1, 0, 8, 0, 224, 40733, 'Phase 1', 'Warrior', 'Arms', 'Wrists', 'Both', 'Wristbands of the Sentinel Huntress'), +(1, 0, 9, 0, 224, 40527, 'Phase 1', 'Warrior', 'Arms', 'Hands', 'Both', 'Valorous Dreadnaught Gauntlets'), +(1, 0, 10, 0, 224, 40075, 'Phase 1', 'Warrior', 'Arms', 'Finger1', 'Both', 'Ruthlessness'), +(1, 0, 12, 0, 224, 40256, 'Phase 1', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Grim Toll'), +(1, 0, 14, 0, 224, 40250, 'Phase 1', 'Warrior', 'Arms', 'Back', 'Both', 'Aged Winter Cloak'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 245, 46151, 'Phase 2', 'Warrior', 'Arms', 'Head', 'Both', 'Conqueror''s Siegebreaker Helmet'), +(1, 0, 1, 0, 245, 45459, 'Phase 2', 'Warrior', 'Arms', 'Neck', 'Both', 'Frigid Strength of Hodir'), +(1, 0, 2, 0, 245, 46149, 'Phase 2', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Conqueror''s Siegebreaker Shoulderplates'), +(1, 0, 4, 0, 245, 46146, 'Phase 2', 'Warrior', 'Arms', 'Chest', 'Both', 'Conqueror''s Siegebreaker Battleplate'), +(1, 0, 5, 0, 245, 45241, 'Phase 2', 'Warrior', 'Arms', 'Waist', 'Both', 'Belt of Colossal Rage'), +(1, 0, 6, 0, 245, 45536, 'Phase 2', 'Warrior', 'Arms', 'Legs', 'Both', 'Legguards of Cunning Deception'), +(1, 0, 7, 0, 245, 45599, 'Phase 2', 'Warrior', 'Arms', 'Feet', 'Both', 'Sabatons of Lifeless Night'), +(1, 0, 8, 0, 245, 45663, 'Phase 2', 'Warrior', 'Arms', 'Wrists', 'Both', 'Armbands of Bedlam'), +(1, 0, 9, 0, 245, 46148, 'Phase 2', 'Warrior', 'Arms', 'Hands', 'Both', 'Conqueror''s Siegebreaker Gauntlets'), +(1, 0, 10, 0, 245, 45608, 'Phase 2', 'Warrior', 'Arms', 'Finger1', 'Both', 'Brann''s Signet Ring'), +(1, 0, 12, 0, 245, 46038, 'Phase 2', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Dark Matter'), +(1, 0, 14, 0, 245, 46032, 'Phase 2', 'Warrior', 'Arms', 'Back', 'Both', 'Drape of the Faceless General'), +(1, 0, 17, 0, 245, 45296, 'Phase 2', 'Warrior', 'Arms', 'Ranged', 'Both', 'Twirling Blades'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 1, 258, 48383, 'Phase 3', 'Warrior', 'Arms', 'Head', 'Alliance', 'Wrynn''s Helmet of Triumph'), +(1, 0, 0, 2, 258, 48398, 'Phase 3', 'Warrior', 'Arms', 'Head', 'Horde', 'Hellscream''s Helmet of Triumph'), +(1, 0, 1, 1, 258, 47915, 'Phase 3', 'Warrior', 'Arms', 'Neck', 'Alliance', 'Collar of Ceaseless Torment'), +(1, 0, 1, 2, 258, 47988, 'Phase 3', 'Warrior', 'Arms', 'Neck', 'Horde', 'Collar of Unending Torment'), +(1, 0, 2, 1, 258, 48381, 'Phase 3', 'Warrior', 'Arms', 'Shoulders', 'Alliance', 'Wrynn''s Shoulderplates of Triumph'), +(1, 0, 2, 2, 258, 48400, 'Phase 3', 'Warrior', 'Arms', 'Shoulders', 'Horde', 'Hellscream''s Shoulderplates of Triumph'), +(1, 0, 4, 1, 258, 48385, 'Phase 3', 'Warrior', 'Arms', 'Chest', 'Alliance', 'Wrynn''s Battleplate of Triumph'), +(1, 0, 4, 2, 258, 48396, 'Phase 3', 'Warrior', 'Arms', 'Chest', 'Horde', 'Hellscream''s Battleplate of Triumph'), +(1, 0, 5, 1, 258, 47153, 'Phase 3', 'Warrior', 'Arms', 'Waist', 'Alliance', 'Belt of Deathly Dominion'), +(1, 0, 5, 2, 258, 47472, 'Phase 3', 'Warrior', 'Arms', 'Waist', 'Horde', 'Waistguard of Deathly Dominion'), +(1, 0, 6, 1, 258, 48382, 'Phase 3', 'Warrior', 'Arms', 'Legs', 'Alliance', 'Wrynn''s Legplates of Triumph'), +(1, 0, 6, 2, 258, 48399, 'Phase 3', 'Warrior', 'Arms', 'Legs', 'Horde', 'Hellscream''s Legplates of Triumph'), +(1, 0, 7, 1, 258, 47077, 'Phase 3', 'Warrior', 'Arms', 'Feet', 'Alliance', 'Treads of the Icewalker'), +(1, 0, 7, 2, 258, 47445, 'Phase 3', 'Warrior', 'Arms', 'Feet', 'Horde', 'Icewalker Treads'), +(1, 0, 8, 1, 258, 47074, 'Phase 3', 'Warrior', 'Arms', 'Wrists', 'Alliance', 'Bracers of the Untold Massacre'), +(1, 0, 8, 2, 258, 47442, 'Phase 3', 'Warrior', 'Arms', 'Wrists', 'Horde', 'Bracers of the Silent Massacre'), +(1, 0, 9, 1, 258, 47240, 'Phase 3', 'Warrior', 'Arms', 'Hands', 'Alliance', 'Gloves of Bitter Reprisal'), +(1, 0, 9, 2, 258, 47492, 'Phase 3', 'Warrior', 'Arms', 'Hands', 'Horde', 'Gauntlets of Bitter Reprisal'), +(1, 0, 10, 0, 258, 45608, 'Phase 3', 'Warrior', 'Arms', 'Finger1', 'Both', 'Brann''s Signet Ring'), +(1, 0, 12, 0, 258, 46038, 'Phase 3', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Dark Matter'), +(1, 0, 14, 1, 258, 47545, 'Phase 3', 'Warrior', 'Arms', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(1, 0, 14, 2, 258, 47546, 'Phase 3', 'Warrior', 'Arms', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(1, 0, 17, 1, 258, 46995, 'Phase 3', 'Warrior', 'Arms', 'Ranged', 'Alliance', 'Talonstrike'), +(1, 0, 17, 2, 258, 47428, 'Phase 3', 'Warrior', 'Arms', 'Ranged', 'Horde', 'Death''s Head Crossbow'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 264, 51227, 'Phase 4', 'Warrior', 'Arms', 'Head', 'Both', 'Sanctified Ymirjar Lord''s Helmet'), +(1, 0, 1, 0, 264, 50728, 'Phase 4', 'Warrior', 'Arms', 'Neck', 'Both', 'Lana''thel''s Chain of Flagellation'), +(1, 0, 2, 0, 264, 51229, 'Phase 4', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Sanctified Ymirjar Lord''s Shoulderplates'), +(1, 0, 4, 0, 264, 51225, 'Phase 4', 'Warrior', 'Arms', 'Chest', 'Both', 'Sanctified Ymirjar Lord''s Battleplate'), +(1, 0, 5, 0, 264, 50707, 'Phase 4', 'Warrior', 'Arms', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(1, 0, 6, 0, 264, 50645, 'Phase 4', 'Warrior', 'Arms', 'Legs', 'Both', 'Leggings of Northern Lights'), +(1, 0, 7, 0, 264, 50639, 'Phase 4', 'Warrior', 'Arms', 'Feet', 'Both', 'Blood-Soaked Saronite Stompers'), +(1, 0, 8, 0, 264, 50670, 'Phase 4', 'Warrior', 'Arms', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(1, 0, 9, 0, 264, 51226, 'Phase 4', 'Warrior', 'Arms', 'Hands', 'Both', 'Sanctified Ymirjar Lord''s Gauntlets'), +(1, 0, 10, 0, 264, 50402, 'Phase 4', 'Warrior', 'Arms', 'Finger1', 'Both', 'Ashen Band of Endless Vengeance'), +(1, 0, 12, 0, 264, 50363, 'Phase 4', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(1, 0, 14, 1, 264, 47545, 'Phase 4', 'Warrior', 'Arms', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(1, 0, 14, 2, 264, 47546, 'Phase 4', 'Warrior', 'Arms', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(1, 0, 17, 0, 264, 50733, 'Phase 4', 'Warrior', 'Arms', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 0, 0, 0, 290, 51227, 'Phase 5', 'Warrior', 'Arms', 'Head', 'Both', 'Sanctified Ymirjar Lord''s Helmet'), +(1, 0, 1, 0, 290, 54581, 'Phase 5', 'Warrior', 'Arms', 'Neck', 'Both', 'Penumbra Pendant'), +(1, 0, 2, 0, 290, 51229, 'Phase 5', 'Warrior', 'Arms', 'Shoulders', 'Both', 'Sanctified Ymirjar Lord''s Shoulderplates'), +(1, 0, 4, 0, 290, 51225, 'Phase 5', 'Warrior', 'Arms', 'Chest', 'Both', 'Sanctified Ymirjar Lord''s Battleplate'), +(1, 0, 5, 0, 290, 50620, 'Phase 5', 'Warrior', 'Arms', 'Waist', 'Both', 'Coldwraith Links'), +(1, 0, 6, 0, 290, 51228, 'Phase 5', 'Warrior', 'Arms', 'Legs', 'Both', 'Sanctified Ymirjar Lord''s Legplates'), +(1, 0, 7, 0, 290, 54578, 'Phase 5', 'Warrior', 'Arms', 'Feet', 'Both', 'Apocalypse''s Advance'), +(1, 0, 8, 0, 290, 50670, 'Phase 5', 'Warrior', 'Arms', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(1, 0, 9, 0, 290, 50675, 'Phase 5', 'Warrior', 'Arms', 'Hands', 'Both', 'Aldriana''s Gloves of Secrecy'), +(1, 0, 10, 0, 290, 50657, 'Phase 5', 'Warrior', 'Arms', 'Finger1', 'Both', 'Skeleton Lord''s Circle'), +(1, 0, 11, 0, 290, 52572, 'Phase 5', 'Warrior', 'Arms', 'Finger2', 'Both', 'Ashen Band of Endless Might'), +(1, 0, 12, 0, 290, 50363, 'Phase 5', 'Warrior', 'Arms', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(1, 0, 13, 0, 290, 54590, 'Phase 5', 'Warrior', 'Arms', 'Trinket2', 'Both', 'Sharpened Twilight Scale'), +(1, 0, 14, 0, 290, 47545, 'Phase 5', 'Warrior', 'Arms', 'Back', 'Both', 'Vereesa''s Dexterity'), +(1, 0, 15, 0, 290, 49623, 'Phase 5', 'Warrior', 'Arms', 'MainHand', 'Both', 'Shadowmourne'), +(1, 0, 17, 0, 290, 50733, 'Phase 5', 'Warrior', 'Arms', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- Fury (tab 1) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 66, 13404, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Head', 'Both', 'Mask of the Unforgiven'), +(1, 1, 1, 0, 66, 15411, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Neck', 'Both', 'Mark of Fordring'), +(1, 1, 2, 0, 66, 12927, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(1, 1, 4, 0, 66, 11726, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Chest', 'Both', 'Savage Gladiator Chain'), +(1, 1, 5, 0, 66, 13142, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Waist', 'Both', 'Brigam Girdle'), +(1, 1, 6, 0, 66, 14554, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Legs', 'Both', 'Cloudkeeper Legplates'), +(1, 1, 7, 0, 66, 14616, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Feet', 'Both', 'Bloodmail Boots'), +(1, 1, 8, 0, 66, 12936, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Wrists', 'Both', 'Battleborn Armbraces'), +(1, 1, 9, 0, 66, 14551, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Hands', 'Both', 'Edgemaster''s Handguards'), +(1, 1, 10, 0, 66, 17713, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Finger1', 'Both', 'Blackstone Ring'), +(1, 1, 11, 0, 66, 13098, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Finger2', 'Both', 'Painweaver Band'), +(1, 1, 12, 0, 66, 13965, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(1, 1, 13, 0, 66, 11815, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Hand of Justice'), +(1, 1, 14, 0, 66, 13340, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Back', 'Both', 'Cape of the Black Baron'), +(1, 1, 15, 0, 66, 12940, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'MainHand', 'Both', 'Dal''Rend''s Sacred Charge'), +(1, 1, 16, 0, 66, 12939, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'OffHand', 'Both', 'Dal''Rend''s Tribal Guardian'), +(1, 1, 17, 0, 66, 12651, 'Phase 1 (Pre-Raid)', 'Warrior', 'Fury', 'Ranged', 'Both', 'Blackcrow'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 76, 13404, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Head', 'Both', 'Mask of the Unforgiven'), +(1, 1, 1, 0, 76, 15411, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Neck', 'Both', 'Mark of Fordring'), +(1, 1, 2, 0, 76, 12927, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(1, 1, 4, 0, 76, 11726, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Chest', 'Both', 'Savage Gladiator Chain'), +(1, 1, 5, 0, 76, 13142, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Waist', 'Both', 'Brigam Girdle'), +(1, 1, 6, 0, 76, 14554, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Legs', 'Both', 'Cloudkeeper Legplates'), +(1, 1, 7, 0, 76, 14616, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Feet', 'Both', 'Bloodmail Boots'), +(1, 1, 8, 0, 76, 12936, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Wrists', 'Both', 'Battleborn Armbraces'), +(1, 1, 9, 0, 76, 14551, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Hands', 'Both', 'Edgemaster''s Handguards'), +(1, 1, 10, 0, 76, 17713, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Finger1', 'Both', 'Blackstone Ring'), +(1, 1, 11, 0, 76, 13098, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Finger2', 'Both', 'Painweaver Band'), +(1, 1, 12, 0, 76, 13965, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(1, 1, 13, 0, 76, 11815, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Hand of Justice'), +(1, 1, 14, 0, 76, 13340, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Back', 'Both', 'Cape of the Black Baron'), +(1, 1, 15, 0, 76, 12940, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'MainHand', 'Both', 'Dal''Rend''s Sacred Charge'), +(1, 1, 16, 0, 76, 12939, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'OffHand', 'Both', 'Dal''Rend''s Tribal Guardian'), +(1, 1, 17, 0, 76, 18323, 'Phase 2 (Pre-Raid)', 'Warrior', 'Fury', 'Ranged', 'Both', 'Satyr''s Bow'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 78, 12640, 'Phase 2', 'Warrior', 'Fury', 'Head', 'Both', 'Lionheart Helm'), +(1, 1, 1, 0, 78, 18404, 'Phase 2', 'Warrior', 'Fury', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(1, 1, 2, 0, 78, 12927, 'Phase 2', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(1, 1, 4, 0, 78, 11726, 'Phase 2', 'Warrior', 'Fury', 'Chest', 'Both', 'Savage Gladiator Chain'), +(1, 1, 5, 0, 78, 19137, 'Phase 2', 'Warrior', 'Fury', 'Waist', 'Both', 'Onslaught Girdle'), +(1, 1, 6, 0, 78, 14554, 'Phase 2', 'Warrior', 'Fury', 'Legs', 'Both', 'Cloudkeeper Legplates'), +(1, 1, 7, 0, 78, 14616, 'Phase 2', 'Warrior', 'Fury', 'Feet', 'Both', 'Bloodmail Boots'), +(1, 1, 8, 0, 78, 19146, 'Phase 2', 'Warrior', 'Fury', 'Wrists', 'Both', 'Wristguards of Stability'), +(1, 1, 9, 0, 78, 14551, 'Phase 2', 'Warrior', 'Fury', 'Hands', 'Both', 'Edgemaster''s Handguards'), +(1, 1, 10, 0, 78, 17063, 'Phase 2', 'Warrior', 'Fury', 'Finger1', 'Both', 'Band of Accuria'), +(1, 1, 11, 0, 78, 18821, 'Phase 2', 'Warrior', 'Fury', 'Finger2', 'Both', 'Quick Strike Ring'), +(1, 1, 12, 0, 78, 13965, 'Phase 2', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(1, 1, 13, 0, 78, 11815, 'Phase 2', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Hand of Justice'), +(1, 1, 14, 0, 78, 18541, 'Phase 2', 'Warrior', 'Fury', 'Back', 'Both', 'Puissant Cape'), +(1, 1, 15, 0, 78, 17075, 'Phase 2', 'Warrior', 'Fury', 'MainHand', 'Both', 'Vis''kag the Bloodletter'), +(1, 1, 16, 0, 78, 18832, 'Phase 2', 'Warrior', 'Fury', 'OffHand', 'Both', 'Brutality Blade'), +(1, 1, 17, 0, 78, 17069, 'Phase 2', 'Warrior', 'Fury', 'Ranged', 'Both', 'Striker''s Mark'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 83, 12640, 'Phase 3', 'Warrior', 'Fury', 'Head', 'Both', 'Lionheart Helm'), +(1, 1, 1, 0, 83, 18404, 'Phase 3', 'Warrior', 'Fury', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(1, 1, 2, 0, 83, 19394, 'Phase 3', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Drake Talon Pauldrons'), +(1, 1, 4, 0, 83, 11726, 'Phase 3', 'Warrior', 'Fury', 'Chest', 'Both', 'Savage Gladiator Chain'), +(1, 1, 5, 0, 83, 19137, 'Phase 3', 'Warrior', 'Fury', 'Waist', 'Both', 'Onslaught Girdle'), +(1, 1, 6, 0, 83, 19402, 'Phase 3', 'Warrior', 'Fury', 'Legs', 'Both', 'Legguards of the Fallen Crusader'), +(1, 1, 7, 0, 83, 19387, 'Phase 3', 'Warrior', 'Fury', 'Feet', 'Both', 'Chromatic Boots'), +(1, 1, 8, 0, 83, 19146, 'Phase 3', 'Warrior', 'Fury', 'Wrists', 'Both', 'Wristguards of Stability'), +(1, 1, 9, 0, 83, 14551, 'Phase 3', 'Warrior', 'Fury', 'Hands', 'Both', 'Edgemaster''s Handguards'), +(1, 1, 10, 0, 83, 17063, 'Phase 3', 'Warrior', 'Fury', 'Finger1', 'Both', 'Band of Accuria'), +(1, 1, 11, 0, 83, 19384, 'Phase 3', 'Warrior', 'Fury', 'Finger2', 'Both', 'Master Dragonslayer''s Ring'), +(1, 1, 12, 0, 83, 19406, 'Phase 3', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Drake Fang Talisman'), +(1, 1, 13, 0, 83, 11815, 'Phase 3', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Hand of Justice'), +(1, 1, 14, 0, 83, 18541, 'Phase 3', 'Warrior', 'Fury', 'Back', 'Both', 'Puissant Cape'), +(1, 1, 15, 0, 83, 19352, 'Phase 3', 'Warrior', 'Fury', 'MainHand', 'Both', 'Chromatically Tempered Sword'), +(1, 1, 16, 0, 83, 19351, 'Phase 3', 'Warrior', 'Fury', 'OffHand', 'Both', 'Maladath, Runed Blade of the Black Flight'), +(1, 1, 17, 0, 83, 17069, 'Phase 3', 'Warrior', 'Fury', 'Ranged', 'Both', 'Striker''s Mark'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 88, 12640, 'Phase 5', 'Warrior', 'Fury', 'Head', 'Both', 'Lionheart Helm'), +(1, 1, 1, 0, 88, 18404, 'Phase 5', 'Warrior', 'Fury', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(1, 1, 2, 0, 88, 21330, 'Phase 5', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Conqueror''s Spaulders'), +(1, 1, 4, 0, 88, 21814, 'Phase 5', 'Warrior', 'Fury', 'Chest', 'Both', 'Breastplate of Annihilation'), +(1, 1, 5, 0, 88, 19137, 'Phase 5', 'Warrior', 'Fury', 'Waist', 'Both', 'Onslaught Girdle'), +(1, 1, 6, 0, 88, 22385, 'Phase 5', 'Warrior', 'Fury', 'Legs', 'Both', 'Titanic Leggings'), +(1, 1, 7, 0, 88, 19387, 'Phase 5', 'Warrior', 'Fury', 'Feet', 'Both', 'Chromatic Boots'), +(1, 1, 8, 0, 88, 21618, 'Phase 5', 'Warrior', 'Fury', 'Wrists', 'Both', 'Hive Defiler Wristguards'), +(1, 1, 9, 0, 88, 14551, 'Phase 5', 'Warrior', 'Fury', 'Hands', 'Both', 'Edgemaster''s Handguards'), +(1, 1, 10, 0, 88, 17063, 'Phase 5', 'Warrior', 'Fury', 'Finger1', 'Both', 'Band of Accuria'), +(1, 1, 11, 0, 88, 19384, 'Phase 5', 'Warrior', 'Fury', 'Finger2', 'Both', 'Master Dragonslayer''s Ring'), +(1, 1, 12, 0, 88, 19406, 'Phase 5', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Drake Fang Talisman'), +(1, 1, 13, 0, 88, 21670, 'Phase 5', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Badge of the Swarmguard'), +(1, 1, 14, 0, 88, 18541, 'Phase 5', 'Warrior', 'Fury', 'Back', 'Both', 'Puissant Cape'), +(1, 1, 15, 0, 88, 21650, 'Phase 5', 'Warrior', 'Fury', 'MainHand', 'Both', 'Ancient Qiraji Ripper'), +(1, 1, 16, 0, 88, 21650, 'Phase 5', 'Warrior', 'Fury', 'OffHand', 'Both', 'Ancient Qiraji Ripper'), +(1, 1, 17, 0, 88, 17069, 'Phase 5', 'Warrior', 'Fury', 'Ranged', 'Both', 'Striker''s Mark'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 92, 12640, 'Phase 6', 'Warrior', 'Fury', 'Head', 'Both', 'Lionheart Helm'), +(1, 1, 1, 0, 92, 18404, 'Phase 6', 'Warrior', 'Fury', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(1, 1, 2, 0, 92, 21330, 'Phase 6', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Conqueror''s Spaulders'), +(1, 1, 4, 0, 92, 23000, 'Phase 6', 'Warrior', 'Fury', 'Chest', 'Both', 'Plated Abomination Ribcage'), +(1, 1, 5, 0, 92, 19137, 'Phase 6', 'Warrior', 'Fury', 'Waist', 'Both', 'Onslaught Girdle'), +(1, 1, 6, 0, 92, 22385, 'Phase 6', 'Warrior', 'Fury', 'Legs', 'Both', 'Titanic Leggings'), +(1, 1, 7, 0, 92, 19387, 'Phase 6', 'Warrior', 'Fury', 'Feet', 'Both', 'Chromatic Boots'), +(1, 1, 8, 0, 92, 22936, 'Phase 6', 'Warrior', 'Fury', 'Wrists', 'Both', 'Wristguards of Vengeance'), +(1, 1, 9, 0, 92, 21581, 'Phase 6', 'Warrior', 'Fury', 'Hands', 'Both', 'Gauntlets of Annihilation'), +(1, 1, 11, 0, 92, 19384, 'Phase 6', 'Warrior', 'Fury', 'Finger2', 'Both', 'Master Dragonslayer''s Ring'), +(1, 1, 12, 0, 92, 19406, 'Phase 6', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Drake Fang Talisman'), +(1, 1, 13, 0, 92, 21670, 'Phase 6', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Badge of the Swarmguard'), +(1, 1, 14, 0, 92, 23045, 'Phase 6', 'Warrior', 'Fury', 'Back', 'Both', 'Shroud of Dominion'), +(1, 1, 15, 0, 92, 23054, 'Phase 6', 'Warrior', 'Fury', 'MainHand', 'Both', 'Gressil, Dawn of Ruin'), +(1, 1, 16, 0, 92, 23577, 'Phase 6', 'Warrior', 'Fury', 'OffHand', 'Both', 'The Hungering Cold'), +(1, 1, 17, 0, 92, 17069, 'Phase 6', 'Warrior', 'Fury', 'Ranged', 'Both', 'Striker''s Mark'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 120, 32087, 'Pre-Raid', 'Warrior', 'Fury', 'Head', 'Both', 'Mask of the Deceiver'), +(1, 1, 1, 0, 120, 29349, 'Pre-Raid', 'Warrior', 'Fury', 'Neck', 'Both', 'Adamantine Chain of the Unbroken'), +(1, 1, 2, 0, 120, 33173, 'Pre-Raid', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Ragesteel Shoulders'), +(1, 1, 4, 0, 120, 23522, 'Pre-Raid', 'Warrior', 'Fury', 'Chest', 'Both', 'Ragesteel Breastplate'), +(1, 1, 5, 0, 120, 27985, 'Pre-Raid', 'Warrior', 'Fury', 'Waist', 'Both', 'Deathforge Girdle'), +(1, 1, 6, 0, 120, 30538, 'Pre-Raid', 'Warrior', 'Fury', 'Legs', 'Both', 'Midnight Legguards'), +(1, 1, 7, 0, 120, 25686, 'Pre-Raid', 'Warrior', 'Fury', 'Feet', 'Both', 'Fel Leather Boots'), +(1, 1, 8, 0, 120, 23537, 'Pre-Raid', 'Warrior', 'Fury', 'Wrists', 'Both', 'Black Felsteel Bracers'), +(1, 1, 9, 0, 120, 25685, 'Pre-Raid', 'Warrior', 'Fury', 'Hands', 'Both', 'Fel Leather Gloves'), +(1, 1, 10, 0, 120, 29379, 'Pre-Raid', 'Warrior', 'Fury', 'Finger1', 'Both', 'Ring of Arathi Warlords'), +(1, 1, 11, 0, 120, 30834, 'Pre-Raid', 'Warrior', 'Fury', 'Finger2', 'Both', 'Shapeshifter''s Signet'), +(1, 1, 12, 0, 120, 29383, 'Pre-Raid', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(1, 1, 13, 0, 120, 28034, 'Pre-Raid', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Hourglass of the Unraveller'), +(1, 1, 14, 0, 120, 24259, 'Pre-Raid', 'Warrior', 'Fury', 'Back', 'Both', 'Vengeance Wrap'), +(1, 1, 15, 0, 120, 28438, 'Pre-Raid', 'Warrior', 'Fury', 'MainHand', 'Both', 'Dragonmaw'), +(1, 1, 16, 0, 120, 23542, 'Pre-Raid', 'Warrior', 'Fury', 'OffHand', 'Both', 'Fel Edged Battleaxe'), +(1, 1, 17, 0, 120, 30279, 'Pre-Raid', 'Warrior', 'Fury', 'Ranged', 'Both', 'Mama''s Insurance'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 125, 29021, 'Phase 1', 'Warrior', 'Fury', 'Head', 'Both', 'Warbringer Battle-Helm'), +(1, 1, 1, 0, 125, 29349, 'Phase 1', 'Warrior', 'Fury', 'Neck', 'Both', 'Adamantine Chain of the Unbroken'), +(1, 1, 2, 0, 125, 29023, 'Phase 1', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Warbringer Shoulderplates'), +(1, 1, 4, 0, 125, 29019, 'Phase 1', 'Warrior', 'Fury', 'Chest', 'Both', 'Warbringer Breastplate'), +(1, 1, 5, 0, 125, 28779, 'Phase 1', 'Warrior', 'Fury', 'Waist', 'Both', 'Girdle of the Endless Pit'), +(1, 1, 6, 0, 125, 28741, 'Phase 1', 'Warrior', 'Fury', 'Legs', 'Both', 'Skulker''s Greaves'), +(1, 1, 7, 0, 125, 28608, 'Phase 1', 'Warrior', 'Fury', 'Feet', 'Both', 'Ironstriders of Urgency'), +(1, 1, 8, 0, 125, 28795, 'Phase 1', 'Warrior', 'Fury', 'Wrists', 'Both', 'Bladespire Warbands'), +(1, 1, 9, 0, 125, 28824, 'Phase 1', 'Warrior', 'Fury', 'Hands', 'Both', 'Gauntlets of Martial Perfection'), +(1, 1, 10, 0, 125, 28757, 'Phase 1', 'Warrior', 'Fury', 'Finger1', 'Both', 'Ring of a Thousand Marks'), +(1, 1, 11, 0, 125, 30834, 'Phase 1', 'Warrior', 'Fury', 'Finger2', 'Both', 'Shapeshifter''s Signet'), +(1, 1, 12, 0, 125, 29383, 'Phase 1', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(1, 1, 13, 0, 125, 28830, 'Phase 1', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(1, 1, 14, 0, 125, 24259, 'Phase 1', 'Warrior', 'Fury', 'Back', 'Both', 'Vengeance Wrap'), +(1, 1, 15, 0, 125, 28438, 'Phase 1', 'Warrior', 'Fury', 'MainHand', 'Both', 'Dragonmaw'), +(1, 1, 16, 0, 125, 31332, 'Phase 1', 'Warrior', 'Fury', 'OffHand', 'Both', 'Blinkstrike'), +(1, 1, 17, 0, 125, 30724, 'Phase 1', 'Warrior', 'Fury', 'Ranged', 'Both', 'Barrel-Blade Longrifle'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 141, 30120, 'Phase 2', 'Warrior', 'Fury', 'Head', 'Both', 'Destroyer Battle-Helm'), +(1, 1, 1, 0, 141, 30022, 'Phase 2', 'Warrior', 'Fury', 'Neck', 'Both', 'Pendant of the Perilous'), +(1, 1, 2, 0, 141, 30122, 'Phase 2', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Destroyer Shoulderblades'), +(1, 1, 4, 0, 141, 30118, 'Phase 2', 'Warrior', 'Fury', 'Chest', 'Both', 'Destroyer Breastplate'), +(1, 1, 5, 0, 141, 30106, 'Phase 2', 'Warrior', 'Fury', 'Waist', 'Both', 'Belt of One-Hundred Deaths'), +(1, 1, 6, 0, 141, 29995, 'Phase 2', 'Warrior', 'Fury', 'Legs', 'Both', 'Leggings of Murderous Intent'), +(1, 1, 7, 0, 141, 30081, 'Phase 2', 'Warrior', 'Fury', 'Feet', 'Both', 'Warboots of Obliteration'), +(1, 1, 8, 0, 141, 30057, 'Phase 2', 'Warrior', 'Fury', 'Wrists', 'Both', 'Bracers of Eradication'), +(1, 1, 9, 0, 141, 30119, 'Phase 2', 'Warrior', 'Fury', 'Hands', 'Both', 'Destroyer Gauntlets'), +(1, 1, 10, 0, 141, 29997, 'Phase 2', 'Warrior', 'Fury', 'Finger1', 'Both', 'Band of the Ranger-General'), +(1, 1, 11, 0, 141, 28757, 'Phase 2', 'Warrior', 'Fury', 'Finger2', 'Both', 'Ring of a Thousand Marks'), +(1, 1, 12, 0, 141, 29383, 'Phase 2', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(1, 1, 13, 0, 141, 28830, 'Phase 2', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(1, 1, 14, 0, 141, 24259, 'Phase 2', 'Warrior', 'Fury', 'Back', 'Both', 'Vengeance Wrap'), +(1, 1, 15, 0, 141, 28439, 'Phase 2', 'Warrior', 'Fury', 'MainHand', 'Both', 'Dragonstrike'), +(1, 1, 16, 0, 141, 30082, 'Phase 2', 'Warrior', 'Fury', 'OffHand', 'Both', 'Talon of Azshara'), +(1, 1, 17, 0, 141, 30105, 'Phase 2', 'Warrior', 'Fury', 'Ranged', 'Both', 'Serpent Spine Longbow'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 200, 41386, 'Pre-Raid', 'Warrior', 'Fury', 'Head', 'Both', 'Spiked Titansteel Helm'), +(1, 1, 1, 0, 200, 42645, 'Pre-Raid', 'Warrior', 'Fury', 'Neck', 'Both', 'Titanium Impact Choker'), +(1, 1, 2, 0, 200, 37627, 'Pre-Raid', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Snake Den Spaulders'), +(1, 1, 4, 0, 200, 39606, 'Pre-Raid', 'Warrior', 'Fury', 'Chest', 'Both', 'Heroes'' Dreadnaught Battleplate'), +(1, 1, 5, 0, 200, 37826, 'Pre-Raid', 'Warrior', 'Fury', 'Waist', 'Both', 'The General''s Steel Girdle'), +(1, 1, 6, 0, 200, 39607, 'Pre-Raid', 'Warrior', 'Fury', 'Legs', 'Both', 'Heroes'' Dreadnaught Legplates'), +(1, 1, 7, 0, 200, 43402, 'Pre-Raid', 'Warrior', 'Fury', 'Feet', 'Both', 'The Obliterator Greaves'), +(1, 1, 8, 0, 200, 37891, 'Pre-Raid', 'Warrior', 'Fury', 'Wrists', 'Both', 'Cast Iron Shackles'), +(1, 1, 9, 0, 200, 39609, 'Pre-Raid', 'Warrior', 'Fury', 'Hands', 'Both', 'Heroes'' Dreadnaught Gauntlets'), +(1, 1, 10, 0, 200, 37151, 'Pre-Raid', 'Warrior', 'Fury', 'Finger1', 'Both', 'Band of Frosted Thorns'), +(1, 1, 12, 0, 200, 40684, 'Pre-Raid', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Mirror of Truth'), +(1, 1, 14, 0, 200, 43566, 'Pre-Raid', 'Warrior', 'Fury', 'Back', 'Both', 'Ice Striker''s Cloak'), +(1, 1, 15, 0, 200, 41257, 'Pre-Raid', 'Warrior', 'Fury', 'MainHand', 'Both', 'Titansteel Destroyer'), +(1, 1, 17, 0, 200, 43284, 'Pre-Raid', 'Warrior', 'Fury', 'Ranged', 'Both', 'Amanitar Skullbow'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 224, 44006, 'Phase 1', 'Warrior', 'Fury', 'Head', 'Both', 'Obsidian Greathelm'), +(1, 1, 1, 0, 224, 44664, 'Phase 1', 'Warrior', 'Fury', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(1, 1, 2, 0, 224, 40530, 'Phase 1', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Valorous Dreadnaught Shoulderplates'), +(1, 1, 4, 0, 224, 40539, 'Phase 1', 'Warrior', 'Fury', 'Chest', 'Both', 'Chestguard of the Recluse'), +(1, 1, 5, 0, 224, 40205, 'Phase 1', 'Warrior', 'Fury', 'Waist', 'Both', 'Stalk-Skin Belt'), +(1, 1, 6, 0, 224, 40529, 'Phase 1', 'Warrior', 'Fury', 'Legs', 'Both', 'Valorous Dreadnaught Legplates'), +(1, 1, 7, 0, 224, 40591, 'Phase 1', 'Warrior', 'Fury', 'Feet', 'Both', 'Melancholy Sabatons'), +(1, 1, 8, 0, 224, 39765, 'Phase 1', 'Warrior', 'Fury', 'Wrists', 'Both', 'Sinner''s Bindings'), +(1, 1, 9, 0, 224, 40541, 'Phase 1', 'Warrior', 'Fury', 'Hands', 'Both', 'Frosted Adroit Handguards'), +(1, 1, 10, 0, 224, 40717, 'Phase 1', 'Warrior', 'Fury', 'Finger1', 'Both', 'Ring of Invincibility'), +(1, 1, 12, 0, 224, 40256, 'Phase 1', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Grim Toll'), +(1, 1, 14, 0, 224, 40403, 'Phase 1', 'Warrior', 'Fury', 'Back', 'Both', 'Drape of the Deadly Foe'), +(1, 1, 17, 0, 224, 40385, 'Phase 1', 'Warrior', 'Fury', 'Ranged', 'Both', 'Envoy of Mortality'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 245, 46151, 'Phase 2', 'Warrior', 'Fury', 'Head', 'Both', 'Conqueror''s Siegebreaker Helmet'), +(1, 1, 1, 0, 245, 45517, 'Phase 2', 'Warrior', 'Fury', 'Neck', 'Both', 'Pendulum of Infinity'), +(1, 1, 2, 0, 245, 46037, 'Phase 2', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Shoulderplates of the Celestial Watch'), +(1, 1, 4, 0, 245, 46146, 'Phase 2', 'Warrior', 'Fury', 'Chest', 'Both', 'Conqueror''s Siegebreaker Battleplate'), +(1, 1, 5, 0, 245, 46041, 'Phase 2', 'Warrior', 'Fury', 'Waist', 'Both', 'Starfall Girdle'), +(1, 1, 6, 0, 245, 45536, 'Phase 2', 'Warrior', 'Fury', 'Legs', 'Both', 'Legguards of Cunning Deception'), +(1, 1, 7, 0, 245, 45599, 'Phase 2', 'Warrior', 'Fury', 'Feet', 'Both', 'Sabatons of Lifeless Night'), +(1, 1, 9, 0, 245, 46148, 'Phase 2', 'Warrior', 'Fury', 'Hands', 'Both', 'Conqueror''s Siegebreaker Gauntlets'), +(1, 1, 10, 0, 245, 45608, 'Phase 2', 'Warrior', 'Fury', 'Finger1', 'Both', 'Brann''s Signet Ring'), +(1, 1, 12, 0, 245, 46038, 'Phase 2', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Dark Matter'), +(1, 1, 14, 0, 245, 46032, 'Phase 2', 'Warrior', 'Fury', 'Back', 'Both', 'Drape of the Faceless General'), +(1, 1, 17, 0, 245, 45296, 'Phase 2', 'Warrior', 'Fury', 'Ranged', 'Both', 'Twirling Blades'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 1, 258, 48383, 'Phase 3', 'Warrior', 'Fury', 'Head', 'Alliance', 'Wrynn''s Helmet of Triumph'), +(1, 1, 0, 2, 258, 48398, 'Phase 3', 'Warrior', 'Fury', 'Head', 'Horde', 'Hellscream''s Helmet of Triumph'), +(1, 1, 1, 1, 258, 47060, 'Phase 3', 'Warrior', 'Fury', 'Neck', 'Alliance', 'Charge of the Demon Lord'), +(1, 1, 1, 2, 258, 47433, 'Phase 3', 'Warrior', 'Fury', 'Neck', 'Horde', 'Charge of the Eredar'), +(1, 1, 2, 1, 258, 48381, 'Phase 3', 'Warrior', 'Fury', 'Shoulders', 'Alliance', 'Wrynn''s Shoulderplates of Triumph'), +(1, 1, 2, 2, 258, 48400, 'Phase 3', 'Warrior', 'Fury', 'Shoulders', 'Horde', 'Hellscream''s Shoulderplates of Triumph'), +(1, 1, 4, 1, 258, 48385, 'Phase 3', 'Warrior', 'Fury', 'Chest', 'Alliance', 'Wrynn''s Battleplate of Triumph'), +(1, 1, 4, 2, 258, 48396, 'Phase 3', 'Warrior', 'Fury', 'Chest', 'Horde', 'Hellscream''s Battleplate of Triumph'), +(1, 1, 5, 1, 258, 47002, 'Phase 3', 'Warrior', 'Fury', 'Waist', 'Alliance', 'Bloodbath Belt'), +(1, 1, 5, 2, 258, 47429, 'Phase 3', 'Warrior', 'Fury', 'Waist', 'Horde', 'Bloodbath Girdle'), +(1, 1, 6, 1, 258, 48382, 'Phase 3', 'Warrior', 'Fury', 'Legs', 'Alliance', 'Wrynn''s Legplates of Triumph'), +(1, 1, 6, 2, 258, 48399, 'Phase 3', 'Warrior', 'Fury', 'Legs', 'Horde', 'Hellscream''s Legplates of Triumph'), +(1, 1, 7, 1, 258, 47154, 'Phase 3', 'Warrior', 'Fury', 'Feet', 'Alliance', 'Greaves of the 7th Legion'), +(1, 1, 7, 2, 258, 47473, 'Phase 3', 'Warrior', 'Fury', 'Feet', 'Horde', 'Greaves of the Saronite Citadel'), +(1, 1, 8, 1, 258, 47074, 'Phase 3', 'Warrior', 'Fury', 'Wrists', 'Alliance', 'Bracers of the Untold Massacre'), +(1, 1, 8, 2, 258, 47442, 'Phase 3', 'Warrior', 'Fury', 'Wrists', 'Horde', 'Bracers of the Silent Massacre'), +(1, 1, 9, 1, 258, 47240, 'Phase 3', 'Warrior', 'Fury', 'Hands', 'Alliance', 'Gloves of Bitter Reprisal'), +(1, 1, 9, 2, 258, 47492, 'Phase 3', 'Warrior', 'Fury', 'Hands', 'Horde', 'Gauntlets of Bitter Reprisal'), +(1, 1, 10, 1, 258, 47075, 'Phase 3', 'Warrior', 'Fury', 'Finger1', 'Alliance', 'Ring of Callous Aggression'), +(1, 1, 10, 2, 258, 47443, 'Phase 3', 'Warrior', 'Fury', 'Finger1', 'Horde', 'Band of Callous Aggression'), +(1, 1, 12, 1, 258, 47131, 'Phase 3', 'Warrior', 'Fury', 'Trinket1', 'Alliance', 'Death''s Verdict'), +(1, 1, 12, 2, 258, 47464, 'Phase 3', 'Warrior', 'Fury', 'Trinket1', 'Horde', 'Death''s Choice'), +(1, 1, 14, 1, 258, 47545, 'Phase 3', 'Warrior', 'Fury', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(1, 1, 14, 2, 258, 47546, 'Phase 3', 'Warrior', 'Fury', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(1, 1, 17, 1, 258, 46995, 'Phase 3', 'Warrior', 'Fury', 'Ranged', 'Alliance', 'Talonstrike'), +(1, 1, 17, 2, 258, 47428, 'Phase 3', 'Warrior', 'Fury', 'Ranged', 'Horde', 'Death''s Head Crossbow'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 264, 51227, 'Phase 4', 'Warrior', 'Fury', 'Head', 'Both', 'Sanctified Ymirjar Lord''s Helmet'), +(1, 1, 1, 0, 264, 50633, 'Phase 4', 'Warrior', 'Fury', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(1, 1, 2, 0, 264, 51229, 'Phase 4', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Sanctified Ymirjar Lord''s Shoulderplates'), +(1, 1, 4, 0, 264, 51225, 'Phase 4', 'Warrior', 'Fury', 'Chest', 'Both', 'Sanctified Ymirjar Lord''s Battleplate'), +(1, 1, 5, 0, 264, 50620, 'Phase 4', 'Warrior', 'Fury', 'Waist', 'Both', 'Coldwraith Links'), +(1, 1, 6, 0, 264, 51228, 'Phase 4', 'Warrior', 'Fury', 'Legs', 'Both', 'Sanctified Ymirjar Lord''s Legplates'), +(1, 1, 7, 0, 264, 50639, 'Phase 4', 'Warrior', 'Fury', 'Feet', 'Both', 'Blood-Soaked Saronite Stompers'), +(1, 1, 8, 0, 264, 50670, 'Phase 4', 'Warrior', 'Fury', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(1, 1, 9, 0, 264, 50675, 'Phase 4', 'Warrior', 'Fury', 'Hands', 'Both', 'Aldriana''s Gloves of Secrecy'), +(1, 1, 10, 1, 264, 47075, 'Phase 4', 'Warrior', 'Fury', 'Finger1', 'Alliance', 'Ring of Callous Aggression'), +(1, 1, 10, 2, 264, 47443, 'Phase 4', 'Warrior', 'Fury', 'Finger1', 'Horde', 'Band of Callous Aggression'), +(1, 1, 12, 0, 264, 50363, 'Phase 4', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(1, 1, 14, 0, 264, 50653, 'Phase 4', 'Warrior', 'Fury', 'Back', 'Both', 'Shadowvault Slayer''s Cloak'), +(1, 1, 17, 0, 264, 50733, 'Phase 4', 'Warrior', 'Fury', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 1, 0, 0, 290, 51227, 'Phase 5', 'Warrior', 'Fury', 'Head', 'Both', 'Sanctified Ymirjar Lord''s Helmet'), +(1, 1, 1, 0, 290, 54581, 'Phase 5', 'Warrior', 'Fury', 'Neck', 'Both', 'Penumbra Pendant'), +(1, 1, 2, 0, 290, 51229, 'Phase 5', 'Warrior', 'Fury', 'Shoulders', 'Both', 'Sanctified Ymirjar Lord''s Shoulderplates'), +(1, 1, 4, 0, 290, 51225, 'Phase 5', 'Warrior', 'Fury', 'Chest', 'Both', 'Sanctified Ymirjar Lord''s Battleplate'), +(1, 1, 5, 0, 290, 50620, 'Phase 5', 'Warrior', 'Fury', 'Waist', 'Both', 'Coldwraith Links'), +(1, 1, 6, 0, 290, 51228, 'Phase 5', 'Warrior', 'Fury', 'Legs', 'Both', 'Sanctified Ymirjar Lord''s Legplates'), +(1, 1, 7, 0, 290, 54578, 'Phase 5', 'Warrior', 'Fury', 'Feet', 'Both', 'Apocalypse''s Advance'), +(1, 1, 8, 0, 290, 50670, 'Phase 5', 'Warrior', 'Fury', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(1, 1, 9, 0, 290, 50675, 'Phase 5', 'Warrior', 'Fury', 'Hands', 'Both', 'Aldriana''s Gloves of Secrecy'), +(1, 1, 10, 0, 290, 50657, 'Phase 5', 'Warrior', 'Fury', 'Finger1', 'Both', 'Skeleton Lord''s Circle'), +(1, 1, 11, 0, 290, 52572, 'Phase 5', 'Warrior', 'Fury', 'Finger2', 'Both', 'Ashen Band of Endless Might'), +(1, 1, 12, 0, 290, 50363, 'Phase 5', 'Warrior', 'Fury', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(1, 1, 13, 0, 290, 54590, 'Phase 5', 'Warrior', 'Fury', 'Trinket2', 'Both', 'Sharpened Twilight Scale'), +(1, 1, 14, 0, 290, 47545, 'Phase 5', 'Warrior', 'Fury', 'Back', 'Both', 'Vereesa''s Dexterity'), +(1, 1, 15, 0, 290, 50603, 'Phase 5', 'Warrior', 'Fury', 'MainHand', 'Both', 'Cryptmaker'), +(1, 1, 16, 0, 290, 50603, 'Phase 5', 'Warrior', 'Fury', 'OffHand', 'Both', 'Cryptmaker'), +(1, 1, 17, 0, 290, 50733, 'Phase 5', 'Warrior', 'Fury', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- Protection (tab 2) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 66, 12952, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Head', 'Both', 'Gyth''s Skull'), +(1, 2, 1, 0, 66, 13091, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Neck', 'Both', 'Medallion of Grand Marshal Morris'), +(1, 2, 2, 0, 66, 14552, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Stockade Pauldrons'), +(1, 2, 4, 0, 66, 14624, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Chest', 'Both', 'Deathbone Chestplate'), +(1, 2, 5, 0, 66, 14620, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Waist', 'Both', 'Deathbone Girdle'), +(1, 2, 6, 0, 66, 11927, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Legs', 'Both', 'Legplates of the Eternal Guardian'), +(1, 2, 7, 0, 66, 14621, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Feet', 'Both', 'Deathbone Sabatons'), +(1, 2, 8, 0, 66, 12550, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Wrists', 'Both', 'Runed Golem Shackles'), +(1, 2, 9, 0, 66, 13072, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Hands', 'Both', 'Stonegrip Gauntlets'), +(1, 2, 10, 0, 66, 11669, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Finger1', 'Both', 'Naglering'), +(1, 2, 11, 0, 66, 10795, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Finger2', 'Both', 'Drakeclaw Band'), +(1, 2, 13, 0, 66, 10779, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Demon''s Blood'), +(1, 2, 14, 0, 66, 13397, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Back', 'Both', 'Stoneskin Gargoyle Cape'), +(1, 2, 15, 0, 66, 15806, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'MainHand', 'Both', 'Mirah''s Song'), +(1, 2, 16, 0, 66, 12602, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'OffHand', 'Both', 'Draconian Deflector'), +(1, 2, 17, 0, 66, 12651, 'Phase 1 (Pre-Raid)', 'Warrior', 'Protection', 'Ranged', 'Both', 'Blackcrow'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 76, 12952, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Head', 'Both', 'Gyth''s Skull'), +(1, 2, 1, 0, 76, 13091, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Neck', 'Both', 'Medallion of Grand Marshal Morris'), +(1, 2, 2, 0, 76, 14552, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Stockade Pauldrons'), +(1, 2, 4, 0, 76, 14624, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Chest', 'Both', 'Deathbone Chestplate'), +(1, 2, 5, 0, 76, 14620, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Waist', 'Both', 'Deathbone Girdle'), +(1, 2, 6, 0, 76, 11927, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Legs', 'Both', 'Legplates of the Eternal Guardian'), +(1, 2, 7, 0, 76, 14621, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Feet', 'Both', 'Deathbone Sabatons'), +(1, 2, 8, 0, 76, 18754, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Wrists', 'Both', 'Fel Hardened Bracers'), +(1, 2, 9, 0, 76, 13072, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Hands', 'Both', 'Stonegrip Gauntlets'), +(1, 2, 10, 0, 76, 11669, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Finger1', 'Both', 'Naglering'), +(1, 2, 11, 0, 76, 10795, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Finger2', 'Both', 'Drakeclaw Band'), +(1, 2, 13, 0, 76, 10779, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Demon''s Blood'), +(1, 2, 14, 0, 76, 18495, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Back', 'Both', 'Redoubt Cloak'), +(1, 2, 15, 0, 76, 15806, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'MainHand', 'Both', 'Mirah''s Song'), +(1, 2, 16, 0, 76, 12602, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'OffHand', 'Both', 'Draconian Deflector'), +(1, 2, 17, 0, 76, 18323, 'Phase 2 (Pre-Raid)', 'Warrior', 'Protection', 'Ranged', 'Both', 'Satyr''s Bow'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 78, 16963, 'Phase 2', 'Warrior', 'Protection', 'Head', 'Both', 'Helm of Wrath'), +(1, 2, 1, 0, 78, 17065, 'Phase 2', 'Warrior', 'Protection', 'Neck', 'Both', 'Medallion of Steadfast Might'), +(1, 2, 2, 0, 78, 16868, 'Phase 2', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Pauldrons of Might'), +(1, 2, 4, 0, 78, 14624, 'Phase 2', 'Warrior', 'Protection', 'Chest', 'Both', 'Deathbone Chestplate'), +(1, 2, 5, 0, 78, 16864, 'Phase 2', 'Warrior', 'Protection', 'Waist', 'Both', 'Belt of Might'), +(1, 2, 6, 0, 78, 16962, 'Phase 2', 'Warrior', 'Protection', 'Legs', 'Both', 'Legplates of Wrath'), +(1, 2, 7, 0, 78, 16862, 'Phase 2', 'Warrior', 'Protection', 'Feet', 'Both', 'Sabatons of Might'), +(1, 2, 8, 0, 78, 16861, 'Phase 2', 'Warrior', 'Protection', 'Wrists', 'Both', 'Bracers of Might'), +(1, 2, 9, 0, 78, 13072, 'Phase 2', 'Warrior', 'Protection', 'Hands', 'Both', 'Stonegrip Gauntlets'), +(1, 2, 10, 0, 78, 11669, 'Phase 2', 'Warrior', 'Protection', 'Finger1', 'Both', 'Naglering'), +(1, 2, 11, 0, 78, 18879, 'Phase 2', 'Warrior', 'Protection', 'Finger2', 'Both', 'Heavy Dark Iron Ring'), +(1, 2, 13, 0, 78, 18406, 'Phase 2', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Onyxia Blood Talisman'), +(1, 2, 14, 0, 78, 18495, 'Phase 2', 'Warrior', 'Protection', 'Back', 'Both', 'Redoubt Cloak'), +(1, 2, 15, 0, 78, 18203, 'Phase 2', 'Warrior', 'Protection', 'MainHand', 'Both', 'Eskhandar''s Right Claw'), +(1, 2, 16, 0, 78, 17066, 'Phase 2', 'Warrior', 'Protection', 'OffHand', 'Both', 'Drillborer Disk'), +(1, 2, 17, 0, 78, 18323, 'Phase 2', 'Warrior', 'Protection', 'Ranged', 'Both', 'Satyr''s Bow'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 83, 16963, 'Phase 3', 'Warrior', 'Protection', 'Head', 'Both', 'Helm of Wrath'), +(1, 2, 1, 0, 83, 19383, 'Phase 3', 'Warrior', 'Protection', 'Neck', 'Both', 'Master Dragonslayer''s Medallion'), +(1, 2, 2, 0, 83, 16961, 'Phase 3', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Pauldrons of Wrath'), +(1, 2, 4, 0, 83, 16966, 'Phase 3', 'Warrior', 'Protection', 'Chest', 'Both', 'Breastplate of Wrath'), +(1, 2, 5, 0, 83, 16960, 'Phase 3', 'Warrior', 'Protection', 'Waist', 'Both', 'Waistband of Wrath'), +(1, 2, 6, 0, 83, 16962, 'Phase 3', 'Warrior', 'Protection', 'Legs', 'Both', 'Legplates of Wrath'), +(1, 2, 7, 0, 83, 16965, 'Phase 3', 'Warrior', 'Protection', 'Feet', 'Both', 'Sabatons of Wrath'), +(1, 2, 8, 0, 83, 16959, 'Phase 3', 'Warrior', 'Protection', 'Wrists', 'Both', 'Bracelets of Wrath'), +(1, 2, 9, 0, 83, 16964, 'Phase 3', 'Warrior', 'Protection', 'Hands', 'Both', 'Gauntlets of Wrath'), +(1, 2, 10, 0, 83, 11669, 'Phase 3', 'Warrior', 'Protection', 'Finger1', 'Both', 'Naglering'), +(1, 2, 11, 0, 83, 18879, 'Phase 3', 'Warrior', 'Protection', 'Finger2', 'Both', 'Heavy Dark Iron Ring'), +(1, 2, 12, 0, 83, 19431, 'Phase 3', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Styleen''s Impeding Scarab'), +(1, 2, 13, 0, 83, 18406, 'Phase 3', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Onyxia Blood Talisman'), +(1, 2, 14, 0, 83, 18495, 'Phase 3', 'Warrior', 'Protection', 'Back', 'Both', 'Redoubt Cloak'), +(1, 2, 15, 0, 83, 19335, 'Phase 3', 'Warrior', 'Protection', 'MainHand', 'Both', 'Spineshatter'), +(1, 2, 17, 0, 83, 19368, 'Phase 3', 'Warrior', 'Protection', 'Ranged', 'Both', 'Dragonbreath Hand Cannon'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 88, 16963, 'Phase 5', 'Warrior', 'Protection', 'Head', 'Both', 'Helm of Wrath'), +(1, 2, 1, 0, 88, 22732, 'Phase 5', 'Warrior', 'Protection', 'Neck', 'Both', 'Mark of C''Thun'), +(1, 2, 2, 0, 88, 21639, 'Phase 5', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Pauldrons of the Unrelenting'), +(1, 2, 4, 0, 88, 16966, 'Phase 5', 'Warrior', 'Protection', 'Chest', 'Both', 'Breastplate of Wrath'), +(1, 2, 5, 0, 88, 21598, 'Phase 5', 'Warrior', 'Protection', 'Waist', 'Both', 'Royal Qiraji Belt'), +(1, 2, 6, 0, 88, 16962, 'Phase 5', 'Warrior', 'Protection', 'Legs', 'Both', 'Legplates of Wrath'), +(1, 2, 7, 0, 88, 16965, 'Phase 5', 'Warrior', 'Protection', 'Feet', 'Both', 'Sabatons of Wrath'), +(1, 2, 8, 0, 88, 16959, 'Phase 5', 'Warrior', 'Protection', 'Wrists', 'Both', 'Bracelets of Wrath'), +(1, 2, 9, 0, 88, 21674, 'Phase 5', 'Warrior', 'Protection', 'Hands', 'Both', 'Gauntlets of Steadfast Determination'), +(1, 2, 10, 0, 88, 21601, 'Phase 5', 'Warrior', 'Protection', 'Finger1', 'Both', 'Ring of Emperor Vek''lor'), +(1, 2, 11, 0, 88, 18879, 'Phase 5', 'Warrior', 'Protection', 'Finger2', 'Both', 'Heavy Dark Iron Ring'), +(1, 2, 12, 0, 88, 19431, 'Phase 5', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Styleen''s Impeding Scarab'), +(1, 2, 13, 0, 88, 18406, 'Phase 5', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Onyxia Blood Talisman'), +(1, 2, 14, 0, 88, 19888, 'Phase 5', 'Warrior', 'Protection', 'Back', 'Both', 'Overlord''s Embrace'), +(1, 2, 15, 0, 88, 19363, 'Phase 5', 'Warrior', 'Protection', 'MainHand', 'Both', 'Crul''shorukh, Edge of Chaos'), +(1, 2, 16, 0, 88, 21269, 'Phase 5', 'Warrior', 'Protection', 'OffHand', 'Both', 'Blessed Qiraji Bulwark'), +(1, 2, 17, 0, 88, 19368, 'Phase 5', 'Warrior', 'Protection', 'Ranged', 'Both', 'Dragonbreath Hand Cannon'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 92, 22418, 'Phase 6', 'Warrior', 'Protection', 'Head', 'Both', 'Dreadnaught Helmet'), +(1, 2, 1, 0, 92, 22732, 'Phase 6', 'Warrior', 'Protection', 'Neck', 'Both', 'Mark of C''Thun'), +(1, 2, 2, 0, 92, 22419, 'Phase 6', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Dreadnaught Pauldrons'), +(1, 2, 4, 0, 92, 22416, 'Phase 6', 'Warrior', 'Protection', 'Chest', 'Both', 'Dreadnaught Breastplate'), +(1, 2, 5, 0, 92, 22422, 'Phase 6', 'Warrior', 'Protection', 'Waist', 'Both', 'Dreadnaught Waistguard'), +(1, 2, 6, 0, 92, 22417, 'Phase 6', 'Warrior', 'Protection', 'Legs', 'Both', 'Dreadnaught Legplates'), +(1, 2, 7, 0, 92, 22420, 'Phase 6', 'Warrior', 'Protection', 'Feet', 'Both', 'Dreadnaught Sabatons'), +(1, 2, 8, 0, 92, 22423, 'Phase 6', 'Warrior', 'Protection', 'Wrists', 'Both', 'Dreadnaught Bracers'), +(1, 2, 9, 0, 92, 22421, 'Phase 6', 'Warrior', 'Protection', 'Hands', 'Both', 'Dreadnaught Gauntlets'), +(1, 2, 10, 0, 92, 21601, 'Phase 6', 'Warrior', 'Protection', 'Finger1', 'Both', 'Ring of Emperor Vek''lor'), +(1, 2, 11, 0, 92, 23059, 'Phase 6', 'Warrior', 'Protection', 'Finger2', 'Both', 'Ring of the Dreadnaught'), +(1, 2, 12, 0, 92, 19431, 'Phase 6', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Styleen''s Impeding Scarab'), +(1, 2, 13, 0, 92, 19406, 'Phase 6', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Drake Fang Talisman'), +(1, 2, 14, 0, 92, 22938, 'Phase 6', 'Warrior', 'Protection', 'Back', 'Both', 'Cryptfiend Silk Cloak'), +(1, 2, 15, 0, 92, 23577, 'Phase 6', 'Warrior', 'Protection', 'MainHand', 'Both', 'The Hungering Cold'), +(1, 2, 16, 0, 92, 23043, 'Phase 6', 'Warrior', 'Protection', 'OffHand', 'Both', 'The Face of Death'), +(1, 2, 17, 0, 92, 19368, 'Phase 6', 'Warrior', 'Protection', 'Ranged', 'Both', 'Dragonbreath Hand Cannon'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 120, 32083, 'Pre-Raid', 'Warrior', 'Protection', 'Head', 'Both', 'Faceguard of Determination'), +(1, 2, 1, 0, 120, 29386, 'Pre-Raid', 'Warrior', 'Protection', 'Neck', 'Both', 'Necklace of the Juggernaut'), +(1, 2, 2, 0, 120, 27803, 'Pre-Raid', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Shoulderguards of the Bold'), +(1, 2, 4, 0, 120, 28205, 'Pre-Raid', 'Warrior', 'Protection', 'Chest', 'Both', 'Breastplate of the Bold'), +(1, 2, 5, 0, 120, 31460, 'Pre-Raid', 'Warrior', 'Protection', 'Waist', 'Both', 'Sha''tari Vindicator''s Waistguard'), +(1, 2, 6, 0, 120, 29184, 'Pre-Raid', 'Warrior', 'Protection', 'Legs', 'Both', 'Timewarden''s Leggings'), +(1, 2, 7, 0, 120, 29239, 'Pre-Raid', 'Warrior', 'Protection', 'Feet', 'Both', 'Eaglecrest Warboots'), +(1, 2, 8, 0, 120, 29463, 'Pre-Raid', 'Warrior', 'Protection', 'Wrists', 'Both', 'Amber Bands of the Aggressor'), +(1, 2, 9, 0, 120, 27475, 'Pre-Raid', 'Warrior', 'Protection', 'Hands', 'Both', 'Gauntlets of the Bold'), +(1, 2, 10, 0, 120, 30834, 'Pre-Raid', 'Warrior', 'Protection', 'Finger1', 'Both', 'Shapeshifter''s Signet'), +(1, 2, 12, 0, 120, 29387, 'Pre-Raid', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Gnomeregan Auto-Blocker 600'), +(1, 2, 13, 0, 120, 23836, 'Pre-Raid', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Goblin Rocket Launcher'), +(1, 2, 14, 0, 120, 27804, 'Pre-Raid', 'Warrior', 'Protection', 'Back', 'Both', 'Devilshark Cape'), +(1, 2, 15, 0, 120, 28438, 'Pre-Raid', 'Warrior', 'Protection', 'MainHand', 'Both', 'Dragonmaw'), +(1, 2, 16, 0, 120, 29266, 'Pre-Raid', 'Warrior', 'Protection', 'OffHand', 'Both', 'Azure-Shield of Coldarra'), +(1, 2, 17, 0, 120, 32756, 'Pre-Raid', 'Warrior', 'Protection', 'Ranged', 'Both', 'Gyro-Balanced Khorium Destroyer'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 125, 29011, 'Phase 1', 'Warrior', 'Protection', 'Head', 'Both', 'Warbringer Greathelm'), +(1, 2, 1, 0, 125, 28516, 'Phase 1', 'Warrior', 'Protection', 'Neck', 'Both', 'Barbed Choker of Discipline'), +(1, 2, 2, 0, 125, 29016, 'Phase 1', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Warbringer Shoulderguards'), +(1, 2, 4, 0, 125, 29012, 'Phase 1', 'Warrior', 'Protection', 'Chest', 'Both', 'Warbringer Chestguard'), +(1, 2, 5, 0, 125, 28566, 'Phase 1', 'Warrior', 'Protection', 'Waist', 'Both', 'Crimson Girdle of the Indomitable'), +(1, 2, 6, 0, 125, 28621, 'Phase 1', 'Warrior', 'Protection', 'Legs', 'Both', 'Wrynn Dynasty Greaves'), +(1, 2, 7, 0, 125, 28747, 'Phase 1', 'Warrior', 'Protection', 'Feet', 'Both', 'Battlescar Boots'), +(1, 2, 8, 0, 125, 28502, 'Phase 1', 'Warrior', 'Protection', 'Wrists', 'Both', 'Vambraces of Courage'), +(1, 2, 9, 0, 125, 28518, 'Phase 1', 'Warrior', 'Protection', 'Hands', 'Both', 'Iron Gauntlets of the Maiden'), +(1, 2, 10, 0, 125, 29279, 'Phase 1', 'Warrior', 'Protection', 'Finger1', 'Both', 'Violet Signet of the Great Protector'), +(1, 2, 11, 0, 125, 30834, 'Phase 1', 'Warrior', 'Protection', 'Finger2', 'Both', 'Shapeshifter''s Signet'), +(1, 2, 12, 0, 125, 29387, 'Phase 1', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Gnomeregan Auto-Blocker 600'), +(1, 2, 13, 0, 125, 23836, 'Phase 1', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Goblin Rocket Launcher'), +(1, 2, 14, 0, 125, 28660, 'Phase 1', 'Warrior', 'Protection', 'Back', 'Both', 'Gilded Thorium Cloak'), +(1, 2, 15, 0, 125, 28438, 'Phase 1', 'Warrior', 'Protection', 'MainHand', 'Both', 'Dragonmaw'), +(1, 2, 16, 0, 125, 28825, 'Phase 1', 'Warrior', 'Protection', 'OffHand', 'Both', 'Aldori Legacy Defender'), +(1, 2, 17, 0, 125, 30724, 'Phase 1', 'Warrior', 'Protection', 'Ranged', 'Both', 'Barrel-Blade Longrifle'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 141, 30115, 'Phase 2', 'Warrior', 'Protection', 'Head', 'Both', 'Destroyer Greathelm'), +(1, 2, 1, 0, 141, 33066, 'Phase 2', 'Warrior', 'Protection', 'Neck', 'Both', 'Veteran''s Pendant of Triumph'), +(1, 2, 2, 0, 141, 30117, 'Phase 2', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Destroyer Shoulderguards'), +(1, 2, 4, 0, 141, 30113, 'Phase 2', 'Warrior', 'Protection', 'Chest', 'Both', 'Destroyer Chestguard'), +(1, 2, 5, 0, 141, 30106, 'Phase 2', 'Warrior', 'Protection', 'Waist', 'Both', 'Belt of One-Hundred Deaths'), +(1, 2, 6, 0, 141, 30116, 'Phase 2', 'Warrior', 'Protection', 'Legs', 'Both', 'Destroyer Legguards'), +(1, 2, 7, 0, 141, 32793, 'Phase 2', 'Warrior', 'Protection', 'Feet', 'Both', 'Veteran''s Plate Greaves'), +(1, 2, 8, 0, 141, 32818, 'Phase 2', 'Warrior', 'Protection', 'Wrists', 'Both', 'Veteran''s Plate Bracers'), +(1, 2, 9, 2, 141, 29998, 'Phase 2', 'Warrior', 'Protection', 'Hands', 'Horde', 'Royal Gauntlets of Silvermoon'), +(1, 2, 10, 0, 141, 30834, 'Phase 2', 'Warrior', 'Protection', 'Finger1', 'Both', 'Shapeshifter''s Signet'), +(1, 2, 11, 0, 141, 29283, 'Phase 2', 'Warrior', 'Protection', 'Finger2', 'Both', 'Violet Signet of the Master Assassin'), +(1, 2, 12, 0, 141, 23836, 'Phase 2', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Goblin Rocket Launcher'), +(1, 2, 13, 0, 141, 28121, 'Phase 2', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Icon of Unyielding Courage'), +(1, 2, 14, 0, 141, 29994, 'Phase 2', 'Warrior', 'Protection', 'Back', 'Both', 'Thalassian Wildercloak'), +(1, 2, 15, 0, 141, 28439, 'Phase 2', 'Warrior', 'Protection', 'MainHand', 'Both', 'Dragonstrike'), +(1, 2, 16, 0, 141, 28825, 'Phase 2', 'Warrior', 'Protection', 'OffHand', 'Both', 'Aldori Legacy Defender'), +(1, 2, 17, 0, 141, 32756, 'Phase 2', 'Warrior', 'Protection', 'Ranged', 'Both', 'Gyro-Balanced Khorium Destroyer'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 156, 32521, 'Phase 3', 'Warrior', 'Protection', 'Head', 'Both', 'Faceplate of the Impenetrable'), +(1, 2, 1, 0, 156, 33066, 'Phase 3', 'Warrior', 'Protection', 'Neck', 'Both', 'Veteran''s Pendant of Triumph'), +(1, 2, 2, 0, 156, 33732, 'Phase 3', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Vengeful Gladiator''s Plate Shoulders'), +(1, 2, 4, 0, 156, 33728, 'Phase 3', 'Warrior', 'Protection', 'Chest', 'Both', 'Vengeful Gladiator''s Plate Chestpiece'), +(1, 2, 5, 0, 156, 30106, 'Phase 3', 'Warrior', 'Protection', 'Waist', 'Both', 'Belt of One-Hundred Deaths'), +(1, 2, 6, 0, 156, 30978, 'Phase 3', 'Warrior', 'Protection', 'Legs', 'Both', 'Onslaught Legguards'), +(1, 2, 7, 0, 156, 33812, 'Phase 3', 'Warrior', 'Protection', 'Feet', 'Both', 'Vindicator''s Plate Greaves'), +(1, 2, 8, 0, 156, 33813, 'Phase 3', 'Warrior', 'Protection', 'Wrists', 'Both', 'Vindicator''s Plate Bracers'), +(1, 2, 10, 0, 156, 30834, 'Phase 3', 'Warrior', 'Protection', 'Finger1', 'Both', 'Shapeshifter''s Signet'), +(1, 2, 11, 0, 156, 33919, 'Phase 3', 'Warrior', 'Protection', 'Finger2', 'Both', 'Vindicator''s Band of Triumph'), +(1, 2, 12, 0, 156, 31858, 'Phase 3', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Darkmoon Card: Vengeance'), +(1, 2, 13, 0, 156, 32501, 'Phase 3', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Shadowmoon Insignia'), +(1, 2, 14, 0, 156, 34010, 'Phase 3', 'Warrior', 'Protection', 'Back', 'Both', 'Pepe''s Shroud of Pacification'), +(1, 2, 15, 0, 156, 32254, 'Phase 3', 'Warrior', 'Protection', 'MainHand', 'Both', 'The Brutalizer'), +(1, 2, 16, 0, 156, 32375, 'Phase 3', 'Warrior', 'Protection', 'OffHand', 'Both', 'Bulwark of Azzinoth'), +(1, 2, 17, 0, 156, 32253, 'Phase 3', 'Warrior', 'Protection', 'Ranged', 'Both', 'Legionkiller'); + +-- ilvl 164 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 164, 35068, 'Phase 5', 'Warrior', 'Protection', 'Head', 'Both', 'Brutal Gladiator''s Plate Helm'), +(1, 2, 1, 0, 164, 34178, 'Phase 5', 'Warrior', 'Protection', 'Neck', 'Both', 'Collar of the Pit Lord'), +(1, 2, 2, 0, 164, 34388, 'Phase 5', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Pauldrons of Berserking'), +(1, 2, 4, 0, 164, 35066, 'Phase 5', 'Warrior', 'Protection', 'Chest', 'Both', 'Brutal Gladiator''s Plate Chestpiece'), +(1, 2, 5, 0, 164, 34547, 'Phase 5', 'Warrior', 'Protection', 'Waist', 'Both', 'Onslaught Waistguard'), +(1, 2, 6, 0, 164, 34381, 'Phase 5', 'Warrior', 'Protection', 'Legs', 'Both', 'Felstrength Legplates'), +(1, 2, 7, 0, 164, 34568, 'Phase 5', 'Warrior', 'Protection', 'Feet', 'Both', 'Onslaught Boots'), +(1, 2, 8, 0, 164, 34442, 'Phase 5', 'Warrior', 'Protection', 'Wrists', 'Both', 'Onslaught Wristguards'), +(1, 2, 9, 0, 164, 34378, 'Phase 5', 'Warrior', 'Protection', 'Hands', 'Both', 'Hard Khorium Battlefists'), +(1, 2, 10, 0, 164, 35131, 'Phase 5', 'Warrior', 'Protection', 'Finger1', 'Both', 'Guardian''s Band of Triumph'), +(1, 2, 11, 0, 164, 34213, 'Phase 5', 'Warrior', 'Protection', 'Finger2', 'Both', 'Ring of Hardened Resolve'), +(1, 2, 12, 0, 164, 34473, 'Phase 5', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Commendation of Kael''thas'), +(1, 2, 13, 0, 164, 31858, 'Phase 5', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Darkmoon Card: Vengeance'), +(1, 2, 14, 0, 164, 34190, 'Phase 5', 'Warrior', 'Protection', 'Back', 'Both', 'Crimson Paragon''s Cover'), +(1, 2, 15, 0, 164, 34164, 'Phase 5', 'Warrior', 'Protection', 'MainHand', 'Both', 'Dragonscale-Encrusted Longblade'), +(1, 2, 16, 0, 164, 34185, 'Phase 5', 'Warrior', 'Protection', 'OffHand', 'Both', 'Sword Breaker''s Bulwark'), +(1, 2, 17, 0, 164, 32253, 'Phase 5', 'Warrior', 'Protection', 'Ranged', 'Both', 'Legionkiller'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 200, 41387, 'Pre-Raid', 'Warrior', 'Protection', 'Head', 'Both', 'Tempered Titansteel Helm'), +(1, 2, 1, 0, 200, 40679, 'Pre-Raid', 'Warrior', 'Protection', 'Neck', 'Both', 'Chained Military Gorget'), +(1, 2, 2, 0, 200, 34389, 'Pre-Raid', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Spaulders of the Thalassian Defender'), +(1, 2, 4, 0, 200, 39611, 'Pre-Raid', 'Warrior', 'Protection', 'Chest', 'Both', 'Heroes'' Dreadnaught Breastplate'), +(1, 2, 5, 0, 200, 37379, 'Pre-Raid', 'Warrior', 'Protection', 'Waist', 'Both', 'Skadi''s Iron Belt'), +(1, 2, 6, 0, 200, 43500, 'Pre-Raid', 'Warrior', 'Protection', 'Legs', 'Both', 'Bolstered Legplates'), +(1, 2, 7, 0, 200, 44201, 'Pre-Raid', 'Warrior', 'Protection', 'Feet', 'Both', 'Sabatons of Draconic Vigor'), +(1, 2, 8, 0, 200, 37620, 'Pre-Raid', 'Warrior', 'Protection', 'Wrists', 'Both', 'Bracers of the Herald'), +(1, 2, 9, 0, 200, 39622, 'Pre-Raid', 'Warrior', 'Protection', 'Hands', 'Both', 'Heroes'' Dreadnaught Handguards'), +(1, 2, 10, 0, 200, 34213, 'Pre-Raid', 'Warrior', 'Protection', 'Finger1', 'Both', 'Ring of Hardened Resolve'), +(1, 2, 12, 0, 200, 37220, 'Pre-Raid', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Essence of Gossamer'), +(1, 2, 14, 0, 200, 43565, 'Pre-Raid', 'Warrior', 'Protection', 'Back', 'Both', 'Durable Nerubhide Cape'), +(1, 2, 15, 0, 200, 37401, 'Pre-Raid', 'Warrior', 'Protection', 'MainHand', 'Both', 'Red Sword of Courage'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 224, 40546, 'Phase 1', 'Warrior', 'Protection', 'Head', 'Both', 'Valorous Dreadnaught Greathelm'), +(1, 2, 1, 0, 224, 44665, 'Phase 1', 'Warrior', 'Protection', 'Neck', 'Both', 'Nexus War Champion Beads'), +(1, 2, 2, 0, 224, 40548, 'Phase 1', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Valorous Dreadnaught Pauldrons'), +(1, 2, 4, 0, 224, 40544, 'Phase 1', 'Warrior', 'Protection', 'Chest', 'Both', 'Valorous Dreadnaught Breastplate'), +(1, 2, 5, 0, 224, 39759, 'Phase 1', 'Warrior', 'Protection', 'Waist', 'Both', 'Ablative Chitin Girdle'), +(1, 2, 6, 0, 224, 40589, 'Phase 1', 'Warrior', 'Protection', 'Legs', 'Both', 'Legplates of Sovereignty'), +(1, 2, 7, 0, 224, 39717, 'Phase 1', 'Warrior', 'Protection', 'Feet', 'Both', 'Inexorable Sabatons'), +(1, 2, 8, 0, 224, 39764, 'Phase 1', 'Warrior', 'Protection', 'Wrists', 'Both', 'Bindings of the Hapless Prey'), +(1, 2, 9, 0, 224, 40545, 'Phase 1', 'Warrior', 'Protection', 'Hands', 'Both', 'Valorous Dreadnaught Handguards'), +(1, 2, 10, 0, 224, 40718, 'Phase 1', 'Warrior', 'Protection', 'Finger1', 'Both', 'Signet of the Impregnable Fortress'), +(1, 2, 12, 0, 224, 40257, 'Phase 1', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Defender''s Code'), +(1, 2, 14, 0, 224, 40722, 'Phase 1', 'Warrior', 'Protection', 'Back', 'Both', 'Platinum Mesh Cloak'), +(1, 2, 15, 0, 224, 40402, 'Phase 1', 'Warrior', 'Protection', 'MainHand', 'Both', 'Last Laugh'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 245, 46166, 'Phase 2', 'Warrior', 'Protection', 'Head', 'Both', 'Conqueror''s Siegebreaker Greathelm'), +(1, 2, 1, 0, 245, 45485, 'Phase 2', 'Warrior', 'Protection', 'Neck', 'Both', 'Bronze Pendant of the Vanir'), +(1, 2, 2, 0, 245, 46167, 'Phase 2', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Conqueror''s Siegebreaker Pauldrons'), +(1, 2, 4, 0, 245, 46162, 'Phase 2', 'Warrior', 'Protection', 'Chest', 'Both', 'Conqueror''s Siegebreaker Breastplate'), +(1, 2, 5, 0, 245, 45139, 'Phase 2', 'Warrior', 'Protection', 'Waist', 'Both', 'Dragonslayer''s Brace'), +(1, 2, 6, 0, 245, 46169, 'Phase 2', 'Warrior', 'Protection', 'Legs', 'Both', 'Conqueror''s Siegebreaker Legguards'), +(1, 2, 7, 0, 245, 45542, 'Phase 2', 'Warrior', 'Protection', 'Feet', 'Both', 'Greaves of the Stonewarder'), +(1, 2, 8, 0, 245, 45111, 'Phase 2', 'Warrior', 'Protection', 'Wrists', 'Both', 'Mimiron''s Inferno Couplings'), +(1, 2, 9, 0, 245, 45487, 'Phase 2', 'Warrior', 'Protection', 'Hands', 'Both', 'Handguards of Revitalization'), +(1, 2, 10, 0, 245, 45471, 'Phase 2', 'Warrior', 'Protection', 'Finger1', 'Both', 'Fate''s Clutch'), +(1, 2, 12, 0, 245, 45158, 'Phase 2', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Heart of Iron'), +(1, 2, 14, 0, 245, 45496, 'Phase 2', 'Warrior', 'Protection', 'Back', 'Both', 'Titanskin Cloak'), +(1, 2, 17, 0, 245, 45137, 'Phase 2', 'Warrior', 'Protection', 'Ranged', 'Both', 'Veranus'' Bane'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 258, 48433, 'Phase 3', 'Warrior', 'Protection', 'Head', 'Both', 'Greathelm of Triumph'), +(1, 2, 2, 0, 258, 48455, 'Phase 3', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Pauldrons of Triumph'), +(1, 2, 4, 1, 258, 46968, 'Phase 3', 'Warrior', 'Protection', 'Chest', 'Alliance', 'Chestplate of the Towering Monstrosity'), +(1, 2, 4, 2, 258, 47415, 'Phase 3', 'Warrior', 'Protection', 'Chest', 'Horde', 'Hauberk of the Towering Monstrosity'), +(1, 2, 5, 1, 258, 47076, 'Phase 3', 'Warrior', 'Protection', 'Waist', 'Alliance', 'Girdle of Bloodied Scars'), +(1, 2, 5, 2, 258, 47444, 'Phase 3', 'Warrior', 'Protection', 'Waist', 'Horde', 'Belt of Bloodied Scars'), +(1, 2, 6, 0, 258, 48447, 'Phase 3', 'Warrior', 'Protection', 'Legs', 'Both', 'Legguards of Triumph'), +(1, 2, 7, 1, 258, 47952, 'Phase 3', 'Warrior', 'Protection', 'Feet', 'Alliance', 'Sabatons of the Lingering Vortex & Dawnbreaker Greaves'), +(1, 2, 8, 1, 258, 47918, 'Phase 3', 'Warrior', 'Protection', 'Wrists', 'Alliance', 'Dreadscale Armguards & Bracers of the Shieldmaiden'), +(1, 2, 9, 0, 258, 48453, 'Phase 3', 'Warrior', 'Protection', 'Hands', 'Both', 'Handguards of Triumph'), +(1, 2, 10, 1, 258, 45471, 'Phase 3', 'Warrior', 'Protection', 'Finger1', 'Alliance', 'Fate''s Clutch'), +(1, 2, 12, 1, 258, 47216, 'Phase 3', 'Warrior', 'Protection', 'Trinket1', 'Alliance', 'The Black Heart'), +(1, 2, 14, 1, 258, 47549, 'Phase 3', 'Warrior', 'Protection', 'Back', 'Alliance', 'Magni''s Resolution'), +(1, 2, 14, 2, 258, 47550, 'Phase 3', 'Warrior', 'Protection', 'Back', 'Horde', 'Cairne''s Endurance'), +(1, 2, 17, 0, 258, 47660, 'Phase 3', 'Warrior', 'Protection', 'Ranged', 'Both', 'Blades of the Sable Cross'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 264, 50640, 'Phase 4', 'Warrior', 'Protection', 'Head', 'Both', 'Broken Ram Skull Helm'), +(1, 2, 2, 0, 264, 51224, 'Phase 4', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Sanctified Ymirjar Lord''s Pauldrons'), +(1, 2, 4, 0, 264, 51220, 'Phase 4', 'Warrior', 'Protection', 'Chest', 'Both', 'Sanctified Ymirjar Lord''s Breastplate'), +(1, 2, 5, 0, 264, 50691, 'Phase 4', 'Warrior', 'Protection', 'Waist', 'Both', 'Belt of Broken Bones'), +(1, 2, 6, 0, 264, 51223, 'Phase 4', 'Warrior', 'Protection', 'Legs', 'Both', 'Sanctified Ymirjar Lord''s Legguards'), +(1, 2, 7, 0, 264, 50625, 'Phase 4', 'Warrior', 'Protection', 'Feet', 'Both', 'Grinning Skull Greatboots'), +(1, 2, 8, 0, 264, 50611, 'Phase 4', 'Warrior', 'Protection', 'Wrists', 'Both', 'Bracers of Dark Reckoning'), +(1, 2, 9, 0, 264, 51222, 'Phase 4', 'Warrior', 'Protection', 'Hands', 'Both', 'Sanctified Ymirjar Lord''s Handguards'), +(1, 2, 10, 0, 264, 50622, 'Phase 4', 'Warrior', 'Protection', 'Finger1', 'Both', 'Devium''s Eternally Cold Ring'), +(1, 2, 12, 0, 264, 50364, 'Phase 4', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Sindragosa''s Flawless Fang'), +(1, 2, 14, 0, 264, 50718, 'Phase 4', 'Warrior', 'Protection', 'Back', 'Both', 'Royal Crimson Cloak'), +(1, 2, 17, 0, 264, 51834, 'Phase 4', 'Warrior', 'Protection', 'Ranged', 'Both', 'Dreamhunter''s Carbine'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(1, 2, 0, 0, 290, 50640, 'Phase 5', 'Warrior', 'Protection', 'Head', 'Both', 'Broken Ram Skull Helm'), +(1, 2, 1, 0, 290, 50682, 'Phase 5', 'Warrior', 'Protection', 'Neck', 'Both', 'Bile-Encrusted Medallion'), +(1, 2, 2, 0, 290, 51847, 'Phase 5', 'Warrior', 'Protection', 'Shoulders', 'Both', 'Spaulders of the Blood Princes'), +(1, 2, 4, 0, 290, 51220, 'Phase 5', 'Warrior', 'Protection', 'Chest', 'Both', 'Sanctified Ymirjar Lord''s Breastplate'), +(1, 2, 5, 0, 290, 50691, 'Phase 5', 'Warrior', 'Protection', 'Waist', 'Both', 'Belt of Broken Bones'), +(1, 2, 6, 0, 290, 50612, 'Phase 5', 'Warrior', 'Protection', 'Legs', 'Both', 'Legguards of Lost Hope'), +(1, 2, 7, 0, 290, 54579, 'Phase 5', 'Warrior', 'Protection', 'Feet', 'Both', 'Treads of Impending Resurrection'), +(1, 2, 8, 0, 290, 51901, 'Phase 5', 'Warrior', 'Protection', 'Wrists', 'Both', 'Gargoyle Spit Bracers'), +(1, 2, 9, 0, 290, 51222, 'Phase 5', 'Warrior', 'Protection', 'Hands', 'Both', 'Sanctified Ymirjar Lord''s Handguards'), +(1, 2, 10, 0, 290, 50622, 'Phase 5', 'Warrior', 'Protection', 'Finger1', 'Both', 'Devium''s Eternally Cold Ring'), +(1, 2, 11, 0, 290, 50404, 'Phase 5', 'Warrior', 'Protection', 'Finger2', 'Both', 'Ashen Band of Endless Courage'), +(1, 2, 12, 0, 290, 54591, 'Phase 5', 'Warrior', 'Protection', 'Trinket1', 'Both', 'Petrified Twilight Scale'), +(1, 2, 13, 0, 290, 50364, 'Phase 5', 'Warrior', 'Protection', 'Trinket2', 'Both', 'Sindragosa''s Flawless Fang'), +(1, 2, 14, 0, 290, 50466, 'Phase 5', 'Warrior', 'Protection', 'Back', 'Both', 'Sentinel''s Winter Cloak'), +(1, 2, 15, 0, 290, 50738, 'Phase 5', 'Warrior', 'Protection', 'MainHand', 'Both', 'Mithrios, Bronzebeard''s Legacy'), +(1, 2, 16, 0, 290, 50729, 'Phase 5', 'Warrior', 'Protection', 'OffHand', 'Both', 'Icecrown Glacial Wall'), +(1, 2, 17, 0, 290, 51834, 'Phase 5', 'Warrior', 'Protection', 'Ranged', 'Both', 'Dreamhunter''s Carbine'); + + +-- ============================================================ +-- Paladin (2) +-- ============================================================ +-- Holy (tab 0) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 66, 12633, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Head', 'Both', 'Whitesoul Helm'), +(2, 0, 1, 0, 66, 18723, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Neck', 'Both', 'Animated Chain Necklace'), +(2, 0, 2, 0, 66, 18720, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Shroud of the Nathrezim'), +(2, 0, 4, 0, 66, 13346, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Chest', 'Both', 'Robes of the Exalted'), +(2, 0, 5, 0, 66, 18702, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Waist', 'Both', 'Belt of the Ordained'), +(2, 0, 6, 0, 66, 11841, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Legs', 'Both', 'Senior Designer''s Pantaloons'), +(2, 0, 7, 0, 66, 13954, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Feet', 'Both', 'Verdant Footpads'), +(2, 0, 8, 0, 66, 13969, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Wrists', 'Both', 'Loomguard Armbraces'), +(2, 0, 9, 0, 66, 10787, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Hands', 'Both', 'Atal''ai Gloves'), +(2, 0, 10, 0, 66, 16058, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Finger1', 'Both', 'Fordring''s Seal'), +(2, 0, 11, 0, 66, 18103, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Finger2', 'Both', 'Band of Rumination'), +(2, 0, 12, 0, 66, 11819, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Second Wind'), +(2, 0, 13, 0, 66, 12930, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Briarwood Reed'), +(2, 0, 14, 0, 66, 13386, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'Back', 'Both', 'Archivist Cape'), +(2, 0, 15, 0, 66, 11923, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'MainHand', 'Both', 'The Hammer of Grace'), +(2, 0, 16, 0, 66, 11928, 'Phase 1 (Pre-Raid)', 'Paladin', 'Holy', 'OffHand', 'Both', 'Thaurissan''s Royal Scepter'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 76, 18490, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Head', 'Both', 'Insightful Hood'), +(2, 0, 1, 0, 76, 18723, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Neck', 'Both', 'Animated Chain Necklace'), +(2, 0, 2, 0, 76, 18720, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Shroud of the Nathrezim'), +(2, 0, 4, 0, 76, 13346, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Chest', 'Both', 'Robes of the Exalted'), +(2, 0, 5, 0, 76, 18702, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Waist', 'Both', 'Belt of the Ordained'), +(2, 0, 6, 0, 76, 18386, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Legs', 'Both', 'Padre''s Trousers'), +(2, 0, 7, 0, 76, 18507, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Feet', 'Both', 'Boots of the Full Moon'), +(2, 0, 8, 0, 76, 13969, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Wrists', 'Both', 'Loomguard Armbraces'), +(2, 0, 9, 0, 76, 18527, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Hands', 'Both', 'Harmonious Gauntlets'), +(2, 0, 10, 0, 76, 16058, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Finger1', 'Both', 'Fordring''s Seal'), +(2, 0, 11, 0, 76, 18103, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Finger2', 'Both', 'Band of Rumination'), +(2, 0, 12, 0, 76, 11819, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Second Wind'), +(2, 0, 13, 0, 76, 12930, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Briarwood Reed'), +(2, 0, 14, 0, 76, 18510, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'Back', 'Both', 'Hide of the Wild'), +(2, 0, 15, 0, 76, 11923, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'MainHand', 'Both', 'The Hammer of Grace'), +(2, 0, 16, 0, 76, 18523, 'Phase 2 (Pre-Raid)', 'Paladin', 'Holy', 'OffHand', 'Both', 'Brightly Glowing Stone'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 78, 18490, 'Phase 2', 'Paladin', 'Holy', 'Head', 'Both', 'Insightful Hood'), +(2, 0, 1, 0, 78, 18723, 'Phase 2', 'Paladin', 'Holy', 'Neck', 'Both', 'Animated Chain Necklace'), +(2, 0, 2, 0, 78, 18810, 'Phase 2', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Wild Growth Spaulders'), +(2, 0, 4, 0, 78, 19145, 'Phase 2', 'Paladin', 'Holy', 'Chest', 'Both', 'Robe of Volatile Power'), +(2, 0, 5, 0, 78, 19162, 'Phase 2', 'Paladin', 'Holy', 'Waist', 'Both', 'Corehound Belt'), +(2, 0, 6, 0, 78, 18875, 'Phase 2', 'Paladin', 'Holy', 'Legs', 'Both', 'Salamander Scale Pants'), +(2, 0, 7, 0, 78, 18507, 'Phase 2', 'Paladin', 'Holy', 'Feet', 'Both', 'Boots of the Full Moon'), +(2, 0, 8, 0, 78, 13969, 'Phase 2', 'Paladin', 'Holy', 'Wrists', 'Both', 'Loomguard Armbraces'), +(2, 0, 9, 0, 78, 18527, 'Phase 2', 'Paladin', 'Holy', 'Hands', 'Both', 'Harmonious Gauntlets'), +(2, 0, 10, 0, 78, 19140, 'Phase 2', 'Paladin', 'Holy', 'Finger1', 'Both', 'Cauterizing Band'), +(2, 0, 11, 0, 78, 19140, 'Phase 2', 'Paladin', 'Holy', 'Finger2', 'Both', 'Cauterizing Band'), +(2, 0, 12, 0, 78, 17064, 'Phase 2', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Shard of the Scale'), +(2, 0, 13, 0, 78, 12930, 'Phase 2', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Briarwood Reed'), +(2, 0, 14, 0, 78, 18510, 'Phase 2', 'Paladin', 'Holy', 'Back', 'Both', 'Hide of the Wild'), +(2, 0, 15, 0, 78, 17103, 'Phase 2', 'Paladin', 'Holy', 'MainHand', 'Both', 'Azuresong Mageblade'), +(2, 0, 16, 0, 78, 18523, 'Phase 2', 'Paladin', 'Holy', 'OffHand', 'Both', 'Brightly Glowing Stone'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 83, 19375, 'Phase 3', 'Paladin', 'Holy', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(2, 0, 1, 0, 83, 18723, 'Phase 3', 'Paladin', 'Holy', 'Neck', 'Both', 'Animated Chain Necklace'), +(2, 0, 2, 0, 83, 18810, 'Phase 3', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Wild Growth Spaulders'), +(2, 0, 4, 0, 83, 19145, 'Phase 3', 'Paladin', 'Holy', 'Chest', 'Both', 'Robe of Volatile Power'), +(2, 0, 5, 0, 83, 19162, 'Phase 3', 'Paladin', 'Holy', 'Waist', 'Both', 'Corehound Belt'), +(2, 0, 6, 0, 83, 19385, 'Phase 3', 'Paladin', 'Holy', 'Legs', 'Both', 'Empowered Leggings'), +(2, 0, 7, 0, 83, 19437, 'Phase 3', 'Paladin', 'Holy', 'Feet', 'Both', 'Boots of Pure Thought'), +(2, 0, 8, 0, 83, 13969, 'Phase 3', 'Paladin', 'Holy', 'Wrists', 'Both', 'Loomguard Armbraces'), +(2, 0, 9, 0, 83, 19390, 'Phase 3', 'Paladin', 'Holy', 'Hands', 'Both', 'Taut Dragonhide Gloves'), +(2, 0, 10, 0, 83, 19382, 'Phase 3', 'Paladin', 'Holy', 'Finger1', 'Both', 'Pure Elementium Band'), +(2, 0, 11, 0, 83, 19140, 'Phase 3', 'Paladin', 'Holy', 'Finger2', 'Both', 'Cauterizing Band'), +(2, 0, 12, 0, 83, 17064, 'Phase 3', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Shard of the Scale'), +(2, 0, 13, 0, 83, 19395, 'Phase 3', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Rejuvenating Gem'), +(2, 0, 14, 0, 83, 19430, 'Phase 3', 'Paladin', 'Holy', 'Back', 'Both', 'Shroud of Pure Thought'), +(2, 0, 15, 0, 83, 19360, 'Phase 3', 'Paladin', 'Holy', 'MainHand', 'Both', 'Lok''amir il Romathis'), +(2, 0, 16, 0, 83, 19312, 'Phase 3', 'Paladin', 'Holy', 'OffHand', 'Both', 'Lei of the Lifegiver'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 88, 20628, 'Phase 5', 'Paladin', 'Holy', 'Head', 'Both', 'Deviate Growth Cap'), +(2, 0, 1, 0, 88, 21712, 'Phase 5', 'Paladin', 'Holy', 'Neck', 'Both', 'Amulet of the Fallen God'), +(2, 0, 2, 0, 88, 18810, 'Phase 5', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Wild Growth Spaulders'), +(2, 0, 4, 0, 88, 21663, 'Phase 5', 'Paladin', 'Holy', 'Chest', 'Both', 'Robes of the Guardian Saint'), +(2, 0, 5, 0, 88, 21582, 'Phase 5', 'Paladin', 'Holy', 'Waist', 'Both', 'Grasp of the Old God'), +(2, 0, 6, 0, 88, 21667, 'Phase 5', 'Paladin', 'Holy', 'Legs', 'Both', 'Legplates of Blazing Light'), +(2, 0, 7, 0, 88, 19437, 'Phase 5', 'Paladin', 'Holy', 'Feet', 'Both', 'Boots of Pure Thought'), +(2, 0, 8, 0, 88, 21604, 'Phase 5', 'Paladin', 'Holy', 'Wrists', 'Both', 'Bracelets of Royal Redemption'), +(2, 0, 9, 0, 88, 20264, 'Phase 5', 'Paladin', 'Holy', 'Hands', 'Both', 'Peacekeeper Gauntlets'), +(2, 0, 10, 0, 88, 19382, 'Phase 5', 'Paladin', 'Holy', 'Finger1', 'Both', 'Pure Elementium Band'), +(2, 0, 11, 0, 88, 21620, 'Phase 5', 'Paladin', 'Holy', 'Finger2', 'Both', 'Ring of the Martyr'), +(2, 0, 12, 0, 88, 17064, 'Phase 5', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Shard of the Scale'), +(2, 0, 13, 0, 88, 19395, 'Phase 5', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Rejuvenating Gem'), +(2, 0, 14, 0, 88, 21583, 'Phase 5', 'Paladin', 'Holy', 'Back', 'Both', 'Cloak of Clarity'), +(2, 0, 15, 0, 88, 21839, 'Phase 5', 'Paladin', 'Holy', 'MainHand', 'Both', 'Scepter of the False Prophet'), +(2, 0, 16, 0, 88, 21666, 'Phase 5', 'Paladin', 'Holy', 'OffHand', 'Both', 'Sartura''s Might'), +(2, 0, 17, 0, 88, 22402, 'Phase 5', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Grace'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 92, 20628, 'Phase 6', 'Paladin', 'Holy', 'Head', 'Both', 'Deviate Growth Cap'), +(2, 0, 1, 0, 92, 23057, 'Phase 6', 'Paladin', 'Holy', 'Neck', 'Both', 'Gem of Trapped Innocents'), +(2, 0, 2, 0, 92, 22429, 'Phase 6', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Redemption Spaulders'), +(2, 0, 4, 0, 92, 22425, 'Phase 6', 'Paladin', 'Holy', 'Chest', 'Both', 'Redemption Tunic'), +(2, 0, 5, 0, 92, 21582, 'Phase 6', 'Paladin', 'Holy', 'Waist', 'Both', 'Grasp of the Old God'), +(2, 0, 6, 0, 92, 22427, 'Phase 6', 'Paladin', 'Holy', 'Legs', 'Both', 'Redemption Legguards'), +(2, 0, 7, 0, 92, 22430, 'Phase 6', 'Paladin', 'Holy', 'Feet', 'Both', 'Redemption Boots'), +(2, 0, 8, 0, 92, 21604, 'Phase 6', 'Paladin', 'Holy', 'Wrists', 'Both', 'Bracelets of Royal Redemption'), +(2, 0, 9, 0, 92, 20264, 'Phase 6', 'Paladin', 'Holy', 'Hands', 'Both', 'Peacekeeper Gauntlets'), +(2, 0, 10, 0, 92, 19382, 'Phase 6', 'Paladin', 'Holy', 'Finger1', 'Both', 'Pure Elementium Band'), +(2, 0, 11, 0, 92, 23066, 'Phase 6', 'Paladin', 'Holy', 'Finger2', 'Both', 'Ring of Redemption'), +(2, 0, 12, 0, 92, 23047, 'Phase 6', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Eye of the Dead'), +(2, 0, 13, 0, 92, 19395, 'Phase 6', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Rejuvenating Gem'), +(2, 0, 14, 0, 92, 23050, 'Phase 6', 'Paladin', 'Holy', 'Back', 'Both', 'Cloak of the Necropolis'), +(2, 0, 15, 0, 92, 23056, 'Phase 6', 'Paladin', 'Holy', 'MainHand', 'Both', 'Hammer of the Twisting Nether'), +(2, 0, 16, 0, 92, 23075, 'Phase 6', 'Paladin', 'Holy', 'OffHand', 'Both', 'Death''s Bargain'), +(2, 0, 17, 0, 92, 23006, 'Phase 6', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Light'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 120, 32084, 'Pre-Raid', 'Paladin', 'Holy', 'Head', 'Both', 'Helmet of the Steadfast Champion'), +(2, 0, 1, 0, 120, 29374, 'Pre-Raid', 'Paladin', 'Holy', 'Neck', 'Both', 'Necklace of Eternal Hope'), +(2, 0, 2, 0, 120, 27775, 'Pre-Raid', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Hallowed Pauldrons'), +(2, 0, 4, 0, 120, 28230, 'Pre-Raid', 'Paladin', 'Holy', 'Chest', 'Both', 'Hallowed Garments'), +(2, 0, 5, 0, 120, 24256, 'Pre-Raid', 'Paladin', 'Holy', 'Waist', 'Both', 'Girdle of Ruination'), +(2, 0, 6, 0, 120, 30541, 'Pre-Raid', 'Paladin', 'Holy', 'Legs', 'Both', 'Stormsong Kilt'), +(2, 0, 7, 0, 120, 27411, 'Pre-Raid', 'Paladin', 'Holy', 'Feet', 'Both', 'Slippers of Serenity'), +(2, 0, 8, 0, 120, 23539, 'Pre-Raid', 'Paladin', 'Holy', 'Wrists', 'Both', 'Blessed Bracers'), +(2, 0, 9, 0, 120, 27457, 'Pre-Raid', 'Paladin', 'Holy', 'Hands', 'Both', 'Life Bearer''s Gauntlets'), +(2, 0, 10, 0, 120, 29373, 'Pre-Raid', 'Paladin', 'Holy', 'Finger1', 'Both', 'Band of Halos'), +(2, 0, 10, 2, 120, 29168, 'Pre-Raid', 'Paladin', 'Holy', 'Finger1', 'Horde', 'Ancestral Band'), +(2, 0, 12, 0, 120, 29376, 'Pre-Raid', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Essence of the Martyr'), +(2, 0, 13, 0, 120, 30841, 'Pre-Raid', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Lower City Prayer Book'), +(2, 0, 14, 0, 120, 29354, 'Pre-Raid', 'Paladin', 'Holy', 'Back', 'Both', 'Light-Touched Stole of Altruism'), +(2, 0, 15, 0, 120, 23556, 'Pre-Raid', 'Paladin', 'Holy', 'MainHand', 'Both', 'Hand of Eternity'), +(2, 0, 16, 0, 120, 29267, 'Pre-Raid', 'Paladin', 'Holy', 'OffHand', 'Both', 'Light-Bearer''s Faith Shield'), +(2, 0, 17, 0, 120, 25644, 'Pre-Raid', 'Paladin', 'Holy', 'Ranged', 'Both', 'Blessed Book of Nagrand'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 125, 29061, 'Phase 1', 'Paladin', 'Holy', 'Head', 'Both', 'Justicar Diadem'), +(2, 0, 1, 0, 125, 30726, 'Phase 1', 'Paladin', 'Holy', 'Neck', 'Both', 'Archaic Charm of Presence'), +(2, 0, 2, 0, 125, 29064, 'Phase 1', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Justicar Pauldrons'), +(2, 0, 4, 0, 125, 29062, 'Phase 1', 'Paladin', 'Holy', 'Chest', 'Both', 'Justicar Chestpiece'), +(2, 0, 5, 0, 125, 28799, 'Phase 1', 'Paladin', 'Holy', 'Waist', 'Both', 'Belt of Divine Inspiration'), +(2, 0, 6, 0, 125, 30727, 'Phase 1', 'Paladin', 'Holy', 'Legs', 'Both', 'Gilded Trousers of Benediction'), +(2, 0, 7, 0, 125, 30737, 'Phase 1', 'Paladin', 'Holy', 'Feet', 'Both', 'Gold-Leaf Wildboots'), +(2, 0, 8, 0, 125, 28512, 'Phase 1', 'Paladin', 'Holy', 'Wrists', 'Both', 'Bracers of Justice'), +(2, 0, 9, 0, 125, 28505, 'Phase 1', 'Paladin', 'Holy', 'Hands', 'Both', 'Gauntlets of Renewed Hope'), +(2, 0, 10, 0, 125, 28790, 'Phase 1', 'Paladin', 'Holy', 'Finger1', 'Both', 'Naaru Lightwarden''s Band'), +(2, 0, 11, 0, 125, 30736, 'Phase 1', 'Paladin', 'Holy', 'Finger2', 'Both', 'Ring of Flowing Light'), +(2, 0, 12, 0, 125, 28590, 'Phase 1', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Ribbon of Sacrifice'), +(2, 0, 13, 0, 125, 29376, 'Phase 1', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Essence of the Martyr'), +(2, 0, 14, 0, 125, 28765, 'Phase 1', 'Paladin', 'Holy', 'Back', 'Both', 'Stainless Cloak of the Pure Hearted'), +(2, 0, 15, 0, 125, 28771, 'Phase 1', 'Paladin', 'Holy', 'MainHand', 'Both', 'Light''s Justice'), +(2, 0, 16, 0, 125, 29458, 'Phase 1', 'Paladin', 'Holy', 'OffHand', 'Both', 'Aegis of the Vindicator'), +(2, 0, 17, 0, 125, 28592, 'Phase 1', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Souls Redeemed'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 141, 30136, 'Phase 2', 'Paladin', 'Holy', 'Head', 'Both', 'Crystalforge Greathelm'), +(2, 0, 1, 0, 141, 30018, 'Phase 2', 'Paladin', 'Holy', 'Neck', 'Both', 'Lord Sanguinar''s Claim'), +(2, 0, 2, 0, 141, 30138, 'Phase 2', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Crystalforge Pauldrons'), +(2, 0, 4, 0, 141, 30134, 'Phase 2', 'Paladin', 'Holy', 'Chest', 'Both', 'Crystalforge Chestpiece'), +(2, 0, 5, 0, 141, 29965, 'Phase 2', 'Paladin', 'Holy', 'Waist', 'Both', 'Girdle of the Righteous Path'), +(2, 0, 6, 0, 141, 29991, 'Phase 2', 'Paladin', 'Holy', 'Legs', 'Both', 'Sunhawk Leggings'), +(2, 0, 7, 0, 141, 30027, 'Phase 2', 'Paladin', 'Holy', 'Feet', 'Both', 'Boots of Courage Unending'), +(2, 0, 8, 0, 141, 30047, 'Phase 2', 'Paladin', 'Holy', 'Wrists', 'Both', 'Blackfathom Warbands'), +(2, 0, 9, 0, 141, 30112, 'Phase 2', 'Paladin', 'Holy', 'Hands', 'Both', 'Glorious Gauntlets of Crestfall'), +(2, 0, 10, 0, 141, 28790, 'Phase 2', 'Paladin', 'Holy', 'Finger1', 'Both', 'Naaru Lightwarden''s Band'), +(2, 0, 11, 0, 141, 29920, 'Phase 2', 'Paladin', 'Holy', 'Finger2', 'Both', 'Phoenix-Ring of Rebirth'), +(2, 0, 12, 0, 141, 29376, 'Phase 2', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Essence of the Martyr'), +(2, 0, 13, 0, 141, 28590, 'Phase 2', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Ribbon of Sacrifice'), +(2, 0, 14, 0, 141, 29989, 'Phase 2', 'Paladin', 'Holy', 'Back', 'Both', 'Sunshower Light Cloak'), +(2, 0, 15, 0, 141, 30108, 'Phase 2', 'Paladin', 'Holy', 'MainHand', 'Both', 'Lightfathom Scepter'), +(2, 0, 16, 0, 141, 29458, 'Phase 2', 'Paladin', 'Holy', 'OffHand', 'Both', 'Aegis of the Vindicator'), +(2, 0, 17, 0, 141, 28592, 'Phase 2', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Souls Redeemed'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 156, 30988, 'Phase 3', 'Paladin', 'Holy', 'Head', 'Both', 'Lightbringer Greathelm'), +(2, 0, 1, 0, 156, 32370, 'Phase 3', 'Paladin', 'Holy', 'Neck', 'Both', 'Nadina''s Pendant of Purity'), +(2, 0, 2, 0, 156, 30996, 'Phase 3', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Lightbringer Pauldrons'), +(2, 0, 4, 0, 156, 30992, 'Phase 3', 'Paladin', 'Holy', 'Chest', 'Both', 'Lightbringer Chestpiece'), +(2, 0, 5, 0, 156, 30897, 'Phase 3', 'Paladin', 'Holy', 'Waist', 'Both', 'Girdle of Hope'), +(2, 0, 6, 0, 156, 30994, 'Phase 3', 'Paladin', 'Holy', 'Legs', 'Both', 'Lightbringer Leggings'), +(2, 0, 7, 0, 156, 32243, 'Phase 3', 'Paladin', 'Holy', 'Feet', 'Both', 'Pearl Inlaid Boots'), +(2, 0, 8, 0, 156, 30862, 'Phase 3', 'Paladin', 'Holy', 'Wrists', 'Both', 'Blessed Adamantite Bracers'), +(2, 0, 9, 0, 156, 30112, 'Phase 3', 'Paladin', 'Holy', 'Hands', 'Both', 'Glorious Gauntlets of Crestfall'), +(2, 0, 10, 0, 156, 32528, 'Phase 3', 'Paladin', 'Holy', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(2, 0, 11, 0, 156, 32238, 'Phase 3', 'Paladin', 'Holy', 'Finger2', 'Both', 'Ring of Calming Waves'), +(2, 0, 12, 0, 156, 29376, 'Phase 3', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Essence of the Martyr'), +(2, 0, 13, 0, 156, 32496, 'Phase 3', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Memento of Tyrande'), +(2, 0, 14, 0, 156, 32524, 'Phase 3', 'Paladin', 'Holy', 'Back', 'Both', 'Shroud of the Highborne'), +(2, 0, 15, 0, 156, 32500, 'Phase 3', 'Paladin', 'Holy', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(2, 0, 16, 0, 156, 32255, 'Phase 3', 'Paladin', 'Holy', 'OffHand', 'Both', 'Felstone Bulwark'), +(2, 0, 17, 0, 156, 28592, 'Phase 3', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Souls Redeemed'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 164, 30988, 'Phase 4', 'Paladin', 'Holy', 'Head', 'Both', 'Lightbringer Greathelm'), +(2, 0, 1, 0, 164, 33281, 'Phase 4', 'Paladin', 'Holy', 'Neck', 'Both', 'Brooch of Nature''s Mercy'), +(2, 0, 2, 0, 164, 30996, 'Phase 4', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Lightbringer Pauldrons'), +(2, 0, 4, 0, 164, 30992, 'Phase 4', 'Paladin', 'Holy', 'Chest', 'Both', 'Lightbringer Chestpiece'), +(2, 0, 5, 0, 164, 30897, 'Phase 4', 'Paladin', 'Holy', 'Waist', 'Both', 'Girdle of Hope'), +(2, 0, 6, 0, 164, 30994, 'Phase 4', 'Paladin', 'Holy', 'Legs', 'Both', 'Lightbringer Leggings'), +(2, 0, 7, 0, 164, 33324, 'Phase 4', 'Paladin', 'Holy', 'Feet', 'Both', 'Treads of the Life Path'), +(2, 0, 8, 0, 164, 30862, 'Phase 4', 'Paladin', 'Holy', 'Wrists', 'Both', 'Blessed Adamantite Bracers'), +(2, 0, 9, 0, 164, 30112, 'Phase 4', 'Paladin', 'Holy', 'Hands', 'Both', 'Glorious Gauntlets of Crestfall'), +(2, 0, 10, 0, 164, 32528, 'Phase 4', 'Paladin', 'Holy', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(2, 0, 11, 0, 164, 32238, 'Phase 4', 'Paladin', 'Holy', 'Finger2', 'Both', 'Ring of Calming Waves'), +(2, 0, 12, 0, 164, 29376, 'Phase 4', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Essence of the Martyr'), +(2, 0, 13, 0, 164, 32496, 'Phase 4', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Memento of Tyrande'), +(2, 0, 14, 0, 164, 32524, 'Phase 4', 'Paladin', 'Holy', 'Back', 'Both', 'Shroud of the Highborne'), +(2, 0, 15, 0, 164, 32500, 'Phase 4', 'Paladin', 'Holy', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(2, 0, 16, 0, 164, 32255, 'Phase 4', 'Paladin', 'Holy', 'OffHand', 'Both', 'Felstone Bulwark'), +(2, 0, 17, 0, 164, 28592, 'Phase 4', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Souls Redeemed'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 200, 44949, 'Pre-Raid', 'Paladin', 'Holy', 'Head', 'Both', 'Unbreakable Healing Amplifiers'), +(2, 0, 1, 0, 200, 42647, 'Pre-Raid', 'Paladin', 'Holy', 'Neck', 'Both', 'Titanium Spellshock Necklace'), +(2, 0, 2, 0, 200, 37673, 'Pre-Raid', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Dark Runic Mantle'), +(2, 0, 4, 0, 200, 39629, 'Pre-Raid', 'Paladin', 'Holy', 'Chest', 'Both', 'Heroes'' Redemption Tunic'), +(2, 0, 5, 0, 200, 40691, 'Pre-Raid', 'Paladin', 'Holy', 'Waist', 'Both', 'Magroth''s Meditative Cincture'), +(2, 0, 6, 0, 200, 37362, 'Pre-Raid', 'Paladin', 'Holy', 'Legs', 'Both', 'Leggings of Protective Auras'), +(2, 0, 7, 0, 200, 44202, 'Pre-Raid', 'Paladin', 'Holy', 'Feet', 'Both', 'Sandals of Crimson Fury'), +(2, 0, 8, 0, 200, 40741, 'Pre-Raid', 'Paladin', 'Holy', 'Wrists', 'Both', 'Cuffs of the Shadow Ascendant'), +(2, 0, 9, 0, 200, 39632, 'Pre-Raid', 'Paladin', 'Holy', 'Hands', 'Both', 'Heroes'' Redemption Gloves'), +(2, 0, 12, 0, 200, 44255, 'Pre-Raid', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(2, 0, 14, 0, 200, 34242, 'Pre-Raid', 'Paladin', 'Holy', 'Back', 'Both', 'Tattered Cape of Antonidas'), +(2, 0, 15, 0, 200, 37169, 'Pre-Raid', 'Paladin', 'Holy', 'MainHand', 'Both', 'War Mace of Unrequited Love'), +(2, 0, 17, 0, 200, 40705, 'Pre-Raid', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Renewal'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 224, 44007, 'Phase 1', 'Paladin', 'Holy', 'Head', 'Both', 'Headpiece of Reconciliation'), +(2, 0, 1, 0, 224, 44661, 'Phase 1', 'Paladin', 'Holy', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(2, 0, 2, 0, 224, 40573, 'Phase 1', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Valorous Redemption Spaulders'), +(2, 0, 4, 0, 224, 40569, 'Phase 1', 'Paladin', 'Holy', 'Chest', 'Both', 'Valorous Redemption Tunic'), +(2, 0, 5, 0, 224, 40561, 'Phase 1', 'Paladin', 'Holy', 'Waist', 'Both', 'Leash of Heedless Magic'), +(2, 0, 6, 0, 224, 40572, 'Phase 1', 'Paladin', 'Holy', 'Legs', 'Both', 'Valorous Redemption Greaves'), +(2, 0, 7, 0, 224, 40592, 'Phase 1', 'Paladin', 'Holy', 'Feet', 'Both', 'Boots of Healing Energies'), +(2, 0, 8, 0, 224, 40332, 'Phase 1', 'Paladin', 'Holy', 'Wrists', 'Both', 'Abetment Bracers'), +(2, 0, 9, 0, 224, 40570, 'Phase 1', 'Paladin', 'Holy', 'Hands', 'Both', 'Valorous Redemption Gloves'), +(2, 0, 12, 0, 224, 44255, 'Phase 1', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(2, 0, 14, 0, 224, 44005, 'Phase 1', 'Paladin', 'Holy', 'Back', 'Both', 'Pennant Cloak'), +(2, 0, 17, 0, 224, 40705, 'Phase 1', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Renewal'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 245, 46180, 'Phase 2', 'Paladin', 'Holy', 'Head', 'Both', 'Conqueror''s Aegis Headpiece'), +(2, 0, 1, 0, 245, 45443, 'Phase 2', 'Paladin', 'Holy', 'Neck', 'Both', 'Charm of Meticulous Timing'), +(2, 0, 2, 0, 245, 46182, 'Phase 2', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Conqueror''s Aegis Spaulders'), +(2, 0, 4, 0, 245, 45445, 'Phase 2', 'Paladin', 'Holy', 'Chest', 'Both', 'Breastplate of the Devoted'), +(2, 0, 5, 0, 245, 45616, 'Phase 2', 'Paladin', 'Holy', 'Waist', 'Both', 'Star-beaded Clutch'), +(2, 0, 6, 0, 245, 46181, 'Phase 2', 'Paladin', 'Holy', 'Legs', 'Both', 'Conqueror''s Aegis Greaves'), +(2, 0, 7, 0, 245, 45537, 'Phase 2', 'Paladin', 'Holy', 'Feet', 'Both', 'Treads of the False Oracle'), +(2, 0, 8, 0, 245, 45460, 'Phase 2', 'Paladin', 'Holy', 'Wrists', 'Both', 'Bindings of Winter Gale'), +(2, 0, 9, 0, 245, 46155, 'Phase 2', 'Paladin', 'Holy', 'Hands', 'Both', 'Conqueror''s Aegis Gauntlets'), +(2, 0, 12, 0, 245, 46051, 'Phase 2', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Meteorite Crystal'), +(2, 0, 14, 0, 245, 45486, 'Phase 2', 'Paladin', 'Holy', 'Back', 'Both', 'Drape of the Sullen Goddess'), +(2, 0, 15, 0, 245, 46017, 'Phase 2', 'Paladin', 'Holy', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(2, 0, 17, 0, 245, 40705, 'Phase 2', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Renewal'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 258, 48582, 'Phase 3', 'Paladin', 'Holy', 'Head', 'Both', 'Headpiece of Triumph'), +(2, 0, 2, 0, 258, 48580, 'Phase 3', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Spaulders of Triumph'), +(2, 0, 4, 1, 258, 47147, 'Phase 3', 'Paladin', 'Holy', 'Chest', 'Alliance', 'Breastplate of the Frozen Lake'), +(2, 0, 4, 2, 258, 47471, 'Phase 3', 'Paladin', 'Holy', 'Chest', 'Horde', 'Chestplate of the Frozen Lake'), +(2, 0, 5, 1, 258, 47924, 'Phase 3', 'Paladin', 'Holy', 'Waist', 'Alliance', 'Belt of the Frozen Reach'), +(2, 0, 5, 2, 258, 47997, 'Phase 3', 'Paladin', 'Holy', 'Waist', 'Horde', 'Girdle of the Frozen Reach'), +(2, 0, 6, 1, 258, 47190, 'Phase 3', 'Paladin', 'Holy', 'Legs', 'Alliance', 'Legwraps of the Awakening'), +(2, 0, 6, 2, 258, 47479, 'Phase 3', 'Paladin', 'Holy', 'Legs', 'Horde', 'Leggings of the Awakening'), +(2, 0, 7, 1, 258, 46986, 'Phase 3', 'Paladin', 'Holy', 'Feet', 'Alliance', 'Boots of the Courageous'), +(2, 0, 7, 2, 258, 47424, 'Phase 3', 'Paladin', 'Holy', 'Feet', 'Horde', 'Sabatons of the Courageous'), +(2, 0, 8, 0, 258, 45460, 'Phase 3', 'Paladin', 'Holy', 'Wrists', 'Both', 'Bindings of Winter Gale'), +(2, 0, 9, 0, 258, 45665, 'Phase 3', 'Paladin', 'Holy', 'Hands', 'Both', 'Pharos Gloves'), +(2, 0, 10, 1, 258, 47224, 'Phase 3', 'Paladin', 'Holy', 'Finger1', 'Alliance', 'Ring of the Darkmender'), +(2, 0, 10, 2, 258, 47439, 'Phase 3', 'Paladin', 'Holy', 'Finger1', 'Horde', 'Circle of the Darkmender'), +(2, 0, 12, 0, 258, 46051, 'Phase 3', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Meteorite Crystal'), +(2, 0, 14, 1, 258, 47552, 'Phase 3', 'Paladin', 'Holy', 'Back', 'Alliance', 'Jaina''s Radiance'), +(2, 0, 14, 2, 258, 47551, 'Phase 3', 'Paladin', 'Holy', 'Back', 'Horde', 'Aethas'' Intensity'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 264, 51272, 'Phase 4', 'Paladin', 'Holy', 'Head', 'Both', 'Sanctified Lightsworn Headpiece'), +(2, 0, 2, 0, 264, 51273, 'Phase 4', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Sanctified Lightsworn Spaulders'), +(2, 0, 4, 0, 264, 50680, 'Phase 4', 'Paladin', 'Holy', 'Chest', 'Both', 'Rot-Resistant Breastplate'), +(2, 0, 5, 0, 264, 50613, 'Phase 4', 'Paladin', 'Holy', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(2, 0, 6, 0, 264, 51928, 'Phase 4', 'Paladin', 'Holy', 'Legs', 'Both', 'Corrupted Silverplate Leggings'), +(2, 0, 7, 0, 264, 50699, 'Phase 4', 'Paladin', 'Holy', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(2, 0, 8, 0, 264, 50721, 'Phase 4', 'Paladin', 'Holy', 'Wrists', 'Both', 'Crypt Keeper''s Bracers'), +(2, 0, 9, 0, 264, 50650, 'Phase 4', 'Paladin', 'Holy', 'Hands', 'Both', 'Fallen Lord''s Handguards'), +(2, 0, 10, 0, 264, 50400, 'Phase 4', 'Paladin', 'Holy', 'Finger1', 'Both', 'Ashen Band of Endless Wisdom'), +(2, 0, 12, 0, 264, 46051, 'Phase 4', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Meteorite Crystal'), +(2, 0, 14, 0, 264, 50628, 'Phase 4', 'Paladin', 'Holy', 'Back', 'Both', 'Frostbinder''s Shredded Cape'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 0, 0, 0, 290, 51272, 'Phase 5', 'Paladin', 'Holy', 'Head', 'Both', 'Sanctified Lightsworn Headpiece'), +(2, 0, 1, 0, 290, 50182, 'Phase 5', 'Paladin', 'Holy', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(2, 0, 2, 0, 290, 51273, 'Phase 5', 'Paladin', 'Holy', 'Shoulders', 'Both', 'Sanctified Lightsworn Spaulders'), +(2, 0, 4, 0, 290, 50680, 'Phase 5', 'Paladin', 'Holy', 'Chest', 'Both', 'Rot-Resistant Breastplate'), +(2, 0, 5, 0, 290, 54587, 'Phase 5', 'Paladin', 'Holy', 'Waist', 'Both', 'Split Shape Belt'), +(2, 0, 6, 0, 290, 50694, 'Phase 5', 'Paladin', 'Holy', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(2, 0, 7, 0, 290, 54586, 'Phase 5', 'Paladin', 'Holy', 'Feet', 'Both', 'Foreshadow Steps'), +(2, 0, 8, 0, 290, 54582, 'Phase 5', 'Paladin', 'Holy', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(2, 0, 9, 0, 290, 50650, 'Phase 5', 'Paladin', 'Holy', 'Hands', 'Both', 'Fallen Lord''s Handguards'), +(2, 0, 10, 0, 290, 50664, 'Phase 5', 'Paladin', 'Holy', 'Finger1', 'Both', 'Ring of Rapid Ascent'), +(2, 0, 11, 0, 290, 50400, 'Phase 5', 'Paladin', 'Holy', 'Finger2', 'Both', 'Ashen Band of Endless Wisdom'), +(2, 0, 12, 0, 290, 46051, 'Phase 5', 'Paladin', 'Holy', 'Trinket1', 'Both', 'Meteorite Crystal'), +(2, 0, 13, 0, 290, 48724, 'Phase 5', 'Paladin', 'Holy', 'Trinket2', 'Both', 'Talisman of Resurgence'), +(2, 0, 14, 0, 290, 54583, 'Phase 5', 'Paladin', 'Holy', 'Back', 'Both', 'Cloak of Burning Dusk'), +(2, 0, 15, 0, 290, 46017, 'Phase 5', 'Paladin', 'Holy', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(2, 0, 16, 0, 290, 50616, 'Phase 5', 'Paladin', 'Holy', 'OffHand', 'Both', 'Bulwark of Smouldering Steel'), +(2, 0, 17, 0, 290, 40705, 'Phase 5', 'Paladin', 'Holy', 'Ranged', 'Both', 'Libram of Renewal'); + +-- Protection (tab 1) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 66, 12952, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Head', 'Both', 'Gyth''s Skull'), +(2, 1, 1, 0, 66, 13091, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Neck', 'Both', 'Medallion of Grand Marshal Morris'), +(2, 1, 2, 0, 66, 14552, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Stockade Pauldrons'), +(2, 1, 4, 0, 66, 14624, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Chest', 'Both', 'Deathbone Chestplate'), +(2, 1, 5, 0, 66, 14620, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Waist', 'Both', 'Deathbone Girdle'), +(2, 1, 6, 0, 66, 14623, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Legs', 'Both', 'Deathbone Legguards'), +(2, 1, 7, 0, 66, 14621, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Feet', 'Both', 'Deathbone Sabatons'), +(2, 1, 8, 0, 66, 12550, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Wrists', 'Both', 'Runed Golem Shackles'), +(2, 1, 9, 0, 66, 14622, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Hands', 'Both', 'Deathbone Gauntlets'), +(2, 1, 10, 0, 66, 11669, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Finger1', 'Both', 'Naglering'), +(2, 1, 11, 0, 66, 10795, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Finger2', 'Both', 'Drakeclaw Band'), +(2, 1, 13, 0, 66, 10779, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Demon''s Blood'), +(2, 1, 14, 0, 66, 13397, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'Back', 'Both', 'Stoneskin Gargoyle Cape'), +(2, 1, 15, 0, 66, 11784, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'MainHand', 'Both', 'Arbiter''s Blade'), +(2, 1, 16, 0, 66, 12602, 'Phase 1 (Pre-Raid)', 'Paladin', 'Protection', 'OffHand', 'Both', 'Draconian Deflector'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 76, 12952, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Head', 'Both', 'Gyth''s Skull'), +(2, 1, 1, 0, 76, 13091, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Neck', 'Both', 'Medallion of Grand Marshal Morris'), +(2, 1, 2, 0, 76, 14552, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Stockade Pauldrons'), +(2, 1, 4, 0, 76, 14624, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Chest', 'Both', 'Deathbone Chestplate'), +(2, 1, 5, 0, 76, 14620, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Waist', 'Both', 'Deathbone Girdle'), +(2, 1, 6, 0, 76, 14623, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Legs', 'Both', 'Deathbone Legguards'), +(2, 1, 7, 0, 76, 14621, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Feet', 'Both', 'Deathbone Sabatons'), +(2, 1, 8, 0, 76, 18754, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Wrists', 'Both', 'Fel Hardened Bracers'), +(2, 1, 9, 0, 76, 14622, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Hands', 'Both', 'Deathbone Gauntlets'), +(2, 1, 10, 0, 76, 11669, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Finger1', 'Both', 'Naglering'), +(2, 1, 11, 0, 76, 10795, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Finger2', 'Both', 'Drakeclaw Band'), +(2, 1, 13, 0, 76, 10779, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Demon''s Blood'), +(2, 1, 14, 0, 76, 18495, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'Back', 'Both', 'Redoubt Cloak'), +(2, 1, 15, 0, 76, 18396, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'MainHand', 'Both', 'Mind Carver'), +(2, 1, 16, 0, 76, 12602, 'Phase 2 (Pre-Raid)', 'Paladin', 'Protection', 'OffHand', 'Both', 'Draconian Deflector'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 78, 12952, 'Phase 2', 'Paladin', 'Protection', 'Head', 'Both', 'Gyth''s Skull'), +(2, 1, 1, 0, 78, 17065, 'Phase 2', 'Paladin', 'Protection', 'Neck', 'Both', 'Medallion of Steadfast Might'), +(2, 1, 2, 0, 78, 14552, 'Phase 2', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Stockade Pauldrons'), +(2, 1, 4, 0, 78, 14624, 'Phase 2', 'Paladin', 'Protection', 'Chest', 'Both', 'Deathbone Chestplate'), +(2, 1, 5, 0, 78, 14620, 'Phase 2', 'Paladin', 'Protection', 'Waist', 'Both', 'Deathbone Girdle'), +(2, 1, 6, 0, 78, 14623, 'Phase 2', 'Paladin', 'Protection', 'Legs', 'Both', 'Deathbone Legguards'), +(2, 1, 7, 0, 78, 18806, 'Phase 2', 'Paladin', 'Protection', 'Feet', 'Both', 'Core Forged Greaves'), +(2, 1, 8, 0, 78, 18754, 'Phase 2', 'Paladin', 'Protection', 'Wrists', 'Both', 'Fel Hardened Bracers'), +(2, 1, 9, 0, 78, 13072, 'Phase 2', 'Paladin', 'Protection', 'Hands', 'Both', 'Stonegrip Gauntlets'), +(2, 1, 10, 0, 78, 11669, 'Phase 2', 'Paladin', 'Protection', 'Finger1', 'Both', 'Naglering'), +(2, 1, 11, 0, 78, 18879, 'Phase 2', 'Paladin', 'Protection', 'Finger2', 'Both', 'Heavy Dark Iron Ring'), +(2, 1, 13, 0, 78, 18406, 'Phase 2', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Onyxia Blood Talisman'), +(2, 1, 14, 0, 78, 18495, 'Phase 2', 'Paladin', 'Protection', 'Back', 'Both', 'Redoubt Cloak'), +(2, 1, 15, 0, 78, 17103, 'Phase 2', 'Paladin', 'Protection', 'MainHand', 'Both', 'Azuresong Mageblade'), +(2, 1, 16, 0, 78, 17066, 'Phase 2', 'Paladin', 'Protection', 'OffHand', 'Both', 'Drillborer Disk'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 83, 12620, 'Phase 3', 'Paladin', 'Protection', 'Head', 'Both', 'Enchanted Thorium Helm'), +(2, 1, 1, 0, 83, 19383, 'Phase 3', 'Paladin', 'Protection', 'Neck', 'Both', 'Master Dragonslayer''s Medallion'), +(2, 1, 2, 0, 83, 14552, 'Phase 3', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Stockade Pauldrons'), +(2, 1, 4, 0, 83, 12618, 'Phase 3', 'Paladin', 'Protection', 'Chest', 'Both', 'Enchanted Thorium Breastplate'), +(2, 1, 5, 0, 83, 14620, 'Phase 3', 'Paladin', 'Protection', 'Waist', 'Both', 'Deathbone Girdle'), +(2, 1, 6, 0, 83, 14623, 'Phase 3', 'Paladin', 'Protection', 'Legs', 'Both', 'Deathbone Legguards'), +(2, 1, 7, 0, 83, 18806, 'Phase 3', 'Paladin', 'Protection', 'Feet', 'Both', 'Core Forged Greaves'), +(2, 1, 8, 0, 83, 18754, 'Phase 3', 'Paladin', 'Protection', 'Wrists', 'Both', 'Fel Hardened Bracers'), +(2, 1, 9, 0, 83, 13072, 'Phase 3', 'Paladin', 'Protection', 'Hands', 'Both', 'Stonegrip Gauntlets'), +(2, 1, 10, 0, 83, 11669, 'Phase 3', 'Paladin', 'Protection', 'Finger1', 'Both', 'Naglering'), +(2, 1, 11, 0, 83, 18879, 'Phase 3', 'Paladin', 'Protection', 'Finger2', 'Both', 'Heavy Dark Iron Ring'), +(2, 1, 12, 0, 83, 19431, 'Phase 3', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Styleen''s Impeding Scarab'), +(2, 1, 13, 0, 83, 18406, 'Phase 3', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Onyxia Blood Talisman'), +(2, 1, 14, 0, 83, 18495, 'Phase 3', 'Paladin', 'Protection', 'Back', 'Both', 'Redoubt Cloak'), +(2, 1, 15, 0, 83, 19360, 'Phase 3', 'Paladin', 'Protection', 'MainHand', 'Both', 'Lok''amir il Romathis'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 88, 21387, 'Phase 5', 'Paladin', 'Protection', 'Head', 'Both', 'Avenger''s Crown'), +(2, 1, 1, 0, 88, 22732, 'Phase 5', 'Paladin', 'Protection', 'Neck', 'Both', 'Mark of C''Thun'), +(2, 1, 2, 0, 88, 21639, 'Phase 5', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Pauldrons of the Unrelenting'), +(2, 1, 4, 0, 88, 21389, 'Phase 5', 'Paladin', 'Protection', 'Chest', 'Both', 'Avenger''s Breastplate'), +(2, 1, 5, 0, 88, 21598, 'Phase 5', 'Paladin', 'Protection', 'Waist', 'Both', 'Royal Qiraji Belt'), +(2, 1, 6, 0, 88, 19855, 'Phase 5', 'Paladin', 'Protection', 'Legs', 'Both', 'Bloodsoaked Legplates'), +(2, 1, 7, 0, 88, 21706, 'Phase 5', 'Paladin', 'Protection', 'Feet', 'Both', 'Boots of the Unwavering Will'), +(2, 1, 8, 0, 88, 18754, 'Phase 5', 'Paladin', 'Protection', 'Wrists', 'Both', 'Fel Hardened Bracers'), +(2, 1, 9, 0, 88, 21674, 'Phase 5', 'Paladin', 'Protection', 'Hands', 'Both', 'Gauntlets of Steadfast Determination'), +(2, 1, 10, 0, 88, 21200, 'Phase 5', 'Paladin', 'Protection', 'Finger1', 'Both', 'Signet Ring of the Bronze Dragonflight'), +(2, 1, 11, 0, 88, 21601, 'Phase 5', 'Paladin', 'Protection', 'Finger2', 'Both', 'Ring of Emperor Vek''lor'), +(2, 1, 12, 0, 88, 19431, 'Phase 5', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Styleen''s Impeding Scarab'), +(2, 1, 13, 0, 88, 18406, 'Phase 5', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Onyxia Blood Talisman'), +(2, 1, 14, 0, 88, 19888, 'Phase 5', 'Paladin', 'Protection', 'Back', 'Both', 'Overlord''s Embrace'), +(2, 1, 15, 0, 88, 21622, 'Phase 5', 'Paladin', 'Protection', 'MainHand', 'Both', 'Sharpened Silithid Femur'), +(2, 1, 16, 0, 88, 21269, 'Phase 5', 'Paladin', 'Protection', 'OffHand', 'Both', 'Blessed Qiraji Bulwark'), +(2, 1, 17, 0, 88, 22401, 'Phase 5', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Hope'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 92, 21387, 'Phase 6', 'Paladin', 'Protection', 'Head', 'Both', 'Avenger''s Crown'), +(2, 1, 1, 0, 92, 22732, 'Phase 6', 'Paladin', 'Protection', 'Neck', 'Both', 'Mark of C''Thun'), +(2, 1, 2, 0, 92, 21639, 'Phase 6', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Pauldrons of the Unrelenting'), +(2, 1, 4, 0, 92, 21389, 'Phase 6', 'Paladin', 'Protection', 'Chest', 'Both', 'Avenger''s Breastplate'), +(2, 1, 5, 0, 92, 21598, 'Phase 6', 'Paladin', 'Protection', 'Waist', 'Both', 'Royal Qiraji Belt'), +(2, 1, 6, 0, 92, 19855, 'Phase 6', 'Paladin', 'Protection', 'Legs', 'Both', 'Bloodsoaked Legplates'), +(2, 1, 7, 0, 92, 21706, 'Phase 6', 'Paladin', 'Protection', 'Feet', 'Both', 'Boots of the Unwavering Will'), +(2, 1, 8, 0, 92, 18754, 'Phase 6', 'Paladin', 'Protection', 'Wrists', 'Both', 'Fel Hardened Bracers'), +(2, 1, 9, 0, 92, 21674, 'Phase 6', 'Paladin', 'Protection', 'Hands', 'Both', 'Gauntlets of Steadfast Determination'), +(2, 1, 10, 0, 92, 21200, 'Phase 6', 'Paladin', 'Protection', 'Finger1', 'Both', 'Signet Ring of the Bronze Dragonflight'), +(2, 1, 11, 0, 92, 21601, 'Phase 6', 'Paladin', 'Protection', 'Finger2', 'Both', 'Ring of Emperor Vek''lor'), +(2, 1, 12, 0, 92, 19431, 'Phase 6', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Styleen''s Impeding Scarab'), +(2, 1, 13, 0, 92, 18406, 'Phase 6', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Onyxia Blood Talisman'), +(2, 1, 14, 0, 92, 22938, 'Phase 6', 'Paladin', 'Protection', 'Back', 'Both', 'Cryptfiend Silk Cloak'), +(2, 1, 15, 0, 92, 22988, 'Phase 6', 'Paladin', 'Protection', 'MainHand', 'Both', 'The End of Dreams'), +(2, 1, 16, 0, 92, 22818, 'Phase 6', 'Paladin', 'Protection', 'OffHand', 'Both', 'The Plague Bearer'), +(2, 1, 17, 0, 92, 22401, 'Phase 6', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Hope'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 120, 32083, 'Pre-Raid', 'Paladin', 'Protection', 'Head', 'Both', 'Faceguard of Determination'), +(2, 1, 1, 0, 120, 29386, 'Pre-Raid', 'Paladin', 'Protection', 'Neck', 'Both', 'Necklace of the Juggernaut'), +(2, 1, 2, 0, 120, 27739, 'Pre-Raid', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Spaulders of the Righteous'), +(2, 1, 4, 0, 120, 28262, 'Pre-Raid', 'Paladin', 'Protection', 'Chest', 'Both', 'Jade-Skull Breastplate'), +(2, 1, 5, 0, 120, 29253, 'Pre-Raid', 'Paladin', 'Protection', 'Waist', 'Both', 'Girdle of Valorous Deeds'), +(2, 1, 6, 0, 120, 29184, 'Pre-Raid', 'Paladin', 'Protection', 'Legs', 'Both', 'Timewarden''s Leggings'), +(2, 1, 7, 0, 120, 29254, 'Pre-Raid', 'Paladin', 'Protection', 'Feet', 'Both', 'Boots of the Righteous Path'), +(2, 1, 8, 0, 120, 29252, 'Pre-Raid', 'Paladin', 'Protection', 'Wrists', 'Both', 'Bracers of Dignity'), +(2, 1, 9, 0, 120, 27535, 'Pre-Raid', 'Paladin', 'Protection', 'Hands', 'Both', 'Gauntlets of the Righteous'), +(2, 1, 10, 0, 120, 29323, 'Pre-Raid', 'Paladin', 'Protection', 'Finger1', 'Both', 'Andormu''s Tear'), +(2, 1, 11, 0, 120, 28407, 'Pre-Raid', 'Paladin', 'Protection', 'Finger2', 'Both', 'Elementium Band of the Sentry'), +(2, 1, 12, 0, 120, 27891, 'Pre-Raid', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Adamantine Figure'), +(2, 1, 13, 0, 120, 27529, 'Pre-Raid', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Figurine of the Colossus'), +(2, 1, 14, 0, 120, 27804, 'Pre-Raid', 'Paladin', 'Protection', 'Back', 'Both', 'Devilshark Cape'), +(2, 1, 15, 0, 120, 30832, 'Pre-Raid', 'Paladin', 'Protection', 'MainHand', 'Both', 'Gavel of Unearthed Secrets'), +(2, 1, 16, 0, 120, 29266, 'Pre-Raid', 'Paladin', 'Protection', 'OffHand', 'Both', 'Azure-Shield of Coldarra'), +(2, 1, 17, 0, 120, 29388, 'Pre-Raid', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Repentance'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 125, 29068, 'Phase 1', 'Paladin', 'Protection', 'Head', 'Both', 'Justicar Faceguard'), +(2, 1, 1, 0, 125, 28516, 'Phase 1', 'Paladin', 'Protection', 'Neck', 'Both', 'Barbed Choker of Discipline'), +(2, 1, 2, 0, 125, 29070, 'Phase 1', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Justicar Shoulderguards'), +(2, 1, 4, 0, 125, 29066, 'Phase 1', 'Paladin', 'Protection', 'Chest', 'Both', 'Justicar Chestguard'), +(2, 1, 5, 0, 125, 28566, 'Phase 1', 'Paladin', 'Protection', 'Waist', 'Both', 'Crimson Girdle of the Indomitable'), +(2, 1, 6, 0, 125, 29069, 'Phase 1', 'Paladin', 'Protection', 'Legs', 'Both', 'Justicar Legguards'), +(2, 1, 7, 0, 125, 30641, 'Phase 1', 'Paladin', 'Protection', 'Feet', 'Both', 'Boots of Elusion'), +(2, 1, 8, 0, 125, 23538, 'Phase 1', 'Paladin', 'Protection', 'Wrists', 'Both', 'Bracers of the Green Fortress'), +(2, 1, 9, 0, 125, 28518, 'Phase 1', 'Paladin', 'Protection', 'Hands', 'Both', 'Iron Gauntlets of the Maiden'), +(2, 1, 10, 0, 125, 28792, 'Phase 1', 'Paladin', 'Protection', 'Finger1', 'Both', 'A''dal''s Signet of Defense'), +(2, 1, 11, 0, 125, 29279, 'Phase 1', 'Paladin', 'Protection', 'Finger2', 'Both', 'Violet Signet of the Great Protector'), +(2, 1, 12, 0, 125, 28789, 'Phase 1', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Eye of Magtheridon'), +(2, 1, 13, 0, 125, 29370, 'Phase 1', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Icon of the Silver Crescent'), +(2, 1, 14, 0, 125, 28660, 'Phase 1', 'Paladin', 'Protection', 'Back', 'Both', 'Gilded Thorium Cloak'), +(2, 1, 15, 0, 125, 28802, 'Phase 1', 'Paladin', 'Protection', 'MainHand', 'Both', 'Bloodmaw Magus-Blade'), +(2, 1, 16, 0, 125, 28825, 'Phase 1', 'Paladin', 'Protection', 'OffHand', 'Both', 'Aldori Legacy Defender'), +(2, 1, 17, 0, 125, 29388, 'Phase 1', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Repentance'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 141, 30125, 'Phase 2', 'Paladin', 'Protection', 'Head', 'Both', 'Crystalforge Faceguard'), +(2, 1, 1, 0, 141, 30007, 'Phase 2', 'Paladin', 'Protection', 'Neck', 'Both', 'The Darkener''s Grasp'), +(2, 1, 2, 0, 141, 29070, 'Phase 2', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Justicar Shoulderguards'), +(2, 1, 4, 0, 141, 29066, 'Phase 2', 'Paladin', 'Protection', 'Chest', 'Both', 'Justicar Chestguard'), +(2, 1, 5, 0, 141, 30034, 'Phase 2', 'Paladin', 'Protection', 'Waist', 'Both', 'Belt of the Guardian'), +(2, 1, 6, 0, 141, 30126, 'Phase 2', 'Paladin', 'Protection', 'Legs', 'Both', 'Crystalforge Legguards'), +(2, 1, 7, 0, 141, 30033, 'Phase 2', 'Paladin', 'Protection', 'Feet', 'Both', 'Boots of the Protector'), +(2, 1, 8, 0, 141, 32515, 'Phase 2', 'Paladin', 'Protection', 'Wrists', 'Both', 'Wristguards of Determination'), +(2, 1, 9, 0, 141, 30124, 'Phase 2', 'Paladin', 'Protection', 'Hands', 'Both', 'Crystalforge Handguards'), +(2, 1, 10, 0, 141, 30083, 'Phase 2', 'Paladin', 'Protection', 'Finger1', 'Both', 'Ring of Sundered Souls'), +(2, 1, 11, 0, 141, 33054, 'Phase 2', 'Paladin', 'Protection', 'Finger2', 'Both', 'The Seal of Danzalar'), +(2, 1, 12, 0, 141, 28789, 'Phase 2', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Eye of Magtheridon'), +(2, 1, 13, 0, 141, 29370, 'Phase 2', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Icon of the Silver Crescent'), +(2, 1, 14, 0, 141, 29925, 'Phase 2', 'Paladin', 'Protection', 'Back', 'Both', 'Phoenix-Wing Cloak'), +(2, 1, 15, 0, 141, 32963, 'Phase 2', 'Paladin', 'Protection', 'MainHand', 'Both', 'Merciless Gladiator''s Gavel'), +(2, 1, 16, 0, 141, 33313, 'Phase 2', 'Paladin', 'Protection', 'OffHand', 'Both', 'Merciless Gladiator''s Barrier'), +(2, 1, 17, 0, 141, 29388, 'Phase 2', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Repentance'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 156, 32521, 'Phase 3', 'Paladin', 'Protection', 'Head', 'Both', 'Faceplate of the Impenetrable'), +(2, 1, 1, 0, 156, 32362, 'Phase 3', 'Paladin', 'Protection', 'Neck', 'Both', 'Pendant of Titans'), +(2, 1, 2, 0, 156, 30998, 'Phase 3', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Lightbringer Shoulderguards'), +(2, 1, 4, 0, 156, 30991, 'Phase 3', 'Paladin', 'Protection', 'Chest', 'Both', 'Lightbringer Chestguard'), +(2, 1, 5, 0, 156, 32342, 'Phase 3', 'Paladin', 'Protection', 'Waist', 'Both', 'Girdle of Mighty Resolve'), +(2, 1, 6, 0, 156, 30995, 'Phase 3', 'Paladin', 'Protection', 'Legs', 'Both', 'Lightbringer Legguards'), +(2, 1, 7, 0, 156, 32245, 'Phase 3', 'Paladin', 'Protection', 'Feet', 'Both', 'Tide-Stomper''s Greaves'), +(2, 1, 8, 0, 156, 32279, 'Phase 3', 'Paladin', 'Protection', 'Wrists', 'Both', 'The Seeker''s Wristguards'), +(2, 1, 9, 0, 156, 30985, 'Phase 3', 'Paladin', 'Protection', 'Hands', 'Both', 'Lightbringer Handguards'), +(2, 1, 10, 0, 156, 32261, 'Phase 3', 'Paladin', 'Protection', 'Finger1', 'Both', 'Band of the Abyssal Lord'), +(2, 1, 11, 0, 156, 33054, 'Phase 3', 'Paladin', 'Protection', 'Finger2', 'Both', 'The Seal of Danzalar'), +(2, 1, 12, 0, 156, 31858, 'Phase 3', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Darkmoon Card: Vengeance'), +(2, 1, 13, 0, 156, 32501, 'Phase 3', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Shadowmoon Insignia'), +(2, 1, 14, 0, 156, 34010, 'Phase 3', 'Paladin', 'Protection', 'Back', 'Both', 'Pepe''s Shroud of Pacification'), +(2, 1, 15, 0, 156, 30910, 'Phase 3', 'Paladin', 'Protection', 'MainHand', 'Both', 'Tempest of Chaos'), +(2, 1, 16, 0, 156, 32375, 'Phase 3', 'Paladin', 'Protection', 'OffHand', 'Both', 'Bulwark of Azzinoth'), +(2, 1, 17, 0, 156, 29388, 'Phase 3', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Repentance'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 164, 32521, 'Phase 4', 'Paladin', 'Protection', 'Head', 'Both', 'Faceplate of the Impenetrable'), +(2, 1, 1, 0, 164, 32362, 'Phase 4', 'Paladin', 'Protection', 'Neck', 'Both', 'Pendant of Titans'), +(2, 1, 2, 0, 164, 30998, 'Phase 4', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Lightbringer Shoulderguards'), +(2, 1, 4, 0, 164, 30991, 'Phase 4', 'Paladin', 'Protection', 'Chest', 'Both', 'Lightbringer Chestguard'), +(2, 1, 5, 0, 164, 32342, 'Phase 4', 'Paladin', 'Protection', 'Waist', 'Both', 'Girdle of Mighty Resolve'), +(2, 1, 6, 0, 164, 30995, 'Phase 4', 'Paladin', 'Protection', 'Legs', 'Both', 'Lightbringer Legguards'), +(2, 1, 7, 0, 164, 32245, 'Phase 4', 'Paladin', 'Protection', 'Feet', 'Both', 'Tide-Stomper''s Greaves'), +(2, 1, 8, 0, 164, 32279, 'Phase 4', 'Paladin', 'Protection', 'Wrists', 'Both', 'The Seeker''s Wristguards'), +(2, 1, 9, 0, 164, 30985, 'Phase 4', 'Paladin', 'Protection', 'Hands', 'Both', 'Lightbringer Handguards'), +(2, 1, 10, 0, 164, 32261, 'Phase 4', 'Paladin', 'Protection', 'Finger1', 'Both', 'Band of the Abyssal Lord'), +(2, 1, 11, 0, 164, 33054, 'Phase 4', 'Paladin', 'Protection', 'Finger2', 'Both', 'The Seal of Danzalar'), +(2, 1, 12, 0, 164, 31858, 'Phase 4', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Darkmoon Card: Vengeance'), +(2, 1, 13, 0, 164, 32501, 'Phase 4', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Shadowmoon Insignia'), +(2, 1, 14, 0, 164, 33593, 'Phase 4', 'Paladin', 'Protection', 'Back', 'Both', 'Slikk''s Cloak of Placation'), +(2, 1, 15, 0, 164, 30910, 'Phase 4', 'Paladin', 'Protection', 'MainHand', 'Both', 'Tempest of Chaos'), +(2, 1, 16, 0, 164, 32375, 'Phase 4', 'Paladin', 'Protection', 'OffHand', 'Both', 'Bulwark of Azzinoth'), +(2, 1, 17, 0, 164, 29388, 'Phase 4', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Repentance'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 200, 41387, 'Pre-Raid', 'Paladin', 'Protection', 'Head', 'Both', 'Tempered Titansteel Helm'), +(2, 1, 1, 0, 200, 40679, 'Pre-Raid', 'Paladin', 'Protection', 'Neck', 'Both', 'Chained Military Gorget'), +(2, 1, 2, 0, 200, 37814, 'Pre-Raid', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Iron Dwarf Smith Pauldrons'), +(2, 1, 4, 0, 200, 44198, 'Pre-Raid', 'Paladin', 'Protection', 'Chest', 'Both', 'Breastplate of the Solemn Council'), +(2, 1, 5, 0, 200, 37241, 'Pre-Raid', 'Paladin', 'Protection', 'Waist', 'Both', 'Ancient Aligned Girdle'), +(2, 1, 6, 0, 200, 37193, 'Pre-Raid', 'Paladin', 'Protection', 'Legs', 'Both', 'Staggering Legplates'), +(2, 1, 7, 0, 200, 44201, 'Pre-Raid', 'Paladin', 'Protection', 'Feet', 'Both', 'Sabatons of Draconic Vigor'), +(2, 1, 8, 0, 200, 37620, 'Pre-Raid', 'Paladin', 'Protection', 'Wrists', 'Both', 'Bracers of the Herald'), +(2, 1, 9, 0, 200, 37645, 'Pre-Raid', 'Paladin', 'Protection', 'Hands', 'Both', 'Horn-Tipped Gauntlets'), +(2, 1, 10, 0, 200, 36961, 'Pre-Raid', 'Paladin', 'Protection', 'Finger1', 'Both', 'Dragonflight Great-Ring'), +(2, 1, 12, 0, 200, 37220, 'Pre-Raid', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Essence of Gossamer'), +(2, 1, 14, 0, 200, 43565, 'Pre-Raid', 'Paladin', 'Protection', 'Back', 'Both', 'Durable Nerubhide Cape'), +(2, 1, 15, 0, 200, 37401, 'Pre-Raid', 'Paladin', 'Protection', 'MainHand', 'Both', 'Red Sword of Courage'), +(2, 1, 17, 0, 200, 40707, 'Pre-Raid', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Obstruction'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 224, 40581, 'Phase 1', 'Paladin', 'Protection', 'Head', 'Both', 'Valorous Redemption Faceguard'), +(2, 1, 1, 0, 224, 44665, 'Phase 1', 'Paladin', 'Protection', 'Neck', 'Both', 'Nexus War Champion Beads'), +(2, 1, 2, 0, 224, 40584, 'Phase 1', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Valorous Redemption Shoulderguards'), +(2, 1, 4, 0, 224, 40579, 'Phase 1', 'Paladin', 'Protection', 'Chest', 'Both', 'Valorous Redemption Breastplate'), +(2, 1, 5, 0, 224, 39759, 'Phase 1', 'Paladin', 'Protection', 'Waist', 'Both', 'Ablative Chitin Girdle'), +(2, 1, 6, 0, 224, 40589, 'Phase 1', 'Paladin', 'Protection', 'Legs', 'Both', 'Legplates of Sovereignty'), +(2, 1, 7, 0, 224, 40297, 'Phase 1', 'Paladin', 'Protection', 'Feet', 'Both', 'Sabatons of Endurance'), +(2, 1, 8, 0, 224, 39764, 'Phase 1', 'Paladin', 'Protection', 'Wrists', 'Both', 'Bindings of the Hapless Prey'), +(2, 1, 9, 0, 224, 40580, 'Phase 1', 'Paladin', 'Protection', 'Hands', 'Both', 'Valorous Redemption Handguards'), +(2, 1, 10, 0, 224, 40107, 'Phase 1', 'Paladin', 'Protection', 'Finger1', 'Both', 'Sand-Worn Band'), +(2, 1, 12, 0, 224, 40372, 'Phase 1', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Rune of Repulsion'), +(2, 1, 14, 0, 224, 40410, 'Phase 1', 'Paladin', 'Protection', 'Back', 'Both', 'Shadow of the Ghoul'), +(2, 1, 15, 0, 224, 40402, 'Phase 1', 'Paladin', 'Protection', 'MainHand', 'Both', 'Last Laugh'), +(2, 1, 17, 0, 224, 40337, 'Phase 1', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Resurgence'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 245, 46175, 'Phase 2', 'Paladin', 'Protection', 'Head', 'Both', 'Conqueror''s Aegis Faceguard'), +(2, 1, 1, 0, 245, 45485, 'Phase 2', 'Paladin', 'Protection', 'Neck', 'Both', 'Bronze Pendant of the Vanir'), +(2, 1, 2, 0, 245, 46177, 'Phase 2', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Conqueror''s Aegis Shoulderguards'), +(2, 1, 4, 0, 245, 46039, 'Phase 2', 'Paladin', 'Protection', 'Chest', 'Both', 'Breastplate of the Timeless'), +(2, 1, 5, 0, 245, 45825, 'Phase 2', 'Paladin', 'Protection', 'Waist', 'Both', 'Shieldwarder Girdle'), +(2, 1, 6, 0, 245, 45594, 'Phase 2', 'Paladin', 'Protection', 'Legs', 'Both', 'Legplates of the Endless Void'), +(2, 1, 7, 0, 245, 45988, 'Phase 2', 'Paladin', 'Protection', 'Feet', 'Both', 'Greaves of the Iron Army'), +(2, 1, 8, 0, 245, 45111, 'Phase 2', 'Paladin', 'Protection', 'Wrists', 'Both', 'Mimiron''s Inferno Couplings'), +(2, 1, 9, 0, 245, 45487, 'Phase 2', 'Paladin', 'Protection', 'Hands', 'Both', 'Handguards of Revitalization'), +(2, 1, 10, 0, 245, 45471, 'Phase 2', 'Paladin', 'Protection', 'Finger1', 'Both', 'Fate''s Clutch'), +(2, 1, 12, 0, 245, 45158, 'Phase 2', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Heart of Iron'), +(2, 1, 14, 0, 245, 45496, 'Phase 2', 'Paladin', 'Protection', 'Back', 'Both', 'Titanskin Cloak'), +(2, 1, 17, 0, 245, 45145, 'Phase 2', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of the Sacred Shield'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 258, 48644, 'Phase 3', 'Paladin', 'Protection', 'Head', 'Both', 'Faceguard of Triumph'), +(2, 1, 2, 0, 258, 48646, 'Phase 3', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Shoulderguards of Triumph'), +(2, 1, 4, 1, 258, 46968, 'Phase 3', 'Paladin', 'Protection', 'Chest', 'Alliance', 'Chestplate of the Towering Monstrosity'), +(2, 1, 4, 2, 258, 47415, 'Phase 3', 'Paladin', 'Protection', 'Chest', 'Horde', 'Hauberk of the Towering Monstrosity'), +(2, 1, 5, 1, 258, 47076, 'Phase 3', 'Paladin', 'Protection', 'Waist', 'Alliance', 'Girdle of Bloodied Scars'), +(2, 1, 5, 2, 258, 47444, 'Phase 3', 'Paladin', 'Protection', 'Waist', 'Horde', 'Belt of Bloodied Scars'), +(2, 1, 6, 0, 258, 48645, 'Phase 3', 'Paladin', 'Protection', 'Legs', 'Both', 'Legguards of Triumph'), +(2, 1, 7, 1, 258, 47003, 'Phase 3', 'Paladin', 'Protection', 'Feet', 'Alliance', 'Dawnbreaker Greaves'), +(2, 1, 7, 2, 258, 47430, 'Phase 3', 'Paladin', 'Protection', 'Feet', 'Horde', 'Dawnbreaker Sabatons'), +(2, 1, 8, 1, 258, 47918, 'Phase 3', 'Paladin', 'Protection', 'Wrists', 'Alliance', 'Dreadscale Armguards'), +(2, 1, 8, 2, 258, 47991, 'Phase 3', 'Paladin', 'Protection', 'Wrists', 'Horde', 'Dreadscale Bracers'), +(2, 1, 9, 0, 258, 48643, 'Phase 3', 'Paladin', 'Protection', 'Hands', 'Both', 'Handguards of Triumph'), +(2, 1, 10, 1, 258, 45471, 'Phase 3', 'Paladin', 'Protection', 'Finger1', 'Alliance', 'Fate''s Clutch'), +(2, 1, 12, 1, 258, 47216, 'Phase 3', 'Paladin', 'Protection', 'Trinket1', 'Alliance', 'The Black Heart'), +(2, 1, 14, 1, 258, 47549, 'Phase 3', 'Paladin', 'Protection', 'Back', 'Alliance', 'Magni''s Resolution'), +(2, 1, 14, 2, 258, 47550, 'Phase 3', 'Paladin', 'Protection', 'Back', 'Horde', 'Cairne''s Endurance'), +(2, 1, 17, 0, 258, 47664, 'Phase 3', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of Defiance'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 264, 50640, 'Phase 4', 'Paladin', 'Protection', 'Head', 'Both', 'Broken Ram Skull Helm'), +(2, 1, 1, 0, 264, 50627, 'Phase 4', 'Paladin', 'Protection', 'Neck', 'Both', 'Noose of Malachite'), +(2, 1, 2, 0, 264, 51269, 'Phase 4', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Sanctified Lightsworn Shoulderguards'), +(2, 1, 4, 0, 264, 51265, 'Phase 4', 'Paladin', 'Protection', 'Chest', 'Both', 'Sanctified Lightsworn Chestguard'), +(2, 1, 5, 0, 264, 50691, 'Phase 4', 'Paladin', 'Protection', 'Waist', 'Both', 'Belt of Broken Bones'), +(2, 1, 6, 0, 264, 51268, 'Phase 4', 'Paladin', 'Protection', 'Legs', 'Both', 'Sanctified Lightsworn Legguards'), +(2, 1, 7, 0, 264, 50625, 'Phase 4', 'Paladin', 'Protection', 'Feet', 'Both', 'Grinning Skull Greatboots'), +(2, 1, 8, 0, 264, 50611, 'Phase 4', 'Paladin', 'Protection', 'Wrists', 'Both', 'Bracers of Dark Reckoning'), +(2, 1, 9, 0, 264, 51267, 'Phase 4', 'Paladin', 'Protection', 'Hands', 'Both', 'Sanctified Lightsworn Handguards'), +(2, 1, 10, 0, 264, 50404, 'Phase 4', 'Paladin', 'Protection', 'Finger1', 'Both', 'Ashen Band of Endless Courage'), +(2, 1, 12, 0, 264, 50349, 'Phase 4', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Corpse Tongue Coin'), +(2, 1, 14, 0, 264, 50718, 'Phase 4', 'Paladin', 'Protection', 'Back', 'Both', 'Royal Crimson Cloak'), +(2, 1, 15, 0, 264, 50738, 'Phase 4', 'Paladin', 'Protection', 'MainHand', 'Both', 'Mithrios, Bronzebeard''s Legacy'), +(2, 1, 17, 0, 264, 50461, 'Phase 4', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of the Eternal Tower'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 1, 0, 0, 290, 50640, 'Phase 5', 'Paladin', 'Protection', 'Head', 'Both', 'Broken Ram Skull Helm'), +(2, 1, 1, 0, 290, 50682, 'Phase 5', 'Paladin', 'Protection', 'Neck', 'Both', 'Bile-Encrusted Medallion'), +(2, 1, 2, 0, 290, 50660, 'Phase 5', 'Paladin', 'Protection', 'Shoulders', 'Both', 'Boneguard Commander''s Pauldrons'), +(2, 1, 4, 0, 290, 51265, 'Phase 5', 'Paladin', 'Protection', 'Chest', 'Both', 'Sanctified Lightsworn Chestguard'), +(2, 1, 5, 0, 290, 50991, 'Phase 5', 'Paladin', 'Protection', 'Waist', 'Both', 'Verdigris Chain Belt'), +(2, 1, 6, 0, 290, 49904, 'Phase 5', 'Paladin', 'Protection', 'Legs', 'Both', 'Pillars of Might'), +(2, 1, 7, 0, 290, 54579, 'Phase 5', 'Paladin', 'Protection', 'Feet', 'Both', 'Treads of Impending Resurrection'), +(2, 1, 8, 0, 290, 51901, 'Phase 5', 'Paladin', 'Protection', 'Wrists', 'Both', 'Gargoyle Spit Bracers'), +(2, 1, 9, 0, 290, 51267, 'Phase 5', 'Paladin', 'Protection', 'Hands', 'Both', 'Sanctified Lightsworn Handguards'), +(2, 1, 10, 0, 290, 50622, 'Phase 5', 'Paladin', 'Protection', 'Finger1', 'Both', 'Devium''s Eternally Cold Ring'), +(2, 1, 11, 0, 290, 50404, 'Phase 5', 'Paladin', 'Protection', 'Finger2', 'Both', 'Ashen Band of Endless Courage'), +(2, 1, 12, 0, 290, 54591, 'Phase 5', 'Paladin', 'Protection', 'Trinket1', 'Both', 'Petrified Twilight Scale'), +(2, 1, 13, 0, 290, 50364, 'Phase 5', 'Paladin', 'Protection', 'Trinket2', 'Both', 'Sindragosa''s Flawless Fang'), +(2, 1, 14, 0, 290, 50466, 'Phase 5', 'Paladin', 'Protection', 'Back', 'Both', 'Sentinel''s Winter Cloak'), +(2, 1, 15, 0, 290, 50738, 'Phase 5', 'Paladin', 'Protection', 'MainHand', 'Both', 'Mithrios, Bronzebeard''s Legacy'), +(2, 1, 16, 0, 290, 50729, 'Phase 5', 'Paladin', 'Protection', 'OffHand', 'Both', 'Icecrown Glacial Wall'), +(2, 1, 17, 0, 290, 50461, 'Phase 5', 'Paladin', 'Protection', 'Ranged', 'Both', 'Libram of the Eternal Tower'); + +-- Retribution (tab 2) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 66, 13404, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Head', 'Both', 'Mask of the Unforgiven'), +(2, 2, 1, 0, 66, 15411, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Neck', 'Both', 'Mark of Fordring'), +(2, 2, 2, 0, 66, 12927, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(2, 2, 4, 0, 66, 11726, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Chest', 'Both', 'Savage Gladiator Chain'), +(2, 2, 5, 0, 66, 13959, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Waist', 'Both', 'Omokk''s Girth Restrainer'), +(2, 2, 6, 0, 66, 14554, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Legs', 'Both', 'Cloudkeeper Legplates'), +(2, 2, 7, 0, 66, 14616, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Feet', 'Both', 'Bloodmail Boots'), +(2, 2, 8, 0, 66, 12936, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Battleborn Armbraces'), +(2, 2, 9, 0, 66, 13957, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Hands', 'Both', 'Gargoyle Slashers'), +(2, 2, 10, 0, 66, 13098, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Finger1', 'Both', 'Painweaver Band'), +(2, 2, 11, 0, 66, 12548, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Finger2', 'Both', 'Magni''s Will'), +(2, 2, 12, 0, 66, 13965, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(2, 2, 13, 0, 66, 11815, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Trinket2', 'Both', 'Hand of Justice'), +(2, 2, 14, 0, 66, 13340, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'Back', 'Both', 'Cape of the Black Baron'), +(2, 2, 15, 0, 66, 12784, 'Phase 1 (Pre-Raid)', 'Paladin', 'Retribution', 'MainHand', 'Both', 'Arcanite Reaper'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 76, 13404, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Head', 'Both', 'Mask of the Unforgiven'), +(2, 2, 1, 0, 76, 15411, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Neck', 'Both', 'Mark of Fordring'), +(2, 2, 2, 0, 76, 12927, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(2, 2, 4, 0, 76, 11726, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Chest', 'Both', 'Savage Gladiator Chain'), +(2, 2, 5, 0, 76, 13959, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Waist', 'Both', 'Omokk''s Girth Restrainer'), +(2, 2, 6, 0, 76, 14554, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Legs', 'Both', 'Cloudkeeper Legplates'), +(2, 2, 7, 0, 76, 14616, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Feet', 'Both', 'Bloodmail Boots'), +(2, 2, 8, 0, 76, 12936, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Battleborn Armbraces'), +(2, 2, 9, 0, 76, 13957, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Hands', 'Both', 'Gargoyle Slashers'), +(2, 2, 10, 0, 76, 13098, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Finger1', 'Both', 'Painweaver Band'), +(2, 2, 11, 0, 76, 12548, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Finger2', 'Both', 'Magni''s Will'), +(2, 2, 12, 0, 76, 13965, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(2, 2, 13, 0, 76, 11815, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Trinket2', 'Both', 'Hand of Justice'), +(2, 2, 14, 0, 76, 13340, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'Back', 'Both', 'Cape of the Black Baron'), +(2, 2, 15, 0, 76, 12784, 'Phase 2 (Pre-Raid)', 'Paladin', 'Retribution', 'MainHand', 'Both', 'Arcanite Reaper'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 78, 12640, 'Phase 2', 'Paladin', 'Retribution', 'Head', 'Both', 'Lionheart Helm'), +(2, 2, 1, 0, 78, 18404, 'Phase 2', 'Paladin', 'Retribution', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(2, 2, 2, 0, 78, 12927, 'Phase 2', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(2, 2, 4, 0, 78, 11726, 'Phase 2', 'Paladin', 'Retribution', 'Chest', 'Both', 'Savage Gladiator Chain'), +(2, 2, 5, 0, 78, 19137, 'Phase 2', 'Paladin', 'Retribution', 'Waist', 'Both', 'Onslaught Girdle'), +(2, 2, 6, 0, 78, 14554, 'Phase 2', 'Paladin', 'Retribution', 'Legs', 'Both', 'Cloudkeeper Legplates'), +(2, 2, 7, 0, 78, 14616, 'Phase 2', 'Paladin', 'Retribution', 'Feet', 'Both', 'Bloodmail Boots'), +(2, 2, 8, 0, 78, 19146, 'Phase 2', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Wristguards of Stability'), +(2, 2, 9, 0, 78, 19143, 'Phase 2', 'Paladin', 'Retribution', 'Hands', 'Both', 'Flameguard Gauntlets'), +(2, 2, 10, 0, 78, 13098, 'Phase 2', 'Paladin', 'Retribution', 'Finger1', 'Both', 'Painweaver Band'), +(2, 2, 11, 0, 78, 18821, 'Phase 2', 'Paladin', 'Retribution', 'Finger2', 'Both', 'Quick Strike Ring'), +(2, 2, 12, 0, 78, 13965, 'Phase 2', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(2, 2, 13, 0, 78, 11815, 'Phase 2', 'Paladin', 'Retribution', 'Trinket2', 'Both', 'Hand of Justice'), +(2, 2, 14, 0, 78, 13340, 'Phase 2', 'Paladin', 'Retribution', 'Back', 'Both', 'Cape of the Black Baron'), +(2, 2, 15, 0, 78, 17076, 'Phase 2', 'Paladin', 'Retribution', 'MainHand', 'Both', 'Bonereaver''s Edge'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 83, 12640, 'Phase 3', 'Paladin', 'Retribution', 'Head', 'Both', 'Lionheart Helm'), +(2, 2, 1, 0, 83, 18404, 'Phase 3', 'Paladin', 'Retribution', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(2, 2, 2, 0, 83, 19394, 'Phase 3', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Drake Talon Pauldrons'), +(2, 2, 4, 0, 83, 11726, 'Phase 3', 'Paladin', 'Retribution', 'Chest', 'Both', 'Savage Gladiator Chain'), +(2, 2, 5, 0, 83, 19137, 'Phase 3', 'Paladin', 'Retribution', 'Waist', 'Both', 'Onslaught Girdle'), +(2, 2, 6, 0, 83, 19402, 'Phase 3', 'Paladin', 'Retribution', 'Legs', 'Both', 'Legguards of the Fallen Crusader'), +(2, 2, 7, 0, 83, 19387, 'Phase 3', 'Paladin', 'Retribution', 'Feet', 'Both', 'Chromatic Boots'), +(2, 2, 8, 0, 83, 19146, 'Phase 3', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Wristguards of Stability'), +(2, 2, 9, 0, 83, 19143, 'Phase 3', 'Paladin', 'Retribution', 'Hands', 'Both', 'Flameguard Gauntlets'), +(2, 2, 11, 0, 83, 19384, 'Phase 3', 'Paladin', 'Retribution', 'Finger2', 'Both', 'Master Dragonslayer''s Ring'), +(2, 2, 12, 0, 83, 13965, 'Phase 3', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(2, 2, 13, 0, 83, 11815, 'Phase 3', 'Paladin', 'Retribution', 'Trinket2', 'Both', 'Hand of Justice'), +(2, 2, 14, 0, 83, 19436, 'Phase 3', 'Paladin', 'Retribution', 'Back', 'Both', 'Cloak of Draconic Might'), +(2, 2, 15, 0, 83, 19334, 'Phase 3', 'Paladin', 'Retribution', 'MainHand', 'Both', 'The Untamed Blade'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 88, 21387, 'Phase 5', 'Paladin', 'Retribution', 'Head', 'Both', 'Avenger''s Crown'), +(2, 2, 1, 0, 88, 18404, 'Phase 5', 'Paladin', 'Retribution', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(2, 2, 2, 0, 88, 21391, 'Phase 5', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Avenger''s Pauldrons'), +(2, 2, 4, 0, 88, 21389, 'Phase 5', 'Paladin', 'Retribution', 'Chest', 'Both', 'Avenger''s Breastplate'), +(2, 2, 5, 0, 88, 21463, 'Phase 5', 'Paladin', 'Retribution', 'Waist', 'Both', 'Ossirian''s Binding'), +(2, 2, 6, 0, 88, 21390, 'Phase 5', 'Paladin', 'Retribution', 'Legs', 'Both', 'Avenger''s Legguards'), +(2, 2, 7, 0, 88, 21388, 'Phase 5', 'Paladin', 'Retribution', 'Feet', 'Both', 'Avenger''s Greaves'), +(2, 2, 8, 0, 88, 21618, 'Phase 5', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Hive Defiler Wristguards'), +(2, 2, 9, 0, 88, 21623, 'Phase 5', 'Paladin', 'Retribution', 'Hands', 'Both', 'Gauntlets of the Righteous Champion'), +(2, 2, 10, 0, 88, 17063, 'Phase 5', 'Paladin', 'Retribution', 'Finger1', 'Both', 'Band of Accuria'), +(2, 2, 11, 0, 88, 21205, 'Phase 5', 'Paladin', 'Retribution', 'Finger2', 'Both', 'Signet Ring of the Bronze Dragonflight'), +(2, 2, 12, 0, 88, 22321, 'Phase 5', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Heart of Wyrmthalak'), +(2, 2, 13, 0, 88, 19289, 'Phase 5', 'Paladin', 'Retribution', 'Trinket2', 'Both', 'Darkmoon Card: Maelstrom'), +(2, 2, 14, 0, 88, 21701, 'Phase 5', 'Paladin', 'Retribution', 'Back', 'Both', 'Cloak of Concentrated Hatred'), +(2, 2, 15, 0, 88, 21134, 'Phase 5', 'Paladin', 'Retribution', 'MainHand', 'Both', 'Dark Edge of Insanity'), +(2, 2, 17, 0, 88, 23203, 'Phase 5', 'Paladin', 'Retribution', 'Ranged', 'Both', 'Libram of Fervor'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 92, 21387, 'Phase 6', 'Paladin', 'Retribution', 'Head', 'Both', 'Avenger''s Crown'), +(2, 2, 1, 0, 92, 18404, 'Phase 6', 'Paladin', 'Retribution', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(2, 2, 2, 0, 92, 21391, 'Phase 6', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Avenger''s Pauldrons'), +(2, 2, 4, 0, 92, 21389, 'Phase 6', 'Paladin', 'Retribution', 'Chest', 'Both', 'Avenger''s Breastplate'), +(2, 2, 5, 0, 92, 23219, 'Phase 6', 'Paladin', 'Retribution', 'Waist', 'Both', 'Girdle of the Mentor'), +(2, 2, 6, 0, 92, 21390, 'Phase 6', 'Paladin', 'Retribution', 'Legs', 'Both', 'Avenger''s Legguards'), +(2, 2, 7, 0, 92, 21388, 'Phase 6', 'Paladin', 'Retribution', 'Feet', 'Both', 'Avenger''s Greaves'), +(2, 2, 8, 0, 92, 22936, 'Phase 6', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Wristguards of Vengeance'), +(2, 2, 9, 0, 92, 21623, 'Phase 6', 'Paladin', 'Retribution', 'Hands', 'Both', 'Gauntlets of the Righteous Champion'), +(2, 2, 11, 0, 92, 21205, 'Phase 6', 'Paladin', 'Retribution', 'Finger2', 'Both', 'Signet Ring of the Bronze Dragonflight'), +(2, 2, 12, 0, 92, 22321, 'Phase 6', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Heart of Wyrmthalak'), +(2, 2, 13, 0, 92, 19289, 'Phase 6', 'Paladin', 'Retribution', 'Trinket2', 'Both', 'Darkmoon Card: Maelstrom'), +(2, 2, 14, 0, 92, 23045, 'Phase 6', 'Paladin', 'Retribution', 'Back', 'Both', 'Shroud of Dominion'), +(2, 2, 15, 0, 92, 22691, 'Phase 6', 'Paladin', 'Retribution', 'MainHand', 'Both', 'Corrupted Ashbringer'), +(2, 2, 17, 0, 92, 23203, 'Phase 6', 'Paladin', 'Retribution', 'Ranged', 'Both', 'Libram of Fervor'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 200, 41386, 'Pre-Raid', 'Paladin', 'Retribution', 'Head', 'Both', 'Spiked Titansteel Helm'), +(2, 2, 1, 0, 200, 42645, 'Pre-Raid', 'Paladin', 'Retribution', 'Neck', 'Both', 'Titanium Impact Choker'), +(2, 2, 2, 0, 200, 37627, 'Pre-Raid', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Snake Den Spaulders'), +(2, 2, 4, 0, 200, 37612, 'Pre-Raid', 'Paladin', 'Retribution', 'Chest', 'Both', 'Bonegrinder Breastplate'), +(2, 2, 5, 0, 200, 37178, 'Pre-Raid', 'Paladin', 'Retribution', 'Waist', 'Both', 'Strategist''s Belt'), +(2, 2, 6, 0, 200, 37193, 'Pre-Raid', 'Paladin', 'Retribution', 'Legs', 'Both', 'Staggering Legplates'), +(2, 2, 7, 0, 200, 43402, 'Pre-Raid', 'Paladin', 'Retribution', 'Feet', 'Both', 'The Obliterator Greaves'), +(2, 2, 8, 0, 200, 37668, 'Pre-Raid', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Bands of the Stoneforge'), +(2, 2, 9, 0, 200, 37363, 'Pre-Raid', 'Paladin', 'Retribution', 'Hands', 'Both', 'Gauntlets of Dragon Wrath'), +(2, 2, 10, 0, 200, 37151, 'Pre-Raid', 'Paladin', 'Retribution', 'Finger1', 'Both', 'Band of Frosted Thorns'), +(2, 2, 12, 0, 200, 40684, 'Pre-Raid', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Mirror of Truth'), +(2, 2, 14, 0, 200, 43566, 'Pre-Raid', 'Paladin', 'Retribution', 'Back', 'Both', 'Ice Striker''s Cloak'), +(2, 2, 15, 0, 200, 41257, 'Pre-Raid', 'Paladin', 'Retribution', 'MainHand', 'Both', 'Titansteel Destroyer'), +(2, 2, 17, 0, 200, 40706, 'Pre-Raid', 'Paladin', 'Retribution', 'Ranged', 'Both', 'Libram of Reciprocation'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 224, 44006, 'Phase 1', 'Paladin', 'Retribution', 'Head', 'Both', 'Obsidian Greathelm'), +(2, 2, 1, 0, 224, 44664, 'Phase 1', 'Paladin', 'Retribution', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(2, 2, 2, 0, 224, 40578, 'Phase 1', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Valorous Redemption Shoulderplates'), +(2, 2, 4, 0, 224, 40574, 'Phase 1', 'Paladin', 'Retribution', 'Chest', 'Both', 'Valorous Redemption Chestpiece'), +(2, 2, 5, 0, 224, 40278, 'Phase 1', 'Paladin', 'Retribution', 'Waist', 'Both', 'Girdle of Chivalry'), +(2, 2, 6, 0, 224, 44011, 'Phase 1', 'Paladin', 'Retribution', 'Legs', 'Both', 'Leggings of the Honored'), +(2, 2, 7, 0, 224, 40591, 'Phase 1', 'Paladin', 'Retribution', 'Feet', 'Both', 'Melancholy Sabatons'), +(2, 2, 8, 0, 224, 40186, 'Phase 1', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Thrusting Bands'), +(2, 2, 9, 0, 224, 40541, 'Phase 1', 'Paladin', 'Retribution', 'Hands', 'Both', 'Frosted Adroit Handguards'), +(2, 2, 10, 0, 224, 40075, 'Phase 1', 'Paladin', 'Retribution', 'Finger1', 'Both', 'Ruthlessness'), +(2, 2, 12, 0, 224, 42987, 'Phase 1', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(2, 2, 14, 0, 224, 40403, 'Phase 1', 'Paladin', 'Retribution', 'Back', 'Both', 'Drape of the Deadly Foe'), +(2, 2, 17, 0, 224, 40191, 'Phase 1', 'Paladin', 'Retribution', 'Ranged', 'Both', 'Libram of Radiance'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 245, 45472, 'Phase 2', 'Paladin', 'Retribution', 'Head', 'Both', 'Warhelm of the Champion'), +(2, 2, 2, 0, 245, 45245, 'Phase 2', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Shoulderpads of the Intruder'), +(2, 2, 4, 0, 245, 45473, 'Phase 2', 'Paladin', 'Retribution', 'Chest', 'Both', 'Embrace of the Gladiator'), +(2, 2, 5, 0, 245, 46095, 'Phase 2', 'Paladin', 'Retribution', 'Waist', 'Both', 'Soul-Devouring Cinch'), +(2, 2, 6, 0, 245, 45134, 'Phase 2', 'Paladin', 'Retribution', 'Legs', 'Both', 'Plated Leggings of Ruination'), +(2, 2, 7, 0, 245, 45599, 'Phase 2', 'Paladin', 'Retribution', 'Feet', 'Both', 'Sabatons of Lifeless Night'), +(2, 2, 8, 0, 245, 45663, 'Phase 2', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Armbands of Bedlam'), +(2, 2, 9, 0, 245, 45444, 'Phase 2', 'Paladin', 'Retribution', 'Hands', 'Both', 'Gloves of the Steady Hand'), +(2, 2, 10, 0, 245, 45608, 'Phase 2', 'Paladin', 'Retribution', 'Finger1', 'Both', 'Brann''s Signet Ring'), +(2, 2, 12, 0, 245, 45609, 'Phase 2', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Comet''s Trail'), +(2, 2, 14, 0, 245, 46032, 'Phase 2', 'Paladin', 'Retribution', 'Back', 'Both', 'Drape of the Faceless General'), +(2, 2, 17, 0, 245, 42853, 'Phase 2', 'Paladin', 'Retribution', 'Ranged', 'Both', 'Furious Gladiator''s Libram of Fortitude'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 258, 48614, 'Phase 3', 'Paladin', 'Retribution', 'Head', 'Both', 'Helm of Triumph'), +(2, 2, 2, 0, 258, 48612, 'Phase 3', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Shoulderplates of Triumph'), +(2, 2, 4, 1, 258, 46965, 'Phase 3', 'Paladin', 'Retribution', 'Chest', 'Alliance', 'Breastplate of Cruel Intent'), +(2, 2, 4, 2, 258, 47412, 'Phase 3', 'Paladin', 'Retribution', 'Chest', 'Horde', 'Cuirass of Cruel Intent'), +(2, 2, 5, 1, 258, 47002, 'Phase 3', 'Paladin', 'Retribution', 'Waist', 'Alliance', 'Bloodbath Belt'), +(2, 2, 5, 2, 258, 47429, 'Phase 3', 'Paladin', 'Retribution', 'Waist', 'Horde', 'Bloodbath Girdle'), +(2, 2, 6, 1, 258, 47132, 'Phase 3', 'Paladin', 'Retribution', 'Legs', 'Alliance', 'Legguards of Ascension'), +(2, 2, 6, 2, 258, 47465, 'Phase 3', 'Paladin', 'Retribution', 'Legs', 'Horde', 'Legplates of Ascension'), +(2, 2, 7, 1, 258, 47154, 'Phase 3', 'Paladin', 'Retribution', 'Feet', 'Alliance', 'Greaves of the 7th Legion'), +(2, 2, 7, 2, 258, 47473, 'Phase 3', 'Paladin', 'Retribution', 'Feet', 'Horde', 'Greaves of the Saronite Citadel'), +(2, 2, 8, 1, 258, 47155, 'Phase 3', 'Paladin', 'Retribution', 'Wrists', 'Alliance', 'Bracers of Dark Determination'), +(2, 2, 8, 2, 258, 47474, 'Phase 3', 'Paladin', 'Retribution', 'Wrists', 'Horde', 'Armbands of Dark Determination'), +(2, 2, 9, 0, 258, 48615, 'Phase 3', 'Paladin', 'Retribution', 'Hands', 'Both', 'Gauntlets of Triumph'), +(2, 2, 10, 1, 258, 46966, 'Phase 3', 'Paladin', 'Retribution', 'Finger1', 'Alliance', 'Band of the Violent Temperment'), +(2, 2, 10, 2, 258, 47413, 'Phase 3', 'Paladin', 'Retribution', 'Finger1', 'Horde', 'Ring of the Violent Temperament'), +(2, 2, 12, 1, 258, 47131, 'Phase 3', 'Paladin', 'Retribution', 'Trinket1', 'Alliance', 'Death''s Verdict'), +(2, 2, 12, 2, 258, 47464, 'Phase 3', 'Paladin', 'Retribution', 'Trinket1', 'Horde', 'Death''s Choice'), +(2, 2, 14, 1, 258, 47547, 'Phase 3', 'Paladin', 'Retribution', 'Back', 'Alliance', 'Varian''s Furor'), +(2, 2, 14, 2, 258, 47548, 'Phase 3', 'Paladin', 'Retribution', 'Back', 'Horde', 'Garrosh''s Rage'), +(2, 2, 17, 0, 258, 47661, 'Phase 3', 'Paladin', 'Retribution', 'Ranged', 'Both', 'Libram of Valiance'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 264, 51277, 'Phase 4', 'Paladin', 'Retribution', 'Head', 'Both', 'Sanctified Lightsworn Helmet'), +(2, 2, 1, 0, 264, 50633, 'Phase 4', 'Paladin', 'Retribution', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(2, 2, 2, 0, 264, 51279, 'Phase 4', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Sanctified Lightsworn Shoulderplates'), +(2, 2, 4, 0, 264, 51275, 'Phase 4', 'Paladin', 'Retribution', 'Chest', 'Both', 'Sanctified Lightsworn Battleplate'), +(2, 2, 5, 0, 264, 50707, 'Phase 4', 'Paladin', 'Retribution', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(2, 2, 6, 0, 264, 51278, 'Phase 4', 'Paladin', 'Retribution', 'Legs', 'Both', 'Sanctified Lightsworn Legplates'), +(2, 2, 7, 0, 264, 50607, 'Phase 4', 'Paladin', 'Retribution', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(2, 2, 8, 0, 264, 50659, 'Phase 4', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Polar Bear Claw Bracers'), +(2, 2, 9, 0, 264, 50690, 'Phase 4', 'Paladin', 'Retribution', 'Hands', 'Both', 'Fleshrending Gauntlets'), +(2, 2, 10, 0, 264, 50657, 'Phase 4', 'Paladin', 'Retribution', 'Finger1', 'Both', 'Skeleton Lord''s Circle'), +(2, 2, 12, 0, 264, 50706, 'Phase 4', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Tiny Abomination in a Jar'), +(2, 2, 14, 0, 264, 50653, 'Phase 4', 'Paladin', 'Retribution', 'Back', 'Both', 'Shadowvault Slayer''s Cloak'), +(2, 2, 15, 0, 264, 49623, 'Phase 4', 'Paladin', 'Retribution', 'MainHand', 'Both', 'Shadowmourne'), +(2, 2, 17, 0, 264, 50455, 'Phase 4', 'Paladin', 'Retribution', 'Ranged', 'Both', 'Libram of Three Truths'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(2, 2, 0, 0, 290, 51277, 'Phase 5', 'Paladin', 'Retribution', 'Head', 'Both', 'Sanctified Lightsworn Helmet'), +(2, 2, 1, 0, 290, 54581, 'Phase 5', 'Paladin', 'Retribution', 'Neck', 'Both', 'Penumbra Pendant'), +(2, 2, 2, 0, 290, 51279, 'Phase 5', 'Paladin', 'Retribution', 'Shoulders', 'Both', 'Sanctified Lightsworn Shoulderplates'), +(2, 2, 4, 0, 290, 51275, 'Phase 5', 'Paladin', 'Retribution', 'Chest', 'Both', 'Sanctified Lightsworn Battleplate'), +(2, 2, 5, 0, 290, 50707, 'Phase 5', 'Paladin', 'Retribution', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(2, 2, 6, 0, 290, 51278, 'Phase 5', 'Paladin', 'Retribution', 'Legs', 'Both', 'Sanctified Lightsworn Legplates'), +(2, 2, 7, 0, 290, 54578, 'Phase 5', 'Paladin', 'Retribution', 'Feet', 'Both', 'Apocalypse''s Advance'), +(2, 2, 8, 0, 290, 54580, 'Phase 5', 'Paladin', 'Retribution', 'Wrists', 'Both', 'Umbrage Armbands'), +(2, 2, 9, 0, 290, 50690, 'Phase 5', 'Paladin', 'Retribution', 'Hands', 'Both', 'Fleshrending Gauntlets'), +(2, 2, 10, 0, 290, 50402, 'Phase 5', 'Paladin', 'Retribution', 'Finger1', 'Both', 'Ashen Band of Endless Vengeance'), +(2, 2, 11, 0, 290, 54576, 'Phase 5', 'Paladin', 'Retribution', 'Finger2', 'Both', 'Signet of Twilight'), +(2, 2, 12, 0, 290, 54590, 'Phase 5', 'Paladin', 'Retribution', 'Trinket1', 'Both', 'Sharpened Twilight Scale'), +(2, 2, 13, 0, 290, 50706, 'Phase 5', 'Paladin', 'Retribution', 'Trinket2', 'Both', 'Tiny Abomination in a Jar'), +(2, 2, 14, 0, 290, 50653, 'Phase 5', 'Paladin', 'Retribution', 'Back', 'Both', 'Shadowvault Slayer''s Cloak'), +(2, 2, 15, 0, 290, 49623, 'Phase 5', 'Paladin', 'Retribution', 'MainHand', 'Both', 'Shadowmourne'), +(2, 2, 17, 0, 290, 50455, 'Phase 5', 'Paladin', 'Retribution', 'Ranged', 'Both', 'Libram of Three Truths'); + + +-- ============================================================ +-- Hunter (3) +-- ============================================================ +-- Beast Mastery (tab 0) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 66, 13404, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Mask of the Unforgiven'), +(3, 0, 1, 0, 66, 15411, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Mark of Fordring'), +(3, 0, 2, 0, 66, 12927, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(3, 0, 4, 0, 66, 11726, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Savage Gladiator Chain'), +(3, 0, 5, 0, 66, 14502, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Waist', 'Both', 'Frostbite Girdle'), +(3, 0, 6, 0, 66, 15062, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Devilsaur Leggings'), +(3, 0, 7, 0, 66, 13967, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Windreaver Greaves'), +(3, 0, 8, 0, 66, 13211, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Slashclaw Bracers'), +(3, 0, 9, 0, 66, 15063, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(3, 0, 10, 0, 66, 13098, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Finger1', 'Both', 'Painweaver Band'), +(3, 0, 11, 0, 66, 17713, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Finger2', 'Both', 'Blackstone Ring'), +(3, 0, 12, 0, 66, 13965, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 0, 13, 0, 66, 11815, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Trinket2', 'Both', 'Hand of Justice'), +(3, 0, 14, 0, 66, 13340, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Back', 'Both', 'Cape of the Black Baron'), +(3, 0, 15, 0, 66, 12940, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'MainHand', 'Both', 'Dal''Rend''s Sacred Charge'), +(3, 0, 16, 0, 66, 12939, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'OffHand', 'Both', 'Dal''Rend''s Tribal Guardian'), +(3, 0, 17, 0, 66, 18738, 'Phase 1 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Ranged', 'Both', 'Carapace Spine Crossbow'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 76, 18421, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Backwood Helm'), +(3, 0, 1, 0, 76, 15411, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Mark of Fordring'), +(3, 0, 2, 0, 76, 13358, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Wyrmtongue Shoulders'), +(3, 0, 4, 0, 76, 11726, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Savage Gladiator Chain'), +(3, 0, 5, 0, 76, 18393, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Waist', 'Both', 'Warpwood Binding'), +(3, 0, 6, 0, 76, 15062, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Devilsaur Leggings'), +(3, 0, 7, 0, 76, 13967, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Windreaver Greaves'), +(3, 0, 8, 0, 76, 13211, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Slashclaw Bracers'), +(3, 0, 9, 0, 76, 15063, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(3, 0, 10, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Finger1', 'Both', 'Tarnished Elven Ring'), +(3, 0, 11, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Finger2', 'Both', 'Tarnished Elven Ring'), +(3, 0, 12, 0, 76, 13965, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 0, 13, 0, 76, 18473, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Trinket2', 'Both', 'Royal Seal of Eldre''Thalas'), +(3, 0, 14, 0, 76, 13340, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Back', 'Both', 'Cape of the Black Baron'), +(3, 0, 15, 0, 76, 18520, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'MainHand', 'Both', 'Barbarous Blade'), +(3, 0, 17, 0, 76, 18738, 'Phase 2 (Pre-Raid)', 'Hunter', 'Beast Mastery', 'Ranged', 'Both', 'Carapace Spine Crossbow'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 78, 16846, 'Phase 2', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Giantstalker''s Helmet'), +(3, 0, 1, 0, 78, 18404, 'Phase 2', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(3, 0, 2, 0, 78, 16848, 'Phase 2', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Giantstalker''s Epaulets'), +(3, 0, 4, 0, 78, 16845, 'Phase 2', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Giantstalker''s Breastplate'), +(3, 0, 5, 0, 78, 16851, 'Phase 2', 'Hunter', 'Beast Mastery', 'Waist', 'Both', 'Giantstalker''s Belt'), +(3, 0, 6, 0, 78, 16847, 'Phase 2', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Giantstalker''s Leggings'), +(3, 0, 7, 0, 78, 16849, 'Phase 2', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Giantstalker''s Boots'), +(3, 0, 8, 0, 78, 16850, 'Phase 2', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Giantstalker''s Bracers'), +(3, 0, 9, 0, 78, 16852, 'Phase 2', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Giantstalker''s Gloves'), +(3, 0, 10, 0, 78, 17063, 'Phase 2', 'Hunter', 'Beast Mastery', 'Finger1', 'Both', 'Band of Accuria'), +(3, 0, 11, 0, 78, 18821, 'Phase 2', 'Hunter', 'Beast Mastery', 'Finger2', 'Both', 'Quick Strike Ring'), +(3, 0, 12, 0, 78, 13965, 'Phase 2', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 0, 13, 0, 78, 18473, 'Phase 2', 'Hunter', 'Beast Mastery', 'Trinket2', 'Both', 'Royal Seal of Eldre''Thalas'), +(3, 0, 14, 0, 78, 17102, 'Phase 2', 'Hunter', 'Beast Mastery', 'Back', 'Both', 'Cloak of the Shrouded Mists'), +(3, 0, 15, 0, 78, 18832, 'Phase 2', 'Hunter', 'Beast Mastery', 'MainHand', 'Both', 'Brutality Blade'), +(3, 0, 16, 0, 78, 18805, 'Phase 2', 'Hunter', 'Beast Mastery', 'OffHand', 'Both', 'Core Hound Tooth'), +(3, 0, 17, 0, 78, 18713, 'Phase 2', 'Hunter', 'Beast Mastery', 'Ranged', 'Both', 'Rhok''delar, Longbow of the Ancient Keepers'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 83, 16939, 'Phase 3', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Dragonstalker''s Helm'), +(3, 0, 1, 0, 83, 19377, 'Phase 3', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(3, 0, 2, 0, 83, 16937, 'Phase 3', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Dragonstalker''s Spaulders'), +(3, 0, 4, 0, 83, 16942, 'Phase 3', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Dragonstalker''s Breastplate'), +(3, 0, 5, 0, 83, 16936, 'Phase 3', 'Hunter', 'Beast Mastery', 'Waist', 'Both', 'Dragonstalker''s Belt'), +(3, 0, 6, 0, 83, 16938, 'Phase 3', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Dragonstalker''s Legguards'), +(3, 0, 7, 0, 83, 16941, 'Phase 3', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Dragonstalker''s Greaves'), +(3, 0, 8, 0, 83, 16935, 'Phase 3', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Dragonstalker''s Bracers'), +(3, 0, 9, 0, 83, 16940, 'Phase 3', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Dragonstalker''s Gauntlets'), +(3, 0, 10, 0, 83, 19325, 'Phase 3', 'Hunter', 'Beast Mastery', 'Finger1', 'Both', 'Don Julio''s Band'), +(3, 0, 11, 0, 83, 18821, 'Phase 3', 'Hunter', 'Beast Mastery', 'Finger2', 'Both', 'Quick Strike Ring'), +(3, 0, 12, 0, 83, 13965, 'Phase 3', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 0, 13, 0, 83, 19406, 'Phase 3', 'Hunter', 'Beast Mastery', 'Trinket2', 'Both', 'Drake Fang Talisman'), +(3, 0, 14, 0, 83, 17102, 'Phase 3', 'Hunter', 'Beast Mastery', 'Back', 'Both', 'Cloak of the Shrouded Mists'), +(3, 0, 15, 0, 83, 18832, 'Phase 3', 'Hunter', 'Beast Mastery', 'MainHand', 'Both', 'Brutality Blade'), +(3, 0, 16, 0, 83, 18805, 'Phase 3', 'Hunter', 'Beast Mastery', 'OffHand', 'Both', 'Core Hound Tooth'), +(3, 0, 17, 0, 83, 19361, 'Phase 3', 'Hunter', 'Beast Mastery', 'Ranged', 'Both', 'Ashjre''thul, Crossbow of Smiting'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 88, 16939, 'Phase 5', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Dragonstalker''s Helm'), +(3, 0, 1, 0, 88, 19377, 'Phase 5', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(3, 0, 2, 0, 88, 16937, 'Phase 5', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Dragonstalker''s Spaulders'), +(3, 0, 4, 0, 88, 16942, 'Phase 5', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Dragonstalker''s Breastplate'), +(3, 0, 5, 0, 88, 16936, 'Phase 5', 'Hunter', 'Beast Mastery', 'Waist', 'Both', 'Dragonstalker''s Belt'), +(3, 0, 6, 0, 88, 16938, 'Phase 5', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Dragonstalker''s Legguards'), +(3, 0, 7, 0, 88, 16941, 'Phase 5', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Dragonstalker''s Greaves'), +(3, 0, 8, 0, 88, 16935, 'Phase 5', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Dragonstalker''s Bracers'), +(3, 0, 9, 0, 88, 16940, 'Phase 5', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Dragonstalker''s Gauntlets'), +(3, 0, 10, 0, 88, 19325, 'Phase 5', 'Hunter', 'Beast Mastery', 'Finger1', 'Both', 'Don Julio''s Band'), +(3, 0, 11, 0, 88, 17063, 'Phase 5', 'Hunter', 'Beast Mastery', 'Finger2', 'Both', 'Band of Accuria'), +(3, 0, 12, 0, 88, 21670, 'Phase 5', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Badge of the Swarmguard'), +(3, 0, 13, 0, 88, 23570, 'Phase 5', 'Hunter', 'Beast Mastery', 'Trinket2', 'Both', 'Jom Gabbar'), +(3, 0, 14, 0, 88, 21710, 'Phase 5', 'Hunter', 'Beast Mastery', 'Back', 'Both', 'Cloak of the Fallen God'), +(3, 0, 15, 0, 88, 21673, 'Phase 5', 'Hunter', 'Beast Mastery', 'MainHand', 'Both', 'Silithid Claw'), +(3, 0, 16, 0, 88, 19859, 'Phase 5', 'Hunter', 'Beast Mastery', 'OffHand', 'Both', 'Fang of the Faceless'), +(3, 0, 17, 0, 88, 19361, 'Phase 5', 'Hunter', 'Beast Mastery', 'Ranged', 'Both', 'Ashjre''thul, Crossbow of Smiting'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 92, 22438, 'Phase 6', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Cryptstalker Headpiece'), +(3, 0, 1, 0, 92, 23053, 'Phase 6', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Stormrage''s Talisman of Seething'), +(3, 0, 2, 0, 92, 22439, 'Phase 6', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Cryptstalker Spaulders'), +(3, 0, 4, 0, 92, 22436, 'Phase 6', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Cryptstalker Tunic'), +(3, 0, 5, 0, 92, 22442, 'Phase 6', 'Hunter', 'Beast Mastery', 'Waist', 'Both', 'Cryptstalker Girdle'), +(3, 0, 6, 0, 92, 22437, 'Phase 6', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Cryptstalker Legguards'), +(3, 0, 7, 0, 92, 22440, 'Phase 6', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Cryptstalker Boots'), +(3, 0, 8, 0, 92, 22443, 'Phase 6', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Cryptstalker Wristguards'), +(3, 0, 9, 0, 92, 16571, 'Phase 6', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'General''s Chain Gloves'), +(3, 0, 10, 0, 92, 23067, 'Phase 6', 'Hunter', 'Beast Mastery', 'Finger1', 'Both', 'Ring of the Cryptstalker'), +(3, 0, 11, 0, 92, 22961, 'Phase 6', 'Hunter', 'Beast Mastery', 'Finger2', 'Both', 'Band of Reanimation'), +(3, 0, 12, 0, 92, 21670, 'Phase 6', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Badge of the Swarmguard'), +(3, 0, 13, 0, 92, 23041, 'Phase 6', 'Hunter', 'Beast Mastery', 'Trinket2', 'Both', 'Slayer''s Crest'), +(3, 0, 14, 0, 92, 23045, 'Phase 6', 'Hunter', 'Beast Mastery', 'Back', 'Both', 'Shroud of Dominion'), +(3, 0, 15, 0, 92, 22816, 'Phase 6', 'Hunter', 'Beast Mastery', 'MainHand', 'Both', 'Hatchet of Sundered Bone'), +(3, 0, 16, 0, 92, 22802, 'Phase 6', 'Hunter', 'Beast Mastery', 'OffHand', 'Both', 'Kingsfall'), +(3, 0, 17, 0, 92, 22812, 'Phase 6', 'Hunter', 'Beast Mastery', 'Ranged', 'Both', 'Nerubian Slavemaker'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 200, 37188, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Plunderer''s Helmet'), +(3, 0, 1, 0, 200, 40678, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Pendant of the Outcast Hero'), +(3, 0, 2, 0, 200, 37679, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Spaulders of the Abomination'), +(3, 0, 4, 0, 200, 39579, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Heroes'' Cryptstalker Tunic'), +(3, 0, 6, 0, 200, 37669, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Leggings of the Stone Halls'), +(3, 0, 7, 0, 200, 44297, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Boots of the Neverending Path'), +(3, 0, 8, 0, 200, 41224, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Deadly Gladiator''s Wristguards of Triumph'), +(3, 0, 9, 0, 200, 43734, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Heroes'' Cryptstalker Handguards'), +(3, 0, 10, 0, 200, 40586, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Finger1', 'Both', 'Band of the Kirin Tor'), +(3, 0, 12, 0, 200, 44253, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(3, 0, 14, 0, 200, 42068, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Back', 'Both', 'Deadly Gladiator''s Cloak of Victory'), +(3, 0, 15, 0, 200, 44249, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'MainHand', 'Both', 'Runeblade of Demonstrable Power'), +(3, 0, 17, 0, 200, 44504, 'Pre-Raid', 'Hunter', 'Beast Mastery', 'Ranged', 'Both', 'Nesingwary 4000'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 224, 40505, 'Phase 1', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Valorous Cryptstalker Headpiece'), +(3, 0, 1, 0, 224, 44664, 'Phase 1', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(3, 0, 2, 0, 224, 40507, 'Phase 1', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Valorous Cryptstalker Spaulders'), +(3, 0, 4, 0, 224, 43998, 'Phase 1', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Chestguard of Flagrant Prowess'), +(3, 0, 5, 0, 224, 39762, 'Phase 1', 'Hunter', 'Beast Mastery', 'Waist', 'Both', 'Torn Web Wrapping'), +(3, 0, 6, 0, 224, 40331, 'Phase 1', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Leggings of Failed Escape'), +(3, 0, 7, 0, 224, 40549, 'Phase 1', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Boots of the Renewed Flight'), +(3, 0, 8, 0, 224, 40282, 'Phase 1', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Slime Stream Bands'), +(3, 0, 9, 0, 224, 40541, 'Phase 1', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Frosted Adroit Handguards'), +(3, 0, 10, 0, 224, 40474, 'Phase 1', 'Hunter', 'Beast Mastery', 'Finger1', 'Both', 'Surge Needle Ring'), +(3, 0, 12, 0, 224, 40431, 'Phase 1', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Fury of the Five Flights'), +(3, 0, 14, 0, 224, 40403, 'Phase 1', 'Hunter', 'Beast Mastery', 'Back', 'Both', 'Drape of the Deadly Foe'), +(3, 0, 17, 0, 224, 40385, 'Phase 1', 'Hunter', 'Beast Mastery', 'Ranged', 'Both', 'Envoy of Mortality'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 245, 45610, 'Phase 2', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Boundless Gaze'), +(3, 0, 1, 0, 245, 45517, 'Phase 2', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Pendulum of Infinity'), +(3, 0, 2, 0, 245, 45300, 'Phase 2', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Mantle of Fiery Vengeance'), +(3, 0, 4, 0, 245, 45473, 'Phase 2', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Embrace of the Gladiator'), +(3, 0, 5, 0, 245, 45553, 'Phase 2', 'Hunter', 'Beast Mastery', 'Waist', 'Both', 'Belt of Dragons'), +(3, 0, 6, 0, 245, 45536, 'Phase 2', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Legguards of Cunning Deception'), +(3, 0, 7, 0, 245, 45989, 'Phase 2', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Tempered Mercury Greaves'), +(3, 0, 8, 0, 245, 45869, 'Phase 2', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Fluxing Energy Coils'), +(3, 0, 9, 0, 245, 45444, 'Phase 2', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Gloves of the Steady Hand'), +(3, 0, 12, 0, 245, 46038, 'Phase 2', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Dark Matter'), +(3, 0, 14, 0, 245, 46032, 'Phase 2', 'Hunter', 'Beast Mastery', 'Back', 'Both', 'Drape of the Faceless General'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 258, 48262, 'Phase 3', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Windrunner''s Headpiece of Triumph'), +(3, 0, 1, 1, 258, 47060, 'Phase 3', 'Hunter', 'Beast Mastery', 'Neck', 'Alliance', 'Charge of the Demon Lord'), +(3, 0, 1, 2, 258, 47433, 'Phase 3', 'Hunter', 'Beast Mastery', 'Neck', 'Horde', 'Charge of the Eredar'), +(3, 0, 2, 0, 258, 48260, 'Phase 3', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Windrunner''s Spaulders of Triumph'), +(3, 0, 4, 0, 258, 48264, 'Phase 3', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Windrunner''s Tunic of Triumph'), +(3, 0, 5, 1, 258, 47153, 'Phase 3', 'Hunter', 'Beast Mastery', 'Waist', 'Alliance', 'Belt of Deathly Dominion'), +(3, 0, 5, 2, 258, 47472, 'Phase 3', 'Hunter', 'Beast Mastery', 'Waist', 'Horde', 'Waistguard of Deathly Dominion'), +(3, 0, 6, 1, 258, 47191, 'Phase 3', 'Hunter', 'Beast Mastery', 'Legs', 'Alliance', 'Legguards of the Lurking Threat'), +(3, 0, 6, 2, 258, 47480, 'Phase 3', 'Hunter', 'Beast Mastery', 'Legs', 'Horde', 'Leggings of the Lurking Threat'), +(3, 0, 7, 1, 258, 47109, 'Phase 3', 'Hunter', 'Beast Mastery', 'Feet', 'Alliance', 'Sabatons of Ruthless Judgment'), +(3, 0, 7, 2, 258, 47457, 'Phase 3', 'Hunter', 'Beast Mastery', 'Feet', 'Horde', 'Greaves of Ruthless Judgment'), +(3, 0, 8, 1, 258, 47074, 'Phase 3', 'Hunter', 'Beast Mastery', 'Wrists', 'Alliance', 'Bracers of the Untold Massacre'), +(3, 0, 8, 2, 258, 47442, 'Phase 3', 'Hunter', 'Beast Mastery', 'Wrists', 'Horde', 'Bracers of the Silent Massacre'), +(3, 0, 9, 0, 258, 48263, 'Phase 3', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Windrunner''s Handguards of Triumph'), +(3, 0, 10, 1, 258, 47075, 'Phase 3', 'Hunter', 'Beast Mastery', 'Finger1', 'Alliance', 'Ring of Callous Aggression'), +(3, 0, 10, 2, 258, 47443, 'Phase 3', 'Hunter', 'Beast Mastery', 'Finger1', 'Horde', 'Band of Callous Aggression'), +(3, 0, 12, 2, 258, 45931, 'Phase 3', 'Hunter', 'Beast Mastery', 'Trinket1', 'Horde', 'Mjolnir Runestone'), +(3, 0, 14, 1, 258, 47545, 'Phase 3', 'Hunter', 'Beast Mastery', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(3, 0, 14, 2, 258, 47546, 'Phase 3', 'Hunter', 'Beast Mastery', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(3, 0, 17, 1, 258, 47521, 'Phase 3', 'Hunter', 'Beast Mastery', 'Ranged', 'Alliance', 'BRK 1000'), +(3, 0, 17, 2, 258, 47523, 'Phase 3', 'Hunter', 'Beast Mastery', 'Ranged', 'Horde', 'Fezzik''s Autocannon'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 0, 0, 0, 264, 51286, 'Phase 4', 'Hunter', 'Beast Mastery', 'Head', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Headpiece'), +(3, 0, 1, 0, 264, 50633, 'Phase 4', 'Hunter', 'Beast Mastery', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(3, 0, 2, 0, 264, 51288, 'Phase 4', 'Hunter', 'Beast Mastery', 'Shoulders', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Spaulders'), +(3, 0, 4, 0, 264, 51289, 'Phase 4', 'Hunter', 'Beast Mastery', 'Chest', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Tunic'), +(3, 0, 5, 0, 264, 50688, 'Phase 4', 'Hunter', 'Beast Mastery', 'Waist', 'Both', 'Nerub''ar Stalker''s Cord'), +(3, 0, 6, 0, 264, 50645, 'Phase 4', 'Hunter', 'Beast Mastery', 'Legs', 'Both', 'Leggings of Northern Lights'), +(3, 0, 7, 0, 264, 50607, 'Phase 4', 'Hunter', 'Beast Mastery', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(3, 0, 8, 0, 264, 50655, 'Phase 4', 'Hunter', 'Beast Mastery', 'Wrists', 'Both', 'Scourge Hunter''s Vambraces'), +(3, 0, 9, 0, 264, 51285, 'Phase 4', 'Hunter', 'Beast Mastery', 'Hands', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Handguards'), +(3, 0, 10, 0, 264, 50402, 'Phase 4', 'Hunter', 'Beast Mastery', 'Finger1', 'Both', 'Ashen Band of Endless Vengeance'), +(3, 0, 12, 0, 264, 50363, 'Phase 4', 'Hunter', 'Beast Mastery', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(3, 0, 14, 1, 264, 47545, 'Phase 4', 'Hunter', 'Beast Mastery', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(3, 0, 14, 2, 264, 47546, 'Phase 4', 'Hunter', 'Beast Mastery', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(3, 0, 17, 0, 264, 50733, 'Phase 4', 'Hunter', 'Beast Mastery', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- Marksmanship (tab 1) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 66, 13404, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Mask of the Unforgiven'), +(3, 1, 1, 0, 66, 15411, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Mark of Fordring'), +(3, 1, 2, 0, 66, 12927, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(3, 1, 4, 0, 66, 11726, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Savage Gladiator Chain'), +(3, 1, 5, 0, 66, 14502, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Frostbite Girdle'), +(3, 1, 6, 0, 66, 15062, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Devilsaur Leggings'), +(3, 1, 7, 0, 66, 13967, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Windreaver Greaves'), +(3, 1, 8, 0, 66, 13211, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Slashclaw Bracers'), +(3, 1, 9, 0, 66, 15063, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(3, 1, 10, 0, 66, 13098, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Painweaver Band'), +(3, 1, 11, 0, 66, 17713, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Finger2', 'Both', 'Blackstone Ring'), +(3, 1, 12, 0, 66, 13965, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 1, 13, 0, 66, 11815, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Trinket2', 'Both', 'Hand of Justice'), +(3, 1, 14, 0, 66, 13340, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Cape of the Black Baron'), +(3, 1, 15, 0, 66, 12940, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Dal''Rend''s Sacred Charge'), +(3, 1, 16, 0, 66, 12939, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'OffHand', 'Both', 'Dal''Rend''s Tribal Guardian'), +(3, 1, 17, 0, 66, 18738, 'Phase 1 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Ranged', 'Both', 'Carapace Spine Crossbow'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 76, 18421, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Backwood Helm'), +(3, 1, 1, 0, 76, 15411, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Mark of Fordring'), +(3, 1, 2, 0, 76, 13358, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Wyrmtongue Shoulders'), +(3, 1, 4, 0, 76, 11726, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Savage Gladiator Chain'), +(3, 1, 5, 0, 76, 18393, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Warpwood Binding'), +(3, 1, 6, 0, 76, 15062, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Devilsaur Leggings'), +(3, 1, 7, 0, 76, 13967, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Windreaver Greaves'), +(3, 1, 8, 0, 76, 13211, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Slashclaw Bracers'), +(3, 1, 9, 0, 76, 15063, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(3, 1, 10, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Tarnished Elven Ring'), +(3, 1, 11, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Finger2', 'Both', 'Tarnished Elven Ring'), +(3, 1, 12, 0, 76, 13965, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 1, 13, 0, 76, 18473, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Trinket2', 'Both', 'Royal Seal of Eldre''Thalas'), +(3, 1, 14, 0, 76, 13340, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Cape of the Black Baron'), +(3, 1, 15, 0, 76, 18520, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Barbarous Blade'), +(3, 1, 17, 0, 76, 18738, 'Phase 2 (Pre-Raid)', 'Hunter', 'Marksmanship', 'Ranged', 'Both', 'Carapace Spine Crossbow'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 78, 16846, 'Phase 2', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Giantstalker''s Helmet'), +(3, 1, 1, 0, 78, 18404, 'Phase 2', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(3, 1, 2, 0, 78, 16848, 'Phase 2', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Giantstalker''s Epaulets'), +(3, 1, 4, 0, 78, 16845, 'Phase 2', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Giantstalker''s Breastplate'), +(3, 1, 5, 0, 78, 16851, 'Phase 2', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Giantstalker''s Belt'), +(3, 1, 6, 0, 78, 16847, 'Phase 2', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Giantstalker''s Leggings'), +(3, 1, 7, 0, 78, 16849, 'Phase 2', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Giantstalker''s Boots'), +(3, 1, 8, 0, 78, 16850, 'Phase 2', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Giantstalker''s Bracers'), +(3, 1, 9, 0, 78, 16852, 'Phase 2', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Giantstalker''s Gloves'), +(3, 1, 10, 0, 78, 17063, 'Phase 2', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Band of Accuria'), +(3, 1, 11, 0, 78, 18821, 'Phase 2', 'Hunter', 'Marksmanship', 'Finger2', 'Both', 'Quick Strike Ring'), +(3, 1, 12, 0, 78, 13965, 'Phase 2', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 1, 13, 0, 78, 18473, 'Phase 2', 'Hunter', 'Marksmanship', 'Trinket2', 'Both', 'Royal Seal of Eldre''Thalas'), +(3, 1, 14, 0, 78, 17102, 'Phase 2', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Cloak of the Shrouded Mists'), +(3, 1, 15, 0, 78, 18832, 'Phase 2', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Brutality Blade'), +(3, 1, 16, 0, 78, 18805, 'Phase 2', 'Hunter', 'Marksmanship', 'OffHand', 'Both', 'Core Hound Tooth'), +(3, 1, 17, 0, 78, 18713, 'Phase 2', 'Hunter', 'Marksmanship', 'Ranged', 'Both', 'Rhok''delar, Longbow of the Ancient Keepers'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 83, 16939, 'Phase 3', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Dragonstalker''s Helm'), +(3, 1, 1, 0, 83, 19377, 'Phase 3', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(3, 1, 2, 0, 83, 16937, 'Phase 3', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Dragonstalker''s Spaulders'), +(3, 1, 4, 0, 83, 16942, 'Phase 3', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Dragonstalker''s Breastplate'), +(3, 1, 5, 0, 83, 16936, 'Phase 3', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Dragonstalker''s Belt'), +(3, 1, 6, 0, 83, 16938, 'Phase 3', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Dragonstalker''s Legguards'), +(3, 1, 7, 0, 83, 16941, 'Phase 3', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Dragonstalker''s Greaves'), +(3, 1, 8, 0, 83, 16935, 'Phase 3', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Dragonstalker''s Bracers'), +(3, 1, 9, 0, 83, 16940, 'Phase 3', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Dragonstalker''s Gauntlets'), +(3, 1, 10, 0, 83, 19325, 'Phase 3', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Don Julio''s Band'), +(3, 1, 11, 0, 83, 18821, 'Phase 3', 'Hunter', 'Marksmanship', 'Finger2', 'Both', 'Quick Strike Ring'), +(3, 1, 12, 0, 83, 13965, 'Phase 3', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 1, 13, 0, 83, 19406, 'Phase 3', 'Hunter', 'Marksmanship', 'Trinket2', 'Both', 'Drake Fang Talisman'), +(3, 1, 14, 0, 83, 17102, 'Phase 3', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Cloak of the Shrouded Mists'), +(3, 1, 15, 0, 83, 18832, 'Phase 3', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Brutality Blade'), +(3, 1, 16, 0, 83, 18805, 'Phase 3', 'Hunter', 'Marksmanship', 'OffHand', 'Both', 'Core Hound Tooth'), +(3, 1, 17, 0, 83, 19361, 'Phase 3', 'Hunter', 'Marksmanship', 'Ranged', 'Both', 'Ashjre''thul, Crossbow of Smiting'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 88, 16939, 'Phase 5', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Dragonstalker''s Helm'), +(3, 1, 1, 0, 88, 19377, 'Phase 5', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(3, 1, 2, 0, 88, 16937, 'Phase 5', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Dragonstalker''s Spaulders'), +(3, 1, 4, 0, 88, 16942, 'Phase 5', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Dragonstalker''s Breastplate'), +(3, 1, 5, 0, 88, 16936, 'Phase 5', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Dragonstalker''s Belt'), +(3, 1, 6, 0, 88, 16938, 'Phase 5', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Dragonstalker''s Legguards'), +(3, 1, 7, 0, 88, 16941, 'Phase 5', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Dragonstalker''s Greaves'), +(3, 1, 8, 0, 88, 16935, 'Phase 5', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Dragonstalker''s Bracers'), +(3, 1, 9, 0, 88, 16940, 'Phase 5', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Dragonstalker''s Gauntlets'), +(3, 1, 10, 0, 88, 19325, 'Phase 5', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Don Julio''s Band'), +(3, 1, 11, 0, 88, 17063, 'Phase 5', 'Hunter', 'Marksmanship', 'Finger2', 'Both', 'Band of Accuria'), +(3, 1, 12, 0, 88, 21670, 'Phase 5', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Badge of the Swarmguard'), +(3, 1, 13, 0, 88, 23570, 'Phase 5', 'Hunter', 'Marksmanship', 'Trinket2', 'Both', 'Jom Gabbar'), +(3, 1, 14, 0, 88, 21710, 'Phase 5', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Cloak of the Fallen God'), +(3, 1, 15, 0, 88, 21673, 'Phase 5', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Silithid Claw'), +(3, 1, 16, 0, 88, 19859, 'Phase 5', 'Hunter', 'Marksmanship', 'OffHand', 'Both', 'Fang of the Faceless'), +(3, 1, 17, 0, 88, 19361, 'Phase 5', 'Hunter', 'Marksmanship', 'Ranged', 'Both', 'Ashjre''thul, Crossbow of Smiting'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 92, 22438, 'Phase 6', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Cryptstalker Headpiece'), +(3, 1, 1, 0, 92, 23053, 'Phase 6', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Stormrage''s Talisman of Seething'), +(3, 1, 2, 0, 92, 22439, 'Phase 6', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Cryptstalker Spaulders'), +(3, 1, 4, 0, 92, 22436, 'Phase 6', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Cryptstalker Tunic'), +(3, 1, 5, 0, 92, 22442, 'Phase 6', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Cryptstalker Girdle'), +(3, 1, 6, 0, 92, 22437, 'Phase 6', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Cryptstalker Legguards'), +(3, 1, 7, 0, 92, 22440, 'Phase 6', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Cryptstalker Boots'), +(3, 1, 8, 0, 92, 22443, 'Phase 6', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Cryptstalker Wristguards'), +(3, 1, 9, 0, 92, 16571, 'Phase 6', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'General''s Chain Gloves'), +(3, 1, 10, 0, 92, 23067, 'Phase 6', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Ring of the Cryptstalker'), +(3, 1, 11, 0, 92, 22961, 'Phase 6', 'Hunter', 'Marksmanship', 'Finger2', 'Both', 'Band of Reanimation'), +(3, 1, 12, 0, 92, 21670, 'Phase 6', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Badge of the Swarmguard'), +(3, 1, 13, 0, 92, 23041, 'Phase 6', 'Hunter', 'Marksmanship', 'Trinket2', 'Both', 'Slayer''s Crest'), +(3, 1, 14, 0, 92, 23045, 'Phase 6', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Shroud of Dominion'), +(3, 1, 15, 0, 92, 22816, 'Phase 6', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Hatchet of Sundered Bone'), +(3, 1, 16, 0, 92, 22802, 'Phase 6', 'Hunter', 'Marksmanship', 'OffHand', 'Both', 'Kingsfall'), +(3, 1, 17, 0, 92, 22812, 'Phase 6', 'Hunter', 'Marksmanship', 'Ranged', 'Both', 'Nerubian Slavemaker'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 200, 37188, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Plunderer''s Helmet'), +(3, 1, 1, 0, 200, 42645, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Titanium Impact Choker'), +(3, 1, 2, 0, 200, 37679, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Spaulders of the Abomination'), +(3, 1, 4, 0, 200, 37144, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Hauberk of the Arcane Wraith'), +(3, 1, 5, 0, 200, 37407, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Sovereign''s Belt'), +(3, 1, 6, 0, 200, 37669, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Leggings of the Stone Halls'), +(3, 1, 7, 0, 200, 37167, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Dragon Slayer''s Sabatons'), +(3, 1, 8, 0, 200, 37170, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Interwoven Scale Bracers'), +(3, 1, 9, 0, 200, 37886, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Handgrips of the Savage Emissary'), +(3, 1, 10, 0, 200, 42642, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Titanium Impact Band'), +(3, 1, 12, 0, 200, 40684, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Mirror of Truth'), +(3, 1, 14, 0, 200, 43566, 'Pre-Raid', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Ice Striker''s Cloak'), +(3, 1, 15, 0, 200, 44249, 'Pre-Raid', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Runeblade of Demonstrable Power'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 224, 40543, 'Phase 1', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Blue Aspect Helm'), +(3, 1, 1, 0, 224, 44664, 'Phase 1', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(3, 1, 2, 0, 224, 40507, 'Phase 1', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Valorous Cryptstalker Spaulders'), +(3, 1, 4, 0, 224, 40193, 'Phase 1', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Tunic of Masked Suffering'), +(3, 1, 5, 0, 224, 40275, 'Phase 1', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Depraved Linked Belt'), +(3, 1, 6, 0, 224, 40506, 'Phase 1', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Valorous Cryptstalker Legguards'), +(3, 1, 7, 0, 224, 40549, 'Phase 1', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Boots of the Renewed Flight'), +(3, 1, 8, 0, 224, 40282, 'Phase 1', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Slime Stream Bands'), +(3, 1, 9, 0, 224, 40541, 'Phase 1', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Frosted Adroit Handguards'), +(3, 1, 10, 0, 224, 40074, 'Phase 1', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Strong-Handed Ring'), +(3, 1, 12, 0, 224, 40684, 'Phase 1', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Mirror of Truth'), +(3, 1, 14, 0, 224, 40403, 'Phase 1', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Drape of the Deadly Foe'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 245, 46143, 'Phase 2', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Conqueror''s Scourgestalker Headpiece'), +(3, 1, 1, 0, 245, 45517, 'Phase 2', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Pendulum of Infinity'), +(3, 1, 2, 0, 245, 46145, 'Phase 2', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Conqueror''s Scourgestalker Spaulders'), +(3, 1, 4, 0, 245, 46141, 'Phase 2', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Conqueror''s Scourgestalker Tunic'), +(3, 1, 5, 0, 245, 45467, 'Phase 2', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Belt of the Betrayed'), +(3, 1, 6, 0, 245, 46144, 'Phase 2', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Conqueror''s Scourgestalker Legguards'), +(3, 1, 7, 0, 245, 45244, 'Phase 2', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Greaves of Swift Vengeance'), +(3, 1, 8, 0, 245, 45454, 'Phase 2', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Frost-bound Chain Bracers'), +(3, 1, 9, 0, 245, 45444, 'Phase 2', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Gloves of the Steady Hand'), +(3, 1, 10, 0, 245, 45456, 'Phase 2', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Loop of the Agile'), +(3, 1, 12, 0, 245, 45931, 'Phase 2', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Mjolnir Runestone'), +(3, 1, 14, 0, 245, 46032, 'Phase 2', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Drape of the Faceless General'), +(3, 1, 15, 0, 245, 45498, 'Phase 2', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Lotrafen, Spear of the Damned'), +(3, 1, 17, 0, 245, 45570, 'Phase 2', 'Hunter', 'Marksmanship', 'Ranged', 'Both', 'Skyforge Crossbow'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 258, 48262, 'Phase 3', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Windrunner''s Headpiece of Triumph'), +(3, 1, 1, 1, 258, 47060, 'Phase 3', 'Hunter', 'Marksmanship', 'Neck', 'Alliance', 'Charge of the Demon Lord'), +(3, 1, 1, 2, 258, 47433, 'Phase 3', 'Hunter', 'Marksmanship', 'Neck', 'Horde', 'Charge of the Eredar'), +(3, 1, 2, 0, 258, 48260, 'Phase 3', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Windrunner''s Spaulders of Triumph'), +(3, 1, 4, 0, 258, 48264, 'Phase 3', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Windrunner''s Tunic of Triumph'), +(3, 1, 5, 1, 258, 47153, 'Phase 3', 'Hunter', 'Marksmanship', 'Waist', 'Alliance', 'Belt of Deathly Dominion'), +(3, 1, 5, 2, 258, 47472, 'Phase 3', 'Hunter', 'Marksmanship', 'Waist', 'Horde', 'Waistguard of Deathly Dominion'), +(3, 1, 6, 1, 258, 47191, 'Phase 3', 'Hunter', 'Marksmanship', 'Legs', 'Alliance', 'Legguards of the Lurking Threat'), +(3, 1, 6, 2, 258, 47480, 'Phase 3', 'Hunter', 'Marksmanship', 'Legs', 'Horde', 'Leggings of the Lurking Threat'), +(3, 1, 7, 1, 258, 47109, 'Phase 3', 'Hunter', 'Marksmanship', 'Feet', 'Alliance', 'Sabatons of Ruthless Judgment'), +(3, 1, 7, 2, 258, 47457, 'Phase 3', 'Hunter', 'Marksmanship', 'Feet', 'Horde', 'Greaves of Ruthless Judgment'), +(3, 1, 8, 1, 258, 47074, 'Phase 3', 'Hunter', 'Marksmanship', 'Wrists', 'Alliance', 'Bracers of the Untold Massacre'), +(3, 1, 8, 2, 258, 47442, 'Phase 3', 'Hunter', 'Marksmanship', 'Wrists', 'Horde', 'Bracers of the Silent Massacre'), +(3, 1, 9, 0, 258, 48263, 'Phase 3', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Windrunner''s Handguards of Triumph'), +(3, 1, 10, 1, 258, 47075, 'Phase 3', 'Hunter', 'Marksmanship', 'Finger1', 'Alliance', 'Ring of Callous Aggression'), +(3, 1, 10, 2, 258, 47443, 'Phase 3', 'Hunter', 'Marksmanship', 'Finger1', 'Horde', 'Band of Callous Aggression'), +(3, 1, 12, 2, 258, 45931, 'Phase 3', 'Hunter', 'Marksmanship', 'Trinket1', 'Horde', 'Mjolnir Runestone'), +(3, 1, 14, 1, 258, 47545, 'Phase 3', 'Hunter', 'Marksmanship', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(3, 1, 14, 2, 258, 47546, 'Phase 3', 'Hunter', 'Marksmanship', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(3, 1, 17, 1, 258, 47521, 'Phase 3', 'Hunter', 'Marksmanship', 'Ranged', 'Alliance', 'BRK 1000'), +(3, 1, 17, 2, 258, 47523, 'Phase 3', 'Hunter', 'Marksmanship', 'Ranged', 'Horde', 'Fezzik''s Autocannon'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 264, 51286, 'Phase 4', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Headpiece'), +(3, 1, 1, 0, 264, 50633, 'Phase 4', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(3, 1, 2, 0, 264, 51288, 'Phase 4', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Spaulders'), +(3, 1, 4, 0, 264, 51289, 'Phase 4', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Tunic'), +(3, 1, 5, 0, 264, 50688, 'Phase 4', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Nerub''ar Stalker''s Cord'), +(3, 1, 6, 0, 264, 50645, 'Phase 4', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Leggings of Northern Lights'), +(3, 1, 7, 0, 264, 50607, 'Phase 4', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(3, 1, 8, 0, 264, 50655, 'Phase 4', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Scourge Hunter''s Vambraces'), +(3, 1, 9, 0, 264, 51285, 'Phase 4', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Handguards'), +(3, 1, 10, 0, 264, 50402, 'Phase 4', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Ashen Band of Endless Vengeance'), +(3, 1, 12, 0, 264, 50363, 'Phase 4', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(3, 1, 14, 1, 264, 47545, 'Phase 4', 'Hunter', 'Marksmanship', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(3, 1, 14, 2, 264, 47546, 'Phase 4', 'Hunter', 'Marksmanship', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(3, 1, 15, 0, 264, 50735, 'Phase 4', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Oathbinder, Charge of the Ranger-General'), +(3, 1, 17, 0, 264, 50733, 'Phase 4', 'Hunter', 'Marksmanship', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 1, 0, 0, 290, 51286, 'Phase 5', 'Hunter', 'Marksmanship', 'Head', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Headpiece'), +(3, 1, 1, 0, 290, 50633, 'Phase 5', 'Hunter', 'Marksmanship', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(3, 1, 2, 0, 290, 51288, 'Phase 5', 'Hunter', 'Marksmanship', 'Shoulders', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Spaulders'), +(3, 1, 4, 0, 290, 51289, 'Phase 5', 'Hunter', 'Marksmanship', 'Chest', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Tunic'), +(3, 1, 5, 0, 290, 50688, 'Phase 5', 'Hunter', 'Marksmanship', 'Waist', 'Both', 'Nerub''ar Stalker''s Cord'), +(3, 1, 6, 0, 290, 50645, 'Phase 5', 'Hunter', 'Marksmanship', 'Legs', 'Both', 'Leggings of Northern Lights'), +(3, 1, 7, 0, 290, 54577, 'Phase 5', 'Hunter', 'Marksmanship', 'Feet', 'Both', 'Returning Footfalls'), +(3, 1, 8, 0, 290, 50655, 'Phase 5', 'Hunter', 'Marksmanship', 'Wrists', 'Both', 'Scourge Hunter''s Vambraces'), +(3, 1, 9, 0, 290, 51285, 'Phase 5', 'Hunter', 'Marksmanship', 'Hands', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Handguards'), +(3, 1, 10, 0, 290, 54576, 'Phase 5', 'Hunter', 'Marksmanship', 'Finger1', 'Both', 'Signet of Twilight'), +(3, 1, 11, 0, 290, 50402, 'Phase 5', 'Hunter', 'Marksmanship', 'Finger2', 'Both', 'Ashen Band of Endless Vengeance'), +(3, 1, 12, 0, 290, 50363, 'Phase 5', 'Hunter', 'Marksmanship', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(3, 1, 13, 0, 290, 54590, 'Phase 5', 'Hunter', 'Marksmanship', 'Trinket2', 'Both', 'Sharpened Twilight Scale'), +(3, 1, 14, 0, 290, 47545, 'Phase 5', 'Hunter', 'Marksmanship', 'Back', 'Both', 'Vereesa''s Dexterity'), +(3, 1, 15, 0, 290, 50735, 'Phase 5', 'Hunter', 'Marksmanship', 'MainHand', 'Both', 'Oathbinder, Charge of the Ranger-General'), +(3, 1, 17, 0, 290, 50733, 'Phase 5', 'Hunter', 'Marksmanship', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- Survival (tab 2) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 66, 13404, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Head', 'Both', 'Mask of the Unforgiven'), +(3, 2, 1, 0, 66, 15411, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Neck', 'Both', 'Mark of Fordring'), +(3, 2, 2, 0, 66, 12927, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(3, 2, 4, 0, 66, 11726, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Chest', 'Both', 'Savage Gladiator Chain'), +(3, 2, 5, 0, 66, 14502, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Waist', 'Both', 'Frostbite Girdle'), +(3, 2, 6, 0, 66, 15062, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Legs', 'Both', 'Devilsaur Leggings'), +(3, 2, 7, 0, 66, 13967, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Feet', 'Both', 'Windreaver Greaves'), +(3, 2, 8, 0, 66, 13211, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Wrists', 'Both', 'Slashclaw Bracers'), +(3, 2, 9, 0, 66, 15063, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(3, 2, 10, 0, 66, 13098, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Finger1', 'Both', 'Painweaver Band'), +(3, 2, 11, 0, 66, 17713, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Finger2', 'Both', 'Blackstone Ring'), +(3, 2, 12, 0, 66, 13965, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 2, 13, 0, 66, 11815, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Trinket2', 'Both', 'Hand of Justice'), +(3, 2, 14, 0, 66, 13340, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Back', 'Both', 'Cape of the Black Baron'), +(3, 2, 15, 0, 66, 12940, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'MainHand', 'Both', 'Dal''Rend''s Sacred Charge'), +(3, 2, 16, 0, 66, 12939, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'OffHand', 'Both', 'Dal''Rend''s Tribal Guardian'), +(3, 2, 17, 0, 66, 18738, 'Phase 1 (Pre-Raid)', 'Hunter', 'Survival', 'Ranged', 'Both', 'Carapace Spine Crossbow'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 76, 18421, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Head', 'Both', 'Backwood Helm'), +(3, 2, 1, 0, 76, 15411, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Neck', 'Both', 'Mark of Fordring'), +(3, 2, 2, 0, 76, 13358, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Wyrmtongue Shoulders'), +(3, 2, 4, 0, 76, 11726, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Chest', 'Both', 'Savage Gladiator Chain'), +(3, 2, 5, 0, 76, 18393, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Waist', 'Both', 'Warpwood Binding'), +(3, 2, 6, 0, 76, 15062, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Legs', 'Both', 'Devilsaur Leggings'), +(3, 2, 7, 0, 76, 13967, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Feet', 'Both', 'Windreaver Greaves'), +(3, 2, 8, 0, 76, 13211, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Wrists', 'Both', 'Slashclaw Bracers'), +(3, 2, 9, 0, 76, 15063, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(3, 2, 10, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Finger1', 'Both', 'Tarnished Elven Ring'), +(3, 2, 11, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Finger2', 'Both', 'Tarnished Elven Ring'), +(3, 2, 12, 0, 76, 13965, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 2, 13, 0, 76, 18473, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Trinket2', 'Both', 'Royal Seal of Eldre''Thalas'), +(3, 2, 14, 0, 76, 13340, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Back', 'Both', 'Cape of the Black Baron'), +(3, 2, 15, 0, 76, 18520, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'MainHand', 'Both', 'Barbarous Blade'), +(3, 2, 17, 0, 76, 18738, 'Phase 2 (Pre-Raid)', 'Hunter', 'Survival', 'Ranged', 'Both', 'Carapace Spine Crossbow'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 78, 16846, 'Phase 2', 'Hunter', 'Survival', 'Head', 'Both', 'Giantstalker''s Helmet'), +(3, 2, 1, 0, 78, 18404, 'Phase 2', 'Hunter', 'Survival', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(3, 2, 2, 0, 78, 16848, 'Phase 2', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Giantstalker''s Epaulets'), +(3, 2, 4, 0, 78, 16845, 'Phase 2', 'Hunter', 'Survival', 'Chest', 'Both', 'Giantstalker''s Breastplate'), +(3, 2, 5, 0, 78, 16851, 'Phase 2', 'Hunter', 'Survival', 'Waist', 'Both', 'Giantstalker''s Belt'), +(3, 2, 6, 0, 78, 16847, 'Phase 2', 'Hunter', 'Survival', 'Legs', 'Both', 'Giantstalker''s Leggings'), +(3, 2, 7, 0, 78, 16849, 'Phase 2', 'Hunter', 'Survival', 'Feet', 'Both', 'Giantstalker''s Boots'), +(3, 2, 8, 0, 78, 16850, 'Phase 2', 'Hunter', 'Survival', 'Wrists', 'Both', 'Giantstalker''s Bracers'), +(3, 2, 9, 0, 78, 16852, 'Phase 2', 'Hunter', 'Survival', 'Hands', 'Both', 'Giantstalker''s Gloves'), +(3, 2, 10, 0, 78, 17063, 'Phase 2', 'Hunter', 'Survival', 'Finger1', 'Both', 'Band of Accuria'), +(3, 2, 11, 0, 78, 18821, 'Phase 2', 'Hunter', 'Survival', 'Finger2', 'Both', 'Quick Strike Ring'), +(3, 2, 12, 0, 78, 13965, 'Phase 2', 'Hunter', 'Survival', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 2, 13, 0, 78, 18473, 'Phase 2', 'Hunter', 'Survival', 'Trinket2', 'Both', 'Royal Seal of Eldre''Thalas'), +(3, 2, 14, 0, 78, 17102, 'Phase 2', 'Hunter', 'Survival', 'Back', 'Both', 'Cloak of the Shrouded Mists'), +(3, 2, 15, 0, 78, 18832, 'Phase 2', 'Hunter', 'Survival', 'MainHand', 'Both', 'Brutality Blade'), +(3, 2, 16, 0, 78, 18805, 'Phase 2', 'Hunter', 'Survival', 'OffHand', 'Both', 'Core Hound Tooth'), +(3, 2, 17, 0, 78, 18713, 'Phase 2', 'Hunter', 'Survival', 'Ranged', 'Both', 'Rhok''delar, Longbow of the Ancient Keepers'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 83, 16939, 'Phase 3', 'Hunter', 'Survival', 'Head', 'Both', 'Dragonstalker''s Helm'), +(3, 2, 1, 0, 83, 19377, 'Phase 3', 'Hunter', 'Survival', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(3, 2, 2, 0, 83, 16937, 'Phase 3', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Dragonstalker''s Spaulders'), +(3, 2, 4, 0, 83, 16942, 'Phase 3', 'Hunter', 'Survival', 'Chest', 'Both', 'Dragonstalker''s Breastplate'), +(3, 2, 5, 0, 83, 16936, 'Phase 3', 'Hunter', 'Survival', 'Waist', 'Both', 'Dragonstalker''s Belt'), +(3, 2, 6, 0, 83, 16938, 'Phase 3', 'Hunter', 'Survival', 'Legs', 'Both', 'Dragonstalker''s Legguards'), +(3, 2, 7, 0, 83, 16941, 'Phase 3', 'Hunter', 'Survival', 'Feet', 'Both', 'Dragonstalker''s Greaves'), +(3, 2, 8, 0, 83, 16935, 'Phase 3', 'Hunter', 'Survival', 'Wrists', 'Both', 'Dragonstalker''s Bracers'), +(3, 2, 9, 0, 83, 16940, 'Phase 3', 'Hunter', 'Survival', 'Hands', 'Both', 'Dragonstalker''s Gauntlets'), +(3, 2, 10, 0, 83, 19325, 'Phase 3', 'Hunter', 'Survival', 'Finger1', 'Both', 'Don Julio''s Band'), +(3, 2, 11, 0, 83, 18821, 'Phase 3', 'Hunter', 'Survival', 'Finger2', 'Both', 'Quick Strike Ring'), +(3, 2, 12, 0, 83, 13965, 'Phase 3', 'Hunter', 'Survival', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(3, 2, 13, 0, 83, 19406, 'Phase 3', 'Hunter', 'Survival', 'Trinket2', 'Both', 'Drake Fang Talisman'), +(3, 2, 14, 0, 83, 17102, 'Phase 3', 'Hunter', 'Survival', 'Back', 'Both', 'Cloak of the Shrouded Mists'), +(3, 2, 15, 0, 83, 18832, 'Phase 3', 'Hunter', 'Survival', 'MainHand', 'Both', 'Brutality Blade'), +(3, 2, 16, 0, 83, 18805, 'Phase 3', 'Hunter', 'Survival', 'OffHand', 'Both', 'Core Hound Tooth'), +(3, 2, 17, 0, 83, 19361, 'Phase 3', 'Hunter', 'Survival', 'Ranged', 'Both', 'Ashjre''thul, Crossbow of Smiting'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 88, 16939, 'Phase 5', 'Hunter', 'Survival', 'Head', 'Both', 'Dragonstalker''s Helm'), +(3, 2, 1, 0, 88, 19377, 'Phase 5', 'Hunter', 'Survival', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(3, 2, 2, 0, 88, 16937, 'Phase 5', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Dragonstalker''s Spaulders'), +(3, 2, 4, 0, 88, 16942, 'Phase 5', 'Hunter', 'Survival', 'Chest', 'Both', 'Dragonstalker''s Breastplate'), +(3, 2, 5, 0, 88, 16936, 'Phase 5', 'Hunter', 'Survival', 'Waist', 'Both', 'Dragonstalker''s Belt'), +(3, 2, 6, 0, 88, 16938, 'Phase 5', 'Hunter', 'Survival', 'Legs', 'Both', 'Dragonstalker''s Legguards'), +(3, 2, 7, 0, 88, 16941, 'Phase 5', 'Hunter', 'Survival', 'Feet', 'Both', 'Dragonstalker''s Greaves'), +(3, 2, 8, 0, 88, 16935, 'Phase 5', 'Hunter', 'Survival', 'Wrists', 'Both', 'Dragonstalker''s Bracers'), +(3, 2, 9, 0, 88, 16940, 'Phase 5', 'Hunter', 'Survival', 'Hands', 'Both', 'Dragonstalker''s Gauntlets'), +(3, 2, 10, 0, 88, 19325, 'Phase 5', 'Hunter', 'Survival', 'Finger1', 'Both', 'Don Julio''s Band'), +(3, 2, 11, 0, 88, 17063, 'Phase 5', 'Hunter', 'Survival', 'Finger2', 'Both', 'Band of Accuria'), +(3, 2, 12, 0, 88, 21670, 'Phase 5', 'Hunter', 'Survival', 'Trinket1', 'Both', 'Badge of the Swarmguard'), +(3, 2, 13, 0, 88, 23570, 'Phase 5', 'Hunter', 'Survival', 'Trinket2', 'Both', 'Jom Gabbar'), +(3, 2, 14, 0, 88, 21710, 'Phase 5', 'Hunter', 'Survival', 'Back', 'Both', 'Cloak of the Fallen God'), +(3, 2, 15, 0, 88, 21673, 'Phase 5', 'Hunter', 'Survival', 'MainHand', 'Both', 'Silithid Claw'), +(3, 2, 16, 0, 88, 19859, 'Phase 5', 'Hunter', 'Survival', 'OffHand', 'Both', 'Fang of the Faceless'), +(3, 2, 17, 0, 88, 19361, 'Phase 5', 'Hunter', 'Survival', 'Ranged', 'Both', 'Ashjre''thul, Crossbow of Smiting'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 92, 22438, 'Phase 6', 'Hunter', 'Survival', 'Head', 'Both', 'Cryptstalker Headpiece'), +(3, 2, 1, 0, 92, 23053, 'Phase 6', 'Hunter', 'Survival', 'Neck', 'Both', 'Stormrage''s Talisman of Seething'), +(3, 2, 2, 0, 92, 22439, 'Phase 6', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Cryptstalker Spaulders'), +(3, 2, 4, 0, 92, 22436, 'Phase 6', 'Hunter', 'Survival', 'Chest', 'Both', 'Cryptstalker Tunic'), +(3, 2, 5, 0, 92, 22442, 'Phase 6', 'Hunter', 'Survival', 'Waist', 'Both', 'Cryptstalker Girdle'), +(3, 2, 6, 0, 92, 22437, 'Phase 6', 'Hunter', 'Survival', 'Legs', 'Both', 'Cryptstalker Legguards'), +(3, 2, 7, 0, 92, 22440, 'Phase 6', 'Hunter', 'Survival', 'Feet', 'Both', 'Cryptstalker Boots'), +(3, 2, 8, 0, 92, 22443, 'Phase 6', 'Hunter', 'Survival', 'Wrists', 'Both', 'Cryptstalker Wristguards'), +(3, 2, 9, 0, 92, 16571, 'Phase 6', 'Hunter', 'Survival', 'Hands', 'Both', 'General''s Chain Gloves'), +(3, 2, 10, 0, 92, 23067, 'Phase 6', 'Hunter', 'Survival', 'Finger1', 'Both', 'Ring of the Cryptstalker'), +(3, 2, 11, 0, 92, 22961, 'Phase 6', 'Hunter', 'Survival', 'Finger2', 'Both', 'Band of Reanimation'), +(3, 2, 12, 0, 92, 21670, 'Phase 6', 'Hunter', 'Survival', 'Trinket1', 'Both', 'Badge of the Swarmguard'), +(3, 2, 13, 0, 92, 23041, 'Phase 6', 'Hunter', 'Survival', 'Trinket2', 'Both', 'Slayer''s Crest'), +(3, 2, 14, 0, 92, 23045, 'Phase 6', 'Hunter', 'Survival', 'Back', 'Both', 'Shroud of Dominion'), +(3, 2, 15, 0, 92, 22816, 'Phase 6', 'Hunter', 'Survival', 'MainHand', 'Both', 'Hatchet of Sundered Bone'), +(3, 2, 16, 0, 92, 22802, 'Phase 6', 'Hunter', 'Survival', 'OffHand', 'Both', 'Kingsfall'), +(3, 2, 17, 0, 92, 22812, 'Phase 6', 'Hunter', 'Survival', 'Ranged', 'Both', 'Nerubian Slavemaker'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 200, 42551, 'Pre-Raid', 'Hunter', 'Survival', 'Head', 'Both', 'Truesight Ice Blinders'), +(3, 2, 1, 0, 200, 40678, 'Pre-Raid', 'Hunter', 'Survival', 'Neck', 'Both', 'Pendant of the Outcast Hero'), +(3, 2, 2, 0, 200, 37679, 'Pre-Raid', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Spaulders of the Abomination'), +(3, 2, 4, 0, 200, 44295, 'Pre-Raid', 'Hunter', 'Survival', 'Chest', 'Both', 'Polished Regimental Hauberk'), +(3, 2, 5, 0, 200, 40692, 'Pre-Raid', 'Hunter', 'Survival', 'Waist', 'Both', 'Vereesa''s Silver Chain Belt'), +(3, 2, 6, 0, 200, 37669, 'Pre-Raid', 'Hunter', 'Survival', 'Legs', 'Both', 'Leggings of the Stone Halls'), +(3, 2, 7, 0, 200, 44297, 'Pre-Raid', 'Hunter', 'Survival', 'Feet', 'Both', 'Boots of the Neverending Path'), +(3, 2, 8, 0, 200, 37170, 'Pre-Raid', 'Hunter', 'Survival', 'Wrists', 'Both', 'Interwoven Scale Bracers'), +(3, 2, 9, 0, 200, 37886, 'Pre-Raid', 'Hunter', 'Survival', 'Hands', 'Both', 'Handgrips of the Savage Emissary'), +(3, 2, 10, 0, 200, 37685, 'Pre-Raid', 'Hunter', 'Survival', 'Finger1', 'Both', 'Mobius Band'), +(3, 2, 12, 0, 200, 40684, 'Pre-Raid', 'Hunter', 'Survival', 'Trinket1', 'Both', 'Mirror of Truth'), +(3, 2, 14, 0, 200, 43406, 'Pre-Raid', 'Hunter', 'Survival', 'Back', 'Both', 'Cloak of the Gushing Wound'), +(3, 2, 15, 0, 200, 37883, 'Pre-Raid', 'Hunter', 'Survival', 'MainHand', 'Both', 'Staff of Trickery'), +(3, 2, 17, 0, 200, 37191, 'Pre-Raid', 'Hunter', 'Survival', 'Ranged', 'Both', 'Drake-Mounted Crossbow'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 224, 40505, 'Phase 1', 'Hunter', 'Survival', 'Head', 'Both', 'Valorous Cryptstalker Headpiece'), +(3, 2, 1, 0, 224, 44664, 'Phase 1', 'Hunter', 'Survival', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(3, 2, 2, 0, 224, 40507, 'Phase 1', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Valorous Cryptstalker Spaulders'), +(3, 2, 4, 0, 224, 43998, 'Phase 1', 'Hunter', 'Survival', 'Chest', 'Both', 'Chestguard of Flagrant Prowess'), +(3, 2, 5, 0, 224, 39762, 'Phase 1', 'Hunter', 'Survival', 'Waist', 'Both', 'Torn Web Wrapping'), +(3, 2, 6, 0, 224, 40331, 'Phase 1', 'Hunter', 'Survival', 'Legs', 'Both', 'Leggings of Failed Escape'), +(3, 2, 7, 0, 224, 40549, 'Phase 1', 'Hunter', 'Survival', 'Feet', 'Both', 'Boots of the Renewed Flight'), +(3, 2, 8, 0, 224, 40282, 'Phase 1', 'Hunter', 'Survival', 'Wrists', 'Both', 'Slime Stream Bands'), +(3, 2, 9, 0, 224, 40541, 'Phase 1', 'Hunter', 'Survival', 'Hands', 'Both', 'Frosted Adroit Handguards'), +(3, 2, 10, 0, 224, 40074, 'Phase 1', 'Hunter', 'Survival', 'Finger1', 'Both', 'Strong-Handed Ring'), +(3, 2, 12, 0, 224, 40431, 'Phase 1', 'Hunter', 'Survival', 'Trinket1', 'Both', 'Fury of the Five Flights'), +(3, 2, 14, 0, 224, 40403, 'Phase 1', 'Hunter', 'Survival', 'Back', 'Both', 'Drape of the Deadly Foe'), +(3, 2, 15, 0, 224, 40388, 'Phase 1', 'Hunter', 'Survival', 'MainHand', 'Both', 'Journey''s End'), +(3, 2, 17, 0, 224, 40385, 'Phase 1', 'Hunter', 'Survival', 'Ranged', 'Both', 'Envoy of Mortality'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 245, 45610, 'Phase 2', 'Hunter', 'Survival', 'Head', 'Both', 'Boundless Gaze'), +(3, 2, 1, 0, 245, 45517, 'Phase 2', 'Hunter', 'Survival', 'Neck', 'Both', 'Pendulum of Infinity'), +(3, 2, 2, 0, 245, 45300, 'Phase 2', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Mantle of Fiery Vengeance'), +(3, 2, 4, 0, 245, 45473, 'Phase 2', 'Hunter', 'Survival', 'Chest', 'Both', 'Embrace of the Gladiator'), +(3, 2, 5, 0, 245, 46095, 'Phase 2', 'Hunter', 'Survival', 'Waist', 'Both', 'Soul-Devouring Cinch'), +(3, 2, 6, 0, 245, 45536, 'Phase 2', 'Hunter', 'Survival', 'Legs', 'Both', 'Legguards of Cunning Deception'), +(3, 2, 7, 0, 245, 45244, 'Phase 2', 'Hunter', 'Survival', 'Feet', 'Both', 'Greaves of Swift Vengeance'), +(3, 2, 8, 0, 245, 45869, 'Phase 2', 'Hunter', 'Survival', 'Wrists', 'Both', 'Fluxing Energy Coils'), +(3, 2, 9, 0, 245, 45444, 'Phase 2', 'Hunter', 'Survival', 'Hands', 'Both', 'Gloves of the Steady Hand'), +(3, 2, 12, 0, 245, 44253, 'Phase 2', 'Hunter', 'Survival', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(3, 2, 14, 0, 245, 46032, 'Phase 2', 'Hunter', 'Survival', 'Back', 'Both', 'Drape of the Faceless General'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 258, 48262, 'Phase 3', 'Hunter', 'Survival', 'Head', 'Both', 'Windrunner''s Headpiece of Triumph'), +(3, 2, 1, 1, 258, 47060, 'Phase 3', 'Hunter', 'Survival', 'Neck', 'Alliance', 'Charge of the Demon Lord'), +(3, 2, 1, 2, 258, 47433, 'Phase 3', 'Hunter', 'Survival', 'Neck', 'Horde', 'Charge of the Eredar'), +(3, 2, 2, 0, 258, 48260, 'Phase 3', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Windrunner''s Spaulders of Triumph'), +(3, 2, 4, 0, 258, 48264, 'Phase 3', 'Hunter', 'Survival', 'Chest', 'Both', 'Windrunner''s Tunic of Triumph'), +(3, 2, 5, 1, 258, 47153, 'Phase 3', 'Hunter', 'Survival', 'Waist', 'Alliance', 'Belt of Deathly Dominion'), +(3, 2, 5, 2, 258, 47472, 'Phase 3', 'Hunter', 'Survival', 'Waist', 'Horde', 'Waistguard of Deathly Dominion'), +(3, 2, 6, 1, 258, 47191, 'Phase 3', 'Hunter', 'Survival', 'Legs', 'Alliance', 'Legguards of the Lurking Threat'), +(3, 2, 6, 2, 258, 47480, 'Phase 3', 'Hunter', 'Survival', 'Legs', 'Horde', 'Leggings of the Lurking Threat'), +(3, 2, 7, 1, 258, 47109, 'Phase 3', 'Hunter', 'Survival', 'Feet', 'Alliance', 'Sabatons of Ruthless Judgment'), +(3, 2, 7, 2, 258, 47457, 'Phase 3', 'Hunter', 'Survival', 'Feet', 'Horde', 'Greaves of Ruthless Judgment'), +(3, 2, 8, 1, 258, 47074, 'Phase 3', 'Hunter', 'Survival', 'Wrists', 'Alliance', 'Bracers of the Untold Massacre'), +(3, 2, 8, 2, 258, 47442, 'Phase 3', 'Hunter', 'Survival', 'Wrists', 'Horde', 'Bracers of the Silent Massacre'), +(3, 2, 9, 0, 258, 48263, 'Phase 3', 'Hunter', 'Survival', 'Hands', 'Both', 'Windrunner''s Handguards of Triumph'), +(3, 2, 10, 1, 258, 47075, 'Phase 3', 'Hunter', 'Survival', 'Finger1', 'Alliance', 'Ring of Callous Aggression'), +(3, 2, 10, 2, 258, 47443, 'Phase 3', 'Hunter', 'Survival', 'Finger1', 'Horde', 'Band of Callous Aggression'), +(3, 2, 12, 2, 258, 44253, 'Phase 3', 'Hunter', 'Survival', 'Trinket1', 'Horde', 'Darkmoon Card: Greatness'), +(3, 2, 14, 1, 258, 47545, 'Phase 3', 'Hunter', 'Survival', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(3, 2, 14, 2, 258, 47546, 'Phase 3', 'Hunter', 'Survival', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(3, 2, 17, 1, 258, 47521, 'Phase 3', 'Hunter', 'Survival', 'Ranged', 'Alliance', 'BRK 1000'), +(3, 2, 17, 2, 258, 47523, 'Phase 3', 'Hunter', 'Survival', 'Ranged', 'Horde', 'Fezzik''s Autocannon'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(3, 2, 0, 0, 264, 51286, 'Phase 4', 'Hunter', 'Survival', 'Head', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Headpiece'), +(3, 2, 1, 0, 264, 50633, 'Phase 4', 'Hunter', 'Survival', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(3, 2, 2, 0, 264, 51288, 'Phase 4', 'Hunter', 'Survival', 'Shoulders', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Spaulders'), +(3, 2, 4, 0, 264, 51289, 'Phase 4', 'Hunter', 'Survival', 'Chest', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Tunic'), +(3, 2, 5, 0, 264, 50688, 'Phase 4', 'Hunter', 'Survival', 'Waist', 'Both', 'Nerub''ar Stalker''s Cord'), +(3, 2, 6, 0, 264, 50645, 'Phase 4', 'Hunter', 'Survival', 'Legs', 'Both', 'Leggings of Northern Lights'), +(3, 2, 7, 0, 264, 50607, 'Phase 4', 'Hunter', 'Survival', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(3, 2, 8, 0, 264, 50655, 'Phase 4', 'Hunter', 'Survival', 'Wrists', 'Both', 'Scourge Hunter''s Vambraces'), +(3, 2, 9, 0, 264, 51285, 'Phase 4', 'Hunter', 'Survival', 'Hands', 'Both', 'Sanctified Ahn''Kahar Blood Hunter''s Handguards'), +(3, 2, 10, 0, 264, 50402, 'Phase 4', 'Hunter', 'Survival', 'Finger1', 'Both', 'Ashen Band of Endless Vengeance'), +(3, 2, 12, 1, 264, 47131, 'Phase 4', 'Hunter', 'Survival', 'Trinket1', 'Alliance', 'Death''s Verdict'), +(3, 2, 12, 2, 264, 47464, 'Phase 4', 'Hunter', 'Survival', 'Trinket1', 'Horde', 'Death''s Choice'), +(3, 2, 14, 1, 264, 47545, 'Phase 4', 'Hunter', 'Survival', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(3, 2, 14, 2, 264, 47546, 'Phase 4', 'Hunter', 'Survival', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(3, 2, 17, 0, 264, 50733, 'Phase 4', 'Hunter', 'Survival', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + + +-- ============================================================ +-- Rogue (4) +-- ============================================================ +-- Assassination (tab 0) +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 0, 0, 0, 120, 32087, 'Pre-Raid', 'Rogue', 'Assassination', 'Head', 'Both', 'Mask of the Deceiver'), +(4, 0, 1, 0, 120, 29381, 'Pre-Raid', 'Rogue', 'Assassination', 'Neck', 'Both', 'Choker of Vile Intent'), +(4, 0, 2, 0, 120, 27797, 'Pre-Raid', 'Rogue', 'Assassination', 'Shoulders', 'Both', 'Wastewalker Shoulderpads'), +(4, 0, 4, 0, 120, 30730, 'Pre-Raid', 'Rogue', 'Assassination', 'Chest', 'Both', 'Terrorweave Tunic'), +(4, 0, 5, 0, 120, 29247, 'Pre-Raid', 'Rogue', 'Assassination', 'Waist', 'Both', 'Girdle of the Deathdealer'), +(4, 0, 6, 0, 120, 30538, 'Pre-Raid', 'Rogue', 'Assassination', 'Legs', 'Both', 'Midnight Legguards'), +(4, 0, 7, 0, 120, 25686, 'Pre-Raid', 'Rogue', 'Assassination', 'Feet', 'Both', 'Fel Leather Boots'), +(4, 0, 8, 0, 120, 29246, 'Pre-Raid', 'Rogue', 'Assassination', 'Wrists', 'Both', 'Nightfall Wristguards'), +(4, 0, 9, 0, 120, 27531, 'Pre-Raid', 'Rogue', 'Assassination', 'Hands', 'Both', 'Wastewalker Gloves'), +(4, 0, 10, 0, 120, 30738, 'Pre-Raid', 'Rogue', 'Assassination', 'Finger1', 'Both', 'Ring of Reciprocity'), +(4, 0, 12, 0, 120, 28288, 'Pre-Raid', 'Rogue', 'Assassination', 'Trinket1', 'Both', 'Abacus of Violent Odds'), +(4, 0, 13, 0, 120, 29383, 'Pre-Raid', 'Rogue', 'Assassination', 'Trinket2', 'Both', 'Bloodlust Brooch'), +(4, 0, 14, 0, 120, 24259, 'Pre-Raid', 'Rogue', 'Assassination', 'Back', 'Both', 'Vengeance Wrap'), +(4, 0, 15, 0, 120, 31331, 'Pre-Raid', 'Rogue', 'Assassination', 'MainHand', 'Both', 'The Night Blade'), +(4, 0, 16, 0, 120, 29346, 'Pre-Raid', 'Rogue', 'Assassination', 'OffHand', 'Both', 'Feltooth Eviscerator'), +(4, 0, 17, 2, 120, 29152, 'Pre-Raid', 'Rogue', 'Assassination', 'Ranged', 'Horde', 'Marksman''s Bow'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 0, 0, 0, 125, 29044, 'Phase 1', 'Rogue', 'Assassination', 'Head', 'Both', 'Netherblade Facemask'), +(4, 0, 1, 0, 125, 29381, 'Phase 1', 'Rogue', 'Assassination', 'Neck', 'Both', 'Choker of Vile Intent'), +(4, 0, 2, 0, 125, 27797, 'Phase 1', 'Rogue', 'Assassination', 'Shoulders', 'Both', 'Wastewalker Shoulderpads'), +(4, 0, 4, 0, 125, 29045, 'Phase 1', 'Rogue', 'Assassination', 'Chest', 'Both', 'Netherblade Chestpiece'), +(4, 0, 5, 0, 125, 29247, 'Phase 1', 'Rogue', 'Assassination', 'Waist', 'Both', 'Girdle of the Deathdealer'), +(4, 0, 6, 0, 125, 28741, 'Phase 1', 'Rogue', 'Assassination', 'Legs', 'Both', 'Skulker''s Greaves'), +(4, 0, 7, 0, 125, 28545, 'Phase 1', 'Rogue', 'Assassination', 'Feet', 'Both', 'Edgewalker Longboots'), +(4, 0, 8, 0, 125, 29246, 'Phase 1', 'Rogue', 'Assassination', 'Wrists', 'Both', 'Nightfall Wristguards'), +(4, 0, 9, 0, 125, 27531, 'Phase 1', 'Rogue', 'Assassination', 'Hands', 'Both', 'Wastewalker Gloves'), +(4, 0, 10, 0, 125, 28649, 'Phase 1', 'Rogue', 'Assassination', 'Finger1', 'Both', 'Garona''s Signet Ring'), +(4, 0, 11, 0, 125, 28757, 'Phase 1', 'Rogue', 'Assassination', 'Finger2', 'Both', 'Ring of a Thousand Marks'), +(4, 0, 12, 0, 125, 29383, 'Phase 1', 'Rogue', 'Assassination', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(4, 0, 13, 0, 125, 28830, 'Phase 1', 'Rogue', 'Assassination', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(4, 0, 14, 0, 125, 28672, 'Phase 1', 'Rogue', 'Assassination', 'Back', 'Both', 'Drape of the Dark Reavers'), +(4, 0, 15, 0, 125, 28312, 'Phase 1', 'Rogue', 'Assassination', 'MainHand', 'Both', 'Gladiator''s Shanker'), +(4, 0, 16, 0, 125, 29346, 'Phase 1', 'Rogue', 'Assassination', 'OffHand', 'Both', 'Feltooth Eviscerator'), +(4, 0, 17, 0, 125, 28772, 'Phase 1', 'Rogue', 'Assassination', 'Ranged', 'Both', 'Sunfury Bow of the Phoenix'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 0, 0, 0, 200, 42550, 'Pre-Raid', 'Rogue', 'Assassination', 'Head', 'Both', 'Weakness Spectralizers'), +(4, 0, 1, 0, 200, 40678, 'Pre-Raid', 'Rogue', 'Assassination', 'Neck', 'Both', 'Pendant of the Outcast Hero'), +(4, 0, 2, 0, 200, 43481, 'Pre-Raid', 'Rogue', 'Assassination', 'Shoulders', 'Both', 'Trollwoven Spaulders'), +(4, 0, 4, 0, 200, 39558, 'Pre-Raid', 'Rogue', 'Assassination', 'Chest', 'Both', 'Heroes'' Bonescythe Breastplate'), +(4, 0, 5, 0, 200, 40694, 'Pre-Raid', 'Rogue', 'Assassination', 'Waist', 'Both', 'Jorach''s Crocolisk Skin Belt'), +(4, 0, 8, 0, 200, 34448, 'Pre-Raid', 'Rogue', 'Assassination', 'Wrists', 'Both', 'Slayer''s Bracers'), +(4, 0, 9, 0, 200, 39560, 'Pre-Raid', 'Rogue', 'Assassination', 'Hands', 'Both', 'Heroes'' Bonescythe Gauntlets'), +(4, 0, 12, 0, 200, 40684, 'Pre-Raid', 'Rogue', 'Assassination', 'Trinket1', 'Both', 'Mirror of Truth'), +(4, 0, 14, 0, 200, 43566, 'Pre-Raid', 'Rogue', 'Assassination', 'Back', 'Both', 'Ice Striker''s Cloak'), +(4, 0, 15, 0, 200, 37856, 'Pre-Raid', 'Rogue', 'Assassination', 'MainHand', 'Both', 'Librarian''s Paper Cutter'), +(4, 0, 17, 0, 200, 44504, 'Pre-Raid', 'Rogue', 'Assassination', 'Ranged', 'Both', 'Nesingwary 4000'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 0, 0, 0, 224, 43729, 'Phase 1', 'Rogue', 'Assassination', 'Head', 'Both', 'Valorous Bonescythe Helmet'), +(4, 0, 1, 0, 224, 44664, 'Phase 1', 'Rogue', 'Assassination', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(4, 0, 2, 0, 224, 40502, 'Phase 1', 'Rogue', 'Assassination', 'Shoulders', 'Both', 'Valorous Bonescythe Pauldrons'), +(4, 0, 4, 0, 224, 40539, 'Phase 1', 'Rogue', 'Assassination', 'Chest', 'Both', 'Chestguard of the Recluse'), +(4, 0, 5, 0, 224, 40205, 'Phase 1', 'Rogue', 'Assassination', 'Waist', 'Both', 'Stalk-Skin Belt'), +(4, 0, 6, 0, 224, 44011, 'Phase 1', 'Rogue', 'Assassination', 'Legs', 'Both', 'Leggings of the Honored'), +(4, 0, 7, 0, 224, 39701, 'Phase 1', 'Rogue', 'Assassination', 'Feet', 'Both', 'Dawnwalkers'), +(4, 0, 8, 0, 224, 39765, 'Phase 1', 'Rogue', 'Assassination', 'Wrists', 'Both', 'Sinner''s Bindings'), +(4, 0, 9, 0, 224, 40541, 'Phase 1', 'Rogue', 'Assassination', 'Hands', 'Both', 'Frosted Adroit Handguards'), +(4, 0, 10, 0, 224, 40474, 'Phase 1', 'Rogue', 'Assassination', 'Finger1', 'Both', 'Surge Needle Ring'), +(4, 0, 12, 0, 224, 44253, 'Phase 1', 'Rogue', 'Assassination', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(4, 0, 14, 0, 224, 40403, 'Phase 1', 'Rogue', 'Assassination', 'Back', 'Both', 'Drape of the Deadly Foe'), +(4, 0, 15, 0, 224, 39714, 'Phase 1', 'Rogue', 'Assassination', 'MainHand', 'Both', 'Webbed Death'), +(4, 0, 17, 0, 224, 40385, 'Phase 1', 'Rogue', 'Assassination', 'Ranged', 'Both', 'Envoy of Mortality'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 0, 0, 0, 245, 46125, 'Phase 2', 'Rogue', 'Assassination', 'Head', 'Both', 'Conqueror''s Terrorblade Helmet'), +(4, 0, 1, 0, 245, 45517, 'Phase 2', 'Rogue', 'Assassination', 'Neck', 'Both', 'Pendulum of Infinity'), +(4, 0, 2, 0, 245, 45245, 'Phase 2', 'Rogue', 'Assassination', 'Shoulders', 'Both', 'Shoulderpads of the Intruder'), +(4, 0, 4, 0, 245, 45473, 'Phase 2', 'Rogue', 'Assassination', 'Chest', 'Both', 'Embrace of the Gladiator'), +(4, 0, 5, 0, 245, 46095, 'Phase 2', 'Rogue', 'Assassination', 'Waist', 'Both', 'Soul-Devouring Cinch'), +(4, 0, 6, 0, 245, 45536, 'Phase 2', 'Rogue', 'Assassination', 'Legs', 'Both', 'Legguards of Cunning Deception'), +(4, 0, 7, 0, 245, 45564, 'Phase 2', 'Rogue', 'Assassination', 'Feet', 'Both', 'Footpads of Silence'), +(4, 0, 8, 0, 245, 45611, 'Phase 2', 'Rogue', 'Assassination', 'Wrists', 'Both', 'Solar Bindings'), +(4, 0, 9, 0, 245, 46124, 'Phase 2', 'Rogue', 'Assassination', 'Hands', 'Both', 'Conqueror''s Terrorblade Gauntlets'), +(4, 0, 12, 0, 245, 45609, 'Phase 2', 'Rogue', 'Assassination', 'Trinket1', 'Both', 'Comet''s Trail'), +(4, 0, 14, 0, 245, 45461, 'Phase 2', 'Rogue', 'Assassination', 'Back', 'Both', 'Drape of Icy Intent'), +(4, 0, 17, 0, 245, 45570, 'Phase 2', 'Rogue', 'Assassination', 'Ranged', 'Both', 'Skyforge Crossbow'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 0, 0, 0, 258, 48230, 'Phase 3', 'Rogue', 'Assassination', 'Head', 'Both', 'Helmet of Triumph'), +(4, 0, 2, 0, 258, 48228, 'Phase 3', 'Rogue', 'Assassination', 'Shoulders', 'Both', 'Pauldrons of Triumph'), +(4, 0, 4, 0, 258, 48232, 'Phase 3', 'Rogue', 'Assassination', 'Chest', 'Both', 'Breastplate of Triumph'), +(4, 0, 5, 1, 258, 47112, 'Phase 3', 'Rogue', 'Assassination', 'Waist', 'Alliance', 'Belt of the Merciless Killer'), +(4, 0, 5, 2, 258, 47460, 'Phase 3', 'Rogue', 'Assassination', 'Waist', 'Horde', 'Belt of the Pitiless Killer'), +(4, 0, 6, 1, 258, 46975, 'Phase 3', 'Rogue', 'Assassination', 'Legs', 'Alliance', 'Leggings of the Broken Beast'), +(4, 0, 6, 2, 258, 47420, 'Phase 3', 'Rogue', 'Assassination', 'Legs', 'Horde', 'Legwraps of the Broken Beast'), +(4, 0, 7, 1, 258, 47077, 'Phase 3', 'Rogue', 'Assassination', 'Feet', 'Alliance', 'Treads of the Icewalker'), +(4, 0, 7, 2, 258, 47445, 'Phase 3', 'Rogue', 'Assassination', 'Feet', 'Horde', 'Icewalker Treads'), +(4, 0, 8, 1, 258, 47155, 'Phase 3', 'Rogue', 'Assassination', 'Wrists', 'Alliance', 'Bracers of Dark Determination'), +(4, 0, 8, 2, 258, 47474, 'Phase 3', 'Rogue', 'Assassination', 'Wrists', 'Horde', 'Armbands of Dark Determination'), +(4, 0, 9, 0, 258, 48231, 'Phase 3', 'Rogue', 'Assassination', 'Hands', 'Both', 'Gauntlets of Triumph'), +(4, 0, 10, 1, 258, 47075, 'Phase 3', 'Rogue', 'Assassination', 'Finger1', 'Alliance', 'Ring of Callous Aggression'), +(4, 0, 10, 2, 258, 47443, 'Phase 3', 'Rogue', 'Assassination', 'Finger1', 'Horde', 'Band of Callous Aggression'), +(4, 0, 12, 1, 258, 47131, 'Phase 3', 'Rogue', 'Assassination', 'Trinket1', 'Alliance', 'Death''s Verdict'), +(4, 0, 12, 2, 258, 47464, 'Phase 3', 'Rogue', 'Assassination', 'Trinket1', 'Horde', 'Death''s Choice'), +(4, 0, 14, 1, 258, 47545, 'Phase 3', 'Rogue', 'Assassination', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(4, 0, 14, 2, 258, 47546, 'Phase 3', 'Rogue', 'Assassination', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(4, 0, 17, 1, 258, 47521, 'Phase 3', 'Rogue', 'Assassination', 'Ranged', 'Alliance', 'BRK 1000'), +(4, 0, 17, 2, 258, 47523, 'Phase 3', 'Rogue', 'Assassination', 'Ranged', 'Horde', 'Fezzik''s Autocannon'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 0, 0, 0, 264, 51252, 'Phase 4', 'Rogue', 'Assassination', 'Head', 'Both', 'Sanctified Shadowblade Helmet'), +(4, 0, 2, 0, 264, 51254, 'Phase 4', 'Rogue', 'Assassination', 'Shoulders', 'Both', 'Sanctified Shadowblade Pauldrons'), +(4, 0, 4, 0, 264, 50656, 'Phase 4', 'Rogue', 'Assassination', 'Chest', 'Both', 'Ikfirus''s Sack of Wonder'), +(4, 0, 5, 0, 264, 50707, 'Phase 4', 'Rogue', 'Assassination', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(4, 0, 6, 0, 264, 51253, 'Phase 4', 'Rogue', 'Assassination', 'Legs', 'Both', 'Sanctified Shadowblade Legplates'), +(4, 0, 7, 0, 264, 50607, 'Phase 4', 'Rogue', 'Assassination', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(4, 0, 8, 0, 264, 50670, 'Phase 4', 'Rogue', 'Assassination', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(4, 0, 9, 0, 264, 51251, 'Phase 4', 'Rogue', 'Assassination', 'Hands', 'Both', 'Sanctified Shadowblade Gauntlets'), +(4, 0, 10, 0, 264, 50604, 'Phase 4', 'Rogue', 'Assassination', 'Finger1', 'Both', 'Band of the Bone Colossus'), +(4, 0, 12, 0, 264, 50363, 'Phase 4', 'Rogue', 'Assassination', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(4, 0, 14, 0, 264, 50653, 'Phase 4', 'Rogue', 'Assassination', 'Back', 'Both', 'Shadowvault Slayer''s Cloak'), +(4, 0, 17, 0, 264, 50733, 'Phase 4', 'Rogue', 'Assassination', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 0, 0, 0, 290, 50713, 'Phase 5', 'Rogue', 'Assassination', 'Head', 'Both', 'Geistlord''s Punishment Sack'), +(4, 0, 1, 0, 290, 50633, 'Phase 5', 'Rogue', 'Assassination', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(4, 0, 2, 0, 290, 50646, 'Phase 5', 'Rogue', 'Assassination', 'Shoulders', 'Both', 'Cultist''s Bloodsoaked Spaulders'), +(4, 0, 4, 0, 290, 50656, 'Phase 5', 'Rogue', 'Assassination', 'Chest', 'Both', 'Ikfirus''s Sack of Wonder'), +(4, 0, 5, 0, 290, 47112, 'Phase 5', 'Rogue', 'Assassination', 'Waist', 'Both', 'Belt of the Merciless Killer'), +(4, 0, 6, 0, 290, 51253, 'Phase 5', 'Rogue', 'Assassination', 'Legs', 'Both', 'Sanctified Shadowblade Legplates'), +(4, 0, 7, 0, 290, 50607, 'Phase 5', 'Rogue', 'Assassination', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(4, 0, 8, 0, 290, 54580, 'Phase 5', 'Rogue', 'Assassination', 'Wrists', 'Both', 'Umbrage Armbands'), +(4, 0, 9, 0, 290, 51251, 'Phase 5', 'Rogue', 'Assassination', 'Hands', 'Both', 'Sanctified Shadowblade Gauntlets'), +(4, 0, 10, 0, 290, 50402, 'Phase 5', 'Rogue', 'Assassination', 'Finger1', 'Both', 'Ashen Band of Endless Vengeance'), +(4, 0, 11, 0, 290, 54576, 'Phase 5', 'Rogue', 'Assassination', 'Finger2', 'Both', 'Signet of Twilight'), +(4, 0, 12, 0, 290, 54590, 'Phase 5', 'Rogue', 'Assassination', 'Trinket1', 'Both', 'Sharpened Twilight Scale'), +(4, 0, 13, 0, 290, 50706, 'Phase 5', 'Rogue', 'Assassination', 'Trinket2', 'Both', 'Tiny Abomination in a Jar'), +(4, 0, 14, 0, 290, 50653, 'Phase 5', 'Rogue', 'Assassination', 'Back', 'Both', 'Shadowvault Slayer''s Cloak'), +(4, 0, 15, 0, 290, 50736, 'Phase 5', 'Rogue', 'Assassination', 'MainHand', 'Both', 'Heaven''s Fall, Kryss of a Thousand Lies'), +(4, 0, 16, 0, 290, 50621, 'Phase 5', 'Rogue', 'Assassination', 'OffHand', 'Both', 'Lungbreaker'), +(4, 0, 17, 0, 290, 50733, 'Phase 5', 'Rogue', 'Assassination', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- Combat (tab 1) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 66, 13404, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Head', 'Both', 'Mask of the Unforgiven'), +(4, 1, 1, 0, 66, 15411, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Neck', 'Both', 'Mark of Fordring'), +(4, 1, 2, 0, 66, 12927, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(4, 1, 4, 0, 66, 14637, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Chest', 'Both', 'Cadaverous Armor'), +(4, 1, 5, 0, 66, 13252, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Waist', 'Both', 'Cloudrunner Girdle'), +(4, 1, 6, 0, 66, 15062, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Legs', 'Both', 'Devilsaur Leggings'), +(4, 1, 7, 0, 66, 12553, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Feet', 'Both', 'Swiftwalker Boots'), +(4, 1, 8, 0, 66, 13120, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Wrists', 'Both', 'Deepfury Bracers'), +(4, 1, 9, 0, 66, 15063, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(4, 1, 10, 0, 66, 13098, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Finger1', 'Both', 'Painweaver Band'), +(4, 1, 11, 0, 66, 17713, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Finger2', 'Both', 'Blackstone Ring'), +(4, 1, 12, 0, 66, 13965, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(4, 1, 13, 0, 66, 11815, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Trinket2', 'Both', 'Hand of Justice'), +(4, 1, 14, 0, 66, 13340, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Back', 'Both', 'Cape of the Black Baron'), +(4, 1, 15, 0, 66, 12783, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'MainHand', 'Both', 'Heartseeker'), +(4, 1, 16, 0, 66, 14555, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'OffHand', 'Both', 'Alcor''s Sunrazor'), +(4, 1, 17, 0, 66, 12651, 'Phase 1 (Pre-Raid)', 'Rogue', 'Combat', 'Ranged', 'Both', 'Blackcrow'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 76, 13404, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Head', 'Both', 'Mask of the Unforgiven'), +(4, 1, 1, 0, 76, 15411, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Neck', 'Both', 'Mark of Fordring'), +(4, 1, 2, 0, 76, 12927, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(4, 1, 4, 0, 76, 14637, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Chest', 'Both', 'Cadaverous Armor'), +(4, 1, 5, 0, 76, 18505, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Waist', 'Both', 'Mugger''s Belt'), +(4, 1, 6, 0, 76, 15062, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Legs', 'Both', 'Devilsaur Leggings'), +(4, 1, 7, 0, 76, 12553, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Feet', 'Both', 'Swiftwalker Boots'), +(4, 1, 8, 0, 76, 18375, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Wrists', 'Both', 'Bracers of the Eclipse'), +(4, 1, 9, 0, 76, 15063, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(4, 1, 10, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Finger1', 'Both', 'Tarnished Elven Ring'), +(4, 1, 11, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Finger2', 'Both', 'Tarnished Elven Ring'), +(4, 1, 12, 0, 76, 13965, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(4, 1, 13, 0, 76, 11815, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Trinket2', 'Both', 'Hand of Justice'), +(4, 1, 14, 0, 76, 13340, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Back', 'Both', 'Cape of the Black Baron'), +(4, 1, 15, 0, 76, 12783, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'MainHand', 'Both', 'Heartseeker'), +(4, 1, 16, 0, 76, 14555, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'OffHand', 'Both', 'Alcor''s Sunrazor'), +(4, 1, 17, 0, 76, 18323, 'Phase 2 (Pre-Raid)', 'Rogue', 'Combat', 'Ranged', 'Both', 'Satyr''s Bow'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 78, 16908, 'Phase 2', 'Rogue', 'Combat', 'Head', 'Both', 'Bloodfang Hood'), +(4, 1, 1, 0, 78, 18404, 'Phase 2', 'Rogue', 'Combat', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(4, 1, 2, 0, 78, 16823, 'Phase 2', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Nightslayer Shoulder Pads'), +(4, 1, 4, 0, 78, 16820, 'Phase 2', 'Rogue', 'Combat', 'Chest', 'Both', 'Nightslayer Chestpiece'), +(4, 1, 5, 0, 78, 16827, 'Phase 2', 'Rogue', 'Combat', 'Waist', 'Both', 'Nightslayer Belt'), +(4, 1, 6, 0, 78, 16909, 'Phase 2', 'Rogue', 'Combat', 'Legs', 'Both', 'Bloodfang Pants'), +(4, 1, 7, 0, 78, 16824, 'Phase 2', 'Rogue', 'Combat', 'Feet', 'Both', 'Nightslayer Boots'), +(4, 1, 8, 0, 78, 18375, 'Phase 2', 'Rogue', 'Combat', 'Wrists', 'Both', 'Bracers of the Eclipse'), +(4, 1, 9, 0, 78, 18823, 'Phase 2', 'Rogue', 'Combat', 'Hands', 'Both', 'Aged Core Leather Gloves'), +(4, 1, 10, 0, 78, 17063, 'Phase 2', 'Rogue', 'Combat', 'Finger1', 'Both', 'Band of Accuria'), +(4, 1, 11, 0, 78, 18500, 'Phase 2', 'Rogue', 'Combat', 'Finger2', 'Both', 'Tarnished Elven Ring'), +(4, 1, 12, 0, 78, 13965, 'Phase 2', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(4, 1, 13, 0, 78, 11815, 'Phase 2', 'Rogue', 'Combat', 'Trinket2', 'Both', 'Hand of Justice'), +(4, 1, 14, 0, 78, 13340, 'Phase 2', 'Rogue', 'Combat', 'Back', 'Both', 'Cape of the Black Baron'), +(4, 1, 15, 0, 78, 18816, 'Phase 2', 'Rogue', 'Combat', 'MainHand', 'Both', 'Perdition''s Blade'), +(4, 1, 16, 0, 78, 18805, 'Phase 2', 'Rogue', 'Combat', 'OffHand', 'Both', 'Core Hound Tooth'), +(4, 1, 17, 0, 78, 17069, 'Phase 2', 'Rogue', 'Combat', 'Ranged', 'Both', 'Striker''s Mark'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 83, 16908, 'Phase 3', 'Rogue', 'Combat', 'Head', 'Both', 'Bloodfang Hood'), +(4, 1, 1, 0, 83, 19377, 'Phase 3', 'Rogue', 'Combat', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(4, 1, 2, 0, 83, 16823, 'Phase 3', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Nightslayer Shoulder Pads'), +(4, 1, 4, 0, 83, 16905, 'Phase 3', 'Rogue', 'Combat', 'Chest', 'Both', 'Bloodfang Chestpiece'), +(4, 1, 5, 0, 83, 16910, 'Phase 3', 'Rogue', 'Combat', 'Waist', 'Both', 'Bloodfang Belt'), +(4, 1, 6, 0, 83, 16909, 'Phase 3', 'Rogue', 'Combat', 'Legs', 'Both', 'Bloodfang Pants'), +(4, 1, 7, 0, 83, 19381, 'Phase 3', 'Rogue', 'Combat', 'Feet', 'Both', 'Boots of the Shadow Flame'), +(4, 1, 8, 0, 83, 16911, 'Phase 3', 'Rogue', 'Combat', 'Wrists', 'Both', 'Bloodfang Bracers'), +(4, 1, 9, 0, 83, 18823, 'Phase 3', 'Rogue', 'Combat', 'Hands', 'Both', 'Aged Core Leather Gloves'), +(4, 1, 10, 0, 83, 17063, 'Phase 3', 'Rogue', 'Combat', 'Finger1', 'Both', 'Band of Accuria'), +(4, 1, 11, 0, 83, 19384, 'Phase 3', 'Rogue', 'Combat', 'Finger2', 'Both', 'Master Dragonslayer''s Ring'), +(4, 1, 12, 0, 83, 19406, 'Phase 3', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Drake Fang Talisman'), +(4, 1, 13, 0, 83, 11815, 'Phase 3', 'Rogue', 'Combat', 'Trinket2', 'Both', 'Hand of Justice'), +(4, 1, 14, 0, 83, 19398, 'Phase 3', 'Rogue', 'Combat', 'Back', 'Both', 'Cloak of Firemaw'), +(4, 1, 15, 0, 83, 18816, 'Phase 3', 'Rogue', 'Combat', 'MainHand', 'Both', 'Perdition''s Blade'), +(4, 1, 16, 0, 83, 18805, 'Phase 3', 'Rogue', 'Combat', 'OffHand', 'Both', 'Core Hound Tooth'), +(4, 1, 17, 0, 83, 17069, 'Phase 3', 'Rogue', 'Combat', 'Ranged', 'Both', 'Striker''s Mark'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 88, 21360, 'Phase 5', 'Rogue', 'Combat', 'Head', 'Both', 'Deathdealer''s Helm'), +(4, 1, 1, 0, 88, 19377, 'Phase 5', 'Rogue', 'Combat', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(4, 1, 2, 0, 88, 21361, 'Phase 5', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Deathdealer''s Spaulders'), +(4, 1, 4, 0, 88, 21364, 'Phase 5', 'Rogue', 'Combat', 'Chest', 'Both', 'Deathdealer''s Vest'), +(4, 1, 5, 0, 88, 21586, 'Phase 5', 'Rogue', 'Combat', 'Waist', 'Both', 'Belt of Never-ending Agony'), +(4, 1, 6, 0, 88, 21362, 'Phase 5', 'Rogue', 'Combat', 'Legs', 'Both', 'Deathdealer''s Leggings'), +(4, 1, 7, 0, 88, 21359, 'Phase 5', 'Rogue', 'Combat', 'Feet', 'Both', 'Deathdealer''s Boots'), +(4, 1, 8, 0, 88, 21602, 'Phase 5', 'Rogue', 'Combat', 'Wrists', 'Both', 'Qiraji Execution Bracers'), +(4, 1, 9, 0, 88, 18823, 'Phase 5', 'Rogue', 'Combat', 'Hands', 'Both', 'Aged Core Leather Gloves'), +(4, 1, 10, 0, 88, 17063, 'Phase 5', 'Rogue', 'Combat', 'Finger1', 'Both', 'Band of Accuria'), +(4, 1, 11, 0, 88, 19384, 'Phase 5', 'Rogue', 'Combat', 'Finger2', 'Both', 'Master Dragonslayer''s Ring'), +(4, 1, 12, 0, 88, 19406, 'Phase 5', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Drake Fang Talisman'), +(4, 1, 13, 0, 88, 23570, 'Phase 5', 'Rogue', 'Combat', 'Trinket2', 'Both', 'Jom Gabbar'), +(4, 1, 14, 0, 88, 21701, 'Phase 5', 'Rogue', 'Combat', 'Back', 'Both', 'Cloak of Concentrated Hatred'), +(4, 1, 15, 0, 88, 21126, 'Phase 5', 'Rogue', 'Combat', 'MainHand', 'Both', 'Death''s Sting'), +(4, 1, 16, 0, 88, 21244, 'Phase 5', 'Rogue', 'Combat', 'OffHand', 'Both', 'Blessed Qiraji Pugio'), +(4, 1, 17, 0, 88, 17069, 'Phase 5', 'Rogue', 'Combat', 'Ranged', 'Both', 'Striker''s Mark'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 92, 22478, 'Phase 6', 'Rogue', 'Combat', 'Head', 'Both', 'Bonescythe Helmet'), +(4, 1, 1, 0, 92, 19377, 'Phase 6', 'Rogue', 'Combat', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(4, 1, 2, 0, 92, 22479, 'Phase 6', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Bonescythe Pauldrons'), +(4, 1, 4, 0, 92, 22476, 'Phase 6', 'Rogue', 'Combat', 'Chest', 'Both', 'Bonescythe Breastplate'), +(4, 1, 5, 0, 92, 21586, 'Phase 6', 'Rogue', 'Combat', 'Waist', 'Both', 'Belt of Never-ending Agony'), +(4, 1, 6, 0, 92, 22477, 'Phase 6', 'Rogue', 'Combat', 'Legs', 'Both', 'Bonescythe Legplates'), +(4, 1, 7, 0, 92, 22480, 'Phase 6', 'Rogue', 'Combat', 'Feet', 'Both', 'Bonescythe Sabatons'), +(4, 1, 8, 0, 92, 22483, 'Phase 6', 'Rogue', 'Combat', 'Wrists', 'Both', 'Bonescythe Bracers'), +(4, 1, 9, 0, 92, 22481, 'Phase 6', 'Rogue', 'Combat', 'Hands', 'Both', 'Bonescythe Gauntlets'), +(4, 1, 10, 0, 92, 23060, 'Phase 6', 'Rogue', 'Combat', 'Finger1', 'Both', 'Bonescythe Ring'), +(4, 1, 12, 0, 92, 23041, 'Phase 6', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Slayer''s Crest'), +(4, 1, 13, 0, 92, 22954, 'Phase 6', 'Rogue', 'Combat', 'Trinket2', 'Both', 'Kiss of the Spider'), +(4, 1, 14, 0, 92, 23045, 'Phase 6', 'Rogue', 'Combat', 'Back', 'Both', 'Shroud of Dominion'), +(4, 1, 15, 0, 92, 22802, 'Phase 6', 'Rogue', 'Combat', 'MainHand', 'Both', 'Kingsfall'), +(4, 1, 16, 0, 92, 21126, 'Phase 6', 'Rogue', 'Combat', 'OffHand', 'Both', 'Death''s Sting'), +(4, 1, 17, 0, 92, 22812, 'Phase 6', 'Rogue', 'Combat', 'Ranged', 'Both', 'Nerubian Slavemaker'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 120, 32087, 'Pre-Raid', 'Rogue', 'Combat', 'Head', 'Both', 'Mask of the Deceiver'), +(4, 1, 1, 0, 120, 29381, 'Pre-Raid', 'Rogue', 'Combat', 'Neck', 'Both', 'Choker of Vile Intent'), +(4, 1, 2, 0, 120, 27797, 'Pre-Raid', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Wastewalker Shoulderpads'), +(4, 1, 4, 0, 120, 30730, 'Pre-Raid', 'Rogue', 'Combat', 'Chest', 'Both', 'Terrorweave Tunic'), +(4, 1, 5, 0, 120, 29247, 'Pre-Raid', 'Rogue', 'Combat', 'Waist', 'Both', 'Girdle of the Deathdealer'), +(4, 1, 6, 0, 120, 30538, 'Pre-Raid', 'Rogue', 'Combat', 'Legs', 'Both', 'Midnight Legguards'), +(4, 1, 7, 0, 120, 25686, 'Pre-Raid', 'Rogue', 'Combat', 'Feet', 'Both', 'Fel Leather Boots'), +(4, 1, 8, 0, 120, 29246, 'Pre-Raid', 'Rogue', 'Combat', 'Wrists', 'Both', 'Nightfall Wristguards'), +(4, 1, 9, 0, 120, 27531, 'Pre-Raid', 'Rogue', 'Combat', 'Hands', 'Both', 'Wastewalker Gloves'), +(4, 1, 10, 0, 120, 30738, 'Pre-Raid', 'Rogue', 'Combat', 'Finger1', 'Both', 'Ring of Reciprocity'), +(4, 1, 12, 0, 120, 28288, 'Pre-Raid', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Abacus of Violent Odds'), +(4, 1, 13, 0, 120, 29383, 'Pre-Raid', 'Rogue', 'Combat', 'Trinket2', 'Both', 'Bloodlust Brooch'), +(4, 1, 14, 0, 120, 24259, 'Pre-Raid', 'Rogue', 'Combat', 'Back', 'Both', 'Vengeance Wrap'), +(4, 1, 15, 0, 120, 31332, 'Pre-Raid', 'Rogue', 'Combat', 'MainHand', 'Both', 'Blinkstrike'), +(4, 1, 16, 0, 120, 28189, 'Pre-Raid', 'Rogue', 'Combat', 'OffHand', 'Both', 'Latro''s Shifting Sword'), +(4, 1, 17, 2, 120, 29152, 'Pre-Raid', 'Rogue', 'Combat', 'Ranged', 'Horde', 'Marksman''s Bow'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 125, 29044, 'Phase 1', 'Rogue', 'Combat', 'Head', 'Both', 'Netherblade Facemask'), +(4, 1, 1, 0, 125, 29381, 'Phase 1', 'Rogue', 'Combat', 'Neck', 'Both', 'Choker of Vile Intent'), +(4, 1, 2, 0, 125, 27797, 'Phase 1', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Wastewalker Shoulderpads'), +(4, 1, 4, 0, 125, 29045, 'Phase 1', 'Rogue', 'Combat', 'Chest', 'Both', 'Netherblade Chestpiece'), +(4, 1, 5, 0, 125, 29247, 'Phase 1', 'Rogue', 'Combat', 'Waist', 'Both', 'Girdle of the Deathdealer'), +(4, 1, 6, 0, 125, 28741, 'Phase 1', 'Rogue', 'Combat', 'Legs', 'Both', 'Skulker''s Greaves'), +(4, 1, 7, 0, 125, 28545, 'Phase 1', 'Rogue', 'Combat', 'Feet', 'Both', 'Edgewalker Longboots'), +(4, 1, 8, 0, 125, 29246, 'Phase 1', 'Rogue', 'Combat', 'Wrists', 'Both', 'Nightfall Wristguards'), +(4, 1, 9, 0, 125, 27531, 'Phase 1', 'Rogue', 'Combat', 'Hands', 'Both', 'Wastewalker Gloves'), +(4, 1, 10, 0, 125, 28649, 'Phase 1', 'Rogue', 'Combat', 'Finger1', 'Both', 'Garona''s Signet Ring'), +(4, 1, 11, 0, 125, 28757, 'Phase 1', 'Rogue', 'Combat', 'Finger2', 'Both', 'Ring of a Thousand Marks'), +(4, 1, 12, 0, 125, 29383, 'Phase 1', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(4, 1, 13, 0, 125, 28830, 'Phase 1', 'Rogue', 'Combat', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(4, 1, 14, 0, 125, 28672, 'Phase 1', 'Rogue', 'Combat', 'Back', 'Both', 'Drape of the Dark Reavers'), +(4, 1, 15, 0, 125, 31332, 'Phase 1', 'Rogue', 'Combat', 'MainHand', 'Both', 'Blinkstrike'), +(4, 1, 16, 0, 125, 28307, 'Phase 1', 'Rogue', 'Combat', 'OffHand', 'Both', 'Gladiator''s Quickblade'), +(4, 1, 17, 0, 125, 28772, 'Phase 1', 'Rogue', 'Combat', 'Ranged', 'Both', 'Sunfury Bow of the Phoenix'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 200, 42550, 'Pre-Raid', 'Rogue', 'Combat', 'Head', 'Both', 'Weakness Spectralizers'), +(4, 1, 1, 0, 200, 42645, 'Pre-Raid', 'Rogue', 'Combat', 'Neck', 'Both', 'Titanium Impact Choker'), +(4, 1, 2, 0, 200, 43481, 'Pre-Raid', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Trollwoven Spaulders'), +(4, 1, 4, 0, 200, 39558, 'Pre-Raid', 'Rogue', 'Combat', 'Chest', 'Both', 'Heroes'' Bonescythe Breastplate'), +(4, 1, 5, 0, 200, 43484, 'Pre-Raid', 'Rogue', 'Combat', 'Waist', 'Both', 'Trollwoven Girdle'), +(4, 1, 6, 0, 200, 39564, 'Pre-Raid', 'Rogue', 'Combat', 'Legs', 'Both', 'Heroes'' Bonescythe Legplates'), +(4, 1, 7, 0, 200, 37666, 'Pre-Raid', 'Rogue', 'Combat', 'Feet', 'Both', 'Boots of the Whirling Mist'), +(4, 1, 8, 0, 200, 37853, 'Pre-Raid', 'Rogue', 'Combat', 'Wrists', 'Both', 'Advanced Tooled-Leather Bands'), +(4, 1, 9, 0, 200, 39560, 'Pre-Raid', 'Rogue', 'Combat', 'Hands', 'Both', 'Heroes'' Bonescythe Gauntlets'), +(4, 1, 10, 0, 200, 42642, 'Pre-Raid', 'Rogue', 'Combat', 'Finger1', 'Both', 'Titanium Impact Band'), +(4, 1, 12, 0, 200, 40684, 'Pre-Raid', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Mirror of Truth'), +(4, 1, 14, 0, 200, 43566, 'Pre-Raid', 'Rogue', 'Combat', 'Back', 'Both', 'Ice Striker''s Cloak'), +(4, 1, 15, 0, 200, 37871, 'Pre-Raid', 'Rogue', 'Combat', 'MainHand', 'Both', 'The Key'), +(4, 1, 17, 0, 200, 43284, 'Pre-Raid', 'Rogue', 'Combat', 'Ranged', 'Both', 'Amanitar Skullbow'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 224, 40499, 'Phase 1', 'Rogue', 'Combat', 'Head', 'Both', 'Valorous Bonescythe Helmet'), +(4, 1, 1, 0, 224, 44664, 'Phase 1', 'Rogue', 'Combat', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(4, 1, 2, 0, 224, 40502, 'Phase 1', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Valorous Bonescythe Pauldrons'), +(4, 1, 4, 0, 224, 40539, 'Phase 1', 'Rogue', 'Combat', 'Chest', 'Both', 'Chestguard of the Recluse'), +(4, 1, 5, 0, 224, 40205, 'Phase 1', 'Rogue', 'Combat', 'Waist', 'Both', 'Stalk-Skin Belt'), +(4, 1, 6, 0, 224, 44011, 'Phase 1', 'Rogue', 'Combat', 'Legs', 'Both', 'Leggings of the Honored'), +(4, 1, 7, 0, 224, 39701, 'Phase 1', 'Rogue', 'Combat', 'Feet', 'Both', 'Dawnwalkers'), +(4, 1, 8, 0, 224, 39765, 'Phase 1', 'Rogue', 'Combat', 'Wrists', 'Both', 'Sinner''s Bindings'), +(4, 1, 9, 0, 224, 40541, 'Phase 1', 'Rogue', 'Combat', 'Hands', 'Both', 'Frosted Adroit Handguards'), +(4, 1, 10, 0, 224, 40074, 'Phase 1', 'Rogue', 'Combat', 'Finger1', 'Both', 'Strong-Handed Ring'), +(4, 1, 12, 0, 224, 40684, 'Phase 1', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Mirror of Truth'), +(4, 1, 14, 0, 224, 40403, 'Phase 1', 'Rogue', 'Combat', 'Back', 'Both', 'Drape of the Deadly Foe'), +(4, 1, 15, 0, 224, 40383, 'Phase 1', 'Rogue', 'Combat', 'MainHand', 'Both', 'Calamity''s Grasp'), +(4, 1, 17, 0, 224, 40385, 'Phase 1', 'Rogue', 'Combat', 'Ranged', 'Both', 'Envoy of Mortality'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 245, 46125, 'Phase 2', 'Rogue', 'Combat', 'Head', 'Both', 'Conqueror''s Terrorblade Helmet'), +(4, 1, 1, 0, 245, 45517, 'Phase 2', 'Rogue', 'Combat', 'Neck', 'Both', 'Pendulum of Infinity'), +(4, 1, 2, 0, 245, 46127, 'Phase 2', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Conqueror''s Terrorblade Pauldrons'), +(4, 1, 4, 0, 245, 45473, 'Phase 2', 'Rogue', 'Combat', 'Chest', 'Both', 'Embrace of the Gladiator'), +(4, 1, 5, 0, 245, 46095, 'Phase 2', 'Rogue', 'Combat', 'Waist', 'Both', 'Soul-Devouring Cinch'), +(4, 1, 6, 0, 245, 45536, 'Phase 2', 'Rogue', 'Combat', 'Legs', 'Both', 'Legguards of Cunning Deception'), +(4, 1, 7, 0, 245, 45564, 'Phase 2', 'Rogue', 'Combat', 'Feet', 'Both', 'Footpads of Silence'), +(4, 1, 8, 0, 245, 45611, 'Phase 2', 'Rogue', 'Combat', 'Wrists', 'Both', 'Solar Bindings'), +(4, 1, 9, 0, 245, 46124, 'Phase 2', 'Rogue', 'Combat', 'Hands', 'Both', 'Conqueror''s Terrorblade Gauntlets'), +(4, 1, 10, 0, 245, 45456, 'Phase 2', 'Rogue', 'Combat', 'Finger1', 'Both', 'Loop of the Agile'), +(4, 1, 12, 0, 245, 45609, 'Phase 2', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Comet''s Trail'), +(4, 1, 14, 0, 245, 46032, 'Phase 2', 'Rogue', 'Combat', 'Back', 'Both', 'Drape of the Faceless General'), +(4, 1, 17, 0, 245, 45296, 'Phase 2', 'Rogue', 'Combat', 'Ranged', 'Both', 'Twirling Blades'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 258, 48230, 'Phase 3', 'Rogue', 'Combat', 'Head', 'Both', 'Helmet of Triumph'), +(4, 1, 2, 0, 258, 48228, 'Phase 3', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Pauldrons of Triumph'), +(4, 1, 4, 0, 258, 48232, 'Phase 3', 'Rogue', 'Combat', 'Chest', 'Both', 'Breastplate of Triumph'), +(4, 1, 5, 1, 258, 47112, 'Phase 3', 'Rogue', 'Combat', 'Waist', 'Alliance', 'Belt of the Merciless Killer'), +(4, 1, 5, 2, 258, 47460, 'Phase 3', 'Rogue', 'Combat', 'Waist', 'Horde', 'Belt of the Pitiless Killer'), +(4, 1, 6, 1, 258, 46975, 'Phase 3', 'Rogue', 'Combat', 'Legs', 'Alliance', 'Leggings of the Broken Beast'), +(4, 1, 6, 2, 258, 47420, 'Phase 3', 'Rogue', 'Combat', 'Legs', 'Horde', 'Legwraps of the Broken Beast'), +(4, 1, 7, 1, 258, 47077, 'Phase 3', 'Rogue', 'Combat', 'Feet', 'Alliance', 'Treads of the Icewalker'), +(4, 1, 7, 2, 258, 47445, 'Phase 3', 'Rogue', 'Combat', 'Feet', 'Horde', 'Icewalker Treads'), +(4, 1, 8, 1, 258, 47155, 'Phase 3', 'Rogue', 'Combat', 'Wrists', 'Alliance', 'Bracers of Dark Determination'), +(4, 1, 8, 2, 258, 47474, 'Phase 3', 'Rogue', 'Combat', 'Wrists', 'Horde', 'Armbands of Dark Determination'), +(4, 1, 9, 0, 258, 48231, 'Phase 3', 'Rogue', 'Combat', 'Hands', 'Both', 'Gauntlets of Triumph'), +(4, 1, 10, 1, 258, 47075, 'Phase 3', 'Rogue', 'Combat', 'Finger1', 'Alliance', 'Ring of Callous Aggression'), +(4, 1, 10, 2, 258, 47443, 'Phase 3', 'Rogue', 'Combat', 'Finger1', 'Horde', 'Band of Callous Aggression'), +(4, 1, 12, 1, 258, 47131, 'Phase 3', 'Rogue', 'Combat', 'Trinket1', 'Alliance', 'Death''s Verdict'), +(4, 1, 12, 2, 258, 47464, 'Phase 3', 'Rogue', 'Combat', 'Trinket1', 'Horde', 'Death''s Choice'), +(4, 1, 14, 1, 258, 47545, 'Phase 3', 'Rogue', 'Combat', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(4, 1, 14, 2, 258, 47546, 'Phase 3', 'Rogue', 'Combat', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(4, 1, 17, 1, 258, 47521, 'Phase 3', 'Rogue', 'Combat', 'Ranged', 'Alliance', 'BRK 1000'), +(4, 1, 17, 2, 258, 47523, 'Phase 3', 'Rogue', 'Combat', 'Ranged', 'Horde', 'Fezzik''s Autocannon'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 264, 51252, 'Phase 4', 'Rogue', 'Combat', 'Head', 'Both', 'Sanctified Shadowblade Helmet'), +(4, 1, 2, 0, 264, 51254, 'Phase 4', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Sanctified Shadowblade Pauldrons'), +(4, 1, 4, 0, 264, 50656, 'Phase 4', 'Rogue', 'Combat', 'Chest', 'Both', 'Ikfirus''s Sack of Wonder'), +(4, 1, 5, 0, 264, 50707, 'Phase 4', 'Rogue', 'Combat', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(4, 1, 6, 0, 264, 50697, 'Phase 4', 'Rogue', 'Combat', 'Legs', 'Both', 'Gangrenous Leggings'), +(4, 1, 7, 0, 264, 50607, 'Phase 4', 'Rogue', 'Combat', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(4, 1, 8, 0, 264, 50670, 'Phase 4', 'Rogue', 'Combat', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(4, 1, 9, 0, 264, 50675, 'Phase 4', 'Rogue', 'Combat', 'Hands', 'Both', 'Aldriana''s Gloves of Secrecy'), +(4, 1, 10, 0, 264, 50618, 'Phase 4', 'Rogue', 'Combat', 'Finger1', 'Both', 'Frostbrood Sapphire Ring'), +(4, 1, 12, 0, 264, 50363, 'Phase 4', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(4, 1, 14, 1, 264, 47545, 'Phase 4', 'Rogue', 'Combat', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(4, 1, 14, 2, 264, 47546, 'Phase 4', 'Rogue', 'Combat', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(4, 1, 17, 0, 264, 50733, 'Phase 4', 'Rogue', 'Combat', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 1, 0, 0, 290, 51252, 'Phase 5', 'Rogue', 'Combat', 'Head', 'Both', 'Sanctified Shadowblade Helmet'), +(4, 1, 1, 0, 290, 50633, 'Phase 5', 'Rogue', 'Combat', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(4, 1, 2, 0, 290, 51254, 'Phase 5', 'Rogue', 'Combat', 'Shoulders', 'Both', 'Sanctified Shadowblade Pauldrons'), +(4, 1, 4, 0, 290, 51250, 'Phase 5', 'Rogue', 'Combat', 'Chest', 'Both', 'Sanctified Shadowblade Breastplate'), +(4, 1, 5, 0, 290, 50707, 'Phase 5', 'Rogue', 'Combat', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(4, 1, 6, 0, 290, 51253, 'Phase 5', 'Rogue', 'Combat', 'Legs', 'Both', 'Sanctified Shadowblade Legplates'), +(4, 1, 7, 0, 290, 50607, 'Phase 5', 'Rogue', 'Combat', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(4, 1, 8, 0, 290, 50670, 'Phase 5', 'Rogue', 'Combat', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(4, 1, 9, 0, 290, 50675, 'Phase 5', 'Rogue', 'Combat', 'Hands', 'Both', 'Aldriana''s Gloves of Secrecy'), +(4, 1, 10, 0, 290, 50402, 'Phase 5', 'Rogue', 'Combat', 'Finger1', 'Both', 'Ashen Band of Endless Vengeance'), +(4, 1, 11, 0, 290, 50618, 'Phase 5', 'Rogue', 'Combat', 'Finger2', 'Both', 'Frostbrood Sapphire Ring'), +(4, 1, 12, 0, 290, 50363, 'Phase 5', 'Rogue', 'Combat', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(4, 1, 13, 0, 290, 54590, 'Phase 5', 'Rogue', 'Combat', 'Trinket2', 'Both', 'Sharpened Twilight Scale'), +(4, 1, 14, 0, 290, 47545, 'Phase 5', 'Rogue', 'Combat', 'Back', 'Both', 'Vereesa''s Dexterity'), +(4, 1, 15, 0, 290, 50737, 'Phase 5', 'Rogue', 'Combat', 'MainHand', 'Both', 'Havoc''s Call, Blade of Lordaeron Kings'), +(4, 1, 16, 0, 290, 50654, 'Phase 5', 'Rogue', 'Combat', 'OffHand', 'Both', 'Scourgeborne Waraxe'), +(4, 1, 17, 0, 290, 50733, 'Phase 5', 'Rogue', 'Combat', 'Ranged', 'Both', 'Fal''inrush, Defender of Quel''thalas'); + +-- Subtlety (tab 2) +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 2, 0, 0, 120, 32087, 'Pre-Raid', 'Rogue', 'Subtlety', 'Head', 'Both', 'Mask of the Deceiver'), +(4, 2, 1, 0, 120, 29381, 'Pre-Raid', 'Rogue', 'Subtlety', 'Neck', 'Both', 'Choker of Vile Intent'), +(4, 2, 2, 0, 120, 27797, 'Pre-Raid', 'Rogue', 'Subtlety', 'Shoulders', 'Both', 'Wastewalker Shoulderpads'), +(4, 2, 4, 0, 120, 30730, 'Pre-Raid', 'Rogue', 'Subtlety', 'Chest', 'Both', 'Terrorweave Tunic'), +(4, 2, 5, 0, 120, 29247, 'Pre-Raid', 'Rogue', 'Subtlety', 'Waist', 'Both', 'Girdle of the Deathdealer'), +(4, 2, 6, 0, 120, 30538, 'Pre-Raid', 'Rogue', 'Subtlety', 'Legs', 'Both', 'Midnight Legguards'), +(4, 2, 7, 0, 120, 25686, 'Pre-Raid', 'Rogue', 'Subtlety', 'Feet', 'Both', 'Fel Leather Boots'), +(4, 2, 8, 0, 120, 29246, 'Pre-Raid', 'Rogue', 'Subtlety', 'Wrists', 'Both', 'Nightfall Wristguards'), +(4, 2, 9, 0, 120, 27531, 'Pre-Raid', 'Rogue', 'Subtlety', 'Hands', 'Both', 'Wastewalker Gloves'), +(4, 2, 10, 0, 120, 30738, 'Pre-Raid', 'Rogue', 'Subtlety', 'Finger1', 'Both', 'Ring of Reciprocity'), +(4, 2, 12, 0, 120, 28288, 'Pre-Raid', 'Rogue', 'Subtlety', 'Trinket1', 'Both', 'Abacus of Violent Odds'), +(4, 2, 13, 0, 120, 29383, 'Pre-Raid', 'Rogue', 'Subtlety', 'Trinket2', 'Both', 'Bloodlust Brooch'), +(4, 2, 14, 0, 120, 24259, 'Pre-Raid', 'Rogue', 'Subtlety', 'Back', 'Both', 'Vengeance Wrap'), +(4, 2, 15, 0, 120, 31332, 'Pre-Raid', 'Rogue', 'Subtlety', 'MainHand', 'Both', 'Blinkstrike'), +(4, 2, 16, 0, 120, 28189, 'Pre-Raid', 'Rogue', 'Subtlety', 'OffHand', 'Both', 'Latro''s Shifting Sword'), +(4, 2, 17, 2, 120, 29152, 'Pre-Raid', 'Rogue', 'Subtlety', 'Ranged', 'Horde', 'Marksman''s Bow'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(4, 2, 0, 0, 125, 29044, 'Phase 1', 'Rogue', 'Subtlety', 'Head', 'Both', 'Netherblade Facemask'), +(4, 2, 1, 0, 125, 29381, 'Phase 1', 'Rogue', 'Subtlety', 'Neck', 'Both', 'Choker of Vile Intent'), +(4, 2, 2, 0, 125, 27797, 'Phase 1', 'Rogue', 'Subtlety', 'Shoulders', 'Both', 'Wastewalker Shoulderpads'), +(4, 2, 4, 0, 125, 29045, 'Phase 1', 'Rogue', 'Subtlety', 'Chest', 'Both', 'Netherblade Chestpiece'), +(4, 2, 5, 0, 125, 29247, 'Phase 1', 'Rogue', 'Subtlety', 'Waist', 'Both', 'Girdle of the Deathdealer'), +(4, 2, 6, 0, 125, 28741, 'Phase 1', 'Rogue', 'Subtlety', 'Legs', 'Both', 'Skulker''s Greaves'), +(4, 2, 7, 0, 125, 28545, 'Phase 1', 'Rogue', 'Subtlety', 'Feet', 'Both', 'Edgewalker Longboots'), +(4, 2, 8, 0, 125, 29246, 'Phase 1', 'Rogue', 'Subtlety', 'Wrists', 'Both', 'Nightfall Wristguards'), +(4, 2, 9, 0, 125, 27531, 'Phase 1', 'Rogue', 'Subtlety', 'Hands', 'Both', 'Wastewalker Gloves'), +(4, 2, 10, 0, 125, 28649, 'Phase 1', 'Rogue', 'Subtlety', 'Finger1', 'Both', 'Garona''s Signet Ring'), +(4, 2, 11, 0, 125, 28757, 'Phase 1', 'Rogue', 'Subtlety', 'Finger2', 'Both', 'Ring of a Thousand Marks'), +(4, 2, 12, 0, 125, 29383, 'Phase 1', 'Rogue', 'Subtlety', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(4, 2, 13, 0, 125, 28830, 'Phase 1', 'Rogue', 'Subtlety', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(4, 2, 14, 0, 125, 28672, 'Phase 1', 'Rogue', 'Subtlety', 'Back', 'Both', 'Drape of the Dark Reavers'), +(4, 2, 15, 0, 125, 31332, 'Phase 1', 'Rogue', 'Subtlety', 'MainHand', 'Both', 'Blinkstrike'), +(4, 2, 16, 0, 125, 28307, 'Phase 1', 'Rogue', 'Subtlety', 'OffHand', 'Both', 'Gladiator''s Quickblade'), +(4, 2, 17, 0, 125, 28772, 'Phase 1', 'Rogue', 'Subtlety', 'Ranged', 'Both', 'Sunfury Bow of the Phoenix'); + + +-- ============================================================ +-- Priest (5) +-- ============================================================ +-- Discipline (tab 0) +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 120, 24264, 'Pre-Raid', 'Priest', 'Discipline', 'Head', 'Both', 'Whitemend Hood'), +(5, 0, 1, 0, 120, 30377, 'Pre-Raid', 'Priest', 'Discipline', 'Neck', 'Both', 'Karja''s Medallion'), +(5, 0, 2, 0, 120, 21874, 'Pre-Raid', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Primal Mooncloth Shoulders'), +(5, 0, 4, 0, 120, 21875, 'Pre-Raid', 'Priest', 'Discipline', 'Chest', 'Both', 'Primal Mooncloth Robe'), +(5, 0, 5, 0, 120, 21873, 'Pre-Raid', 'Priest', 'Discipline', 'Waist', 'Both', 'Primal Mooncloth Belt'), +(5, 0, 6, 0, 120, 24261, 'Pre-Raid', 'Priest', 'Discipline', 'Legs', 'Both', 'Whitemend Pants'), +(5, 0, 7, 0, 120, 29251, 'Pre-Raid', 'Priest', 'Discipline', 'Feet', 'Both', 'Boots of the Pious'), +(5, 0, 8, 0, 120, 29249, 'Pre-Raid', 'Priest', 'Discipline', 'Wrists', 'Both', 'Bands of the Benevolent'), +(5, 0, 9, 0, 120, 24393, 'Pre-Raid', 'Priest', 'Discipline', 'Hands', 'Both', 'Bloody Surgeon''s Mitts'), +(5, 0, 10, 0, 120, 27780, 'Pre-Raid', 'Priest', 'Discipline', 'Finger1', 'Both', 'Ring of Fabled Hope'), +(5, 0, 10, 2, 120, 29168, 'Pre-Raid', 'Priest', 'Discipline', 'Finger1', 'Horde', 'Ancestral Band'), +(5, 0, 12, 0, 120, 19288, 'Pre-Raid', 'Priest', 'Discipline', 'Trinket1', 'Both', 'Darkmoon Card: Blue Dragon'), +(5, 0, 13, 0, 120, 29376, 'Pre-Raid', 'Priest', 'Discipline', 'Trinket2', 'Both', 'Essence of the Martyr'), +(5, 0, 14, 0, 120, 29354, 'Pre-Raid', 'Priest', 'Discipline', 'Back', 'Both', 'Light-Touched Stole of Altruism'), +(5, 0, 15, 0, 120, 29353, 'Pre-Raid', 'Priest', 'Discipline', 'MainHand', 'Both', 'Shockwave Truncheon'), +(5, 0, 16, 0, 120, 29170, 'Pre-Raid', 'Priest', 'Discipline', 'OffHand', 'Both', 'Windcaller''s Orb'), +(5, 0, 17, 0, 120, 27885, 'Pre-Raid', 'Priest', 'Discipline', 'Ranged', 'Both', 'Soul-Wand of the Aldor'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 125, 29049, 'Phase 1', 'Priest', 'Discipline', 'Head', 'Both', 'Light-Collar of the Incarnate'), +(5, 0, 1, 0, 125, 30726, 'Phase 1', 'Priest', 'Discipline', 'Neck', 'Both', 'Archaic Charm of Presence'), +(5, 0, 2, 0, 125, 21874, 'Phase 1', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Primal Mooncloth Shoulders'), +(5, 0, 4, 0, 125, 21875, 'Phase 1', 'Priest', 'Discipline', 'Chest', 'Both', 'Primal Mooncloth Robe'), +(5, 0, 5, 0, 125, 21873, 'Phase 1', 'Priest', 'Discipline', 'Waist', 'Both', 'Primal Mooncloth Belt'), +(5, 0, 6, 0, 125, 30727, 'Phase 1', 'Priest', 'Discipline', 'Legs', 'Both', 'Gilded Trousers of Benediction'), +(5, 0, 7, 0, 125, 28663, 'Phase 1', 'Priest', 'Discipline', 'Feet', 'Both', 'Boots of the Incorrupt'), +(5, 0, 8, 0, 125, 29249, 'Phase 1', 'Priest', 'Discipline', 'Wrists', 'Both', 'Bands of the Benevolent'), +(5, 0, 9, 0, 125, 28508, 'Phase 1', 'Priest', 'Discipline', 'Hands', 'Both', 'Gloves of Saintly Blessings'), +(5, 0, 10, 0, 125, 29290, 'Phase 1', 'Priest', 'Discipline', 'Finger1', 'Both', 'Violet Signet of the Grand Restorer'), +(5, 0, 11, 0, 125, 28763, 'Phase 1', 'Priest', 'Discipline', 'Finger2', 'Both', 'Jade Ring of the Everliving'), +(5, 0, 12, 0, 125, 29376, 'Phase 1', 'Priest', 'Discipline', 'Trinket1', 'Both', 'Essence of the Martyr'), +(5, 0, 13, 0, 125, 28823, 'Phase 1', 'Priest', 'Discipline', 'Trinket2', 'Both', 'Eye of Gruul'), +(5, 0, 14, 0, 125, 28765, 'Phase 1', 'Priest', 'Discipline', 'Back', 'Both', 'Stainless Cloak of the Pure Hearted'), +(5, 0, 15, 0, 125, 28771, 'Phase 1', 'Priest', 'Discipline', 'MainHand', 'Both', 'Light''s Justice'), +(5, 0, 16, 0, 125, 29170, 'Phase 1', 'Priest', 'Discipline', 'OffHand', 'Both', 'Windcaller''s Orb'), +(5, 0, 17, 0, 125, 28588, 'Phase 1', 'Priest', 'Discipline', 'Ranged', 'Both', 'Blue Diamond Witchwand'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 141, 30152, 'Phase 2', 'Priest', 'Discipline', 'Head', 'Both', 'Cowl of the Avatar'), +(5, 0, 1, 0, 141, 30018, 'Phase 2', 'Priest', 'Discipline', 'Neck', 'Both', 'Lord Sanguinar''s Claim'), +(5, 0, 2, 0, 141, 30154, 'Phase 2', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Mantle of the Avatar'), +(5, 0, 4, 0, 141, 30150, 'Phase 2', 'Priest', 'Discipline', 'Chest', 'Both', 'Vestments of the Avatar'), +(5, 0, 5, 0, 141, 30036, 'Phase 2', 'Priest', 'Discipline', 'Waist', 'Both', 'Belt of the Long Road'), +(5, 0, 6, 0, 141, 30727, 'Phase 2', 'Priest', 'Discipline', 'Legs', 'Both', 'Gilded Trousers of Benediction'), +(5, 0, 7, 0, 141, 30100, 'Phase 2', 'Priest', 'Discipline', 'Feet', 'Both', 'Soul-Strider Boots'), +(5, 0, 8, 0, 141, 32516, 'Phase 2', 'Priest', 'Discipline', 'Wrists', 'Both', 'Wraps of Purification'), +(5, 0, 9, 0, 141, 30151, 'Phase 2', 'Priest', 'Discipline', 'Hands', 'Both', 'Gloves of the Avatar'), +(5, 0, 10, 0, 141, 30110, 'Phase 2', 'Priest', 'Discipline', 'Finger1', 'Both', 'Coral Band of the Revived'), +(5, 0, 11, 0, 141, 29290, 'Phase 2', 'Priest', 'Discipline', 'Finger2', 'Both', 'Violet Signet of the Grand Restorer'), +(5, 0, 12, 0, 141, 29376, 'Phase 2', 'Priest', 'Discipline', 'Trinket1', 'Both', 'Essence of the Martyr'), +(5, 0, 13, 0, 141, 28823, 'Phase 2', 'Priest', 'Discipline', 'Trinket2', 'Both', 'Eye of Gruul'), +(5, 0, 14, 0, 141, 29989, 'Phase 2', 'Priest', 'Discipline', 'Back', 'Both', 'Sunshower Light Cloak'), +(5, 0, 15, 0, 141, 30108, 'Phase 2', 'Priest', 'Discipline', 'MainHand', 'Both', 'Lightfathom Scepter'), +(5, 0, 16, 0, 141, 29923, 'Phase 2', 'Priest', 'Discipline', 'OffHand', 'Both', 'Talisman of the Sun King'), +(5, 0, 17, 0, 141, 30080, 'Phase 2', 'Priest', 'Discipline', 'Ranged', 'Both', 'Luminescent Rod of the Naaru'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 156, 31063, 'Phase 3', 'Priest', 'Discipline', 'Head', 'Both', 'Cowl of Absolution'), +(5, 0, 1, 0, 156, 32370, 'Phase 3', 'Priest', 'Discipline', 'Neck', 'Both', 'Nadina''s Pendant of Purity'), +(5, 0, 2, 0, 156, 31069, 'Phase 3', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Mantle of Absolution'), +(5, 0, 4, 0, 156, 31066, 'Phase 3', 'Priest', 'Discipline', 'Chest', 'Both', 'Vestments of Absolution'), +(5, 0, 5, 0, 156, 32519, 'Phase 3', 'Priest', 'Discipline', 'Waist', 'Both', 'Belt of Divine Guidance'), +(5, 0, 6, 0, 156, 30912, 'Phase 3', 'Priest', 'Discipline', 'Legs', 'Both', 'Leggings of Eternity'), +(5, 0, 7, 0, 156, 32609, 'Phase 3', 'Priest', 'Discipline', 'Feet', 'Both', 'Boots of the Divine Light'), +(5, 0, 8, 0, 156, 30871, 'Phase 3', 'Priest', 'Discipline', 'Wrists', 'Both', 'Bracers of Martyrdom'), +(5, 0, 9, 0, 156, 31060, 'Phase 3', 'Priest', 'Discipline', 'Hands', 'Both', 'Gloves of Absolution'), +(5, 0, 10, 0, 156, 32528, 'Phase 3', 'Priest', 'Discipline', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(5, 0, 11, 0, 156, 32528, 'Phase 3', 'Priest', 'Discipline', 'Finger2', 'Both', 'Blessed Band of Karabor'), +(5, 0, 12, 0, 156, 29376, 'Phase 3', 'Priest', 'Discipline', 'Trinket1', 'Both', 'Essence of the Martyr'), +(5, 0, 13, 0, 156, 32496, 'Phase 3', 'Priest', 'Discipline', 'Trinket2', 'Both', 'Memento of Tyrande'), +(5, 0, 14, 0, 156, 32524, 'Phase 3', 'Priest', 'Discipline', 'Back', 'Both', 'Shroud of the Highborne'), +(5, 0, 15, 0, 156, 32500, 'Phase 3', 'Priest', 'Discipline', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(5, 0, 16, 0, 156, 30911, 'Phase 3', 'Priest', 'Discipline', 'OffHand', 'Both', 'Scepter of Purification'), +(5, 0, 17, 0, 156, 32363, 'Phase 3', 'Priest', 'Discipline', 'Ranged', 'Both', 'Naaru-Blessed Life Rod'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 164, 31063, 'Phase 4', 'Priest', 'Discipline', 'Head', 'Both', 'Cowl of Absolution'), +(5, 0, 1, 0, 164, 33281, 'Phase 4', 'Priest', 'Discipline', 'Neck', 'Both', 'Brooch of Nature''s Mercy'), +(5, 0, 2, 0, 164, 31069, 'Phase 4', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Mantle of Absolution'), +(5, 0, 4, 0, 164, 31066, 'Phase 4', 'Priest', 'Discipline', 'Chest', 'Both', 'Vestments of Absolution'), +(5, 0, 5, 0, 164, 32519, 'Phase 4', 'Priest', 'Discipline', 'Waist', 'Both', 'Belt of Divine Guidance'), +(5, 0, 6, 0, 164, 30912, 'Phase 4', 'Priest', 'Discipline', 'Legs', 'Both', 'Leggings of Eternity'), +(5, 0, 7, 0, 164, 32609, 'Phase 4', 'Priest', 'Discipline', 'Feet', 'Both', 'Boots of the Divine Light'), +(5, 0, 8, 0, 164, 30871, 'Phase 4', 'Priest', 'Discipline', 'Wrists', 'Both', 'Bracers of Martyrdom'), +(5, 0, 9, 0, 164, 31060, 'Phase 4', 'Priest', 'Discipline', 'Hands', 'Both', 'Gloves of Absolution'), +(5, 0, 10, 0, 164, 32528, 'Phase 4', 'Priest', 'Discipline', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(5, 0, 11, 0, 164, 30110, 'Phase 4', 'Priest', 'Discipline', 'Finger2', 'Both', 'Coral Band of the Revived'), +(5, 0, 12, 0, 164, 29376, 'Phase 4', 'Priest', 'Discipline', 'Trinket1', 'Both', 'Essence of the Martyr'), +(5, 0, 13, 0, 164, 32496, 'Phase 4', 'Priest', 'Discipline', 'Trinket2', 'Both', 'Memento of Tyrande'), +(5, 0, 14, 0, 164, 32524, 'Phase 4', 'Priest', 'Discipline', 'Back', 'Both', 'Shroud of the Highborne'), +(5, 0, 15, 0, 164, 32500, 'Phase 4', 'Priest', 'Discipline', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(5, 0, 16, 0, 164, 30911, 'Phase 4', 'Priest', 'Discipline', 'OffHand', 'Both', 'Scepter of Purification'), +(5, 0, 17, 0, 164, 32363, 'Phase 4', 'Priest', 'Discipline', 'Ranged', 'Both', 'Naaru-Blessed Life Rod'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 200, 37294, 'Pre-Raid', 'Priest', 'Discipline', 'Head', 'Both', 'Crown of Unbridled Magic'), +(5, 0, 1, 0, 200, 40681, 'Pre-Raid', 'Priest', 'Discipline', 'Neck', 'Both', 'Lattice Choker of Light'), +(5, 0, 2, 0, 200, 37673, 'Pre-Raid', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Dark Runic Mantle'), +(5, 0, 4, 0, 200, 44180, 'Pre-Raid', 'Priest', 'Discipline', 'Chest', 'Both', 'Robes of Crackling Flame'), +(5, 0, 5, 0, 200, 40697, 'Pre-Raid', 'Priest', 'Discipline', 'Waist', 'Both', 'Elegant Temple Gardens'' Girdle'), +(5, 0, 6, 0, 200, 37854, 'Pre-Raid', 'Priest', 'Discipline', 'Legs', 'Both', 'Woven Bracae Leggings'), +(5, 0, 7, 0, 200, 44202, 'Pre-Raid', 'Priest', 'Discipline', 'Feet', 'Both', 'Sandals of Crimson Fury'), +(5, 0, 8, 0, 200, 37361, 'Pre-Raid', 'Priest', 'Discipline', 'Wrists', 'Both', 'Cuffs of Winged Levitation'), +(5, 0, 9, 0, 200, 37172, 'Pre-Raid', 'Priest', 'Discipline', 'Hands', 'Both', 'Gloves of Glistening Runes'), +(5, 0, 10, 0, 200, 37694, 'Pre-Raid', 'Priest', 'Discipline', 'Finger1', 'Both', 'Band of Guile'), +(5, 0, 12, 0, 200, 37835, 'Pre-Raid', 'Priest', 'Discipline', 'Trinket1', 'Both', 'Je''Tze''s Bell'), +(5, 0, 14, 0, 200, 41610, 'Pre-Raid', 'Priest', 'Discipline', 'Back', 'Both', 'Deathchill Cloak'), +(5, 0, 15, 0, 200, 37169, 'Pre-Raid', 'Priest', 'Discipline', 'MainHand', 'Both', 'War Mace of Unrequited Love'), +(5, 0, 17, 0, 200, 37619, 'Pre-Raid', 'Priest', 'Discipline', 'Ranged', 'Both', 'Wand of Ahn''kahet'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 224, 40447, 'Phase 1', 'Priest', 'Discipline', 'Head', 'Both', 'Valorous Crown of Faith'), +(5, 0, 1, 0, 224, 44661, 'Phase 1', 'Priest', 'Discipline', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(5, 0, 2, 0, 224, 40450, 'Phase 1', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Valorous Shoulderpads of Faith'), +(5, 0, 4, 0, 224, 44002, 'Phase 1', 'Priest', 'Discipline', 'Chest', 'Both', 'The Sanctum''s Flowing Vestments'), +(5, 0, 5, 0, 224, 40561, 'Phase 1', 'Priest', 'Discipline', 'Waist', 'Both', 'Leash of Heedless Magic'), +(5, 0, 6, 0, 224, 40448, 'Phase 1', 'Priest', 'Discipline', 'Legs', 'Both', 'Valorous Leggings of Faith'), +(5, 0, 7, 0, 224, 40558, 'Phase 1', 'Priest', 'Discipline', 'Feet', 'Both', 'Arcanic Tramplers'), +(5, 0, 8, 0, 224, 44008, 'Phase 1', 'Priest', 'Discipline', 'Wrists', 'Both', 'Unsullied Cuffs'), +(5, 0, 9, 0, 224, 40445, 'Phase 1', 'Priest', 'Discipline', 'Hands', 'Both', 'Valorous Gloves of Faith'), +(5, 0, 10, 0, 224, 40399, 'Phase 1', 'Priest', 'Discipline', 'Finger1', 'Both', 'Signet of Manifested Pain'), +(5, 0, 12, 0, 224, 37835, 'Phase 1', 'Priest', 'Discipline', 'Trinket1', 'Both', 'Je''Tze''s Bell'), +(5, 0, 14, 0, 224, 44005, 'Phase 1', 'Priest', 'Discipline', 'Back', 'Both', 'Pennant Cloak'), +(5, 0, 17, 0, 224, 39426, 'Phase 1', 'Priest', 'Discipline', 'Ranged', 'Both', 'Wand of the Archlich'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 245, 46197, 'Phase 2', 'Priest', 'Discipline', 'Head', 'Both', 'Conqueror''s Cowl of Sanctification'), +(5, 0, 1, 0, 245, 45443, 'Phase 2', 'Priest', 'Discipline', 'Neck', 'Both', 'Charm of Meticulous Timing'), +(5, 0, 2, 0, 245, 46190, 'Phase 2', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Conqueror''s Shoulderpads of Sanctification'), +(5, 0, 4, 0, 245, 46193, 'Phase 2', 'Priest', 'Discipline', 'Chest', 'Both', 'Conqueror''s Robe of Sanctification'), +(5, 0, 5, 0, 245, 45619, 'Phase 2', 'Priest', 'Discipline', 'Waist', 'Both', 'Starwatcher''s Binding'), +(5, 0, 6, 0, 245, 46195, 'Phase 2', 'Priest', 'Discipline', 'Legs', 'Both', 'Conqueror''s Leggings of Sanctification'), +(5, 0, 7, 0, 245, 45135, 'Phase 2', 'Priest', 'Discipline', 'Feet', 'Both', 'Boots of Fiery Resolution'), +(5, 0, 8, 0, 245, 45446, 'Phase 2', 'Priest', 'Discipline', 'Wrists', 'Both', 'Grasps of Reason'), +(5, 0, 9, 0, 245, 45520, 'Phase 2', 'Priest', 'Discipline', 'Hands', 'Both', 'Handwraps of the Vigilant'), +(5, 0, 12, 0, 245, 45535, 'Phase 2', 'Priest', 'Discipline', 'Trinket1', 'Both', 'Show of Faith'), +(5, 0, 14, 0, 245, 45486, 'Phase 2', 'Priest', 'Discipline', 'Back', 'Both', 'Drape of the Sullen Goddess'), +(5, 0, 15, 0, 245, 46017, 'Phase 2', 'Priest', 'Discipline', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(5, 0, 17, 0, 245, 45294, 'Phase 2', 'Priest', 'Discipline', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 258, 46197, 'Phase 3', 'Priest', 'Discipline', 'Head', 'Both', 'Conqueror''s Cowl of Sanctification'), +(5, 0, 2, 0, 258, 46190, 'Phase 3', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Conqueror''s Shoulderpads of Sanctification'), +(5, 0, 4, 0, 258, 48031, 'Phase 3', 'Priest', 'Discipline', 'Chest', 'Both', 'Robe of Triumph'), +(5, 0, 5, 1, 258, 47084, 'Phase 3', 'Priest', 'Discipline', 'Waist', 'Alliance', 'Cord of Biting Cold'), +(5, 0, 5, 2, 258, 47447, 'Phase 3', 'Priest', 'Discipline', 'Waist', 'Horde', 'Belt of Biting Cold'), +(5, 0, 6, 1, 258, 47189, 'Phase 3', 'Priest', 'Discipline', 'Legs', 'Alliance', 'Leggings of the Deepening Void'), +(5, 0, 6, 2, 258, 47478, 'Phase 3', 'Priest', 'Discipline', 'Legs', 'Horde', 'Breeches of the Deepening Void'), +(5, 0, 7, 1, 258, 47097, 'Phase 3', 'Priest', 'Discipline', 'Feet', 'Alliance', 'Boots of the Mourning Widow'), +(5, 0, 7, 2, 258, 47454, 'Phase 3', 'Priest', 'Discipline', 'Feet', 'Horde', 'Sandals of the Mourning Widow'), +(5, 0, 8, 1, 258, 47143, 'Phase 3', 'Priest', 'Discipline', 'Wrists', 'Alliance', 'Bindings of Dark Essence'), +(5, 0, 8, 2, 258, 47467, 'Phase 3', 'Priest', 'Discipline', 'Wrists', 'Horde', 'Dark Essence Bindings'), +(5, 0, 9, 0, 258, 46188, 'Phase 3', 'Priest', 'Discipline', 'Hands', 'Both', 'Conqueror''s Gloves of Sanctification'), +(5, 0, 10, 1, 258, 47237, 'Phase 3', 'Priest', 'Discipline', 'Finger1', 'Alliance', 'Band of Deplorable Violence'), +(5, 0, 10, 2, 258, 47489, 'Phase 3', 'Priest', 'Discipline', 'Finger1', 'Horde', 'Lurid Manifestation'), +(5, 0, 12, 1, 258, 47059, 'Phase 3', 'Priest', 'Discipline', 'Trinket1', 'Alliance', 'Solace of the Defeated'), +(5, 0, 12, 2, 258, 47432, 'Phase 3', 'Priest', 'Discipline', 'Trinket1', 'Horde', 'Solace of the Fallen'), +(5, 0, 14, 1, 258, 47552, 'Phase 3', 'Priest', 'Discipline', 'Back', 'Alliance', 'Jaina''s Radiance'), +(5, 0, 14, 2, 258, 47551, 'Phase 3', 'Priest', 'Discipline', 'Back', 'Horde', 'Aethas'' Intensity'), +(5, 0, 17, 0, 258, 45294, 'Phase 3', 'Priest', 'Discipline', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 264, 51261, 'Phase 4', 'Priest', 'Discipline', 'Head', 'Both', 'Sanctified Crimson Acolyte Hood'), +(5, 0, 2, 0, 264, 51264, 'Phase 4', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Sanctified Crimson Acolyte Shoulderpads'), +(5, 0, 4, 0, 264, 51263, 'Phase 4', 'Priest', 'Discipline', 'Chest', 'Both', 'Sanctified Crimson Acolyte Robe'), +(5, 0, 5, 0, 264, 50702, 'Phase 4', 'Priest', 'Discipline', 'Waist', 'Both', 'Lingering Illness'), +(5, 0, 6, 0, 264, 51262, 'Phase 4', 'Priest', 'Discipline', 'Legs', 'Both', 'Sanctified Crimson Acolyte Leggings'), +(5, 0, 7, 0, 264, 50699, 'Phase 4', 'Priest', 'Discipline', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(5, 0, 8, 0, 264, 50686, 'Phase 4', 'Priest', 'Discipline', 'Wrists', 'Both', 'Death Surgeon''s Sleeves'), +(5, 0, 9, 0, 264, 50722, 'Phase 4', 'Priest', 'Discipline', 'Hands', 'Both', 'San''layn Ritualist Gloves'), +(5, 0, 10, 0, 264, 50720, 'Phase 4', 'Priest', 'Discipline', 'Finger1', 'Both', 'Incarnadine Band of Mending'), +(5, 0, 12, 1, 264, 47059, 'Phase 4', 'Priest', 'Discipline', 'Trinket1', 'Alliance', 'Solace of the Defeated'), +(5, 0, 12, 2, 264, 47432, 'Phase 4', 'Priest', 'Discipline', 'Trinket1', 'Horde', 'Solace of the Fallen'), +(5, 0, 14, 0, 264, 50628, 'Phase 4', 'Priest', 'Discipline', 'Back', 'Both', 'Frostbinder''s Shredded Cape'), +(5, 0, 15, 0, 264, 50734, 'Phase 4', 'Priest', 'Discipline', 'MainHand', 'Both', 'Royal Scepter of Terenas II'), +(5, 0, 17, 0, 264, 50631, 'Phase 4', 'Priest', 'Discipline', 'Ranged', 'Both', 'Nightmare Ender'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 0, 0, 0, 290, 51261, 'Phase 5', 'Priest', 'Discipline', 'Head', 'Both', 'Sanctified Crimson Acolyte Hood'), +(5, 0, 1, 0, 290, 50182, 'Phase 5', 'Priest', 'Discipline', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(5, 0, 2, 0, 290, 51264, 'Phase 5', 'Priest', 'Discipline', 'Shoulders', 'Both', 'Sanctified Crimson Acolyte Shoulderpads'), +(5, 0, 4, 0, 290, 51263, 'Phase 5', 'Priest', 'Discipline', 'Chest', 'Both', 'Sanctified Crimson Acolyte Robe'), +(5, 0, 5, 0, 290, 50613, 'Phase 5', 'Priest', 'Discipline', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(5, 0, 6, 0, 290, 51262, 'Phase 5', 'Priest', 'Discipline', 'Legs', 'Both', 'Sanctified Crimson Acolyte Leggings'), +(5, 0, 7, 0, 290, 50699, 'Phase 5', 'Priest', 'Discipline', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(5, 0, 8, 0, 290, 54582, 'Phase 5', 'Priest', 'Discipline', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(5, 0, 9, 0, 290, 50722, 'Phase 5', 'Priest', 'Discipline', 'Hands', 'Both', 'San''layn Ritualist Gloves'), +(5, 0, 10, 0, 290, 50644, 'Phase 5', 'Priest', 'Discipline', 'Finger1', 'Both', 'Ring of Maddening Whispers'), +(5, 0, 11, 0, 290, 50636, 'Phase 5', 'Priest', 'Discipline', 'Finger2', 'Both', 'Memory of Malygos'), +(5, 0, 12, 0, 290, 50366, 'Phase 5', 'Priest', 'Discipline', 'Trinket1', 'Both', 'Althor''s Abacus'), +(5, 0, 13, 0, 290, 54589, 'Phase 5', 'Priest', 'Discipline', 'Trinket2', 'Both', 'Glowing Twilight Scale'), +(5, 0, 14, 0, 290, 54583, 'Phase 5', 'Priest', 'Discipline', 'Back', 'Both', 'Cloak of Burning Dusk'), +(5, 0, 15, 0, 290, 50734, 'Phase 5', 'Priest', 'Discipline', 'MainHand', 'Both', 'Royal Scepter of Terenas II'), +(5, 0, 16, 0, 290, 50719, 'Phase 5', 'Priest', 'Discipline', 'OffHand', 'Both', 'Shadow Silk Spindle'), +(5, 0, 17, 0, 290, 50631, 'Phase 5', 'Priest', 'Discipline', 'Ranged', 'Both', 'Nightmare Ender'); + +-- Holy (tab 1) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 66, 13102, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Head', 'Both', 'Cassandra''s Grace'), +(5, 1, 1, 0, 66, 18723, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Neck', 'Both', 'Animated Chain Necklace'), +(5, 1, 2, 0, 66, 13013, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Shoulders', 'Both', 'Elder Wizard''s Mantle'), +(5, 1, 4, 0, 66, 14154, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Chest', 'Both', 'Truefaith Vestments'), +(5, 1, 5, 0, 66, 14143, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Waist', 'Both', 'Ghostweave Belt'), +(5, 1, 6, 0, 66, 11841, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Legs', 'Both', 'Senior Designer''s Pantaloons'), +(5, 1, 7, 0, 66, 11822, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Feet', 'Both', 'Omnicast Boots'), +(5, 1, 8, 0, 66, 11766, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Wrists', 'Both', 'Flameweave Cuffs'), +(5, 1, 9, 0, 66, 10787, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Hands', 'Both', 'Atal''ai Gloves'), +(5, 1, 10, 0, 66, 16058, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Finger1', 'Both', 'Fordring''s Seal'), +(5, 1, 11, 0, 66, 13178, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Finger2', 'Both', 'Rosewine Circle'), +(5, 1, 12, 0, 66, 11819, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Trinket1', 'Both', 'Second Wind'), +(5, 1, 13, 0, 66, 12930, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Trinket2', 'Both', 'Briarwood Reed'), +(5, 1, 14, 0, 66, 13386, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Back', 'Both', 'Archivist Cape'), +(5, 1, 15, 0, 66, 11923, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'MainHand', 'Both', 'The Hammer of Grace'), +(5, 1, 16, 0, 66, 11928, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'OffHand', 'Both', 'Thaurissan''s Royal Scepter'), +(5, 1, 17, 0, 66, 16997, 'Phase 1 (Pre-Raid)', 'Priest', 'Holy', 'Ranged', 'Both', 'Stormrager'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 76, 13102, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Head', 'Both', 'Cassandra''s Grace'), +(5, 1, 1, 0, 76, 18723, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Neck', 'Both', 'Animated Chain Necklace'), +(5, 1, 2, 0, 76, 13013, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Shoulders', 'Both', 'Elder Wizard''s Mantle'), +(5, 1, 4, 0, 76, 14154, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Chest', 'Both', 'Truefaith Vestments'), +(5, 1, 5, 0, 76, 18327, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Waist', 'Both', 'Whipvine Cord'), +(5, 1, 6, 0, 76, 18386, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Legs', 'Both', 'Padre''s Trousers'), +(5, 1, 7, 0, 76, 18507, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Feet', 'Both', 'Boots of the Full Moon'), +(5, 1, 8, 0, 76, 11766, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Wrists', 'Both', 'Flameweave Cuffs'), +(5, 1, 9, 0, 76, 10787, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Hands', 'Both', 'Atal''ai Gloves'), +(5, 1, 10, 0, 76, 16058, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Finger1', 'Both', 'Fordring''s Seal'), +(5, 1, 11, 0, 76, 13178, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Finger2', 'Both', 'Rosewine Circle'), +(5, 1, 12, 0, 76, 18469, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Trinket1', 'Both', 'Royal Seal of Eldre''Thalas'), +(5, 1, 13, 0, 76, 18371, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Trinket2', 'Both', 'Mindtap Talisman'), +(5, 1, 14, 0, 76, 18510, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Back', 'Both', 'Hide of the Wild'), +(5, 1, 15, 0, 76, 11923, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'MainHand', 'Both', 'The Hammer of Grace'), +(5, 1, 16, 0, 76, 18523, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'OffHand', 'Both', 'Brightly Glowing Stone'), +(5, 1, 17, 0, 76, 18483, 'Phase 2 (Pre-Raid)', 'Priest', 'Holy', 'Ranged', 'Both', 'Mana Channeling Wand'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 78, 16921, 'Phase 2', 'Priest', 'Holy', 'Head', 'Both', 'Halo of Transcendence'), +(5, 1, 1, 0, 78, 18723, 'Phase 2', 'Priest', 'Holy', 'Neck', 'Both', 'Animated Chain Necklace'), +(5, 1, 2, 0, 78, 16816, 'Phase 2', 'Priest', 'Holy', 'Shoulders', 'Both', 'Mantle of Prophecy'), +(5, 1, 4, 0, 78, 14154, 'Phase 2', 'Priest', 'Holy', 'Chest', 'Both', 'Truefaith Vestments'), +(5, 1, 5, 0, 78, 16817, 'Phase 2', 'Priest', 'Holy', 'Waist', 'Both', 'Girdle of Prophecy'), +(5, 1, 6, 0, 78, 16922, 'Phase 2', 'Priest', 'Holy', 'Legs', 'Both', 'Leggings of Transcendence'), +(5, 1, 7, 0, 78, 16811, 'Phase 2', 'Priest', 'Holy', 'Feet', 'Both', 'Boots of Prophecy'), +(5, 1, 8, 0, 78, 16819, 'Phase 2', 'Priest', 'Holy', 'Wrists', 'Both', 'Vambraces of Prophecy'), +(5, 1, 9, 0, 78, 16812, 'Phase 2', 'Priest', 'Holy', 'Hands', 'Both', 'Gloves of Prophecy'), +(5, 1, 10, 0, 78, 19140, 'Phase 2', 'Priest', 'Holy', 'Finger1', 'Both', 'Cauterizing Band'), +(5, 1, 11, 0, 78, 19140, 'Phase 2', 'Priest', 'Holy', 'Finger2', 'Both', 'Cauterizing Band'), +(5, 1, 12, 0, 78, 18469, 'Phase 2', 'Priest', 'Holy', 'Trinket1', 'Both', 'Royal Seal of Eldre''Thalas'), +(5, 1, 13, 0, 78, 17064, 'Phase 2', 'Priest', 'Holy', 'Trinket2', 'Both', 'Shard of the Scale'), +(5, 1, 14, 0, 78, 18510, 'Phase 2', 'Priest', 'Holy', 'Back', 'Both', 'Hide of the Wild'), +(5, 1, 15, 0, 78, 18608, 'Phase 2', 'Priest', 'Holy', 'MainHand', 'Both', 'Benediction'), +(5, 1, 17, 0, 78, 18483, 'Phase 2', 'Priest', 'Holy', 'Ranged', 'Both', 'Mana Channeling Wand'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 83, 16921, 'Phase 3', 'Priest', 'Holy', 'Head', 'Both', 'Halo of Transcendence'), +(5, 1, 1, 0, 83, 18723, 'Phase 3', 'Priest', 'Holy', 'Neck', 'Both', 'Animated Chain Necklace'), +(5, 1, 2, 0, 83, 16924, 'Phase 3', 'Priest', 'Holy', 'Shoulders', 'Both', 'Pauldrons of Transcendence'), +(5, 1, 4, 0, 83, 16923, 'Phase 3', 'Priest', 'Holy', 'Chest', 'Both', 'Robes of Transcendence'), +(5, 1, 5, 0, 83, 16925, 'Phase 3', 'Priest', 'Holy', 'Waist', 'Both', 'Belt of Transcendence'), +(5, 1, 6, 0, 83, 16922, 'Phase 3', 'Priest', 'Holy', 'Legs', 'Both', 'Leggings of Transcendence'), +(5, 1, 7, 0, 83, 16919, 'Phase 3', 'Priest', 'Holy', 'Feet', 'Both', 'Boots of Transcendence'), +(5, 1, 8, 0, 83, 16926, 'Phase 3', 'Priest', 'Holy', 'Wrists', 'Both', 'Bindings of Transcendence'), +(5, 1, 9, 0, 83, 16920, 'Phase 3', 'Priest', 'Holy', 'Hands', 'Both', 'Handguards of Transcendence'), +(5, 1, 10, 0, 83, 19382, 'Phase 3', 'Priest', 'Holy', 'Finger1', 'Both', 'Pure Elementium Band'), +(5, 1, 11, 0, 83, 19140, 'Phase 3', 'Priest', 'Holy', 'Finger2', 'Both', 'Cauterizing Band'), +(5, 1, 12, 0, 83, 19395, 'Phase 3', 'Priest', 'Holy', 'Trinket1', 'Both', 'Rejuvenating Gem'), +(5, 1, 13, 0, 83, 17064, 'Phase 3', 'Priest', 'Holy', 'Trinket2', 'Both', 'Shard of the Scale'), +(5, 1, 14, 0, 83, 19430, 'Phase 3', 'Priest', 'Holy', 'Back', 'Both', 'Shroud of Pure Thought'), +(5, 1, 15, 0, 83, 18608, 'Phase 3', 'Priest', 'Holy', 'MainHand', 'Both', 'Benediction'), +(5, 1, 17, 0, 83, 19435, 'Phase 3', 'Priest', 'Holy', 'Ranged', 'Both', 'Essence Gatherer'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 88, 21615, 'Phase 5', 'Priest', 'Holy', 'Head', 'Both', 'Don Rigoberto''s Lost Hat'), +(5, 1, 1, 0, 88, 21712, 'Phase 5', 'Priest', 'Holy', 'Neck', 'Both', 'Amulet of the Fallen God'), +(5, 1, 2, 0, 88, 16924, 'Phase 5', 'Priest', 'Holy', 'Shoulders', 'Both', 'Pauldrons of Transcendence'), +(5, 1, 4, 0, 88, 21663, 'Phase 5', 'Priest', 'Holy', 'Chest', 'Both', 'Robes of the Guardian Saint'), +(5, 1, 5, 0, 88, 21582, 'Phase 5', 'Priest', 'Holy', 'Waist', 'Both', 'Grasp of the Old God'), +(5, 1, 6, 0, 88, 16922, 'Phase 5', 'Priest', 'Holy', 'Legs', 'Both', 'Leggings of Transcendence'), +(5, 1, 7, 0, 88, 19437, 'Phase 5', 'Priest', 'Holy', 'Feet', 'Both', 'Boots of Pure Thought'), +(5, 1, 8, 0, 88, 16926, 'Phase 5', 'Priest', 'Holy', 'Wrists', 'Both', 'Bindings of Transcendence'), +(5, 1, 9, 0, 88, 21619, 'Phase 5', 'Priest', 'Holy', 'Hands', 'Both', 'Gloves of the Messiah'), +(5, 1, 10, 0, 88, 19382, 'Phase 5', 'Priest', 'Holy', 'Finger1', 'Both', 'Pure Elementium Band'), +(5, 1, 11, 0, 88, 21620, 'Phase 5', 'Priest', 'Holy', 'Finger2', 'Both', 'Ring of the Martyr'), +(5, 1, 12, 0, 88, 19395, 'Phase 5', 'Priest', 'Holy', 'Trinket1', 'Both', 'Rejuvenating Gem'), +(5, 1, 13, 0, 88, 17064, 'Phase 5', 'Priest', 'Holy', 'Trinket2', 'Both', 'Shard of the Scale'), +(5, 1, 14, 0, 88, 21583, 'Phase 5', 'Priest', 'Holy', 'Back', 'Both', 'Cloak of Clarity'), +(5, 1, 15, 0, 88, 21839, 'Phase 5', 'Priest', 'Holy', 'MainHand', 'Both', 'Scepter of the False Prophet'), +(5, 1, 16, 0, 88, 21666, 'Phase 5', 'Priest', 'Holy', 'OffHand', 'Both', 'Sartura''s Might'), +(5, 1, 17, 0, 88, 21801, 'Phase 5', 'Priest', 'Holy', 'Ranged', 'Both', 'Antenna of Invigoration'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 92, 22514, 'Phase 6', 'Priest', 'Holy', 'Head', 'Both', 'Circlet of Faith'), +(5, 1, 1, 0, 92, 21712, 'Phase 6', 'Priest', 'Holy', 'Neck', 'Both', 'Amulet of the Fallen God'), +(5, 1, 2, 0, 92, 22515, 'Phase 6', 'Priest', 'Holy', 'Shoulders', 'Both', 'Shoulderpads of Faith'), +(5, 1, 4, 0, 92, 22512, 'Phase 6', 'Priest', 'Holy', 'Chest', 'Both', 'Robe of Faith'), +(5, 1, 5, 0, 92, 21582, 'Phase 6', 'Priest', 'Holy', 'Waist', 'Both', 'Grasp of the Old God'), +(5, 1, 6, 0, 92, 22513, 'Phase 6', 'Priest', 'Holy', 'Legs', 'Both', 'Leggings of Faith'), +(5, 1, 7, 0, 92, 22516, 'Phase 6', 'Priest', 'Holy', 'Feet', 'Both', 'Sandals of Faith'), +(5, 1, 8, 0, 92, 21604, 'Phase 6', 'Priest', 'Holy', 'Wrists', 'Both', 'Bracelets of Royal Redemption'), +(5, 1, 9, 0, 92, 22517, 'Phase 6', 'Priest', 'Holy', 'Hands', 'Both', 'Gloves of Faith'), +(5, 1, 10, 0, 92, 23061, 'Phase 6', 'Priest', 'Holy', 'Finger1', 'Both', 'Ring of Faith'), +(5, 1, 11, 0, 92, 22939, 'Phase 6', 'Priest', 'Holy', 'Finger2', 'Both', 'Band of Unanswered Prayers'), +(5, 1, 12, 0, 92, 23027, 'Phase 6', 'Priest', 'Holy', 'Trinket1', 'Both', 'Warmth of Forgiveness'), +(5, 1, 13, 0, 92, 23047, 'Phase 6', 'Priest', 'Holy', 'Trinket2', 'Both', 'Eye of the Dead'), +(5, 1, 14, 0, 92, 22960, 'Phase 6', 'Priest', 'Holy', 'Back', 'Both', 'Cloak of Suturing'), +(5, 1, 15, 0, 92, 23056, 'Phase 6', 'Priest', 'Holy', 'MainHand', 'Both', 'Hammer of the Twisting Nether'), +(5, 1, 16, 0, 92, 23048, 'Phase 6', 'Priest', 'Holy', 'OffHand', 'Both', 'Sapphiron''s Right Eye'), +(5, 1, 17, 0, 92, 23009, 'Phase 6', 'Priest', 'Holy', 'Ranged', 'Both', 'Wand of the Whispering Dead'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 120, 24264, 'Pre-Raid', 'Priest', 'Holy', 'Head', 'Both', 'Whitemend Hood'), +(5, 1, 1, 0, 120, 30377, 'Pre-Raid', 'Priest', 'Holy', 'Neck', 'Both', 'Karja''s Medallion'), +(5, 1, 2, 0, 120, 21874, 'Pre-Raid', 'Priest', 'Holy', 'Shoulders', 'Both', 'Primal Mooncloth Shoulders'), +(5, 1, 4, 0, 120, 21875, 'Pre-Raid', 'Priest', 'Holy', 'Chest', 'Both', 'Primal Mooncloth Robe'), +(5, 1, 5, 0, 120, 21873, 'Pre-Raid', 'Priest', 'Holy', 'Waist', 'Both', 'Primal Mooncloth Belt'), +(5, 1, 6, 0, 120, 24261, 'Pre-Raid', 'Priest', 'Holy', 'Legs', 'Both', 'Whitemend Pants'), +(5, 1, 7, 0, 120, 29251, 'Pre-Raid', 'Priest', 'Holy', 'Feet', 'Both', 'Boots of the Pious'), +(5, 1, 8, 0, 120, 29249, 'Pre-Raid', 'Priest', 'Holy', 'Wrists', 'Both', 'Bands of the Benevolent'), +(5, 1, 9, 0, 120, 24393, 'Pre-Raid', 'Priest', 'Holy', 'Hands', 'Both', 'Bloody Surgeon''s Mitts'), +(5, 1, 10, 0, 120, 27780, 'Pre-Raid', 'Priest', 'Holy', 'Finger1', 'Both', 'Ring of Fabled Hope'), +(5, 1, 10, 2, 120, 29168, 'Pre-Raid', 'Priest', 'Holy', 'Finger1', 'Horde', 'Ancestral Band'), +(5, 1, 12, 0, 120, 19288, 'Pre-Raid', 'Priest', 'Holy', 'Trinket1', 'Both', 'Darkmoon Card: Blue Dragon'), +(5, 1, 13, 0, 120, 29376, 'Pre-Raid', 'Priest', 'Holy', 'Trinket2', 'Both', 'Essence of the Martyr'), +(5, 1, 14, 0, 120, 29354, 'Pre-Raid', 'Priest', 'Holy', 'Back', 'Both', 'Light-Touched Stole of Altruism'), +(5, 1, 15, 0, 120, 29353, 'Pre-Raid', 'Priest', 'Holy', 'MainHand', 'Both', 'Shockwave Truncheon'), +(5, 1, 16, 0, 120, 29170, 'Pre-Raid', 'Priest', 'Holy', 'OffHand', 'Both', 'Windcaller''s Orb'), +(5, 1, 17, 0, 120, 27885, 'Pre-Raid', 'Priest', 'Holy', 'Ranged', 'Both', 'Soul-Wand of the Aldor'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 125, 29049, 'Phase 1', 'Priest', 'Holy', 'Head', 'Both', 'Light-Collar of the Incarnate'), +(5, 1, 1, 0, 125, 30726, 'Phase 1', 'Priest', 'Holy', 'Neck', 'Both', 'Archaic Charm of Presence'), +(5, 1, 2, 0, 125, 21874, 'Phase 1', 'Priest', 'Holy', 'Shoulders', 'Both', 'Primal Mooncloth Shoulders'), +(5, 1, 4, 0, 125, 21875, 'Phase 1', 'Priest', 'Holy', 'Chest', 'Both', 'Primal Mooncloth Robe'), +(5, 1, 5, 0, 125, 21873, 'Phase 1', 'Priest', 'Holy', 'Waist', 'Both', 'Primal Mooncloth Belt'), +(5, 1, 6, 0, 125, 30727, 'Phase 1', 'Priest', 'Holy', 'Legs', 'Both', 'Gilded Trousers of Benediction'), +(5, 1, 7, 0, 125, 28663, 'Phase 1', 'Priest', 'Holy', 'Feet', 'Both', 'Boots of the Incorrupt'), +(5, 1, 8, 0, 125, 29249, 'Phase 1', 'Priest', 'Holy', 'Wrists', 'Both', 'Bands of the Benevolent'), +(5, 1, 9, 0, 125, 28508, 'Phase 1', 'Priest', 'Holy', 'Hands', 'Both', 'Gloves of Saintly Blessings'), +(5, 1, 10, 0, 125, 29290, 'Phase 1', 'Priest', 'Holy', 'Finger1', 'Both', 'Violet Signet of the Grand Restorer'), +(5, 1, 11, 0, 125, 28763, 'Phase 1', 'Priest', 'Holy', 'Finger2', 'Both', 'Jade Ring of the Everliving'), +(5, 1, 12, 0, 125, 29376, 'Phase 1', 'Priest', 'Holy', 'Trinket1', 'Both', 'Essence of the Martyr'), +(5, 1, 13, 0, 125, 28823, 'Phase 1', 'Priest', 'Holy', 'Trinket2', 'Both', 'Eye of Gruul'), +(5, 1, 14, 0, 125, 28765, 'Phase 1', 'Priest', 'Holy', 'Back', 'Both', 'Stainless Cloak of the Pure Hearted'), +(5, 1, 15, 0, 125, 28771, 'Phase 1', 'Priest', 'Holy', 'MainHand', 'Both', 'Light''s Justice'), +(5, 1, 16, 0, 125, 29170, 'Phase 1', 'Priest', 'Holy', 'OffHand', 'Both', 'Windcaller''s Orb'), +(5, 1, 17, 0, 125, 28588, 'Phase 1', 'Priest', 'Holy', 'Ranged', 'Both', 'Blue Diamond Witchwand'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 141, 30152, 'Phase 2', 'Priest', 'Holy', 'Head', 'Both', 'Cowl of the Avatar'), +(5, 1, 1, 0, 141, 30018, 'Phase 2', 'Priest', 'Holy', 'Neck', 'Both', 'Lord Sanguinar''s Claim'), +(5, 1, 2, 0, 141, 30154, 'Phase 2', 'Priest', 'Holy', 'Shoulders', 'Both', 'Mantle of the Avatar'), +(5, 1, 4, 0, 141, 30150, 'Phase 2', 'Priest', 'Holy', 'Chest', 'Both', 'Vestments of the Avatar'), +(5, 1, 5, 0, 141, 30036, 'Phase 2', 'Priest', 'Holy', 'Waist', 'Both', 'Belt of the Long Road'), +(5, 1, 6, 0, 141, 30727, 'Phase 2', 'Priest', 'Holy', 'Legs', 'Both', 'Gilded Trousers of Benediction'), +(5, 1, 7, 0, 141, 30100, 'Phase 2', 'Priest', 'Holy', 'Feet', 'Both', 'Soul-Strider Boots'), +(5, 1, 8, 0, 141, 32516, 'Phase 2', 'Priest', 'Holy', 'Wrists', 'Both', 'Wraps of Purification'), +(5, 1, 9, 0, 141, 30151, 'Phase 2', 'Priest', 'Holy', 'Hands', 'Both', 'Gloves of the Avatar'), +(5, 1, 10, 0, 141, 30110, 'Phase 2', 'Priest', 'Holy', 'Finger1', 'Both', 'Coral Band of the Revived'), +(5, 1, 11, 0, 141, 29290, 'Phase 2', 'Priest', 'Holy', 'Finger2', 'Both', 'Violet Signet of the Grand Restorer'), +(5, 1, 12, 0, 141, 29376, 'Phase 2', 'Priest', 'Holy', 'Trinket1', 'Both', 'Essence of the Martyr'), +(5, 1, 13, 0, 141, 28823, 'Phase 2', 'Priest', 'Holy', 'Trinket2', 'Both', 'Eye of Gruul'), +(5, 1, 14, 0, 141, 29989, 'Phase 2', 'Priest', 'Holy', 'Back', 'Both', 'Sunshower Light Cloak'), +(5, 1, 15, 0, 141, 30108, 'Phase 2', 'Priest', 'Holy', 'MainHand', 'Both', 'Lightfathom Scepter'), +(5, 1, 16, 0, 141, 29923, 'Phase 2', 'Priest', 'Holy', 'OffHand', 'Both', 'Talisman of the Sun King'), +(5, 1, 17, 0, 141, 30080, 'Phase 2', 'Priest', 'Holy', 'Ranged', 'Both', 'Luminescent Rod of the Naaru'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 156, 31063, 'Phase 3', 'Priest', 'Holy', 'Head', 'Both', 'Cowl of Absolution'), +(5, 1, 1, 0, 156, 32370, 'Phase 3', 'Priest', 'Holy', 'Neck', 'Both', 'Nadina''s Pendant of Purity'), +(5, 1, 2, 0, 156, 31069, 'Phase 3', 'Priest', 'Holy', 'Shoulders', 'Both', 'Mantle of Absolution'), +(5, 1, 4, 0, 156, 31066, 'Phase 3', 'Priest', 'Holy', 'Chest', 'Both', 'Vestments of Absolution'), +(5, 1, 5, 0, 156, 32519, 'Phase 3', 'Priest', 'Holy', 'Waist', 'Both', 'Belt of Divine Guidance'), +(5, 1, 6, 0, 156, 30912, 'Phase 3', 'Priest', 'Holy', 'Legs', 'Both', 'Leggings of Eternity'), +(5, 1, 7, 0, 156, 32609, 'Phase 3', 'Priest', 'Holy', 'Feet', 'Both', 'Boots of the Divine Light'), +(5, 1, 8, 0, 156, 30871, 'Phase 3', 'Priest', 'Holy', 'Wrists', 'Both', 'Bracers of Martyrdom'), +(5, 1, 9, 0, 156, 31060, 'Phase 3', 'Priest', 'Holy', 'Hands', 'Both', 'Gloves of Absolution'), +(5, 1, 10, 0, 156, 32528, 'Phase 3', 'Priest', 'Holy', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(5, 1, 11, 0, 156, 32528, 'Phase 3', 'Priest', 'Holy', 'Finger2', 'Both', 'Blessed Band of Karabor'), +(5, 1, 12, 0, 156, 29376, 'Phase 3', 'Priest', 'Holy', 'Trinket1', 'Both', 'Essence of the Martyr'), +(5, 1, 13, 0, 156, 32496, 'Phase 3', 'Priest', 'Holy', 'Trinket2', 'Both', 'Memento of Tyrande'), +(5, 1, 14, 0, 156, 32524, 'Phase 3', 'Priest', 'Holy', 'Back', 'Both', 'Shroud of the Highborne'), +(5, 1, 15, 0, 156, 32500, 'Phase 3', 'Priest', 'Holy', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(5, 1, 16, 0, 156, 30911, 'Phase 3', 'Priest', 'Holy', 'OffHand', 'Both', 'Scepter of Purification'), +(5, 1, 17, 0, 156, 32363, 'Phase 3', 'Priest', 'Holy', 'Ranged', 'Both', 'Naaru-Blessed Life Rod'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 164, 31063, 'Phase 4', 'Priest', 'Holy', 'Head', 'Both', 'Cowl of Absolution'), +(5, 1, 1, 0, 164, 33281, 'Phase 4', 'Priest', 'Holy', 'Neck', 'Both', 'Brooch of Nature''s Mercy'), +(5, 1, 2, 0, 164, 31069, 'Phase 4', 'Priest', 'Holy', 'Shoulders', 'Both', 'Mantle of Absolution'), +(5, 1, 4, 0, 164, 31066, 'Phase 4', 'Priest', 'Holy', 'Chest', 'Both', 'Vestments of Absolution'), +(5, 1, 5, 0, 164, 32519, 'Phase 4', 'Priest', 'Holy', 'Waist', 'Both', 'Belt of Divine Guidance'), +(5, 1, 6, 0, 164, 30912, 'Phase 4', 'Priest', 'Holy', 'Legs', 'Both', 'Leggings of Eternity'), +(5, 1, 7, 0, 164, 32609, 'Phase 4', 'Priest', 'Holy', 'Feet', 'Both', 'Boots of the Divine Light'), +(5, 1, 8, 0, 164, 30871, 'Phase 4', 'Priest', 'Holy', 'Wrists', 'Both', 'Bracers of Martyrdom'), +(5, 1, 9, 0, 164, 31060, 'Phase 4', 'Priest', 'Holy', 'Hands', 'Both', 'Gloves of Absolution'), +(5, 1, 10, 0, 164, 32528, 'Phase 4', 'Priest', 'Holy', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(5, 1, 11, 0, 164, 30110, 'Phase 4', 'Priest', 'Holy', 'Finger2', 'Both', 'Coral Band of the Revived'), +(5, 1, 12, 0, 164, 29376, 'Phase 4', 'Priest', 'Holy', 'Trinket1', 'Both', 'Essence of the Martyr'), +(5, 1, 13, 0, 164, 32496, 'Phase 4', 'Priest', 'Holy', 'Trinket2', 'Both', 'Memento of Tyrande'), +(5, 1, 14, 0, 164, 32524, 'Phase 4', 'Priest', 'Holy', 'Back', 'Both', 'Shroud of the Highborne'), +(5, 1, 15, 0, 164, 32500, 'Phase 4', 'Priest', 'Holy', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(5, 1, 16, 0, 164, 30911, 'Phase 4', 'Priest', 'Holy', 'OffHand', 'Both', 'Scepter of Purification'), +(5, 1, 17, 0, 164, 32363, 'Phase 4', 'Priest', 'Holy', 'Ranged', 'Both', 'Naaru-Blessed Life Rod'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 200, 37294, 'Pre-Raid', 'Priest', 'Holy', 'Head', 'Both', 'Crown of Unbridled Magic'), +(5, 1, 1, 0, 200, 40681, 'Pre-Raid', 'Priest', 'Holy', 'Neck', 'Both', 'Lattice Choker of Light'), +(5, 1, 2, 0, 200, 37673, 'Pre-Raid', 'Priest', 'Holy', 'Shoulders', 'Both', 'Dark Runic Mantle'), +(5, 1, 4, 0, 200, 44180, 'Pre-Raid', 'Priest', 'Holy', 'Chest', 'Both', 'Robes of Crackling Flame'), +(5, 1, 5, 0, 200, 40697, 'Pre-Raid', 'Priest', 'Holy', 'Waist', 'Both', 'Elegant Temple Gardens'' Girdle'), +(5, 1, 6, 0, 200, 37854, 'Pre-Raid', 'Priest', 'Holy', 'Legs', 'Both', 'Woven Bracae Leggings'), +(5, 1, 7, 0, 200, 44202, 'Pre-Raid', 'Priest', 'Holy', 'Feet', 'Both', 'Sandals of Crimson Fury'), +(5, 1, 8, 0, 200, 37361, 'Pre-Raid', 'Priest', 'Holy', 'Wrists', 'Both', 'Cuffs of Winged Levitation'), +(5, 1, 9, 0, 200, 37172, 'Pre-Raid', 'Priest', 'Holy', 'Hands', 'Both', 'Gloves of Glistening Runes'), +(5, 1, 10, 0, 200, 37694, 'Pre-Raid', 'Priest', 'Holy', 'Finger1', 'Both', 'Band of Guile'), +(5, 1, 12, 0, 200, 37835, 'Pre-Raid', 'Priest', 'Holy', 'Trinket1', 'Both', 'Je''Tze''s Bell'), +(5, 1, 14, 0, 200, 41610, 'Pre-Raid', 'Priest', 'Holy', 'Back', 'Both', 'Deathchill Cloak'), +(5, 1, 15, 0, 200, 37169, 'Pre-Raid', 'Priest', 'Holy', 'MainHand', 'Both', 'War Mace of Unrequited Love'), +(5, 1, 17, 0, 200, 37619, 'Pre-Raid', 'Priest', 'Holy', 'Ranged', 'Both', 'Wand of Ahn''kahet'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 224, 40447, 'Phase 1', 'Priest', 'Holy', 'Head', 'Both', 'Valorous Crown of Faith'), +(5, 1, 1, 0, 224, 44661, 'Phase 1', 'Priest', 'Holy', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(5, 1, 2, 0, 224, 40450, 'Phase 1', 'Priest', 'Holy', 'Shoulders', 'Both', 'Valorous Shoulderpads of Faith'), +(5, 1, 4, 0, 224, 44002, 'Phase 1', 'Priest', 'Holy', 'Chest', 'Both', 'The Sanctum''s Flowing Vestments'), +(5, 1, 5, 0, 224, 40561, 'Phase 1', 'Priest', 'Holy', 'Waist', 'Both', 'Leash of Heedless Magic'), +(5, 1, 6, 0, 224, 40448, 'Phase 1', 'Priest', 'Holy', 'Legs', 'Both', 'Valorous Leggings of Faith'), +(5, 1, 7, 0, 224, 40558, 'Phase 1', 'Priest', 'Holy', 'Feet', 'Both', 'Arcanic Tramplers'), +(5, 1, 8, 0, 224, 44008, 'Phase 1', 'Priest', 'Holy', 'Wrists', 'Both', 'Unsullied Cuffs'), +(5, 1, 9, 0, 224, 40445, 'Phase 1', 'Priest', 'Holy', 'Hands', 'Both', 'Valorous Gloves of Faith'), +(5, 1, 10, 0, 224, 40399, 'Phase 1', 'Priest', 'Holy', 'Finger1', 'Both', 'Signet of Manifested Pain'), +(5, 1, 12, 0, 224, 37835, 'Phase 1', 'Priest', 'Holy', 'Trinket1', 'Both', 'Je''Tze''s Bell'), +(5, 1, 14, 0, 224, 44005, 'Phase 1', 'Priest', 'Holy', 'Back', 'Both', 'Pennant Cloak'), +(5, 1, 17, 0, 224, 39426, 'Phase 1', 'Priest', 'Holy', 'Ranged', 'Both', 'Wand of the Archlich'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 245, 45497, 'Phase 2', 'Priest', 'Holy', 'Head', 'Both', 'Crown of Luminescence'), +(5, 1, 1, 0, 245, 45243, 'Phase 2', 'Priest', 'Holy', 'Neck', 'Both', 'Sapphire Amulet of Renewal'), +(5, 1, 2, 0, 245, 46068, 'Phase 2', 'Priest', 'Holy', 'Shoulders', 'Both', 'Amice of Inconceivable Horror'), +(5, 1, 4, 0, 245, 45240, 'Phase 2', 'Priest', 'Holy', 'Chest', 'Both', 'Raiments of the Iron Council'), +(5, 1, 5, 0, 245, 45619, 'Phase 2', 'Priest', 'Holy', 'Waist', 'Both', 'Starwatcher''s Binding'), +(5, 1, 6, 0, 245, 46195, 'Phase 2', 'Priest', 'Holy', 'Legs', 'Both', 'Conqueror''s Leggings of Sanctification'), +(5, 1, 7, 0, 245, 45135, 'Phase 2', 'Priest', 'Holy', 'Feet', 'Both', 'Boots of Fiery Resolution'), +(5, 1, 8, 0, 245, 45460, 'Phase 2', 'Priest', 'Holy', 'Wrists', 'Both', 'Bindings of Winter Gale'), +(5, 1, 9, 0, 245, 46188, 'Phase 2', 'Priest', 'Holy', 'Hands', 'Both', 'Conqueror''s Gloves of Sanctification'), +(5, 1, 12, 0, 245, 45535, 'Phase 2', 'Priest', 'Holy', 'Trinket1', 'Both', 'Show of Faith'), +(5, 1, 14, 0, 245, 45618, 'Phase 2', 'Priest', 'Holy', 'Back', 'Both', 'Sunglimmer Cloak'), +(5, 1, 15, 0, 245, 46017, 'Phase 2', 'Priest', 'Holy', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(5, 1, 17, 0, 245, 45170, 'Phase 2', 'Priest', 'Holy', 'Ranged', 'Both', 'Scepter of Creation'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 258, 48035, 'Phase 3', 'Priest', 'Holy', 'Head', 'Both', 'Cowl of Triumph'), +(5, 1, 2, 0, 258, 48029, 'Phase 3', 'Priest', 'Holy', 'Shoulders', 'Both', 'Shoulderpads of Triumph'), +(5, 1, 4, 1, 258, 46993, 'Phase 3', 'Priest', 'Holy', 'Chest', 'Alliance', 'Flowing Vestments of Ascent'), +(5, 1, 4, 2, 258, 47425, 'Phase 3', 'Priest', 'Holy', 'Chest', 'Horde', 'Flowing Robes of Ascent'), +(5, 1, 5, 1, 258, 46973, 'Phase 3', 'Priest', 'Holy', 'Waist', 'Alliance', 'Cord of the Tenebrous Mist'), +(5, 1, 5, 2, 258, 47419, 'Phase 3', 'Priest', 'Holy', 'Waist', 'Horde', 'Belt of the Tenebrous Mist'), +(5, 1, 6, 1, 258, 47062, 'Phase 3', 'Priest', 'Holy', 'Legs', 'Alliance', 'Leggings of the Soothing Touch'), +(5, 1, 6, 2, 258, 47435, 'Phase 3', 'Priest', 'Holy', 'Legs', 'Horde', 'Pants of the Soothing Touch'), +(5, 1, 7, 1, 258, 47097, 'Phase 3', 'Priest', 'Holy', 'Feet', 'Alliance', 'Boots of the Mourning Widow'), +(5, 1, 7, 2, 258, 47454, 'Phase 3', 'Priest', 'Holy', 'Feet', 'Horde', 'Sandals of the Mourning Widow'), +(5, 1, 8, 1, 258, 47208, 'Phase 3', 'Priest', 'Holy', 'Wrists', 'Alliance', 'Armbands of the Ashen Saint'), +(5, 1, 8, 2, 258, 47485, 'Phase 3', 'Priest', 'Holy', 'Wrists', 'Horde', 'Bindings of the Ashen Saint'), +(5, 1, 9, 0, 258, 45665, 'Phase 3', 'Priest', 'Holy', 'Hands', 'Both', 'Pharos Gloves'), +(5, 1, 10, 1, 258, 47224, 'Phase 3', 'Priest', 'Holy', 'Finger1', 'Alliance', 'Ring of the Darkmender'), +(5, 1, 10, 2, 258, 47439, 'Phase 3', 'Priest', 'Holy', 'Finger1', 'Horde', 'Circle of the Darkmender'), +(5, 1, 12, 1, 258, 47059, 'Phase 3', 'Priest', 'Holy', 'Trinket1', 'Alliance', 'Solace of the Defeated'), +(5, 1, 12, 2, 258, 47432, 'Phase 3', 'Priest', 'Holy', 'Trinket1', 'Horde', 'Solace of the Fallen'), +(5, 1, 14, 1, 258, 47552, 'Phase 3', 'Priest', 'Holy', 'Back', 'Alliance', 'Jaina''s Radiance'), +(5, 1, 14, 2, 258, 47551, 'Phase 3', 'Priest', 'Holy', 'Back', 'Horde', 'Aethas'' Intensity'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 264, 51261, 'Phase 4', 'Priest', 'Holy', 'Head', 'Both', 'Sanctified Crimson Acolyte Hood'), +(5, 1, 2, 0, 264, 51264, 'Phase 4', 'Priest', 'Holy', 'Shoulders', 'Both', 'Sanctified Crimson Acolyte Shoulderpads'), +(5, 1, 4, 0, 264, 50717, 'Phase 4', 'Priest', 'Holy', 'Chest', 'Both', 'Sanguine Silk Robes'), +(5, 1, 5, 0, 264, 50613, 'Phase 4', 'Priest', 'Holy', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(5, 1, 6, 0, 264, 51262, 'Phase 4', 'Priest', 'Holy', 'Legs', 'Both', 'Sanctified Crimson Acolyte Leggings'), +(5, 1, 7, 0, 264, 50699, 'Phase 4', 'Priest', 'Holy', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(5, 1, 8, 0, 264, 50686, 'Phase 4', 'Priest', 'Holy', 'Wrists', 'Both', 'Death Surgeon''s Sleeves'), +(5, 1, 9, 0, 264, 51260, 'Phase 4', 'Priest', 'Holy', 'Hands', 'Both', 'Sanctified Crimson Acolyte Gloves'), +(5, 1, 10, 0, 264, 50636, 'Phase 4', 'Priest', 'Holy', 'Finger1', 'Both', 'Memory of Malygos'), +(5, 1, 12, 1, 264, 47059, 'Phase 4', 'Priest', 'Holy', 'Trinket1', 'Alliance', 'Solace of the Defeated'), +(5, 1, 12, 2, 264, 47432, 'Phase 4', 'Priest', 'Holy', 'Trinket1', 'Horde', 'Solace of the Fallen'), +(5, 1, 14, 0, 264, 50668, 'Phase 4', 'Priest', 'Holy', 'Back', 'Both', 'Greatcloak of the Turned Champion'), +(5, 1, 15, 0, 264, 46017, 'Phase 4', 'Priest', 'Holy', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(5, 1, 17, 0, 264, 50684, 'Phase 4', 'Priest', 'Holy', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 1, 0, 0, 290, 51261, 'Phase 5', 'Priest', 'Holy', 'Head', 'Both', 'Sanctified Crimson Acolyte Hood'), +(5, 1, 1, 0, 290, 50609, 'Phase 5', 'Priest', 'Holy', 'Neck', 'Both', 'Bone Sentinel''s Amulet'), +(5, 1, 2, 0, 290, 51264, 'Phase 5', 'Priest', 'Holy', 'Shoulders', 'Both', 'Sanctified Crimson Acolyte Shoulderpads'), +(5, 1, 4, 0, 290, 50717, 'Phase 5', 'Priest', 'Holy', 'Chest', 'Both', 'Sanguine Silk Robes'), +(5, 1, 5, 0, 290, 50702, 'Phase 5', 'Priest', 'Holy', 'Waist', 'Both', 'Lingering Illness'), +(5, 1, 6, 0, 290, 51262, 'Phase 5', 'Priest', 'Holy', 'Legs', 'Both', 'Sanctified Crimson Acolyte Leggings'), +(5, 1, 7, 0, 290, 50699, 'Phase 5', 'Priest', 'Holy', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(5, 1, 8, 0, 290, 54582, 'Phase 5', 'Priest', 'Holy', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(5, 1, 9, 0, 290, 51260, 'Phase 5', 'Priest', 'Holy', 'Hands', 'Both', 'Sanctified Crimson Acolyte Gloves'), +(5, 1, 10, 0, 290, 50400, 'Phase 5', 'Priest', 'Holy', 'Finger1', 'Both', 'Ashen Band of Endless Wisdom'), +(5, 1, 11, 0, 290, 50636, 'Phase 5', 'Priest', 'Holy', 'Finger2', 'Both', 'Memory of Malygos'), +(5, 1, 12, 0, 290, 50366, 'Phase 5', 'Priest', 'Holy', 'Trinket1', 'Both', 'Althor''s Abacus'), +(5, 1, 13, 0, 290, 54589, 'Phase 5', 'Priest', 'Holy', 'Trinket2', 'Both', 'Glowing Twilight Scale'), +(5, 1, 14, 0, 290, 50668, 'Phase 5', 'Priest', 'Holy', 'Back', 'Both', 'Greatcloak of the Turned Champion'), +(5, 1, 15, 0, 290, 50731, 'Phase 5', 'Priest', 'Holy', 'MainHand', 'Both', 'Archus, Greatstaff of Antonidas'), +(5, 1, 17, 0, 290, 50631, 'Phase 5', 'Priest', 'Holy', 'Ranged', 'Both', 'Nightmare Ender'); + +-- Shadow (tab 2) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 1, 66, 10504, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Head', 'Alliance', 'Green Lens'), +(5, 2, 0, 2, 66, 10504, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Head', 'Horde', 'Green Lens'), +(5, 2, 1, 1, 66, 18691, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Neck', 'Alliance', 'Dark Advisor''s Pendant'), +(5, 2, 1, 2, 66, 18691, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Neck', 'Horde', 'Dark Advisor''s Pendant'), +(5, 2, 2, 1, 66, 14112, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Shoulders', 'Alliance', 'Felcloth Shoulders'), +(5, 2, 2, 2, 66, 14112, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Shoulders', 'Horde', 'Felcloth Shoulders'), +(5, 2, 4, 1, 66, 14136, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Chest', 'Alliance', 'Robe of Winter Night'), +(5, 2, 4, 2, 66, 14136, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Chest', 'Horde', 'Robe of Winter Night'), +(5, 2, 5, 1, 66, 11662, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Waist', 'Alliance', 'Ban''thok Sash'), +(5, 2, 5, 2, 66, 11662, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Waist', 'Horde', 'Ban''thok Sash'), +(5, 2, 6, 1, 66, 13170, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(5, 2, 6, 2, 66, 13170, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Legs', 'Horde', 'Skyshroud Leggings'), +(5, 2, 7, 1, 66, 18735, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Feet', 'Alliance', 'Maleki''s Footwraps'), +(5, 2, 7, 2, 66, 18735, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Feet', 'Horde', 'Maleki''s Footwraps'), +(5, 2, 8, 1, 66, 11766, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(5, 2, 8, 2, 66, 11766, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(5, 2, 9, 1, 66, 13253, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Hands', 'Alliance', 'Hands of Power'), +(5, 2, 9, 2, 66, 13253, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Hands', 'Horde', 'Hands of Power'), +(5, 2, 10, 1, 66, 12543, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(5, 2, 10, 2, 66, 12545, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(5, 2, 11, 1, 66, 13001, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(5, 2, 11, 2, 66, 13001, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Finger2', 'Horde', 'Maiden''s Circle'), +(5, 2, 12, 1, 66, 12930, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(5, 2, 12, 2, 66, 12930, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Trinket1', 'Horde', 'Briarwood Reed'), +(5, 2, 13, 1, 66, 11819, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Trinket2', 'Alliance', 'Second Wind'), +(5, 2, 13, 2, 66, 11819, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Trinket2', 'Horde', 'Second Wind'), +(5, 2, 14, 1, 66, 13386, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Back', 'Alliance', 'Archivist Cape'), +(5, 2, 14, 2, 66, 13386, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Back', 'Horde', 'Archivist Cape'), +(5, 2, 15, 1, 66, 13349, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'MainHand', 'Alliance', 'Scepter of the Unholy'), +(5, 2, 15, 2, 66, 13349, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'MainHand', 'Horde', 'Scepter of the Unholy'), +(5, 2, 16, 1, 66, 10796, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'OffHand', 'Alliance', 'Drakestone'), +(5, 2, 16, 2, 66, 10796, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'OffHand', 'Horde', 'Drakestone'), +(5, 2, 17, 1, 66, 13396, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Ranged', 'Alliance', 'Skul''s Ghastly Touch'), +(5, 2, 17, 2, 66, 13396, 'Phase 1 (Pre-Raid)', 'Priest', 'Shadow', 'Ranged', 'Horde', 'Skul''s Ghastly Touch'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 1, 76, 10504, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Head', 'Alliance', 'Green Lens'), +(5, 2, 0, 2, 76, 10504, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Head', 'Horde', 'Green Lens'), +(5, 2, 1, 1, 76, 18691, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Neck', 'Alliance', 'Dark Advisor''s Pendant'), +(5, 2, 1, 2, 76, 18691, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Neck', 'Horde', 'Dark Advisor''s Pendant'), +(5, 2, 2, 1, 76, 14112, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Shoulders', 'Alliance', 'Felcloth Shoulders'), +(5, 2, 2, 2, 76, 14112, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Shoulders', 'Horde', 'Felcloth Shoulders'), +(5, 2, 4, 1, 76, 14136, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Chest', 'Alliance', 'Robe of Winter Night'), +(5, 2, 4, 2, 76, 14136, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Chest', 'Horde', 'Robe of Winter Night'), +(5, 2, 5, 1, 76, 11662, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Waist', 'Alliance', 'Ban''thok Sash'), +(5, 2, 5, 2, 76, 11662, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Waist', 'Horde', 'Ban''thok Sash'), +(5, 2, 6, 1, 76, 13170, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(5, 2, 6, 2, 76, 13170, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Legs', 'Horde', 'Skyshroud Leggings'), +(5, 2, 7, 1, 76, 18735, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Feet', 'Alliance', 'Maleki''s Footwraps'), +(5, 2, 7, 2, 76, 18735, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Feet', 'Horde', 'Maleki''s Footwraps'), +(5, 2, 8, 1, 76, 11766, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(5, 2, 8, 2, 76, 11766, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(5, 2, 9, 1, 76, 18407, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Hands', 'Alliance', 'Felcloth Gloves'), +(5, 2, 9, 2, 76, 18407, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Hands', 'Horde', 'Felcloth Gloves'), +(5, 2, 10, 1, 76, 12543, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(5, 2, 10, 2, 76, 12545, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(5, 2, 11, 1, 76, 13001, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(5, 2, 11, 2, 76, 13001, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Finger2', 'Horde', 'Maiden''s Circle'), +(5, 2, 12, 1, 76, 12930, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(5, 2, 12, 2, 76, 12930, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Trinket1', 'Horde', 'Briarwood Reed'), +(5, 2, 13, 1, 76, 18371, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Trinket2', 'Alliance', 'Mindtap Talisman'), +(5, 2, 13, 2, 76, 18371, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Trinket2', 'Horde', 'Mindtap Talisman'), +(5, 2, 14, 1, 76, 13386, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Back', 'Alliance', 'Archivist Cape'), +(5, 2, 14, 2, 76, 13386, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Back', 'Horde', 'Archivist Cape'), +(5, 2, 15, 1, 76, 13349, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'MainHand', 'Alliance', 'Scepter of the Unholy'), +(5, 2, 15, 2, 76, 13349, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'MainHand', 'Horde', 'Scepter of the Unholy'), +(5, 2, 16, 1, 76, 10796, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'OffHand', 'Alliance', 'Drakestone'), +(5, 2, 16, 2, 76, 10796, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'OffHand', 'Horde', 'Drakestone'), +(5, 2, 17, 1, 76, 13396, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Ranged', 'Alliance', 'Skul''s Ghastly Touch'), +(5, 2, 17, 2, 76, 13396, 'Phase 2 (Pre-Raid)', 'Priest', 'Shadow', 'Ranged', 'Horde', 'Skul''s Ghastly Touch'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 78, 10504, 'Phase 2', 'Priest', 'Shadow', 'Head', 'Both', 'Green Lens'), +(5, 2, 1, 0, 78, 18814, 'Phase 2', 'Priest', 'Shadow', 'Neck', 'Both', 'Choker of the Fire Lord'), +(5, 2, 2, 0, 78, 14112, 'Phase 2', 'Priest', 'Shadow', 'Shoulders', 'Both', 'Felcloth Shoulders'), +(5, 2, 4, 0, 78, 14136, 'Phase 2', 'Priest', 'Shadow', 'Chest', 'Both', 'Robe of Winter Night'), +(5, 2, 5, 0, 78, 18809, 'Phase 2', 'Priest', 'Shadow', 'Waist', 'Both', 'Sash of Whispered Secrets'), +(5, 2, 6, 0, 78, 19133, 'Phase 2', 'Priest', 'Shadow', 'Legs', 'Both', 'Fel Infused Leggings'), +(5, 2, 7, 0, 78, 19131, 'Phase 2', 'Priest', 'Shadow', 'Feet', 'Both', 'Snowblind Shoes'), +(5, 2, 8, 0, 78, 11766, 'Phase 2', 'Priest', 'Shadow', 'Wrists', 'Both', 'Flameweave Cuffs'), +(5, 2, 9, 0, 78, 18407, 'Phase 2', 'Priest', 'Shadow', 'Hands', 'Both', 'Felcloth Gloves'), +(5, 2, 10, 0, 78, 19147, 'Phase 2', 'Priest', 'Shadow', 'Finger1', 'Both', 'Ring of Spell Power'), +(5, 2, 11, 0, 78, 19147, 'Phase 2', 'Priest', 'Shadow', 'Finger2', 'Both', 'Ring of Spell Power'), +(5, 2, 12, 0, 78, 12930, 'Phase 2', 'Priest', 'Shadow', 'Trinket1', 'Both', 'Briarwood Reed'), +(5, 2, 13, 0, 78, 18820, 'Phase 2', 'Priest', 'Shadow', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(5, 2, 14, 0, 78, 13386, 'Phase 2', 'Priest', 'Shadow', 'Back', 'Both', 'Archivist Cape'), +(5, 2, 15, 0, 78, 18609, 'Phase 2', 'Priest', 'Shadow', 'MainHand', 'Both', 'Anathema'), +(5, 2, 17, 0, 78, 13396, 'Phase 2', 'Priest', 'Shadow', 'Ranged', 'Both', 'Skul''s Ghastly Touch'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 83, 19375, 'Phase 3', 'Priest', 'Shadow', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(5, 2, 1, 0, 83, 18814, 'Phase 3', 'Priest', 'Shadow', 'Neck', 'Both', 'Choker of the Fire Lord'), +(5, 2, 2, 0, 83, 19370, 'Phase 3', 'Priest', 'Shadow', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(5, 2, 4, 0, 83, 14136, 'Phase 3', 'Priest', 'Shadow', 'Chest', 'Both', 'Robe of Winter Night'), +(5, 2, 5, 0, 83, 19400, 'Phase 3', 'Priest', 'Shadow', 'Waist', 'Both', 'Firemaw''s Clutch'), +(5, 2, 6, 0, 83, 19133, 'Phase 3', 'Priest', 'Shadow', 'Legs', 'Both', 'Fel Infused Leggings'), +(5, 2, 7, 0, 83, 19131, 'Phase 3', 'Priest', 'Shadow', 'Feet', 'Both', 'Snowblind Shoes'), +(5, 2, 8, 0, 83, 19374, 'Phase 3', 'Priest', 'Shadow', 'Wrists', 'Both', 'Bracers of Arcane Accuracy'), +(5, 2, 9, 0, 83, 19407, 'Phase 3', 'Priest', 'Shadow', 'Hands', 'Both', 'Ebony Flame Gloves'), +(5, 2, 11, 0, 83, 19434, 'Phase 3', 'Priest', 'Shadow', 'Finger2', 'Both', 'Band of Dark Dominion'), +(5, 2, 12, 0, 83, 19379, 'Phase 3', 'Priest', 'Shadow', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(5, 2, 13, 0, 83, 18820, 'Phase 3', 'Priest', 'Shadow', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(5, 2, 14, 0, 83, 19378, 'Phase 3', 'Priest', 'Shadow', 'Back', 'Both', 'Cloak of the Brood Lord'), +(5, 2, 15, 0, 83, 19360, 'Phase 3', 'Priest', 'Shadow', 'MainHand', 'Both', 'Lok''amir il Romathis'), +(5, 2, 16, 0, 83, 19366, 'Phase 3', 'Priest', 'Shadow', 'OffHand', 'Both', 'Master Dragonslayer''s Orb'), +(5, 2, 17, 0, 83, 13396, 'Phase 3', 'Priest', 'Shadow', 'Ranged', 'Both', 'Skul''s Ghastly Touch'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 88, 21348, 'Phase 5', 'Priest', 'Shadow', 'Head', 'Both', 'Tiara of the Oracle'), +(5, 2, 1, 0, 88, 18814, 'Phase 5', 'Priest', 'Shadow', 'Neck', 'Both', 'Choker of the Fire Lord'), +(5, 2, 2, 0, 88, 19370, 'Phase 5', 'Priest', 'Shadow', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(5, 2, 4, 0, 88, 21351, 'Phase 5', 'Priest', 'Shadow', 'Chest', 'Both', 'Vestments of the Oracle'), +(5, 2, 5, 0, 88, 19400, 'Phase 5', 'Priest', 'Shadow', 'Waist', 'Both', 'Firemaw''s Clutch'), +(5, 2, 6, 0, 88, 19133, 'Phase 5', 'Priest', 'Shadow', 'Legs', 'Both', 'Fel Infused Leggings'), +(5, 2, 7, 0, 88, 19131, 'Phase 5', 'Priest', 'Shadow', 'Feet', 'Both', 'Snowblind Shoes'), +(5, 2, 8, 0, 88, 21611, 'Phase 5', 'Priest', 'Shadow', 'Wrists', 'Both', 'Burrower Bracers'), +(5, 2, 9, 0, 88, 21585, 'Phase 5', 'Priest', 'Shadow', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(5, 2, 10, 0, 88, 21709, 'Phase 5', 'Priest', 'Shadow', 'Finger1', 'Both', 'Ring of the Fallen God'), +(5, 2, 11, 0, 88, 21210, 'Phase 5', 'Priest', 'Shadow', 'Finger2', 'Both', 'Signet Ring of the Bronze Dragonflight'), +(5, 2, 12, 0, 88, 19379, 'Phase 5', 'Priest', 'Shadow', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(5, 2, 13, 0, 88, 18820, 'Phase 5', 'Priest', 'Shadow', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(5, 2, 14, 0, 88, 22731, 'Phase 5', 'Priest', 'Shadow', 'Back', 'Both', 'Cloak of the Devoured'), +(5, 2, 15, 0, 88, 19360, 'Phase 5', 'Priest', 'Shadow', 'MainHand', 'Both', 'Lok''amir il Romathis'), +(5, 2, 16, 0, 88, 19366, 'Phase 5', 'Priest', 'Shadow', 'OffHand', 'Both', 'Master Dragonslayer''s Orb'), +(5, 2, 17, 0, 88, 21603, 'Phase 5', 'Priest', 'Shadow', 'Ranged', 'Both', 'Wand of Qiraji Nobility'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 92, 23035, 'Phase 6', 'Priest', 'Shadow', 'Head', 'Both', 'Preceptor''s Hat'), +(5, 2, 1, 0, 92, 18814, 'Phase 6', 'Priest', 'Shadow', 'Neck', 'Both', 'Choker of the Fire Lord'), +(5, 2, 2, 0, 92, 22983, 'Phase 6', 'Priest', 'Shadow', 'Shoulders', 'Both', 'Rime Covered Mantle'), +(5, 2, 4, 0, 92, 23220, 'Phase 6', 'Priest', 'Shadow', 'Chest', 'Both', 'Crystal Webbed Robe'), +(5, 2, 5, 0, 92, 19400, 'Phase 6', 'Priest', 'Shadow', 'Waist', 'Both', 'Firemaw''s Clutch'), +(5, 2, 6, 0, 92, 19133, 'Phase 6', 'Priest', 'Shadow', 'Legs', 'Both', 'Fel Infused Leggings'), +(5, 2, 7, 0, 92, 19131, 'Phase 6', 'Priest', 'Shadow', 'Feet', 'Both', 'Snowblind Shoes'), +(5, 2, 8, 0, 92, 21611, 'Phase 6', 'Priest', 'Shadow', 'Wrists', 'Both', 'Burrower Bracers'), +(5, 2, 9, 0, 92, 21585, 'Phase 6', 'Priest', 'Shadow', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(5, 2, 10, 0, 92, 21709, 'Phase 6', 'Priest', 'Shadow', 'Finger1', 'Both', 'Ring of the Fallen God'), +(5, 2, 11, 0, 92, 21210, 'Phase 6', 'Priest', 'Shadow', 'Finger2', 'Both', 'Signet Ring of the Bronze Dragonflight'), +(5, 2, 12, 0, 92, 19379, 'Phase 6', 'Priest', 'Shadow', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(5, 2, 13, 0, 92, 23046, 'Phase 6', 'Priest', 'Shadow', 'Trinket2', 'Both', 'The Restrained Essence of Sapphiron'), +(5, 2, 14, 0, 92, 22731, 'Phase 6', 'Priest', 'Shadow', 'Back', 'Both', 'Cloak of the Devoured'), +(5, 2, 15, 0, 92, 22988, 'Phase 6', 'Priest', 'Shadow', 'MainHand', 'Both', 'The End of Dreams'), +(5, 2, 16, 0, 92, 23049, 'Phase 6', 'Priest', 'Shadow', 'OffHand', 'Both', 'Sapphiron''s Left Eye'), +(5, 2, 17, 0, 92, 21603, 'Phase 6', 'Priest', 'Shadow', 'Ranged', 'Both', 'Wand of Qiraji Nobility'); + +-- ilvl 200 (Fresh 80) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 200, 43995, 'Fresh 80', 'Priest', 'Shadow', 'Head', 'Both', 'Enamored Cowl'), +(5, 2, 1, 0, 200, 44658, 'Fresh 80', 'Priest', 'Shadow', 'Neck', 'Both', 'Chain of the Ancient Wyrm'), +(5, 2, 2, 0, 200, 40459, 'Fresh 80', 'Priest', 'Shadow', 'Shoulders', 'Both', 'Valorous Mantle of Faith'), +(5, 2, 4, 0, 200, 40526, 'Fresh 80', 'Priest', 'Shadow', 'Chest', 'Both', 'Gown of the Spell-Weaver'), +(5, 2, 5, 0, 200, 40696, 'Fresh 80', 'Priest', 'Shadow', 'Waist', 'Both', 'Plush Sash of Guzbah'), +(5, 2, 6, 0, 200, 40457, 'Fresh 80', 'Priest', 'Shadow', 'Legs', 'Both', 'Valorous Pants of Faith'), +(5, 2, 7, 0, 200, 40751, 'Fresh 80', 'Priest', 'Shadow', 'Feet', 'Both', 'Slippers of the Holy Light'), +(5, 2, 8, 0, 200, 40740, 'Fresh 80', 'Priest', 'Shadow', 'Wrists', 'Both', 'Wraps of the Astral Traveler'), +(5, 2, 9, 0, 200, 39530, 'Fresh 80', 'Priest', 'Shadow', 'Hands', 'Both', 'Heroes'' Handwraps of Faith'), +(5, 2, 10, 0, 200, 40719, 'Fresh 80', 'Priest', 'Shadow', 'Finger1', 'Both', 'Band of Channeled Magic'), +(5, 2, 12, 0, 200, 39229, 'Fresh 80', 'Priest', 'Shadow', 'Trinket1', 'Both', 'Embrace of the Spider'), +(5, 2, 14, 0, 200, 40723, 'Fresh 80', 'Priest', 'Shadow', 'Back', 'Both', 'Disguise of the Kumiho'), +(5, 2, 15, 0, 200, 39423, 'Fresh 80', 'Priest', 'Shadow', 'MainHand', 'Both', 'Hammer of the Astral Plane'), +(5, 2, 17, 0, 200, 39426, 'Fresh 80', 'Priest', 'Shadow', 'Ranged', 'Both', 'Wand of the Archlich'); + +-- ilvl 213 (Pre-Ulduar) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 213, 40562, 'Pre-Ulduar', 'Priest', 'Shadow', 'Head', 'Both', 'Hood of Rationality'), +(5, 2, 1, 0, 213, 44661, 'Pre-Ulduar', 'Priest', 'Shadow', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(5, 2, 2, 0, 213, 40555, 'Pre-Ulduar', 'Priest', 'Shadow', 'Shoulders', 'Both', 'Mantle of Dissemination'), +(5, 2, 4, 0, 213, 40234, 'Pre-Ulduar', 'Priest', 'Shadow', 'Chest', 'Both', 'Heigan''s Putrid Vestments'), +(5, 2, 5, 0, 213, 40561, 'Pre-Ulduar', 'Priest', 'Shadow', 'Waist', 'Both', 'Leash of Heedless Magic'), +(5, 2, 6, 0, 213, 40560, 'Pre-Ulduar', 'Priest', 'Shadow', 'Legs', 'Both', 'Leggings of the Wanton Spellcaster'), +(5, 2, 7, 0, 213, 40558, 'Pre-Ulduar', 'Priest', 'Shadow', 'Feet', 'Both', 'Arcanic Tramplers'), +(5, 2, 8, 0, 213, 44008, 'Pre-Ulduar', 'Priest', 'Shadow', 'Wrists', 'Both', 'Unsullied Cuffs'), +(5, 2, 9, 0, 213, 40454, 'Pre-Ulduar', 'Priest', 'Shadow', 'Hands', 'Both', 'Valorous Handwraps of Faith'), +(5, 2, 10, 0, 213, 40719, 'Pre-Ulduar', 'Priest', 'Shadow', 'Finger1', 'Both', 'Band of Channeled Magic'), +(5, 2, 12, 0, 213, 40432, 'Pre-Ulduar', 'Priest', 'Shadow', 'Trinket1', 'Both', 'Illustration of the Dragon Soul'), +(5, 2, 14, 0, 213, 44005, 'Pre-Ulduar', 'Priest', 'Shadow', 'Back', 'Both', 'Pennant Cloak'), +(5, 2, 17, 0, 213, 39712, 'Pre-Ulduar', 'Priest', 'Shadow', 'Ranged', 'Both', 'Gemmed Wand of the Nerubians'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 245, 46172, 'Phase 2', 'Priest', 'Shadow', 'Head', 'Both', 'Conqueror''s Circlet of Sanctification'), +(5, 2, 1, 0, 245, 45243, 'Phase 2', 'Priest', 'Shadow', 'Neck', 'Both', 'Sapphire Amulet of Renewal'), +(5, 2, 2, 0, 245, 46165, 'Phase 2', 'Priest', 'Shadow', 'Shoulders', 'Both', 'Conqueror''s Mantle of Sanctification'), +(5, 2, 4, 0, 245, 46168, 'Phase 2', 'Priest', 'Shadow', 'Chest', 'Both', 'Conqueror''s Raiments of Sanctification'), +(5, 2, 5, 0, 245, 45619, 'Phase 2', 'Priest', 'Shadow', 'Waist', 'Both', 'Starwatcher''s Binding'), +(5, 2, 6, 0, 245, 46170, 'Phase 2', 'Priest', 'Shadow', 'Legs', 'Both', 'Conqueror''s Pants of Sanctification'), +(5, 2, 7, 0, 245, 45135, 'Phase 2', 'Priest', 'Shadow', 'Feet', 'Both', 'Boots of Fiery Resolution'), +(5, 2, 8, 0, 245, 45446, 'Phase 2', 'Priest', 'Shadow', 'Wrists', 'Both', 'Grasps of Reason'), +(5, 2, 9, 0, 245, 45665, 'Phase 2', 'Priest', 'Shadow', 'Hands', 'Both', 'Pharos Gloves'), +(5, 2, 10, 0, 245, 45495, 'Phase 2', 'Priest', 'Shadow', 'Finger1', 'Both', 'Conductive Seal'), +(5, 2, 12, 0, 245, 45466, 'Phase 2', 'Priest', 'Shadow', 'Trinket1', 'Both', 'Scale of Fates'), +(5, 2, 14, 0, 245, 45242, 'Phase 2', 'Priest', 'Shadow', 'Back', 'Both', 'Drape of Mortal Downfall'), +(5, 2, 17, 0, 245, 45294, 'Phase 2', 'Priest', 'Shadow', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 258, 48085, 'Phase 3', 'Priest', 'Shadow', 'Head', 'Both', 'Circlet of Triumph'), +(5, 2, 1, 1, 258, 47144, 'Phase 3', 'Priest', 'Shadow', 'Neck', 'Alliance', 'Wail of the Val''kyr'), +(5, 2, 1, 2, 258, 47468, 'Phase 3', 'Priest', 'Shadow', 'Neck', 'Horde', 'Cry of the Val''kyr'), +(5, 2, 2, 1, 258, 48082, 'Phase 3', 'Priest', 'Shadow', 'Shoulders', 'Alliance', 'Velen''s Mantle of Triumph'), +(5, 2, 2, 2, 258, 48090, 'Phase 3', 'Priest', 'Shadow', 'Shoulders', 'Horde', 'Zabra''s Raiments of Triumph'), +(5, 2, 4, 0, 258, 48083, 'Phase 3', 'Priest', 'Shadow', 'Chest', 'Both', 'Raiments of Triumph'), +(5, 2, 5, 1, 258, 46973, 'Phase 3', 'Priest', 'Shadow', 'Waist', 'Alliance', 'Cord of the Tenebrous Mist'), +(5, 2, 5, 2, 258, 47419, 'Phase 3', 'Priest', 'Shadow', 'Waist', 'Horde', 'Belt of the Tenebrous Mist'), +(5, 2, 6, 0, 258, 48084, 'Phase 3', 'Priest', 'Shadow', 'Legs', 'Both', 'Pants of Triumph'), +(5, 2, 7, 1, 258, 47097, 'Phase 3', 'Priest', 'Shadow', 'Feet', 'Alliance', 'Boots of the Mourning Widow'), +(5, 2, 7, 2, 258, 47454, 'Phase 3', 'Priest', 'Shadow', 'Feet', 'Horde', 'Sandals of the Mourning Widow'), +(5, 2, 8, 1, 258, 47208, 'Phase 3', 'Priest', 'Shadow', 'Wrists', 'Alliance', 'Armbands of the Ashen Saint'), +(5, 2, 8, 2, 258, 47467, 'Phase 3', 'Priest', 'Shadow', 'Wrists', 'Horde', 'Dark Essence Bindings'), +(5, 2, 9, 1, 258, 45665, 'Phase 3', 'Priest', 'Shadow', 'Hands', 'Alliance', 'Pharos Gloves'), +(5, 2, 10, 1, 258, 46046, 'Phase 3', 'Priest', 'Shadow', 'Finger1', 'Alliance', 'Nebula Band'), +(5, 2, 10, 2, 258, 47237, 'Phase 3', 'Priest', 'Shadow', 'Finger1', 'Horde', 'Band of Deplorable Violence'), +(5, 2, 12, 2, 258, 45518, 'Phase 3', 'Priest', 'Shadow', 'Trinket1', 'Horde', 'Flare of the Heavens'), +(5, 2, 14, 1, 258, 47552, 'Phase 3', 'Priest', 'Shadow', 'Back', 'Alliance', 'Jaina''s Radiance'), +(5, 2, 14, 2, 258, 47551, 'Phase 3', 'Priest', 'Shadow', 'Back', 'Horde', 'Aethas'' Intensity'), +(5, 2, 17, 1, 258, 47922, 'Phase 3', 'Priest', 'Shadow', 'Ranged', 'Alliance', 'Rod of Imprisoned Souls'), +(5, 2, 17, 2, 258, 45294, 'Phase 3', 'Priest', 'Shadow', 'Ranged', 'Horde', 'Petrified Ivy Sprig'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 264, 51255, 'Phase 4', 'Priest', 'Shadow', 'Head', 'Both', 'Sanctified Crimson Acolyte Cowl'), +(5, 2, 1, 0, 264, 50724, 'Phase 4', 'Priest', 'Shadow', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(5, 2, 2, 0, 264, 51257, 'Phase 4', 'Priest', 'Shadow', 'Shoulders', 'Both', 'Sanctified Crimson Acolyte Mantle'), +(5, 2, 4, 0, 264, 51259, 'Phase 4', 'Priest', 'Shadow', 'Chest', 'Both', 'Sanctified Crimson Acolyte Raiments'), +(5, 2, 5, 0, 264, 50613, 'Phase 4', 'Priest', 'Shadow', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(5, 2, 6, 0, 264, 50694, 'Phase 4', 'Priest', 'Shadow', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(5, 2, 7, 0, 264, 50699, 'Phase 4', 'Priest', 'Shadow', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(5, 2, 8, 0, 264, 50651, 'Phase 4', 'Priest', 'Shadow', 'Wrists', 'Both', 'The Lady''s Brittle Bracers'), +(5, 2, 9, 0, 264, 51256, 'Phase 4', 'Priest', 'Shadow', 'Hands', 'Both', 'Sanctified Crimson Acolyte Handwraps'), +(5, 2, 10, 0, 264, 50398, 'Phase 4', 'Priest', 'Shadow', 'Finger1', 'Both', 'Ashen Band of Endless Destruction'), +(5, 2, 12, 0, 264, 50348, 'Phase 4', 'Priest', 'Shadow', 'Trinket1', 'Both', 'Dislodged Foreign Object'), +(5, 2, 14, 0, 264, 50628, 'Phase 4', 'Priest', 'Shadow', 'Back', 'Both', 'Frostbinder''s Shredded Cape'), +(5, 2, 15, 0, 264, 50734, 'Phase 4', 'Priest', 'Shadow', 'MainHand', 'Both', 'Royal Scepter of Terenas II'), +(5, 2, 17, 0, 264, 50684, 'Phase 4', 'Priest', 'Shadow', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(5, 2, 0, 0, 290, 51255, 'Phase 5', 'Priest', 'Shadow', 'Head', 'Both', 'Sanctified Crimson Acolyte Cowl'), +(5, 2, 1, 0, 290, 50182, 'Phase 5', 'Priest', 'Shadow', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(5, 2, 2, 0, 290, 51257, 'Phase 5', 'Priest', 'Shadow', 'Shoulders', 'Both', 'Sanctified Crimson Acolyte Mantle'), +(5, 2, 4, 0, 290, 51259, 'Phase 5', 'Priest', 'Shadow', 'Chest', 'Both', 'Sanctified Crimson Acolyte Raiments'), +(5, 2, 5, 0, 290, 50613, 'Phase 5', 'Priest', 'Shadow', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(5, 2, 6, 0, 290, 50694, 'Phase 5', 'Priest', 'Shadow', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(5, 2, 7, 0, 290, 50699, 'Phase 5', 'Priest', 'Shadow', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(5, 2, 8, 0, 290, 54582, 'Phase 5', 'Priest', 'Shadow', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(5, 2, 9, 0, 290, 51256, 'Phase 5', 'Priest', 'Shadow', 'Hands', 'Both', 'Sanctified Crimson Acolyte Handwraps'), +(5, 2, 10, 0, 290, 50664, 'Phase 5', 'Priest', 'Shadow', 'Finger1', 'Both', 'Ring of Rapid Ascent'), +(5, 2, 11, 0, 290, 50398, 'Phase 5', 'Priest', 'Shadow', 'Finger2', 'Both', 'Ashen Band of Endless Destruction'), +(5, 2, 12, 0, 290, 54588, 'Phase 5', 'Priest', 'Shadow', 'Trinket1', 'Both', 'Charred Twilight Scale'), +(5, 2, 13, 0, 290, 50348, 'Phase 5', 'Priest', 'Shadow', 'Trinket2', 'Both', 'Dislodged Foreign Object'), +(5, 2, 14, 0, 290, 54583, 'Phase 5', 'Priest', 'Shadow', 'Back', 'Both', 'Cloak of Burning Dusk'), +(5, 2, 15, 0, 290, 50734, 'Phase 5', 'Priest', 'Shadow', 'MainHand', 'Both', 'Royal Scepter of Terenas II'), +(5, 2, 16, 0, 290, 50719, 'Phase 5', 'Priest', 'Shadow', 'OffHand', 'Both', 'Shadow Silk Spindle'), +(5, 2, 17, 0, 290, 50684, 'Phase 5', 'Priest', 'Shadow', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + + +-- ============================================================ +-- Death Knight (6) +-- ============================================================ +-- Blood Tank (tab 0) +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 0, 0, 0, 200, 41387, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Head', 'Both', 'Tempered Titansteel Helm'), +(6, 0, 1, 0, 200, 42646, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Neck', 'Both', 'Titanium Earthguard Chain'), +(6, 0, 2, 0, 200, 37814, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Shoulders', 'Both', 'Iron Dwarf Smith Pauldrons'), +(6, 0, 4, 0, 200, 37735, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Chest', 'Both', 'Ziggurat Imprinted Chestguard'), +(6, 0, 5, 0, 200, 37241, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Waist', 'Both', 'Ancient Aligned Girdle'), +(6, 0, 6, 0, 200, 37193, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Legs', 'Both', 'Staggering Legplates'), +(6, 0, 7, 0, 200, 41392, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Feet', 'Both', 'Tempered Titansteel Treads'), +(6, 0, 8, 0, 200, 37620, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Wrists', 'Both', 'Bracers of the Herald'), +(6, 0, 9, 0, 200, 37645, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Hands', 'Both', 'Horn-Tipped Gauntlets'), +(6, 0, 10, 0, 200, 42643, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Finger1', 'Both', 'Titanium Earthguard Ring'), +(6, 0, 12, 0, 200, 37220, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Trinket1', 'Both', 'Essence of Gossamer'), +(6, 0, 14, 0, 200, 43565, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Back', 'Both', 'Durable Nerubhide Cape'), +(6, 0, 15, 0, 200, 41257, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'MainHand', 'Both', 'Titansteel Destroyer'), +(6, 0, 17, 0, 200, 40714, 'Pre-Raid', 'Death Knight', 'Blood Tank', 'Ranged', 'Both', 'Sigil of the Unfaltering Knight'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 0, 0, 0, 224, 40565, 'Phase 1', 'Death Knight', 'Blood Tank', 'Head', 'Both', 'Valorous Scourgeborne Faceguard'), +(6, 0, 1, 0, 224, 40387, 'Phase 1', 'Death Knight', 'Blood Tank', 'Neck', 'Both', 'Boundless Ambition'), +(6, 0, 2, 0, 224, 40568, 'Phase 1', 'Death Knight', 'Blood Tank', 'Shoulders', 'Both', 'Valorous Scourgeborne Pauldrons'), +(6, 0, 4, 0, 224, 40559, 'Phase 1', 'Death Knight', 'Blood Tank', 'Chest', 'Both', 'Valorous Scourgeborne Chestguard'), +(6, 0, 5, 0, 224, 39759, 'Phase 1', 'Death Knight', 'Blood Tank', 'Waist', 'Both', 'Ablative Chitin Girdle'), +(6, 0, 6, 0, 224, 40589, 'Phase 1', 'Death Knight', 'Blood Tank', 'Legs', 'Both', 'Legplates of Sovereignty'), +(6, 0, 7, 0, 224, 40297, 'Phase 1', 'Death Knight', 'Blood Tank', 'Feet', 'Both', 'Sabatons of Endurance'), +(6, 0, 8, 0, 224, 40306, 'Phase 1', 'Death Knight', 'Blood Tank', 'Wrists', 'Both', 'Bracers of the Unholy Knight'), +(6, 0, 9, 0, 224, 40563, 'Phase 1', 'Death Knight', 'Blood Tank', 'Hands', 'Both', 'Valorous Scourgeborne Handguards'), +(6, 0, 10, 0, 224, 40107, 'Phase 1', 'Death Knight', 'Blood Tank', 'Finger1', 'Both', 'Sand-Worn Band'), +(6, 0, 12, 0, 224, 37220, 'Phase 1', 'Death Knight', 'Blood Tank', 'Trinket1', 'Both', 'Essence of Gossamer'), +(6, 0, 14, 0, 224, 40252, 'Phase 1', 'Death Knight', 'Blood Tank', 'Back', 'Both', 'Cloak of the Shadowed Sun'), +(6, 0, 15, 0, 224, 40406, 'Phase 1', 'Death Knight', 'Blood Tank', 'MainHand', 'Both', 'Inevitable Defeat'), +(6, 0, 17, 0, 224, 40714, 'Phase 1', 'Death Knight', 'Blood Tank', 'Ranged', 'Both', 'Sigil of the Unfaltering Knight'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 0, 0, 0, 245, 46120, 'Phase 2', 'Death Knight', 'Blood Tank', 'Head', 'Both', 'Conqueror''s Darkruned Faceguard'), +(6, 0, 1, 0, 245, 45485, 'Phase 2', 'Death Knight', 'Blood Tank', 'Neck', 'Both', 'Bronze Pendant of the Vanir'), +(6, 0, 2, 0, 245, 46122, 'Phase 2', 'Death Knight', 'Blood Tank', 'Shoulders', 'Both', 'Conqueror''s Darkruned Pauldrons'), +(6, 0, 4, 0, 245, 46039, 'Phase 2', 'Death Knight', 'Blood Tank', 'Chest', 'Both', 'Breastplate of the Timeless'), +(6, 0, 5, 0, 245, 45825, 'Phase 2', 'Death Knight', 'Blood Tank', 'Waist', 'Both', 'Shieldwarder Girdle'), +(6, 0, 6, 0, 245, 45594, 'Phase 2', 'Death Knight', 'Blood Tank', 'Legs', 'Both', 'Legplates of the Endless Void'), +(6, 0, 7, 0, 245, 45988, 'Phase 2', 'Death Knight', 'Blood Tank', 'Feet', 'Both', 'Greaves of the Iron Army'), +(6, 0, 8, 0, 245, 45111, 'Phase 2', 'Death Knight', 'Blood Tank', 'Wrists', 'Both', 'Mimiron''s Inferno Couplings'), +(6, 0, 9, 0, 245, 45487, 'Phase 2', 'Death Knight', 'Blood Tank', 'Hands', 'Both', 'Handguards of Revitalization'), +(6, 0, 10, 0, 245, 45471, 'Phase 2', 'Death Knight', 'Blood Tank', 'Finger1', 'Both', 'Fate''s Clutch'), +(6, 0, 12, 0, 245, 45158, 'Phase 2', 'Death Knight', 'Blood Tank', 'Trinket1', 'Both', 'Heart of Iron'), +(6, 0, 14, 0, 245, 45496, 'Phase 2', 'Death Knight', 'Blood Tank', 'Back', 'Both', 'Titanskin Cloak'), +(6, 0, 15, 0, 245, 45516, 'Phase 2', 'Death Knight', 'Blood', 'MainHand', 'Both', 'Voldrethar, Dark Blade of Oblivion'), +(6, 0, 17, 0, 245, 40207, 'Phase 2', 'Death Knight', 'Blood Tank', 'Ranged', 'Both', 'Sigil of Awareness'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 0, 0, 0, 258, 48545, 'Phase 3', 'Death Knight', 'Blood Tank', 'Head', 'Both', 'Faceguard of Triumph'), +(6, 0, 0, 1, 258, 48488, 'Phase 3', 'Death Knight', 'Blood', 'Head', 'Alliance', 'Thassarian''s Helmet of Triumph'), +(6, 0, 0, 2, 258, 48493, 'Phase 3', 'Death Knight', 'Blood', 'Head', 'Horde', 'Koltira''s Helmet of Triumph'), +(6, 0, 1, 1, 258, 47110, 'Phase 3', 'Death Knight', 'Blood', 'Neck', 'Alliance', 'The Executioner''s Malice'), +(6, 0, 1, 2, 258, 47458, 'Phase 3', 'Death Knight', 'Blood', 'Neck', 'Horde', 'The Executioner''s Vice'), +(6, 0, 2, 0, 258, 48543, 'Phase 3', 'Death Knight', 'Blood Tank', 'Shoulders', 'Both', 'Pauldrons of Triumph'), +(6, 0, 2, 1, 258, 48486, 'Phase 3', 'Death Knight', 'Blood', 'Shoulders', 'Alliance', 'Thassarian''s Shoulderplates of Triumph'), +(6, 0, 2, 2, 258, 48495, 'Phase 3', 'Death Knight', 'Blood', 'Shoulders', 'Horde', 'Koltira''s Shoulderplates of Triumph'), +(6, 0, 4, 1, 258, 46968, 'Phase 3', 'Death Knight', 'Blood Tank', 'Chest', 'Alliance', 'Chestplate of the Towering Monstrosity'), +(6, 0, 4, 2, 258, 47415, 'Phase 3', 'Death Knight', 'Blood Tank', 'Chest', 'Horde', 'Hauberk of the Towering Monstrosity'), +(6, 0, 5, 1, 258, 47076, 'Phase 3', 'Death Knight', 'Blood Tank', 'Waist', 'Alliance', 'Girdle of Bloodied Scars'), +(6, 0, 5, 2, 258, 47444, 'Phase 3', 'Death Knight', 'Blood Tank', 'Waist', 'Horde', 'Belt of Bloodied Scars'), +(6, 0, 6, 0, 258, 48544, 'Phase 3', 'Death Knight', 'Blood Tank', 'Legs', 'Both', 'Legguards of Triumph'), +(6, 0, 6, 1, 258, 48487, 'Phase 3', 'Death Knight', 'Blood', 'Legs', 'Alliance', 'Thassarian''s Legplates of Triumph'), +(6, 0, 6, 2, 258, 48494, 'Phase 3', 'Death Knight', 'Blood', 'Legs', 'Horde', 'Koltira''s Legplates of Triumph'), +(6, 0, 7, 0, 258, 45599, 'Phase 3', 'Death Knight', 'Blood', 'Feet', 'Both', 'Sabatons of Lifeless Night'), +(6, 0, 7, 1, 258, 47003, 'Phase 3', 'Death Knight', 'Blood Tank', 'Feet', 'Alliance', 'Dawnbreaker Greaves'), +(6, 0, 7, 2, 258, 47430, 'Phase 3', 'Death Knight', 'Blood Tank', 'Feet', 'Horde', 'Dawnbreaker Sabatons'), +(6, 0, 8, 0, 258, 45663, 'Phase 3', 'Death Knight', 'Blood', 'Wrists', 'Both', 'Armbands of Bedlam'), +(6, 0, 8, 1, 258, 47111, 'Phase 3', 'Death Knight', 'Blood Tank', 'Wrists', 'Alliance', 'Bracers of the Shieldmaiden'), +(6, 0, 8, 2, 258, 47459, 'Phase 3', 'Death Knight', 'Blood Tank', 'Wrists', 'Horde', 'Armguards of the Shieldmaiden'), +(6, 0, 9, 0, 258, 48546, 'Phase 3', 'Death Knight', 'Blood Tank', 'Hands', 'Both', 'Handguards of Triumph'), +(6, 0, 9, 1, 258, 47240, 'Phase 3', 'Death Knight', 'Blood', 'Hands', 'Alliance', 'Gloves of Bitter Reprisal'), +(6, 0, 9, 2, 258, 47492, 'Phase 3', 'Death Knight', 'Blood', 'Hands', 'Horde', 'Gauntlets of Bitter Reprisal'), +(6, 0, 10, 1, 258, 45471, 'Phase 3', 'Death Knight', 'Blood Tank', 'Finger1', 'Alliance', 'Fate''s Clutch'), +(6, 0, 10, 2, 258, 47413, 'Phase 3', 'Death Knight', 'Blood', 'Finger1', 'Horde', 'Ring of the Violent Temperament'), +(6, 0, 12, 0, 258, 45931, 'Phase 3', 'Death Knight', 'Blood', 'Trinket1', 'Both', 'Mjolnir Runestone'), +(6, 0, 12, 1, 258, 47216, 'Phase 3', 'Death Knight', 'Blood Tank', 'Trinket1', 'Alliance', 'The Black Heart'), +(6, 0, 14, 1, 258, 47549, 'Phase 3', 'Death Knight', 'Blood Tank', 'Back', 'Alliance', 'Magni''s Resolution'), +(6, 0, 14, 2, 258, 47550, 'Phase 3', 'Death Knight', 'Blood Tank', 'Back', 'Horde', 'Cairne''s Endurance'), +(6, 0, 15, 0, 258, 47156, 'Phase 3', 'Death Knight', 'Blood', 'MainHand', 'Both', 'Stormpike Cleaver'), +(6, 0, 15, 2, 258, 47475, 'Phase 3', 'Death Knight', 'Blood', 'MainHand', 'Horde', 'Hellscream Slicer'), +(6, 0, 17, 0, 258, 47672, 'Phase 3', 'Death Knight', 'Blood Tank', 'Ranged', 'Both', 'Sigil of Insolence'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 0, 0, 0, 264, 51306, 'Phase 4', 'Death Knight', 'Blood Tank', 'Head', 'Both', 'Sanctified Scourgelord Faceguard'), +(6, 0, 1, 0, 264, 54581, 'Phase 4', 'Death Knight', 'Blood', 'Neck', 'Both', 'Penumbra Pendant'), +(6, 0, 2, 0, 264, 51309, 'Phase 4', 'Death Knight', 'Blood Tank', 'Shoulders', 'Both', 'Sanctified Scourgelord Pauldrons'), +(6, 0, 4, 0, 264, 51305, 'Phase 4', 'Death Knight', 'Blood Tank', 'Chest', 'Both', 'Sanctified Scourgelord Chestguard'), +(6, 0, 5, 0, 264, 50691, 'Phase 4', 'Death Knight', 'Blood Tank', 'Waist', 'Both', 'Belt of Broken Bones'), +(6, 0, 6, 0, 264, 50612, 'Phase 4', 'Death Knight', 'Blood Tank', 'Legs', 'Both', 'Legguards of Lost Hope'), +(6, 0, 7, 0, 264, 50625, 'Phase 4', 'Death Knight', 'Blood Tank', 'Feet', 'Both', 'Grinning Skull Greatboots'), +(6, 0, 8, 0, 264, 50611, 'Phase 4', 'Death Knight', 'Blood Tank', 'Wrists', 'Both', 'Bracers of Dark Reckoning'), +(6, 0, 9, 0, 264, 51307, 'Phase 4', 'Death Knight', 'Blood Tank', 'Hands', 'Both', 'Sanctified Scourgelord Handguards'), +(6, 0, 10, 0, 264, 50622, 'Phase 4', 'Death Knight', 'Blood Tank', 'Finger1', 'Both', 'Devium''s Eternally Cold Ring'), +(6, 0, 12, 0, 264, 50364, 'Phase 4', 'Death Knight', 'Blood Tank', 'Trinket1', 'Both', 'Sindragosa''s Flawless Fang'), +(6, 0, 14, 0, 264, 50718, 'Phase 4', 'Death Knight', 'Blood Tank', 'Back', 'Both', 'Royal Crimson Cloak'), +(6, 0, 15, 0, 264, 49623, 'Phase 4', 'Death Knight', 'Blood', 'MainHand', 'Both', 'Shadowmourne'), +(6, 0, 17, 0, 264, 50462, 'Phase 4', 'Death Knight', 'Blood Tank', 'Ranged', 'Both', 'Sigil of the Bone Gryphon'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 0, 0, 0, 290, 51306, 'Phase 5', 'DeathKnight', 'BloodTank', 'Head', 'Both', 'Sanctified Scourgelord Faceguard'), +(6, 0, 1, 0, 290, 50682, 'Phase 5', 'DeathKnight', 'BloodTank', 'Neck', 'Both', 'Bile-Encrusted Medallion'), +(6, 0, 2, 0, 290, 51309, 'Phase 5', 'DeathKnight', 'BloodTank', 'Shoulders', 'Both', 'Sanctified Scourgelord Pauldrons'), +(6, 0, 4, 0, 290, 51305, 'Phase 5', 'DeathKnight', 'BloodTank', 'Chest', 'Both', 'Sanctified Scourgelord Chestguard'), +(6, 0, 5, 0, 290, 50991, 'Phase 5', 'DeathKnight', 'BloodTank', 'Waist', 'Both', 'Verdigris Chain Belt'), +(6, 0, 6, 0, 290, 50612, 'Phase 5', 'DeathKnight', 'BloodTank', 'Legs', 'Both', 'Legguards of Lost Hope'), +(6, 0, 7, 0, 290, 54579, 'Phase 5', 'DeathKnight', 'BloodTank', 'Feet', 'Both', 'Treads of Impending Resurrection'), +(6, 0, 8, 0, 290, 51901, 'Phase 5', 'DeathKnight', 'BloodTank', 'Wrists', 'Both', 'Gargoyle Spit Bracers'), +(6, 0, 9, 0, 290, 51307, 'Phase 5', 'DeathKnight', 'BloodTank', 'Hands', 'Both', 'Sanctified Scourgelord Handguards'), +(6, 0, 10, 0, 290, 50622, 'Phase 5', 'DeathKnight', 'BloodTank', 'Finger1', 'Both', 'Devium''s Eternally Cold Ring'), +(6, 0, 11, 0, 290, 50404, 'Phase 5', 'DeathKnight', 'BloodTank', 'Finger2', 'Both', 'Ashen Band of Endless Courage'), +(6, 0, 12, 0, 290, 54591, 'Phase 5', 'DeathKnight', 'BloodTank', 'Trinket1', 'Both', 'Petrified Twilight Scale'), +(6, 0, 13, 0, 290, 50364, 'Phase 5', 'DeathKnight', 'BloodTank', 'Trinket2', 'Both', 'Sindragosa''s Flawless Fang'), +(6, 0, 14, 0, 290, 50466, 'Phase 5', 'DeathKnight', 'BloodTank', 'Back', 'Both', 'Sentinel''s Winter Cloak'), +(6, 0, 15, 0, 290, 49623, 'Phase 5', 'DeathKnight', 'BloodTank', 'MainHand', 'Both', 'Shadowmourne'), +(6, 0, 17, 0, 290, 50462, 'Phase 5', 'DeathKnight', 'BloodTank', 'Ranged', 'Both', 'Sigil of the Bone Gryphon'); + +-- Frost (tab 1) +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 1, 0, 0, 200, 41386, 'Pre-Raid', 'Death Knight', 'Frost', 'Head', 'Both', 'Spiked Titansteel Helm'), +(6, 1, 1, 0, 200, 42645, 'Pre-Raid', 'Death Knight', 'Frost', 'Neck', 'Both', 'Titanium Impact Choker'), +(6, 1, 2, 0, 200, 37627, 'Pre-Raid', 'Death Knight', 'Frost', 'Shoulders', 'Both', 'Snake Den Spaulders'), +(6, 1, 4, 0, 200, 37612, 'Pre-Raid', 'Death Knight', 'Frost', 'Chest', 'Both', 'Bonegrinder Breastplate'), +(6, 1, 5, 0, 200, 37171, 'Pre-Raid', 'Death Knight', 'Frost', 'Waist', 'Both', 'Flame-Bathed Steel Girdle'), +(6, 1, 6, 0, 200, 37193, 'Pre-Raid', 'Death Knight', 'Frost', 'Legs', 'Both', 'Staggering Legplates'), +(6, 1, 7, 0, 200, 41391, 'Pre-Raid', 'Death Knight', 'Frost', 'Feet', 'Both', 'Spiked Titansteel Treads'), +(6, 1, 8, 0, 200, 37668, 'Pre-Raid', 'Death Knight', 'Frost', 'Wrists', 'Both', 'Bands of the Stoneforge'), +(6, 1, 9, 0, 200, 37363, 'Pre-Raid', 'Death Knight', 'Frost', 'Hands', 'Both', 'Gauntlets of Dragon Wrath'), +(6, 1, 10, 0, 200, 37642, 'Pre-Raid', 'Death Knight', 'Frost', 'Finger1', 'Both', 'Hemorrhaging Circle'), +(6, 1, 12, 0, 200, 40684, 'Pre-Raid', 'Death Knight', 'Frost', 'Trinket1', 'Both', 'Mirror of Truth'), +(6, 1, 14, 0, 200, 43566, 'Pre-Raid', 'Death Knight', 'Frost', 'Back', 'Both', 'Ice Striker''s Cloak'), +(6, 1, 15, 0, 200, 41383, 'Pre-Raid', 'Death Knight', 'Frost', 'MainHand', 'Both', 'Titansteel Bonecrusher'), +(6, 1, 17, 0, 200, 40715, 'Pre-Raid', 'Death Knight', 'Frost', 'Ranged', 'Both', 'Sigil of Haunted Dreams'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 1, 0, 0, 224, 44006, 'Phase 1', 'Death Knight', 'Frost', 'Head', 'Both', 'Obsidian Greathelm'), +(6, 1, 1, 0, 224, 44664, 'Phase 1', 'Death Knight', 'Frost', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(6, 1, 2, 0, 224, 40557, 'Phase 1', 'Death Knight', 'Frost', 'Shoulders', 'Both', 'Valorous Scourgeborne Shoulderplates'), +(6, 1, 4, 0, 224, 40550, 'Phase 1', 'Death Knight', 'Frost', 'Chest', 'Both', 'Valorous Scourgeborne Battleplate'), +(6, 1, 5, 0, 224, 40317, 'Phase 1', 'Death Knight', 'Frost', 'Waist', 'Both', 'Girdle of Razuvious'), +(6, 1, 6, 0, 224, 40556, 'Phase 1', 'Death Knight', 'Frost', 'Legs', 'Both', 'Valorous Scourgeborne Legplates'), +(6, 1, 7, 0, 224, 40591, 'Phase 1', 'Death Knight', 'Frost', 'Feet', 'Both', 'Melancholy Sabatons'), +(6, 1, 8, 0, 224, 40330, 'Phase 1', 'Death Knight', 'Frost', 'Wrists', 'Both', 'Bracers of Unrelenting Attack'), +(6, 1, 9, 0, 224, 40552, 'Phase 1', 'Death Knight', 'Frost', 'Hands', 'Both', 'Valorous Scourgeborne Gauntlets'), +(6, 1, 10, 0, 224, 40075, 'Phase 1', 'Death Knight', 'Frost', 'Finger1', 'Both', 'Ruthlessness'), +(6, 1, 12, 0, 224, 40256, 'Phase 1', 'Death Knight', 'Frost', 'Trinket1', 'Both', 'Grim Toll'), +(6, 1, 14, 0, 224, 40403, 'Phase 1', 'Death Knight', 'Frost', 'Back', 'Both', 'Drape of the Deadly Foe'), +(6, 1, 15, 0, 224, 40189, 'Phase 1', 'Death Knight', 'Frost', 'MainHand', 'Both', 'Angry Dread'), +(6, 1, 17, 0, 224, 40207, 'Phase 1', 'Death Knight', 'Frost', 'Ranged', 'Both', 'Sigil of Awareness'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 1, 0, 0, 245, 46115, 'Phase 2', 'Death Knight', 'Frost', 'Head', 'Both', 'Conqueror''s Darkruned Helmet'), +(6, 1, 1, 0, 245, 45459, 'Phase 2', 'Death Knight', 'Frost', 'Neck', 'Both', 'Frigid Strength of Hodir'), +(6, 1, 2, 0, 245, 46117, 'Phase 2', 'Death Knight', 'Frost', 'Shoulders', 'Both', 'Conqueror''s Darkruned Shoulderplates'), +(6, 1, 4, 0, 245, 46111, 'Phase 2', 'Death Knight', 'Frost', 'Chest', 'Both', 'Conqueror''s Darkruned Battleplate'), +(6, 1, 5, 0, 245, 45241, 'Phase 2', 'Death Knight', 'Frost', 'Waist', 'Both', 'Belt of Colossal Rage'), +(6, 1, 6, 0, 245, 45134, 'Phase 2', 'Death Knight', 'Frost', 'Legs', 'Both', 'Plated Leggings of Ruination'), +(6, 1, 7, 0, 245, 45599, 'Phase 2', 'Death Knight', 'Frost', 'Feet', 'Both', 'Sabatons of Lifeless Night'), +(6, 1, 8, 0, 245, 45663, 'Phase 2', 'Death Knight', 'Frost', 'Wrists', 'Both', 'Armbands of Bedlam'), +(6, 1, 9, 0, 245, 46113, 'Phase 2', 'Death Knight', 'Frost', 'Hands', 'Both', 'Conqueror''s Darkruned Gauntlets'), +(6, 1, 10, 0, 245, 45534, 'Phase 2', 'Death Knight', 'Frost', 'Finger1', 'Both', 'Seal of the Betrayed King'), +(6, 1, 12, 0, 245, 45931, 'Phase 2', 'Death Knight', 'Frost', 'Trinket1', 'Both', 'Mjolnir Runestone'), +(6, 1, 14, 0, 245, 46032, 'Phase 2', 'Death Knight', 'Frost', 'Back', 'Both', 'Drape of the Faceless General'), +(6, 1, 15, 0, 245, 46097, 'Phase 2', 'Death Knight', 'Frost', 'MainHand', 'Both', 'Caress of Insanity'), +(6, 1, 17, 0, 245, 40207, 'Phase 2', 'Death Knight', 'Frost', 'Ranged', 'Both', 'Sigil of Awareness'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 1, 0, 1, 258, 48488, 'Phase 3', 'Death Knight', 'Frost', 'Head', 'Alliance', 'Thassarian''s Helmet of Triumph'), +(6, 1, 0, 2, 258, 48493, 'Phase 3', 'Death Knight', 'Frost', 'Head', 'Horde', 'Koltira''s Helmet of Triumph'), +(6, 1, 1, 1, 258, 47110, 'Phase 3', 'Death Knight', 'Frost', 'Neck', 'Alliance', 'The Executioner''s Malice'), +(6, 1, 1, 2, 258, 47458, 'Phase 3', 'Death Knight', 'Frost', 'Neck', 'Horde', 'The Executioner''s Vice'), +(6, 1, 2, 1, 258, 48486, 'Phase 3', 'Death Knight', 'Frost', 'Shoulders', 'Alliance', 'Thassarian''s Shoulderplates of Triumph'), +(6, 1, 2, 2, 258, 48495, 'Phase 3', 'Death Knight', 'Frost', 'Shoulders', 'Horde', 'Koltira''s Shoulderplates of Triumph'), +(6, 1, 4, 1, 258, 48490, 'Phase 3', 'Death Knight', 'Frost', 'Chest', 'Alliance', 'Thassarian''s Battleplate of Triumph'), +(6, 1, 4, 2, 258, 48491, 'Phase 3', 'Death Knight', 'Frost', 'Chest', 'Horde', 'Koltira''s Battleplate of Triumph'), +(6, 1, 5, 0, 258, 45241, 'Phase 3', 'Death Knight', 'Frost', 'Waist', 'Both', 'Belt of Colossal Rage'), +(6, 1, 6, 1, 258, 48487, 'Phase 3', 'Death Knight', 'Frost', 'Legs', 'Alliance', 'Thassarian''s Legplates of Triumph'), +(6, 1, 6, 2, 258, 48494, 'Phase 3', 'Death Knight', 'Frost', 'Legs', 'Horde', 'Koltira''s Legplates of Triumph'), +(6, 1, 7, 0, 258, 45599, 'Phase 3', 'Death Knight', 'Frost', 'Feet', 'Both', 'Sabatons of Lifeless Night'), +(6, 1, 8, 0, 258, 45663, 'Phase 3', 'Death Knight', 'Frost', 'Wrists', 'Both', 'Armbands of Bedlam'), +(6, 1, 9, 1, 258, 47240, 'Phase 3', 'Death Knight', 'Frost', 'Hands', 'Alliance', 'Gloves of Bitter Reprisal'), +(6, 1, 9, 2, 258, 47492, 'Phase 3', 'Death Knight', 'Frost', 'Hands', 'Horde', 'Gauntlets of Bitter Reprisal'), +(6, 1, 10, 1, 258, 46966, 'Phase 3', 'Death Knight', 'Frost', 'Finger1', 'Alliance', 'Band of the Violent Temperment'), +(6, 1, 10, 2, 258, 47413, 'Phase 3', 'Death Knight', 'Frost', 'Finger1', 'Horde', 'Ring of the Violent Temperament'), +(6, 1, 12, 0, 258, 45931, 'Phase 3', 'Death Knight', 'Frost', 'Trinket1', 'Both', 'Mjolnir Runestone'), +(6, 1, 14, 1, 258, 47547, 'Phase 3', 'Death Knight', 'Frost', 'Back', 'Alliance', 'Varian''s Furor'), +(6, 1, 14, 2, 258, 47548, 'Phase 3', 'Death Knight', 'Frost', 'Back', 'Horde', 'Garrosh''s Rage'), +(6, 1, 15, 0, 258, 47156, 'Phase 3', 'Death Knight', 'Frost', 'MainHand', 'Both', 'Stormpike Cleaver'), +(6, 1, 15, 2, 258, 47475, 'Phase 3', 'Death Knight', 'Frost', 'MainHand', 'Horde', 'Hellscream Slicer'), +(6, 1, 17, 0, 258, 40207, 'Phase 3', 'Death Knight', 'Frost', 'Ranged', 'Both', 'Sigil of Awareness'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 1, 0, 0, 264, 51312, 'Phase 4', 'Death Knight', 'Frost', 'Head', 'Both', 'Sanctified Scourgelord Helmet'), +(6, 1, 1, 0, 264, 50728, 'Phase 4', 'Death Knight', 'Frost', 'Neck', 'Both', 'Lana''thel''s Chain of Flagellation'), +(6, 1, 2, 0, 264, 51314, 'Phase 4', 'Death Knight', 'Frost', 'Shoulders', 'Both', 'Sanctified Scourgelord Shoulderplates'), +(6, 1, 4, 0, 264, 51310, 'Phase 4', 'Death Knight', 'Frost', 'Chest', 'Both', 'Sanctified Scourgelord Battleplate'), +(6, 1, 5, 0, 264, 50620, 'Phase 4', 'Death Knight', 'Frost', 'Waist', 'Both', 'Coldwraith Links'), +(6, 1, 6, 0, 264, 51817, 'Phase 4', 'Death Knight', 'Frost', 'Legs', 'Both', 'Legplates of Aetheric Strife'), +(6, 1, 7, 0, 264, 50639, 'Phase 4', 'Death Knight', 'Frost', 'Feet', 'Both', 'Blood-Soaked Saronite Stompers'), +(6, 1, 8, 0, 264, 50659, 'Phase 4', 'Death Knight', 'Frost', 'Wrists', 'Both', 'Polar Bear Claw Bracers'), +(6, 1, 9, 0, 264, 51311, 'Phase 4', 'Death Knight', 'Frost', 'Hands', 'Both', 'Sanctified Scourgelord Gauntlets'), +(6, 1, 10, 0, 264, 52572, 'Phase 4', 'Death Knight', 'Frost', 'Finger1', 'Both', 'Ashen Band of Endless Might'), +(6, 1, 12, 0, 264, 50363, 'Phase 4', 'Death Knight', 'Frost', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(6, 1, 14, 1, 264, 47547, 'Phase 4', 'Death Knight', 'Frost', 'Back', 'Alliance', 'Varian''s Furor'), +(6, 1, 14, 2, 264, 47548, 'Phase 4', 'Death Knight', 'Frost', 'Back', 'Horde', 'Garrosh''s Rage'), +(6, 1, 15, 0, 264, 50737, 'Phase 4', 'Death Knight', 'Frost', 'MainHand', 'Both', 'Havoc''s Call, Blade of Lordaeron Kings'), +(6, 1, 17, 0, 264, 40207, 'Phase 4', 'Death Knight', 'Frost', 'Ranged', 'Both', 'Sigil of Awareness'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 1, 0, 0, 290, 51312, 'Phase 5', 'DeathKnight', 'Frost', 'Head', 'Both', 'Sanctified Scourgelord Helmet'), +(6, 1, 1, 0, 290, 54581, 'Phase 5', 'DeathKnight', 'Frost', 'Neck', 'Both', 'Penumbra Pendant'), +(6, 1, 2, 0, 290, 51314, 'Phase 5', 'DeathKnight', 'Frost', 'Shoulders', 'Both', 'Sanctified Scourgelord Shoulderplates'), +(6, 1, 4, 0, 290, 51310, 'Phase 5', 'DeathKnight', 'Frost', 'Chest', 'Both', 'Sanctified Scourgelord Battleplate'), +(6, 1, 5, 0, 290, 50620, 'Phase 5', 'DeathKnight', 'Frost', 'Waist', 'Both', 'Coldwraith Links'), +(6, 1, 6, 0, 290, 51313, 'Phase 5', 'DeathKnight', 'Frost', 'Legs', 'Both', 'Sanctified Scourgelord Legplates'), +(6, 1, 7, 0, 290, 54578, 'Phase 5', 'DeathKnight', 'Frost', 'Feet', 'Both', 'Apocalypse''s Advance'), +(6, 1, 8, 0, 290, 50670, 'Phase 5', 'DeathKnight', 'Frost', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(6, 1, 9, 0, 290, 50690, 'Phase 5', 'DeathKnight', 'Frost', 'Hands', 'Both', 'Fleshrending Gauntlets'), +(6, 1, 10, 0, 290, 50693, 'Phase 5', 'DeathKnight', 'Frost', 'Finger1', 'Both', 'Might of Blight'), +(6, 1, 11, 0, 290, 52572, 'Phase 5', 'DeathKnight', 'Frost', 'Finger2', 'Both', 'Ashen Band of Endless Might'), +(6, 1, 12, 0, 290, 54590, 'Phase 5', 'DeathKnight', 'Frost', 'Trinket1', 'Both', 'Sharpened Twilight Scale'), +(6, 1, 13, 0, 290, 50363, 'Phase 5', 'DeathKnight', 'Frost', 'Trinket2', 'Both', 'Deathbringer''s Will'), +(6, 1, 14, 0, 290, 50677, 'Phase 5', 'DeathKnight', 'Frost', 'Back', 'Both', 'Winding Sheet'), +(6, 1, 15, 0, 290, 50737, 'Phase 5', 'DeathKnight', 'Frost', 'MainHand', 'Both', 'Havoc''s Call, Blade of Lordaeron Kings'), +(6, 1, 16, 0, 290, 50737, 'Phase 5', 'DeathKnight', 'Frost', 'OffHand', 'Both', 'Havoc''s Call, Blade of Lordaeron Kings'), +(6, 1, 17, 0, 290, 50459, 'Phase 5', 'DeathKnight', 'Frost', 'Ranged', 'Both', 'Sigil of the Hanged Man'); + +-- Unholy (tab 2) +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 2, 0, 0, 200, 41386, 'Pre-Raid', 'Death Knight', 'Unholy', 'Head', 'Both', 'Spiked Titansteel Helm'), +(6, 2, 1, 0, 200, 42645, 'Pre-Raid', 'Death Knight', 'Unholy', 'Neck', 'Both', 'Titanium Impact Choker'), +(6, 2, 2, 0, 200, 37627, 'Pre-Raid', 'Death Knight', 'Unholy', 'Shoulders', 'Both', 'Snake Den Spaulders'), +(6, 2, 4, 0, 200, 39617, 'Pre-Raid', 'Death Knight', 'Unholy', 'Chest', 'Both', 'Heroes'' Scourgeborne Battleplate'), +(6, 2, 5, 0, 200, 37178, 'Pre-Raid', 'Death Knight', 'Unholy', 'Waist', 'Both', 'Strategist''s Belt'), +(6, 2, 6, 0, 200, 39620, 'Pre-Raid', 'Death Knight', 'Unholy', 'Legs', 'Both', 'Heroes'' Scourgeborne Legplates'), +(6, 2, 7, 0, 200, 43402, 'Pre-Raid', 'Death Knight', 'Unholy', 'Feet', 'Both', 'The Obliterator Greaves'), +(6, 2, 8, 0, 200, 37668, 'Pre-Raid', 'Death Knight', 'Unholy', 'Wrists', 'Both', 'Bands of the Stoneforge'), +(6, 2, 9, 0, 200, 39618, 'Pre-Raid', 'Death Knight', 'Unholy', 'Hands', 'Both', 'Heroes'' Scourgeborne Gauntlets'), +(6, 2, 10, 0, 200, 37151, 'Pre-Raid', 'Death Knight', 'Unholy', 'Finger1', 'Both', 'Band of Frosted Thorns'), +(6, 2, 12, 0, 200, 40684, 'Pre-Raid', 'Death Knight', 'Unholy', 'Trinket1', 'Both', 'Mirror of Truth'), +(6, 2, 14, 0, 200, 43566, 'Pre-Raid', 'Death Knight', 'Unholy', 'Back', 'Both', 'Ice Striker''s Cloak'), +(6, 2, 15, 0, 200, 41257, 'Pre-Raid', 'Death Knight', 'Unholy', 'MainHand', 'Both', 'Titansteel Destroyer'), +(6, 2, 17, 0, 200, 40715, 'Pre-Raid', 'Death Knight', 'Unholy', 'Ranged', 'Both', 'Sigil of Haunted Dreams'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 2, 0, 0, 224, 44006, 'Phase 1', 'Death Knight', 'Unholy', 'Head', 'Both', 'Obsidian Greathelm'), +(6, 2, 1, 0, 224, 44664, 'Phase 1', 'Death Knight', 'Unholy', 'Neck', 'Both', 'Favor of the Dragon Queen'), +(6, 2, 2, 0, 224, 40557, 'Phase 1', 'Death Knight', 'Unholy', 'Shoulders', 'Both', 'Valorous Scourgeborne Shoulderplates'), +(6, 2, 4, 0, 224, 40550, 'Phase 1', 'Death Knight', 'Unholy', 'Chest', 'Both', 'Valorous Scourgeborne Battleplate'), +(6, 2, 5, 0, 224, 40317, 'Phase 1', 'Death Knight', 'Unholy', 'Waist', 'Both', 'Girdle of Razuvious'), +(6, 2, 6, 0, 224, 40556, 'Phase 1', 'Death Knight', 'Unholy', 'Legs', 'Both', 'Valorous Scourgeborne Legplates'), +(6, 2, 7, 0, 224, 40206, 'Phase 1', 'Death Knight', 'Unholy', 'Feet', 'Both', 'Iron-Spring Jumpers'), +(6, 2, 8, 0, 224, 40330, 'Phase 1', 'Death Knight', 'Unholy', 'Wrists', 'Both', 'Bracers of Unrelenting Attack'), +(6, 2, 9, 0, 224, 40552, 'Phase 1', 'Death Knight', 'Unholy', 'Hands', 'Both', 'Valorous Scourgeborne Gauntlets'), +(6, 2, 10, 0, 224, 40074, 'Phase 1', 'Death Knight', 'Unholy', 'Finger1', 'Both', 'Strong-Handed Ring'), +(6, 2, 12, 0, 224, 42987, 'Phase 1', 'Death Knight', 'Unholy', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(6, 2, 14, 0, 224, 40250, 'Phase 1', 'Death Knight', 'Unholy', 'Back', 'Both', 'Aged Winter Cloak'), +(6, 2, 17, 0, 224, 40207, 'Phase 1', 'Death Knight', 'Unholy', 'Ranged', 'Both', 'Sigil of Awareness'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 2, 0, 1, 245, 51312, 'Phase 2', 'Death Knight', 'Unholy', 'Head', 'Alliance', 'Sanctified Scourgelord Helmet'), +(6, 2, 0, 2, 245, 45472, 'Phase 2', 'Death Knight', 'Unholy', 'Head', 'Horde', 'Warhelm of the Champion'), +(6, 2, 1, 1, 245, 54581, 'Phase 2', 'Death Knight', 'Unholy', 'Neck', 'Alliance', 'Penumbra Pendant'), +(6, 2, 1, 2, 245, 46040, 'Phase 2', 'Death Knight', 'Unholy', 'Neck', 'Horde', 'Strength of the Heavens'), +(6, 2, 2, 1, 245, 51314, 'Phase 2', 'Death Knight', 'Unholy', 'Shoulders', 'Alliance', 'Sanctified Scourgelord Shoulderplates'), +(6, 2, 2, 2, 245, 46117, 'Phase 2', 'Death Knight', 'Unholy', 'Shoulders', 'Horde', 'Conqueror''s Darkruned Shoulderplates'), +(6, 2, 4, 1, 245, 51310, 'Phase 2', 'Death Knight', 'Unholy', 'Chest', 'Alliance', 'Sanctified Scourgelord Battleplate'), +(6, 2, 4, 2, 245, 46111, 'Phase 2', 'Death Knight', 'Unholy', 'Chest', 'Horde', 'Conqueror''s Darkruned Battleplate'), +(6, 2, 5, 1, 245, 50620, 'Phase 2', 'Death Knight', 'Unholy', 'Waist', 'Alliance', 'Coldwraith Links'), +(6, 2, 5, 2, 245, 45241, 'Phase 2', 'Death Knight', 'Unholy', 'Waist', 'Horde', 'Belt of Colossal Rage'), +(6, 2, 6, 1, 245, 50624, 'Phase 2', 'Death Knight', 'Unholy', 'Legs', 'Alliance', 'Scourge Reaver''s Legplates'), +(6, 2, 6, 2, 245, 46116, 'Phase 2', 'Death Knight', 'Unholy', 'Legs', 'Horde', 'Conqueror''s Darkruned Legplates'), +(6, 2, 7, 1, 245, 54578, 'Phase 2', 'Death Knight', 'Unholy', 'Feet', 'Alliance', 'Apocalypse''s Advance'), +(6, 2, 7, 2, 245, 45599, 'Phase 2', 'Death Knight', 'Unholy', 'Feet', 'Horde', 'Sabatons of Lifeless Night'), +(6, 2, 8, 1, 245, 51842, 'Phase 2', 'Death Knight', 'Unholy', 'Wrists', 'Alliance', 'Collar of Haughty Disdain'), +(6, 2, 8, 2, 245, 45663, 'Phase 2', 'Death Knight', 'Unholy', 'Wrists', 'Horde', 'Armbands of Bedlam'), +(6, 2, 9, 1, 245, 51311, 'Phase 2', 'Death Knight', 'Unholy', 'Hands', 'Alliance', 'Sanctified Scourgelord Gauntlets'), +(6, 2, 9, 2, 245, 46113, 'Phase 2', 'Death Knight', 'Unholy', 'Hands', 'Horde', 'Conqueror''s Darkruned Gauntlets'), +(6, 2, 10, 1, 245, 50693, 'Phase 2', 'Death Knight', 'Unholy', 'Finger1', 'Alliance', 'Might of Blight'), +(6, 2, 10, 2, 245, 45469, 'Phase 2', 'Death Knight', 'Unholy', 'Finger1', 'Horde', 'Sif''s Promise'), +(6, 2, 12, 1, 245, 54590, 'Phase 2', 'Death Knight', 'Unholy', 'Trinket1', 'Alliance', 'Sharpened Twilight Scale'), +(6, 2, 12, 2, 245, 45609, 'Phase 2', 'Death Knight', 'Unholy', 'Trinket1', 'Horde', 'Comet''s Trail'), +(6, 2, 14, 1, 245, 50677, 'Phase 2', 'Death Knight', 'Unholy', 'Back', 'Alliance', 'Winding Sheet'), +(6, 2, 14, 2, 245, 46032, 'Phase 2', 'Death Knight', 'Unholy', 'Back', 'Horde', 'Drape of the Faceless General'), +(6, 2, 17, 1, 245, 50459, 'Phase 2', 'Death Knight', 'Unholy', 'Ranged', 'Alliance', 'Sigil of the Hanged Man'), +(6, 2, 17, 2, 245, 45254, 'Phase 2', 'Death Knight', 'Unholy', 'Ranged', 'Horde', 'Sigil of the Vengeful Heart'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 2, 0, 0, 258, 45472, 'Phase 3', 'Death Knight', 'Unholy', 'Head', 'Both', 'Warhelm of the Champion'), +(6, 2, 1, 0, 258, 46040, 'Phase 3', 'Death Knight', 'Unholy', 'Neck', 'Both', 'Strength of the Heavens'), +(6, 2, 2, 0, 258, 46117, 'Phase 3', 'Death Knight', 'Unholy', 'Shoulders', 'Both', 'Conqueror''s Darkruned Shoulderplates'), +(6, 2, 4, 0, 258, 45712, 'Phase 3', 'Death Knight', 'Unholy', 'Chest', 'Both', 'Chestplate of Titanic Fury'), +(6, 2, 5, 0, 258, 45241, 'Phase 3', 'Death Knight', 'Unholy', 'Waist', 'Both', 'Belt of Colossal Rage'), +(6, 2, 6, 0, 258, 46116, 'Phase 3', 'Death Knight', 'Unholy', 'Legs', 'Both', 'Conqueror''s Darkruned Legplates'), +(6, 2, 7, 0, 258, 45599, 'Phase 3', 'Death Knight', 'Unholy', 'Feet', 'Both', 'Sabatons of Lifeless Night'), +(6, 2, 8, 0, 258, 45663, 'Phase 3', 'Death Knight', 'Unholy', 'Wrists', 'Both', 'Armbands of Bedlam'), +(6, 2, 9, 0, 258, 46113, 'Phase 3', 'Death Knight', 'Unholy', 'Hands', 'Both', 'Conqueror''s Darkruned Gauntlets'), +(6, 2, 10, 0, 258, 45250, 'Phase 3', 'Death Knight', 'Unholy', 'Finger1', 'Both', 'Crazed Construct Ring'), +(6, 2, 12, 0, 258, 40531, 'Phase 3', 'Death Knight', 'Unholy', 'Trinket1', 'Both', 'Mark of Norgannon'), +(6, 2, 14, 0, 258, 45588, 'Phase 3', 'Death Knight', 'Unholy', 'Back', 'Both', 'Drape of the Skyborn'), +(6, 2, 17, 0, 258, 45254, 'Phase 3', 'Death Knight', 'Unholy', 'Ranged', 'Both', 'Sigil of the Vengeful Heart'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 2, 0, 1, 264, 48488, 'Phase 4', 'Death Knight', 'Unholy', 'Head', 'Alliance', 'Thassarian''s Helmet of Triumph'), +(6, 2, 0, 2, 264, 48493, 'Phase 4', 'Death Knight', 'Unholy', 'Head', 'Horde', 'Koltira''s Helmet of Triumph'), +(6, 2, 1, 1, 264, 47110, 'Phase 4', 'Death Knight', 'Unholy', 'Neck', 'Alliance', 'The Executioner''s Malice'), +(6, 2, 1, 2, 264, 47458, 'Phase 4', 'Death Knight', 'Unholy', 'Neck', 'Horde', 'The Executioner''s Vice'), +(6, 2, 2, 1, 264, 48486, 'Phase 4', 'Death Knight', 'Unholy', 'Shoulders', 'Alliance', 'Thassarian''s Shoulderplates of Triumph'), +(6, 2, 2, 2, 264, 48495, 'Phase 4', 'Death Knight', 'Unholy', 'Shoulders', 'Horde', 'Koltira''s Shoulderplates of Triumph'), +(6, 2, 4, 1, 264, 48490, 'Phase 4', 'Death Knight', 'Unholy', 'Chest', 'Alliance', 'Thassarian''s Battleplate of Triumph'), +(6, 2, 4, 2, 264, 48491, 'Phase 4', 'Death Knight', 'Unholy', 'Chest', 'Horde', 'Koltira''s Battleplate of Triumph'), +(6, 2, 5, 1, 264, 47002, 'Phase 4', 'Death Knight', 'Unholy', 'Waist', 'Alliance', 'Bloodbath Belt'), +(6, 2, 5, 2, 264, 47429, 'Phase 4', 'Death Knight', 'Unholy', 'Waist', 'Horde', 'Bloodbath Girdle'), +(6, 2, 6, 1, 264, 47132, 'Phase 4', 'Death Knight', 'Unholy', 'Legs', 'Alliance', 'Legguards of Ascension'), +(6, 2, 6, 2, 264, 47465, 'Phase 4', 'Death Knight', 'Unholy', 'Legs', 'Horde', 'Legplates of Ascension'), +(6, 2, 7, 0, 264, 45599, 'Phase 4', 'Death Knight', 'Unholy', 'Feet', 'Both', 'Sabatons of Lifeless Night'), +(6, 2, 8, 0, 264, 45663, 'Phase 4', 'Death Knight', 'Unholy', 'Wrists', 'Both', 'Armbands of Bedlam'), +(6, 2, 9, 1, 264, 48489, 'Phase 4', 'Death Knight', 'Unholy', 'Hands', 'Alliance', 'Thassarian''s Gauntlets of Triumph'), +(6, 2, 9, 2, 264, 48492, 'Phase 4', 'Death Knight', 'Unholy', 'Hands', 'Horde', 'Koltira''s Gauntlets of Triumph'), +(6, 2, 10, 1, 264, 47920, 'Phase 4', 'Death Knight', 'Unholy', 'Finger1', 'Alliance', 'Carnivorous Band'), +(6, 2, 10, 2, 264, 47993, 'Phase 4', 'Death Knight', 'Unholy', 'Finger1', 'Horde', 'Gormok''s Band'), +(6, 2, 12, 1, 264, 47131, 'Phase 4', 'Death Knight', 'Unholy', 'Trinket1', 'Alliance', 'Death''s Verdict'), +(6, 2, 12, 2, 264, 47464, 'Phase 4', 'Death Knight', 'Unholy', 'Trinket1', 'Horde', 'Death''s Choice'), +(6, 2, 14, 1, 264, 47547, 'Phase 4', 'Death Knight', 'Unholy', 'Back', 'Alliance', 'Varian''s Furor'), +(6, 2, 14, 2, 264, 47548, 'Phase 4', 'Death Knight', 'Unholy', 'Back', 'Horde', 'Garrosh''s Rage'), +(6, 2, 17, 0, 264, 47673, 'Phase 4', 'Death Knight', 'Unholy', 'Ranged', 'Both', 'Sigil of Virulence'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(6, 2, 0, 0, 290, 51312, 'Phase 5', 'DeathKnight', 'Unholy', 'Head', 'Both', 'Sanctified Scourgelord Helmet'), +(6, 2, 1, 0, 290, 54581, 'Phase 5', 'DeathKnight', 'Unholy', 'Neck', 'Both', 'Penumbra Pendant'), +(6, 2, 2, 0, 290, 51314, 'Phase 5', 'DeathKnight', 'Unholy', 'Shoulders', 'Both', 'Sanctified Scourgelord Shoulderplates'), +(6, 2, 4, 0, 290, 51310, 'Phase 5', 'DeathKnight', 'Unholy', 'Chest', 'Both', 'Sanctified Scourgelord Battleplate'), +(6, 2, 5, 0, 290, 50620, 'Phase 5', 'DeathKnight', 'Unholy', 'Waist', 'Both', 'Coldwraith Links'), +(6, 2, 6, 0, 290, 51313, 'Phase 5', 'DeathKnight', 'Unholy', 'Legs', 'Both', 'Sanctified Scourgelord Legplates'), +(6, 2, 7, 0, 290, 54578, 'Phase 5', 'DeathKnight', 'Unholy', 'Feet', 'Both', 'Apocalypse''s Advance'), +(6, 2, 8, 0, 290, 50670, 'Phase 5', 'DeathKnight', 'Unholy', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(6, 2, 9, 0, 290, 50690, 'Phase 5', 'DeathKnight', 'Unholy', 'Hands', 'Both', 'Fleshrending Gauntlets'), +(6, 2, 10, 0, 290, 50693, 'Phase 5', 'DeathKnight', 'Unholy', 'Finger1', 'Both', 'Might of Blight'), +(6, 2, 11, 0, 290, 52572, 'Phase 5', 'DeathKnight', 'Unholy', 'Finger2', 'Both', 'Ashen Band of Endless Might'), +(6, 2, 12, 0, 290, 54590, 'Phase 5', 'DeathKnight', 'Unholy', 'Trinket1', 'Both', 'Sharpened Twilight Scale'), +(6, 2, 13, 0, 290, 50363, 'Phase 5', 'DeathKnight', 'Unholy', 'Trinket2', 'Both', 'Deathbringer''s Will'), +(6, 2, 14, 0, 290, 50677, 'Phase 5', 'DeathKnight', 'Unholy', 'Back', 'Both', 'Winding Sheet'), +(6, 2, 15, 0, 290, 49623, 'Phase 5', 'DeathKnight', 'Unholy', 'MainHand', 'Both', 'Shadowmourne'), +(6, 2, 17, 0, 290, 50459, 'Phase 5', 'DeathKnight', 'Unholy', 'Ranged', 'Both', 'Sigil of the Hanged Man'); + + +-- ============================================================ +-- Shaman (7) +-- ============================================================ +-- Elemental (tab 0) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 66, 10504, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Head', 'Both', 'Green Lens'), +(7, 0, 1, 0, 66, 12103, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Neck', 'Both', 'Star of Mystaria'), +(7, 0, 2, 0, 66, 13013, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Elder Wizard''s Mantle'), +(7, 0, 4, 0, 66, 11924, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Chest', 'Both', 'Robes of the Royal Crown'), +(7, 0, 5, 0, 66, 11662, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Waist', 'Both', 'Ban''thok Sash'), +(7, 0, 6, 0, 66, 13170, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Legs', 'Both', 'Skyshroud Leggings'), +(7, 0, 7, 0, 66, 13954, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Feet', 'Both', 'Verdant Footpads'), +(7, 0, 8, 0, 66, 11765, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Pyremail Wristguards'), +(7, 0, 9, 0, 66, 13253, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Hands', 'Both', 'Hands of Power'), +(7, 0, 10, 0, 66, 12545, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Eye of Orgrimmar'), +(7, 0, 11, 0, 66, 13001, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Maiden''s Circle'), +(7, 0, 12, 0, 66, 12930, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Briarwood Reed'), +(7, 0, 13, 0, 66, 13968, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'Eye of the Beast'), +(7, 0, 14, 0, 66, 15421, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'Back', 'Both', 'Shroud of the Exile'), +(7, 0, 15, 0, 66, 13964, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Witchblade'), +(7, 0, 16, 0, 66, 11904, 'Phase 1 (Pre-Raid)', 'Shaman', 'Elemental', 'OffHand', 'Both', 'Spirit of Aquementas'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 76, 10504, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Head', 'Both', 'Green Lens'), +(7, 0, 1, 0, 76, 12103, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Neck', 'Both', 'Star of Mystaria'), +(7, 0, 2, 0, 76, 23260, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Champion''s Mail Pauldrons'), +(7, 0, 4, 0, 76, 18385, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Chest', 'Both', 'Robe of Everlasting Night'), +(7, 0, 5, 0, 76, 11662, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Waist', 'Both', 'Ban''thok Sash'), +(7, 0, 6, 0, 76, 13170, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Legs', 'Both', 'Skyshroud Leggings'), +(7, 0, 7, 0, 76, 18322, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Feet', 'Both', 'Waterspout Boots'), +(7, 0, 8, 0, 76, 11765, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Pyremail Wristguards'), +(7, 0, 9, 0, 76, 13253, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Hands', 'Both', 'Hands of Power'), +(7, 0, 10, 0, 76, 12545, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Eye of Orgrimmar'), +(7, 0, 11, 0, 76, 13001, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Maiden''s Circle'), +(7, 0, 12, 0, 76, 12930, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Briarwood Reed'), +(7, 0, 13, 0, 76, 13968, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'Eye of the Beast'), +(7, 0, 14, 0, 76, 18496, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'Back', 'Both', 'Heliotrope Cloak'), +(7, 0, 15, 0, 76, 18534, 'Phase 2 (Pre-Raid)', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Rod of the Ogre Magi'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 78, 10504, 'Phase 2', 'Shaman', 'Elemental', 'Head', 'Both', 'Green Lens'), +(7, 0, 1, 0, 78, 18814, 'Phase 2', 'Shaman', 'Elemental', 'Neck', 'Both', 'Choker of the Fire Lord'), +(7, 0, 2, 0, 78, 18829, 'Phase 2', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Deep Earth Spaulders'), +(7, 0, 4, 0, 78, 19145, 'Phase 2', 'Shaman', 'Elemental', 'Chest', 'Both', 'Robe of Volatile Power'), +(7, 0, 5, 0, 78, 19136, 'Phase 2', 'Shaman', 'Elemental', 'Waist', 'Both', 'Mana Igniting Cord'), +(7, 0, 6, 0, 78, 16946, 'Phase 2', 'Shaman', 'Elemental', 'Legs', 'Both', 'Legplates of Ten Storms'), +(7, 0, 7, 0, 78, 18322, 'Phase 2', 'Shaman', 'Elemental', 'Feet', 'Both', 'Waterspout Boots'), +(7, 0, 8, 0, 78, 11765, 'Phase 2', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Pyremail Wristguards'), +(7, 0, 9, 0, 78, 16839, 'Phase 2', 'Shaman', 'Elemental', 'Hands', 'Both', 'Earthfury Gauntlets'), +(7, 0, 10, 0, 78, 19147, 'Phase 2', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Spell Power'), +(7, 0, 11, 0, 78, 19147, 'Phase 2', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Ring of Spell Power'), +(7, 0, 12, 0, 78, 12930, 'Phase 2', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Briarwood Reed'), +(7, 0, 13, 0, 78, 18820, 'Phase 2', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(7, 0, 14, 0, 78, 18496, 'Phase 2', 'Shaman', 'Elemental', 'Back', 'Both', 'Heliotrope Cloak'), +(7, 0, 15, 0, 78, 18842, 'Phase 2', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Staff of Dominance'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 83, 19375, 'Phase 3', 'Shaman', 'Elemental', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(7, 0, 1, 0, 83, 18814, 'Phase 3', 'Shaman', 'Elemental', 'Neck', 'Both', 'Choker of the Fire Lord'), +(7, 0, 2, 0, 83, 18829, 'Phase 3', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Deep Earth Spaulders'), +(7, 0, 4, 0, 83, 19145, 'Phase 3', 'Shaman', 'Elemental', 'Chest', 'Both', 'Robe of Volatile Power'), +(7, 0, 5, 0, 83, 19400, 'Phase 3', 'Shaman', 'Elemental', 'Waist', 'Both', 'Firemaw''s Clutch'), +(7, 0, 6, 0, 83, 16946, 'Phase 3', 'Shaman', 'Elemental', 'Legs', 'Both', 'Legplates of Ten Storms'), +(7, 0, 7, 0, 83, 18322, 'Phase 3', 'Shaman', 'Elemental', 'Feet', 'Both', 'Waterspout Boots'), +(7, 0, 8, 0, 83, 19374, 'Phase 3', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Bracers of Arcane Accuracy'), +(7, 0, 9, 0, 83, 16839, 'Phase 3', 'Shaman', 'Elemental', 'Hands', 'Both', 'Earthfury Gauntlets'), +(7, 0, 10, 0, 83, 19397, 'Phase 3', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Blackrock'), +(7, 0, 12, 0, 83, 19379, 'Phase 3', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(7, 0, 13, 0, 83, 19344, 'Phase 3', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'Natural Alignment Crystal'), +(7, 0, 14, 0, 83, 19378, 'Phase 3', 'Shaman', 'Elemental', 'Back', 'Both', 'Cloak of the Brood Lord'), +(7, 0, 15, 0, 83, 19360, 'Phase 3', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Lok''amir il Romathis'), +(7, 0, 16, 0, 83, 19366, 'Phase 3', 'Shaman', 'Elemental', 'OffHand', 'Both', 'Master Dragonslayer''s Orb'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 88, 19375, 'Phase 5', 'Shaman', 'Elemental', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(7, 0, 1, 0, 88, 21608, 'Phase 5', 'Shaman', 'Elemental', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(7, 0, 2, 0, 88, 21376, 'Phase 5', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Stormcaller''s Pauldrons'), +(7, 0, 4, 0, 88, 21671, 'Phase 5', 'Shaman', 'Elemental', 'Chest', 'Both', 'Robes of the Battleguard'), +(7, 0, 5, 0, 88, 22730, 'Phase 5', 'Shaman', 'Elemental', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(7, 0, 6, 0, 88, 21375, 'Phase 5', 'Shaman', 'Elemental', 'Legs', 'Both', 'Stormcaller''s Leggings'), +(7, 0, 7, 0, 88, 21373, 'Phase 5', 'Shaman', 'Elemental', 'Feet', 'Both', 'Stormcaller''s Footguards'), +(7, 0, 8, 0, 88, 21464, 'Phase 5', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Shackles of the Unscarred'), +(7, 0, 9, 0, 88, 21585, 'Phase 5', 'Shaman', 'Elemental', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(7, 0, 10, 0, 88, 21707, 'Phase 5', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Swarming Thought'), +(7, 0, 11, 0, 88, 21709, 'Phase 5', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Ring of the Fallen God'), +(7, 0, 12, 0, 88, 19379, 'Phase 5', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(7, 0, 13, 0, 88, 19344, 'Phase 5', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'Natural Alignment Crystal'), +(7, 0, 14, 0, 88, 22731, 'Phase 5', 'Shaman', 'Elemental', 'Back', 'Both', 'Cloak of the Devoured'), +(7, 0, 15, 0, 88, 19360, 'Phase 5', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Lok''amir il Romathis'), +(7, 0, 16, 0, 88, 19366, 'Phase 5', 'Shaman', 'Elemental', 'OffHand', 'Both', 'Master Dragonslayer''s Orb'), +(7, 0, 17, 0, 88, 23199, 'Phase 5', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of the Storm'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 92, 19375, 'Phase 6', 'Shaman', 'Elemental', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(7, 0, 1, 0, 92, 22943, 'Phase 6', 'Shaman', 'Elemental', 'Neck', 'Both', 'Malice Stone Pendant'), +(7, 0, 2, 0, 92, 21376, 'Phase 6', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Stormcaller''s Pauldrons'), +(7, 0, 4, 0, 92, 21671, 'Phase 6', 'Shaman', 'Elemental', 'Chest', 'Both', 'Robes of the Battleguard'), +(7, 0, 5, 0, 92, 22730, 'Phase 6', 'Shaman', 'Elemental', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(7, 0, 6, 0, 92, 21375, 'Phase 6', 'Shaman', 'Elemental', 'Legs', 'Both', 'Stormcaller''s Leggings'), +(7, 0, 7, 0, 92, 21373, 'Phase 6', 'Shaman', 'Elemental', 'Feet', 'Both', 'Stormcaller''s Footguards'), +(7, 0, 8, 0, 92, 21464, 'Phase 6', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Shackles of the Unscarred'), +(7, 0, 9, 0, 92, 21585, 'Phase 6', 'Shaman', 'Elemental', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(7, 0, 10, 0, 92, 21707, 'Phase 6', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Swarming Thought'), +(7, 0, 11, 0, 92, 21709, 'Phase 6', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Ring of the Fallen God'), +(7, 0, 12, 0, 92, 19379, 'Phase 6', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(7, 0, 13, 0, 92, 23046, 'Phase 6', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'The Restrained Essence of Sapphiron'), +(7, 0, 14, 0, 92, 23050, 'Phase 6', 'Shaman', 'Elemental', 'Back', 'Both', 'Cloak of the Necropolis'), +(7, 0, 15, 0, 92, 22988, 'Phase 6', 'Shaman', 'Elemental', 'MainHand', 'Both', 'The End of Dreams'), +(7, 0, 16, 0, 92, 23049, 'Phase 6', 'Shaman', 'Elemental', 'OffHand', 'Both', 'Sapphiron''s Left Eye'), +(7, 0, 17, 0, 92, 23199, 'Phase 6', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of the Storm'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 120, 32086, 'Pre-Raid', 'Shaman', 'Elemental', 'Head', 'Both', 'Storm Master''s Helmet'), +(7, 0, 1, 0, 120, 31692, 'Pre-Raid', 'Shaman', 'Elemental', 'Neck', 'Both', 'Natasha''s Ember Necklace'), +(7, 0, 2, 0, 120, 27796, 'Pre-Raid', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Mana-Etched Spaulders'), +(7, 0, 4, 0, 120, 29519, 'Pre-Raid', 'Shaman', 'Elemental', 'Chest', 'Both', 'Netherstrike Breastplate'), +(7, 0, 5, 0, 120, 29520, 'Pre-Raid', 'Shaman', 'Elemental', 'Waist', 'Both', 'Netherstrike Belt'), +(7, 0, 6, 0, 120, 24262, 'Pre-Raid', 'Shaman', 'Elemental', 'Legs', 'Both', 'Spellstrike Pants'), +(7, 0, 7, 0, 120, 28406, 'Pre-Raid', 'Shaman', 'Elemental', 'Feet', 'Both', 'Sigil-Laced Boots'), +(7, 0, 8, 0, 120, 29521, 'Pre-Raid', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Netherstrike Bracers'), +(7, 0, 9, 0, 120, 27465, 'Pre-Raid', 'Shaman', 'Elemental', 'Hands', 'Both', 'Mana-Etched Gloves'), +(7, 0, 10, 0, 120, 29367, 'Pre-Raid', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Cryptic Dreams'), +(7, 0, 11, 0, 120, 29126, 'Pre-Raid', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Seer''s Signet'), +(7, 0, 12, 0, 120, 29370, 'Pre-Raid', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(7, 0, 13, 0, 120, 27683, 'Pre-Raid', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(7, 0, 14, 0, 120, 29369, 'Pre-Raid', 'Shaman', 'Elemental', 'Back', 'Both', 'Shawl of Shifting Probabilities'), +(7, 0, 15, 0, 120, 23554, 'Pre-Raid', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Eternium Runed Blade'), +(7, 0, 16, 0, 120, 29268, 'Pre-Raid', 'Shaman', 'Elemental', 'OffHand', 'Both', 'Mazthoril Honor Shield'), +(7, 0, 17, 0, 120, 28248, 'Pre-Raid', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of the Void'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 125, 29035, 'Phase 1', 'Shaman', 'Elemental', 'Head', 'Both', 'Cyclone Faceguard'), +(7, 0, 1, 0, 125, 28762, 'Phase 1', 'Shaman', 'Elemental', 'Neck', 'Both', 'Adornment of Stolen Souls'), +(7, 0, 2, 0, 125, 29037, 'Phase 1', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Cyclone Shoulderguards'), +(7, 0, 4, 0, 125, 29519, 'Phase 1', 'Shaman', 'Elemental', 'Chest', 'Both', 'Netherstrike Breastplate'), +(7, 0, 5, 0, 125, 29520, 'Phase 1', 'Shaman', 'Elemental', 'Waist', 'Both', 'Netherstrike Belt'), +(7, 0, 6, 0, 125, 30734, 'Phase 1', 'Shaman', 'Elemental', 'Legs', 'Both', 'Leggings of the Seventh Circle'), +(7, 0, 7, 0, 125, 28517, 'Phase 1', 'Shaman', 'Elemental', 'Feet', 'Both', 'Boots of Foretelling'), +(7, 0, 8, 0, 125, 29521, 'Phase 1', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Netherstrike Bracers'), +(7, 0, 9, 0, 125, 30725, 'Phase 1', 'Shaman', 'Elemental', 'Hands', 'Both', 'Anger-Spark Gloves'), +(7, 0, 10, 0, 125, 28753, 'Phase 1', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Recurrence'), +(7, 0, 11, 0, 125, 30667, 'Phase 1', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Ring of Unrelenting Storms'), +(7, 0, 12, 0, 125, 29370, 'Phase 1', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(7, 0, 13, 0, 125, 28785, 'Phase 1', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'The Lightning Capacitor'), +(7, 0, 14, 0, 125, 30735, 'Phase 1', 'Shaman', 'Elemental', 'Back', 'Both', 'Ancient Spellcloak of the Highborne'), +(7, 0, 16, 0, 125, 29268, 'Phase 1', 'Shaman', 'Elemental', 'OffHand', 'Both', 'Mazthoril Honor Shield'), +(7, 0, 17, 0, 125, 28248, 'Phase 1', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of the Void'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 141, 29035, 'Phase 2', 'Shaman', 'Elemental', 'Head', 'Both', 'Cyclone Faceguard'), +(7, 0, 1, 0, 141, 30015, 'Phase 2', 'Shaman', 'Elemental', 'Neck', 'Both', 'The Sun King''s Talisman'), +(7, 0, 2, 0, 141, 29037, 'Phase 2', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Cyclone Shoulderguards'), +(7, 0, 4, 0, 141, 30107, 'Phase 2', 'Shaman', 'Elemental', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(7, 0, 5, 0, 141, 30038, 'Phase 2', 'Shaman', 'Elemental', 'Waist', 'Both', 'Belt of Blasting'), +(7, 0, 6, 0, 141, 30734, 'Phase 2', 'Shaman', 'Elemental', 'Legs', 'Both', 'Leggings of the Seventh Circle'), +(7, 0, 7, 0, 141, 30067, 'Phase 2', 'Shaman', 'Elemental', 'Feet', 'Both', 'Velvet Boots of the Guardian'), +(7, 0, 8, 0, 141, 29918, 'Phase 2', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Mindstorm Wristbands'), +(7, 0, 9, 0, 141, 28780, 'Phase 2', 'Shaman', 'Elemental', 'Hands', 'Both', 'Soul-Eater''s Handwraps'), +(7, 0, 10, 0, 141, 30109, 'Phase 2', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Endless Coils'), +(7, 0, 11, 0, 141, 30667, 'Phase 2', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Ring of Unrelenting Storms'), +(7, 0, 12, 0, 141, 29370, 'Phase 2', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(7, 0, 13, 0, 141, 28785, 'Phase 2', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'The Lightning Capacitor'), +(7, 0, 14, 0, 141, 30735, 'Phase 2', 'Shaman', 'Elemental', 'Back', 'Both', 'Ancient Spellcloak of the Highborne'), +(7, 0, 15, 0, 141, 29988, 'Phase 2', 'Shaman', 'Elemental', 'MainHand', 'Both', 'The Nexus Key'), +(7, 0, 17, 0, 141, 28248, 'Phase 2', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of the Void'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 156, 31014, 'Phase 3', 'Shaman', 'Elemental', 'Head', 'Both', 'Skyshatter Headguard'), +(7, 0, 1, 0, 156, 32349, 'Phase 3', 'Shaman', 'Elemental', 'Neck', 'Both', 'Translucent Spellthread Necklace'), +(7, 0, 2, 0, 156, 31023, 'Phase 3', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Skyshatter Mantle'), +(7, 0, 4, 0, 156, 31017, 'Phase 3', 'Shaman', 'Elemental', 'Chest', 'Both', 'Skyshatter Breastplate'), +(7, 0, 5, 0, 156, 32276, 'Phase 3', 'Shaman', 'Elemental', 'Waist', 'Both', 'Flashfire Girdle'), +(7, 0, 6, 0, 156, 30916, 'Phase 3', 'Shaman', 'Elemental', 'Legs', 'Both', 'Leggings of Channeled Elements'), +(7, 0, 7, 0, 156, 32239, 'Phase 3', 'Shaman', 'Elemental', 'Feet', 'Both', 'Slippers of the Seacaller'), +(7, 0, 8, 0, 156, 32586, 'Phase 3', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(7, 0, 9, 0, 156, 31008, 'Phase 3', 'Shaman', 'Elemental', 'Hands', 'Both', 'Skyshatter Gauntlets'), +(7, 0, 10, 0, 156, 32527, 'Phase 3', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Ancient Knowledge'), +(7, 0, 11, 0, 156, 32527, 'Phase 3', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Ring of Ancient Knowledge'), +(7, 0, 12, 0, 156, 28785, 'Phase 3', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'The Lightning Capacitor'), +(7, 0, 13, 0, 156, 32483, 'Phase 3', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(7, 0, 14, 0, 156, 32524, 'Phase 3', 'Shaman', 'Elemental', 'Back', 'Both', 'Shroud of the Highborne'), +(7, 0, 15, 0, 156, 32374, 'Phase 3', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Zhar''doom, Greatstaff of the Devourer'), +(7, 0, 17, 0, 156, 32330, 'Phase 3', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of Ancestral Guidance'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 164, 31014, 'Phase 4', 'Shaman', 'Elemental', 'Head', 'Both', 'Skyshatter Headguard'), +(7, 0, 1, 0, 164, 33281, 'Phase 4', 'Shaman', 'Elemental', 'Neck', 'Both', 'Brooch of Nature''s Mercy'), +(7, 0, 2, 0, 164, 31023, 'Phase 4', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Skyshatter Mantle'), +(7, 0, 4, 0, 164, 31017, 'Phase 4', 'Shaman', 'Elemental', 'Chest', 'Both', 'Skyshatter Breastplate'), +(7, 0, 5, 0, 164, 32276, 'Phase 4', 'Shaman', 'Elemental', 'Waist', 'Both', 'Flashfire Girdle'), +(7, 0, 6, 0, 164, 33584, 'Phase 4', 'Shaman', 'Elemental', 'Legs', 'Both', 'Pantaloons of Arcane Annihilation'), +(7, 0, 7, 0, 164, 33357, 'Phase 4', 'Shaman', 'Elemental', 'Feet', 'Both', 'Footpads of Madness'), +(7, 0, 8, 0, 164, 32586, 'Phase 4', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(7, 0, 9, 0, 164, 31008, 'Phase 4', 'Shaman', 'Elemental', 'Hands', 'Both', 'Skyshatter Gauntlets'), +(7, 0, 10, 0, 164, 32527, 'Phase 4', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Ancient Knowledge'), +(7, 0, 11, 0, 164, 32527, 'Phase 4', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Ring of Ancient Knowledge'), +(7, 0, 12, 0, 164, 33829, 'Phase 4', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Hex Shrunken Head'), +(7, 0, 13, 0, 164, 32483, 'Phase 4', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(7, 0, 14, 0, 164, 32524, 'Phase 4', 'Shaman', 'Elemental', 'Back', 'Both', 'Shroud of the Highborne'), +(7, 0, 15, 0, 164, 32374, 'Phase 4', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Zhar''doom, Greatstaff of the Devourer'), +(7, 0, 16, 0, 164, 30909, 'Phase 5', 'Shaman', 'Elemental', 'OffHand', 'Both', 'Antonidas''s Aegis of Rapt Concentration'), +(7, 0, 17, 0, 164, 32330, 'Phase 4', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of Ancestral Guidance'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 200, 42555, 'Pre-Raid', 'Shaman', 'Elemental', 'Head', 'Both', 'Electroflux Sight Enhancers'), +(7, 0, 1, 0, 200, 37595, 'Pre-Raid', 'Shaman', 'Elemental', 'Neck', 'Both', 'Necklace of Taldaram'), +(7, 0, 2, 0, 200, 37398, 'Pre-Raid', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Mantle of Discarded Ways'), +(7, 0, 4, 0, 200, 42101, 'Pre-Raid', 'Shaman', 'Elemental', 'Chest', 'Both', 'Ebonweave Robe'), +(7, 0, 5, 0, 200, 37855, 'Pre-Raid', 'Shaman', 'Elemental', 'Waist', 'Both', 'Mail Girdle of the Audient Earth'), +(7, 0, 6, 0, 200, 37695, 'Pre-Raid', 'Shaman', 'Elemental', 'Legs', 'Both', 'Legguards of Nature''s Power'), +(7, 0, 7, 0, 200, 43469, 'Pre-Raid', 'Shaman', 'Elemental', 'Feet', 'Both', 'Revenant''s Treads'), +(7, 0, 8, 0, 200, 37788, 'Pre-Raid', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Limb Regeneration Bracers'), +(7, 0, 9, 0, 200, 37623, 'Pre-Raid', 'Shaman', 'Elemental', 'Hands', 'Both', 'Fiery Obelisk Handguards'), +(7, 0, 10, 0, 200, 37192, 'Pre-Raid', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Annhylde''s Ring'), +(7, 0, 12, 0, 200, 40682, 'Pre-Raid', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Sundial of the Exiled'), +(7, 0, 14, 0, 200, 41610, 'Pre-Raid', 'Shaman', 'Elemental', 'Back', 'Both', 'Deathchill Cloak'), +(7, 0, 15, 0, 200, 41384, 'Pre-Raid', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Titansteel Guardian'), +(7, 0, 17, 0, 200, 40708, 'Pre-Raid', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of the Elemental Plane'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 224, 40516, 'Phase 1', 'Shaman', 'Elemental', 'Head', 'Both', 'Valorous Earthshatter Helm'), +(7, 0, 1, 0, 224, 44661, 'Phase 1', 'Shaman', 'Elemental', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(7, 0, 2, 0, 224, 40518, 'Phase 1', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Valorous Earthshatter Shoulderpads'), +(7, 0, 4, 0, 224, 40283, 'Phase 1', 'Shaman', 'Elemental', 'Chest', 'Both', 'Fallout Impervious Tunic'), +(7, 0, 5, 0, 224, 40327, 'Phase 1', 'Shaman', 'Elemental', 'Waist', 'Both', 'Girdle of Recuperation'), +(7, 0, 6, 0, 224, 40560, 'Phase 1', 'Shaman', 'Elemental', 'Legs', 'Both', 'Leggings of the Wanton Spellcaster'), +(7, 0, 7, 0, 224, 40237, 'Phase 1', 'Shaman', 'Elemental', 'Feet', 'Both', 'Eruption-Scarred Boots'), +(7, 0, 8, 0, 224, 40324, 'Phase 1', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Bands of Mutual Respect'), +(7, 0, 9, 0, 224, 40302, 'Phase 1', 'Shaman', 'Elemental', 'Hands', 'Both', 'Benefactor''s Gauntlets'), +(7, 0, 10, 0, 224, 40399, 'Phase 1', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Signet of Manifested Pain'), +(7, 0, 12, 0, 224, 40255, 'Phase 1', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Dying Curse'), +(7, 0, 14, 0, 224, 44005, 'Phase 1', 'Shaman', 'Elemental', 'Back', 'Both', 'Pennant Cloak'), +(7, 0, 17, 0, 224, 40267, 'Phase 1', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of Hex'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 245, 46209, 'Phase 2', 'Shaman', 'Elemental', 'Head', 'Both', 'Conqueror''s Worldbreaker Helm'), +(7, 0, 2, 0, 245, 46211, 'Phase 2', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Conqueror''s Worldbreaker Shoulderpads'), +(7, 0, 4, 0, 245, 46206, 'Phase 2', 'Shaman', 'Elemental', 'Chest', 'Both', 'Conqueror''s Worldbreaker Hauberk'), +(7, 0, 6, 0, 245, 46210, 'Phase 2', 'Shaman', 'Elemental', 'Legs', 'Both', 'Conqueror''s Worldbreaker Kilt'), +(7, 0, 7, 0, 245, 45537, 'Phase 2', 'Shaman', 'Elemental', 'Feet', 'Both', 'Treads of the False Oracle'), +(7, 0, 8, 1, 245, 45616, 'Phase 2', 'Shaman', 'Elemental', 'Wrists', 'Alliance', 'Star-beaded Clutch'), +(7, 0, 8, 2, 245, 45460, 'Phase 2', 'Shaman', 'Elemental', 'Wrists', 'Horde', 'Bindings of Winter Gale'), +(7, 0, 9, 0, 245, 45665, 'Phase 2', 'Shaman', 'Elemental', 'Hands', 'Both', 'Pharos Gloves'), +(7, 0, 10, 0, 245, 45495, 'Phase 2', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Conductive Seal'), +(7, 0, 12, 0, 245, 45518, 'Phase 2', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Flare of the Heavens'), +(7, 0, 14, 0, 245, 45242, 'Phase 2', 'Shaman', 'Elemental', 'Back', 'Both', 'Drape of Mortal Downfall'), +(7, 0, 17, 0, 245, 40267, 'Phase 2', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Totem of Hex'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 264, 51237, 'Phase 4', 'Shaman', 'Elemental', 'Head', 'Both', 'Sanctified Frost Witch''s Helm'), +(7, 0, 2, 0, 264, 50698, 'Phase 4', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Horrific Flesh Epaulets'), +(7, 0, 4, 0, 264, 51239, 'Phase 4', 'Shaman', 'Elemental', 'Chest', 'Both', 'Sanctified Frost Witch''s Hauberk'), +(7, 0, 5, 0, 264, 50613, 'Phase 4', 'Shaman', 'Elemental', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(7, 0, 6, 0, 264, 51236, 'Phase 4', 'Shaman', 'Elemental', 'Legs', 'Both', 'Sanctified Frost Witch''s Kilt'), +(7, 0, 7, 0, 264, 50699, 'Phase 4', 'Shaman', 'Elemental', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(7, 0, 8, 0, 264, 50687, 'Phase 4', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Bloodsunder''s Bracers'), +(7, 0, 9, 0, 264, 51238, 'Phase 4', 'Shaman', 'Elemental', 'Hands', 'Both', 'Sanctified Frost Witch''s Gloves'), +(7, 0, 10, 0, 264, 50664, 'Phase 4', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Rapid Ascent'), +(7, 0, 12, 0, 264, 50348, 'Phase 4', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Dislodged Foreign Object'), +(7, 0, 14, 0, 264, 50628, 'Phase 4', 'Shaman', 'Elemental', 'Back', 'Both', 'Frostbinder''s Shredded Cape'), +(7, 0, 15, 0, 264, 50734, 'Phase 4', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Royal Scepter of Terenas II'), +(7, 0, 17, 0, 264, 50458, 'Phase 4', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Bizuri''s Totem of Shattered Ice'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 0, 0, 0, 290, 51237, 'Phase 5', 'Shaman', 'Elemental', 'Head', 'Both', 'Sanctified Frost Witch''s Helm'), +(7, 0, 1, 0, 290, 50182, 'Phase 5', 'Shaman', 'Elemental', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(7, 0, 2, 0, 290, 51235, 'Phase 5', 'Shaman', 'Elemental', 'Shoulders', 'Both', 'Sanctified Frost Witch''s Shoulderpads'), +(7, 0, 4, 0, 290, 51239, 'Phase 5', 'Shaman', 'Elemental', 'Chest', 'Both', 'Sanctified Frost Witch''s Hauberk'), +(7, 0, 5, 0, 290, 54587, 'Phase 5', 'Shaman', 'Elemental', 'Waist', 'Both', 'Split Shape Belt'), +(7, 0, 6, 0, 290, 50694, 'Phase 5', 'Shaman', 'Elemental', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(7, 0, 7, 0, 290, 50699, 'Phase 5', 'Shaman', 'Elemental', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(7, 0, 8, 0, 290, 54582, 'Phase 5', 'Shaman', 'Elemental', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(7, 0, 9, 0, 290, 51238, 'Phase 5', 'Shaman', 'Elemental', 'Hands', 'Both', 'Sanctified Frost Witch''s Gloves'), +(7, 0, 10, 0, 290, 50664, 'Phase 5', 'Shaman', 'Elemental', 'Finger1', 'Both', 'Ring of Rapid Ascent'), +(7, 0, 11, 0, 290, 50398, 'Phase 5', 'Shaman', 'Elemental', 'Finger2', 'Both', 'Ashen Band of Endless Destruction'), +(7, 0, 12, 0, 290, 54588, 'Phase 5', 'Shaman', 'Elemental', 'Trinket1', 'Both', 'Charred Twilight Scale'), +(7, 0, 13, 0, 290, 50365, 'Phase 5', 'Shaman', 'Elemental', 'Trinket2', 'Both', 'Phylactery of the Nameless Lich'), +(7, 0, 14, 0, 290, 54583, 'Phase 5', 'Shaman', 'Elemental', 'Back', 'Both', 'Cloak of Burning Dusk'), +(7, 0, 15, 0, 290, 50734, 'Phase 5', 'Shaman', 'Elemental', 'MainHand', 'Both', 'Royal Scepter of Terenas II'), +(7, 0, 16, 0, 290, 50616, 'Phase 5', 'Shaman', 'Elemental', 'OffHand', 'Both', 'Bulwark of Smouldering Steel'), +(7, 0, 17, 0, 290, 50458, 'Phase 5', 'Shaman', 'Elemental', 'Ranged', 'Both', 'Bizuri''s Totem of Shattered Ice'); + +-- Enhancement (tab 1) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 66, 12587, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Head', 'Both', 'Eye of Rend'), +(7, 1, 1, 0, 66, 15411, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Mark of Fordring'), +(7, 1, 2, 0, 66, 12927, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(7, 1, 4, 0, 66, 11726, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Savage Gladiator Chain'), +(7, 1, 5, 0, 66, 22232, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Marksman''s Girdle'), +(7, 1, 6, 0, 66, 15062, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Devilsaur Leggings'), +(7, 1, 7, 0, 66, 14616, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Bloodmail Boots'), +(7, 1, 8, 0, 66, 13211, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Slashclaw Bracers'), +(7, 1, 9, 0, 66, 13957, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Gargoyle Slashers'), +(7, 1, 10, 0, 66, 13098, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Painweaver Band'), +(7, 1, 11, 0, 66, 17713, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Blackstone Ring'), +(7, 1, 12, 0, 66, 13965, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(7, 1, 13, 0, 66, 11815, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Hand of Justice'), +(7, 1, 14, 0, 66, 13340, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'Back', 'Both', 'Cape of the Black Baron'), +(7, 1, 15, 0, 66, 12784, 'Phase 1 (Pre-Raid)', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Arcanite Reaper'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 76, 12587, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Head', 'Both', 'Eye of Rend'), +(7, 1, 1, 0, 76, 15411, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Mark of Fordring'), +(7, 1, 2, 0, 76, 12927, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(7, 1, 4, 0, 76, 11726, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Savage Gladiator Chain'), +(7, 1, 5, 0, 76, 22232, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Marksman''s Girdle'), +(7, 1, 6, 0, 76, 15062, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Devilsaur Leggings'), +(7, 1, 7, 0, 76, 14616, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Bloodmail Boots'), +(7, 1, 8, 0, 76, 13211, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Slashclaw Bracers'), +(7, 1, 9, 0, 76, 13957, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Gargoyle Slashers'), +(7, 1, 10, 0, 76, 13098, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Painweaver Band'), +(7, 1, 11, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Tarnished Elven Ring'), +(7, 1, 12, 0, 76, 13965, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(7, 1, 13, 0, 76, 11815, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Hand of Justice'), +(7, 1, 14, 0, 76, 13340, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'Back', 'Both', 'Cape of the Black Baron'), +(7, 1, 15, 0, 76, 12784, 'Phase 2 (Pre-Raid)', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Arcanite Reaper'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 78, 18817, 'Phase 2', 'Shaman', 'Enhancement', 'Head', 'Both', 'Crown of Destruction'), +(7, 1, 1, 0, 78, 18404, 'Phase 2', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(7, 1, 2, 0, 78, 12927, 'Phase 2', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(7, 1, 4, 0, 78, 11726, 'Phase 2', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Savage Gladiator Chain'), +(7, 1, 5, 0, 78, 18393, 'Phase 2', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Warpwood Binding'), +(7, 1, 6, 0, 78, 15062, 'Phase 2', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Devilsaur Leggings'), +(7, 1, 7, 0, 78, 14616, 'Phase 2', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Bloodmail Boots'), +(7, 1, 8, 0, 78, 19146, 'Phase 2', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Wristguards of Stability'), +(7, 1, 9, 0, 78, 13957, 'Phase 2', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Gargoyle Slashers'), +(7, 1, 10, 0, 78, 17063, 'Phase 2', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Band of Accuria'), +(7, 1, 11, 0, 78, 18821, 'Phase 2', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Quick Strike Ring'), +(7, 1, 12, 0, 78, 13965, 'Phase 2', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(7, 1, 13, 0, 78, 11815, 'Phase 2', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Hand of Justice'), +(7, 1, 14, 0, 78, 13340, 'Phase 2', 'Shaman', 'Enhancement', 'Back', 'Both', 'Cape of the Black Baron'), +(7, 1, 15, 0, 78, 17104, 'Phase 2', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Spinal Reaper'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 83, 18817, 'Phase 3', 'Shaman', 'Enhancement', 'Head', 'Both', 'Crown of Destruction'), +(7, 1, 1, 0, 83, 18404, 'Phase 3', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(7, 1, 2, 0, 83, 12927, 'Phase 3', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(7, 1, 4, 0, 83, 11726, 'Phase 3', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Savage Gladiator Chain'), +(7, 1, 5, 0, 83, 19380, 'Phase 3', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Therazane''s Link'), +(7, 1, 6, 0, 83, 15062, 'Phase 3', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Devilsaur Leggings'), +(7, 1, 7, 0, 83, 19381, 'Phase 3', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Boots of the Shadow Flame'), +(7, 1, 8, 0, 83, 19146, 'Phase 3', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Wristguards of Stability'), +(7, 1, 9, 0, 83, 19157, 'Phase 3', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Chromatic Gauntlets'), +(7, 1, 10, 0, 83, 19384, 'Phase 3', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Master Dragonslayer''s Ring'), +(7, 1, 11, 0, 83, 18821, 'Phase 3', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Quick Strike Ring'), +(7, 1, 12, 0, 83, 13965, 'Phase 3', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(7, 1, 13, 0, 83, 11815, 'Phase 3', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Hand of Justice'), +(7, 1, 14, 0, 83, 19436, 'Phase 3', 'Shaman', 'Enhancement', 'Back', 'Both', 'Cloak of Draconic Might'), +(7, 1, 15, 0, 83, 17104, 'Phase 3', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Spinal Reaper'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 88, 18817, 'Phase 5', 'Shaman', 'Enhancement', 'Head', 'Both', 'Crown of Destruction'), +(7, 1, 1, 0, 88, 18404, 'Phase 5', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(7, 1, 2, 0, 88, 21684, 'Phase 5', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Mantle of the Desert''s Fury'), +(7, 1, 4, 0, 88, 21374, 'Phase 5', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Stormcaller''s Hauberk'), +(7, 1, 5, 0, 88, 21586, 'Phase 5', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Belt of Never-ending Agony'), +(7, 1, 6, 0, 88, 21651, 'Phase 5', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Scaled Sand Reaver Leggings'), +(7, 1, 7, 0, 88, 21705, 'Phase 5', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Boots of the Fallen Prophet'), +(7, 1, 8, 0, 88, 21602, 'Phase 5', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Qiraji Execution Bracers'), +(7, 1, 9, 0, 88, 21624, 'Phase 5', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Gauntlets of Kalimdor'), +(7, 1, 10, 0, 88, 17063, 'Phase 5', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Band of Accuria'), +(7, 1, 11, 0, 88, 18821, 'Phase 5', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Quick Strike Ring'), +(7, 1, 12, 0, 88, 22321, 'Phase 5', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Heart of Wyrmthalak'), +(7, 1, 13, 0, 88, 19289, 'Phase 5', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Darkmoon Card: Maelstrom'), +(7, 1, 14, 0, 88, 21701, 'Phase 5', 'Shaman', 'Enhancement', 'Back', 'Both', 'Cloak of Concentrated Hatred'), +(7, 1, 15, 0, 88, 21134, 'Phase 5', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Dark Edge of Insanity'), +(7, 1, 17, 0, 88, 22395, 'Phase 5', 'Shaman', 'Enhancement', 'Ranged', 'Both', 'Totem of Rage'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 92, 18817, 'Phase 6', 'Shaman', 'Enhancement', 'Head', 'Both', 'Crown of Destruction'), +(7, 1, 1, 0, 92, 18404, 'Phase 6', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(7, 1, 2, 0, 92, 21684, 'Phase 6', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Mantle of the Desert''s Fury'), +(7, 1, 4, 0, 92, 21374, 'Phase 6', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Stormcaller''s Hauberk'), +(7, 1, 5, 0, 92, 21586, 'Phase 6', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Belt of Never-ending Agony'), +(7, 1, 6, 0, 92, 21651, 'Phase 6', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Scaled Sand Reaver Leggings'), +(7, 1, 7, 0, 92, 21705, 'Phase 6', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Boots of the Fallen Prophet'), +(7, 1, 8, 0, 92, 21602, 'Phase 6', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Qiraji Execution Bracers'), +(7, 1, 9, 0, 92, 21624, 'Phase 6', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Gauntlets of Kalimdor'), +(7, 1, 10, 0, 92, 17063, 'Phase 6', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Band of Accuria'), +(7, 1, 12, 0, 92, 22321, 'Phase 6', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Heart of Wyrmthalak'), +(7, 1, 13, 0, 92, 19289, 'Phase 6', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Darkmoon Card: Maelstrom'), +(7, 1, 14, 0, 92, 23045, 'Phase 6', 'Shaman', 'Enhancement', 'Back', 'Both', 'Shroud of Dominion'), +(7, 1, 15, 0, 92, 22798, 'Phase 6', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Might of Menethil'), +(7, 1, 17, 0, 92, 22395, 'Phase 6', 'Shaman', 'Enhancement', 'Ranged', 'Both', 'Totem of Rage'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 120, 28224, 'Pre-Raid', 'Shaman', 'Enhancement', 'Head', 'Both', 'Wastewalker Helm'), +(7, 1, 1, 0, 120, 29381, 'Pre-Raid', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Choker of Vile Intent'), +(7, 1, 2, 0, 120, 27797, 'Pre-Raid', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Wastewalker Shoulderpads'), +(7, 1, 4, 0, 120, 29515, 'Pre-Raid', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Ebon Netherscale Breastplate'), +(7, 1, 5, 0, 120, 29516, 'Pre-Raid', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Ebon Netherscale Belt'), +(7, 1, 6, 0, 120, 31544, 'Pre-Raid', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Clefthoof Hide Leggings'), +(7, 1, 7, 0, 120, 25686, 'Pre-Raid', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Fel Leather Boots'), +(7, 1, 8, 0, 120, 29517, 'Pre-Raid', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Ebon Netherscale Bracers'), +(7, 1, 9, 0, 120, 25685, 'Pre-Raid', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Fel Leather Gloves'), +(7, 1, 10, 0, 120, 30365, 'Pre-Raid', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Overseer''s Signet'), +(7, 1, 11, 0, 120, 30834, 'Pre-Raid', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Shapeshifter''s Signet'), +(7, 1, 12, 0, 120, 28288, 'Pre-Raid', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Abacus of Violent Odds'), +(7, 1, 13, 0, 120, 29383, 'Pre-Raid', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Bloodlust Brooch'), +(7, 1, 14, 0, 120, 24259, 'Pre-Raid', 'Shaman', 'Enhancement', 'Back', 'Both', 'Vengeance Wrap'), +(7, 1, 15, 0, 120, 27872, 'Pre-Raid', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'The Harvester of Souls'), +(7, 1, 16, 0, 120, 27872, 'Pre-Raid', 'Shaman', 'Enhancement', 'OffHand', 'Both', 'The Harvester of Souls'), +(7, 1, 17, 0, 120, 27815, 'Pre-Raid', 'Shaman', 'Enhancement', 'Ranged', 'Both', 'Totem of the Astral Winds'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 125, 29040, 'Phase 1', 'Shaman', 'Enhancement', 'Head', 'Both', 'Cyclone Helm'), +(7, 1, 1, 0, 125, 29381, 'Phase 1', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Choker of Vile Intent'), +(7, 1, 2, 0, 125, 29043, 'Phase 1', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Cyclone Shoulderplates'), +(7, 1, 4, 0, 125, 29515, 'Phase 1', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Ebon Netherscale Breastplate'), +(7, 1, 5, 0, 125, 29516, 'Phase 1', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Ebon Netherscale Belt'), +(7, 1, 6, 0, 125, 30739, 'Phase 1', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Scaled Greaves of the Marksman'), +(7, 1, 7, 0, 125, 28545, 'Phase 1', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Edgewalker Longboots'), +(7, 1, 8, 0, 125, 29517, 'Phase 1', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Ebon Netherscale Bracers'), +(7, 1, 9, 0, 125, 28776, 'Phase 1', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Liar''s Tongue Gloves'), +(7, 1, 10, 0, 125, 28757, 'Phase 1', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Ring of a Thousand Marks'), +(7, 1, 11, 0, 125, 30834, 'Phase 1', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Shapeshifter''s Signet'), +(7, 1, 12, 0, 125, 29383, 'Phase 1', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(7, 1, 13, 0, 125, 28830, 'Phase 1', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(7, 1, 14, 0, 125, 24259, 'Phase 1', 'Shaman', 'Enhancement', 'Back', 'Both', 'Vengeance Wrap'), +(7, 1, 15, 0, 125, 28308, 'Phase 1', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Gladiator''s Cleaver'), +(7, 1, 16, 0, 125, 28308, 'Phase 1', 'Shaman', 'Enhancement', 'OffHand', 'Both', 'Gladiator''s Cleaver'), +(7, 1, 17, 0, 125, 27815, 'Phase 1', 'Shaman', 'Enhancement', 'Ranged', 'Both', 'Totem of the Astral Winds'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 141, 30190, 'Phase 2', 'Shaman', 'Enhancement', 'Head', 'Both', 'Cataclysm Helm'), +(7, 1, 1, 0, 141, 30017, 'Phase 2', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Telonicus''s Pendant of Mayhem'), +(7, 1, 2, 0, 141, 30055, 'Phase 2', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Shoulderpads of the Stranger'), +(7, 1, 4, 0, 141, 30185, 'Phase 2', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Cataclysm Chestplate'), +(7, 1, 5, 0, 141, 30106, 'Phase 2', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Belt of One-Hundred Deaths'), +(7, 1, 6, 0, 141, 30192, 'Phase 2', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Cataclysm Legplates'), +(7, 1, 7, 0, 141, 30039, 'Phase 2', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Boots of Utter Darkness'), +(7, 1, 8, 0, 141, 30091, 'Phase 2', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'True-Aim Stalker Bands'), +(7, 1, 9, 0, 141, 30189, 'Phase 2', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Cataclysm Gauntlets'), +(7, 1, 10, 0, 141, 29997, 'Phase 2', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Band of the Ranger-General'), +(7, 1, 11, 0, 141, 30052, 'Phase 2', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Ring of Lethality'), +(7, 1, 12, 0, 141, 29383, 'Phase 2', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(7, 1, 13, 0, 141, 28830, 'Phase 2', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(7, 1, 14, 0, 141, 29994, 'Phase 2', 'Shaman', 'Enhancement', 'Back', 'Both', 'Thalassian Wildercloak'), +(7, 1, 16, 0, 141, 28433, 'Phase 2', 'Shaman', 'Enhancement', 'OffHand', 'Both', 'Wicked Edge of the Planes'), +(7, 1, 17, 0, 141, 27815, 'Phase 2', 'Shaman', 'Enhancement', 'Ranged', 'Both', 'Totem of the Astral Winds'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 156, 32235, 'Phase 3', 'Shaman', 'Enhancement', 'Head', 'Both', 'Cursed Vision of Sargeras'), +(7, 1, 1, 0, 156, 32260, 'Phase 3', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Choker of Endless Nightmares'), +(7, 1, 2, 0, 156, 32581, 'Phase 3', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Swiftstrike Shoulders'), +(7, 1, 4, 0, 156, 30905, 'Phase 3', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Midnight Chestguard'), +(7, 1, 5, 0, 156, 30106, 'Phase 3', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Belt of One-Hundred Deaths'), +(7, 1, 6, 0, 156, 30900, 'Phase 3', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Bow-Stitched Leggings'), +(7, 1, 7, 0, 156, 32366, 'Phase 3', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Shadowmaster''s Boots'), +(7, 1, 8, 0, 156, 32574, 'Phase 3', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Bindings of Lightning Reflexes'), +(7, 1, 9, 0, 156, 32347, 'Phase 3', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Grips of Damnation'), +(7, 1, 10, 0, 156, 29301, 'Phase 3', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Band of the Eternal Champion'), +(7, 1, 11, 0, 156, 32497, 'Phase 3', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Stormrage Signet Ring'), +(7, 1, 12, 0, 156, 28830, 'Phase 3', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Dragonspine Trophy'), +(7, 1, 13, 0, 156, 32505, 'Phase 3', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Madness of the Betrayer'), +(7, 1, 14, 0, 156, 32323, 'Phase 3', 'Shaman', 'Enhancement', 'Back', 'Both', 'Shadowmoon Destroyer''s Drape'), +(7, 1, 17, 0, 156, 27815, 'Phase 3', 'Shaman', 'Enhancement', 'Ranged', 'Both', 'Totem of the Astral Winds'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 164, 32235, 'Phase 4', 'Shaman', 'Enhancement', 'Head', 'Both', 'Cursed Vision of Sargeras'), +(7, 1, 1, 0, 164, 32260, 'Phase 4', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Choker of Endless Nightmares'), +(7, 1, 2, 0, 164, 32581, 'Phase 4', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Swiftstrike Shoulders'), +(7, 1, 4, 0, 164, 30905, 'Phase 4', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Midnight Chestguard'), +(7, 1, 5, 0, 164, 30106, 'Phase 4', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Belt of One-Hundred Deaths'), +(7, 1, 6, 0, 164, 30900, 'Phase 4', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Bow-Stitched Leggings'), +(7, 1, 7, 0, 164, 32366, 'Phase 4', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Shadowmaster''s Boots'), +(7, 1, 8, 0, 164, 32574, 'Phase 4', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Bindings of Lightning Reflexes'), +(7, 1, 9, 0, 164, 32347, 'Phase 4', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Grips of Damnation'), +(7, 1, 10, 0, 164, 33496, 'Phase 4', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Signet of Primal Wrath'), +(7, 1, 11, 0, 164, 32497, 'Phase 4', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Stormrage Signet Ring'), +(7, 1, 12, 0, 164, 28830, 'Phase 4', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Dragonspine Trophy'), +(7, 1, 13, 0, 164, 32505, 'Phase 4', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Madness of the Betrayer'), +(7, 1, 14, 0, 164, 32323, 'Phase 4', 'Shaman', 'Enhancement', 'Back', 'Both', 'Shadowmoon Destroyer''s Drape'), +(7, 1, 15, 0, 164, 34331, 'Phase 5', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Hand of the Deceiver'), +(7, 1, 16, 0, 164, 34346, 'Phase 5', 'Shaman', 'Enhancement', 'OffHand', 'Both', 'Mounting Vengeance'), +(7, 1, 17, 0, 164, 33507, 'Phase 4', 'Shaman', 'Enhancement', 'Ranged', 'Both', 'Stonebreaker''s Totem'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 224, 39602, 'Phase 1', 'Shaman', 'Enhancement', 'Head', 'Both', 'Heroes'' Earthshatter Faceguard'), +(7, 1, 1, 0, 224, 39146, 'Phase 1', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Collar of Dissolution'), +(7, 1, 2, 0, 224, 40524, 'Phase 1', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Valorous Earthshatter Shoulderguards'), +(7, 1, 4, 0, 224, 39597, 'Phase 1', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Heroes'' Earthshatter Chestguard'), +(7, 1, 5, 0, 224, 39762, 'Phase 1', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Torn Web Wrapping'), +(7, 1, 6, 0, 224, 40522, 'Phase 1', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Valorous Earthshatter War-Kilt'), +(7, 1, 7, 0, 224, 40746, 'Phase 1', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Pack-Ice Striders'), +(7, 1, 8, 0, 224, 40282, 'Phase 1', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Slime Stream Bands'), +(7, 1, 9, 0, 224, 39601, 'Phase 1', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Heroes'' Earthshatter Grips'), +(7, 1, 10, 0, 224, 45688, 'Phase 1', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Inscribed Band of the Kirin Tor'), +(7, 1, 12, 1, 224, 40322, 'Phase 1', 'Shaman', 'Enhancement', 'Trinket1', 'Alliance', 'Totem of Dueling'), +(7, 1, 12, 2, 224, 37390, 'Phase 1', 'Shaman', 'Enhancement', 'Trinket1', 'Horde', 'Meteorite Whetstone'), +(7, 1, 14, 0, 224, 40721, 'Phase 1', 'Shaman', 'Enhancement', 'Back', 'Both', 'Hammerhead Sharkskin Cloak'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 1, 245, 51242, 'Phase 2', 'Shaman', 'Enhancement', 'Head', 'Alliance', 'Sanctified Frost Witch''s Faceguard'), +(7, 1, 0, 2, 245, 40543, 'Phase 2', 'Shaman', 'Enhancement', 'Head', 'Horde', 'Blue Aspect Helm'), +(7, 1, 1, 1, 245, 50658, 'Phase 2', 'Shaman', 'Enhancement', 'Neck', 'Alliance', 'Amulet of the Silent Eulogy'), +(7, 1, 1, 2, 245, 44661, 'Phase 2', 'Shaman', 'Enhancement', 'Neck', 'Horde', 'Wyrmrest Necklace of Power'), +(7, 1, 2, 1, 245, 51240, 'Phase 2', 'Shaman', 'Enhancement', 'Shoulders', 'Alliance', 'Sanctified Frost Witch''s Shoulderguards'), +(7, 1, 2, 2, 245, 40524, 'Phase 2', 'Shaman', 'Enhancement', 'Shoulders', 'Horde', 'Valorous Earthshatter Shoulderguards'), +(7, 1, 4, 1, 245, 50689, 'Phase 2', 'Shaman', 'Enhancement', 'Chest', 'Alliance', 'Carapace of Forgotten Kings'), +(7, 1, 4, 2, 245, 40523, 'Phase 2', 'Shaman', 'Enhancement', 'Chest', 'Horde', 'Valorous Earthshatter Chestguard'), +(7, 1, 5, 1, 245, 54587, 'Phase 2', 'Shaman', 'Enhancement', 'Waist', 'Alliance', 'Split Shape Belt'), +(7, 1, 5, 2, 245, 39762, 'Phase 2', 'Shaman', 'Enhancement', 'Waist', 'Horde', 'Torn Web Wrapping'), +(7, 1, 6, 1, 245, 51241, 'Phase 2', 'Shaman', 'Enhancement', 'Legs', 'Alliance', 'Sanctified Frost Witch''s War-Kilt'), +(7, 1, 6, 2, 245, 40522, 'Phase 2', 'Shaman', 'Enhancement', 'Legs', 'Horde', 'Valorous Earthshatter War-Kilt'), +(7, 1, 7, 1, 245, 50711, 'Phase 2', 'Shaman', 'Enhancement', 'Feet', 'Alliance', 'Treads of the Wasteland'), +(7, 1, 7, 2, 245, 40367, 'Phase 2', 'Shaman', 'Enhancement', 'Feet', 'Horde', 'Boots of the Great Construct'), +(7, 1, 8, 1, 245, 54582, 'Phase 2', 'Shaman', 'Enhancement', 'Wrists', 'Alliance', 'Bracers of Fiery Night'), +(7, 1, 8, 2, 245, 40282, 'Phase 2', 'Shaman', 'Enhancement', 'Wrists', 'Horde', 'Slime Stream Bands'), +(7, 1, 9, 1, 245, 51243, 'Phase 2', 'Shaman', 'Enhancement', 'Hands', 'Alliance', 'Sanctified Frost Witch''s Grips'), +(7, 1, 9, 2, 245, 40520, 'Phase 2', 'Shaman', 'Enhancement', 'Hands', 'Horde', 'Valorous Earthshatter Grips'), +(7, 1, 10, 1, 245, 54576, 'Phase 2', 'Shaman', 'Enhancement', 'Finger1', 'Alliance', 'Signet of Twilight'), +(7, 1, 10, 2, 245, 40074, 'Phase 2', 'Shaman', 'Enhancement', 'Finger1', 'Horde', 'Strong-Handed Ring'), +(7, 1, 12, 1, 245, 50365, 'Phase 2', 'Shaman', 'Enhancement', 'Trinket1', 'Alliance', 'Phylactery of the Nameless Lich'), +(7, 1, 12, 2, 245, 40684, 'Phase 2', 'Shaman', 'Enhancement', 'Trinket1', 'Horde', 'Mirror of Truth'), +(7, 1, 14, 1, 245, 54583, 'Phase 2', 'Shaman', 'Enhancement', 'Back', 'Alliance', 'Cloak of Burning Dusk'), +(7, 1, 14, 2, 245, 40403, 'Phase 2', 'Shaman', 'Enhancement', 'Back', 'Horde', 'Drape of the Deadly Foe'), +(7, 1, 15, 0, 245, 50734, 'Phase 2', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Royal Scepter of Terenas II'), +(7, 1, 17, 1, 245, 50458, 'Phase 2', 'Shaman', 'Enhancement', 'Ranged', 'Alliance', 'Bizuri''s Totem of Shattered Ice'), +(7, 1, 17, 2, 245, 40322, 'Phase 2', 'Shaman', 'Enhancement', 'Ranged', 'Horde', 'Totem of Dueling'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 258, 45610, 'Phase 3', 'Shaman', 'Enhancement', 'Head', 'Both', 'Boundless Gaze'), +(7, 1, 1, 0, 258, 45517, 'Phase 3', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Pendulum of Infinity'), +(7, 1, 2, 0, 258, 46203, 'Phase 3', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Conqueror''s Worldbreaker Shoulderguards'), +(7, 1, 4, 0, 258, 46205, 'Phase 3', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Conqueror''s Worldbreaker Chestguard'), +(7, 1, 5, 0, 258, 45553, 'Phase 3', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Belt of Dragons'), +(7, 1, 6, 0, 258, 46208, 'Phase 3', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Conqueror''s Worldbreaker War-Kilt'), +(7, 1, 7, 0, 258, 45244, 'Phase 3', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Greaves of Swift Vengeance'), +(7, 1, 8, 0, 258, 45611, 'Phase 3', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Solar Bindings'), +(7, 1, 9, 0, 258, 46200, 'Phase 3', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Conqueror''s Worldbreaker Grips'), +(7, 1, 12, 0, 258, 45609, 'Phase 3', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Comet''s Trail'), +(7, 1, 14, 0, 258, 45461, 'Phase 3', 'Shaman', 'Enhancement', 'Back', 'Both', 'Drape of Icy Intent'), +(7, 1, 15, 0, 258, 45132, 'Phase 3', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Golden Saronite Dragon'), +(7, 1, 17, 0, 258, 42608, 'Phase 3', 'Shaman', 'Enhancement', 'Ranged', 'Both', 'Furious Gladiator''s Totem of Indomitability'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 264, 48353, 'Phase 4', 'Shaman', 'Enhancement', 'Head', 'Both', 'Faceguard of Triumph'), +(7, 1, 2, 0, 264, 48351, 'Phase 4', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Shoulderguards of Triumph'), +(7, 1, 4, 1, 264, 46965, 'Phase 4', 'Shaman', 'Enhancement', 'Chest', 'Alliance', 'Breastplate of Cruel Intent'), +(7, 1, 4, 2, 264, 47412, 'Phase 4', 'Shaman', 'Enhancement', 'Chest', 'Horde', 'Cuirass of Cruel Intent'), +(7, 1, 5, 1, 264, 47112, 'Phase 4', 'Shaman', 'Enhancement', 'Waist', 'Alliance', 'Belt of the Merciless Killer'), +(7, 1, 5, 2, 264, 47460, 'Phase 4', 'Shaman', 'Enhancement', 'Waist', 'Horde', 'Belt of the Pitiless Killer'), +(7, 1, 6, 0, 264, 48352, 'Phase 4', 'Shaman', 'Enhancement', 'Legs', 'Both', 'War-Kilt of Triumph'), +(7, 1, 7, 1, 264, 47099, 'Phase 4', 'Shaman', 'Enhancement', 'Feet', 'Alliance', 'Boots of Tremoring Earth'), +(7, 1, 7, 2, 264, 47456, 'Phase 4', 'Shaman', 'Enhancement', 'Feet', 'Horde', 'Sabatons of Tremoring Earth'), +(7, 1, 8, 1, 264, 47143, 'Phase 4', 'Shaman', 'Enhancement', 'Wrists', 'Alliance', 'Bindings of Dark Essence'), +(7, 1, 8, 2, 264, 47467, 'Phase 4', 'Shaman', 'Enhancement', 'Wrists', 'Horde', 'Dark Essence Bindings'), +(7, 1, 9, 0, 264, 48354, 'Phase 4', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Grips of Triumph'), +(7, 1, 10, 1, 264, 47075, 'Phase 4', 'Shaman', 'Enhancement', 'Finger1', 'Alliance', 'Ring of Callous Aggression'), +(7, 1, 10, 2, 264, 47443, 'Phase 4', 'Shaman', 'Enhancement', 'Finger1', 'Horde', 'Band of Callous Aggression'), +(7, 1, 12, 1, 264, 45609, 'Phase 4', 'Shaman', 'Enhancement', 'Trinket1', 'Alliance', 'Comet''s Trail'), +(7, 1, 14, 1, 264, 47552, 'Phase 4', 'Shaman', 'Enhancement', 'Back', 'Alliance', 'Jaina''s Radiance'), +(7, 1, 14, 2, 264, 47551, 'Phase 4', 'Shaman', 'Enhancement', 'Back', 'Horde', 'Aethas'' Intensity'), +(7, 1, 17, 1, 264, 47666, 'Phase 4', 'Shaman', 'Enhancement', 'Ranged', 'Alliance', 'Totem of Electrifying Wind'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 1, 0, 0, 290, 51242, 'Phase 5', 'Shaman', 'Enhancement', 'Head', 'Both', 'Sanctified Frost Witch''s Faceguard'), +(7, 1, 1, 0, 290, 50633, 'Phase 5', 'Shaman', 'Enhancement', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(7, 1, 2, 0, 290, 51240, 'Phase 5', 'Shaman', 'Enhancement', 'Shoulders', 'Both', 'Sanctified Frost Witch''s Shoulderguards'), +(7, 1, 4, 0, 290, 50689, 'Phase 5', 'Shaman', 'Enhancement', 'Chest', 'Both', 'Carapace of Forgotten Kings'), +(7, 1, 5, 0, 290, 50688, 'Phase 5', 'Shaman', 'Enhancement', 'Waist', 'Both', 'Nerub''ar Stalker''s Cord'), +(7, 1, 6, 0, 290, 51241, 'Phase 5', 'Shaman', 'Enhancement', 'Legs', 'Both', 'Sanctified Frost Witch''s War-Kilt'), +(7, 1, 7, 0, 290, 54577, 'Phase 5', 'Shaman', 'Enhancement', 'Feet', 'Both', 'Returning Footfalls'), +(7, 1, 8, 0, 290, 50655, 'Phase 5', 'Shaman', 'Enhancement', 'Wrists', 'Both', 'Scourge Hunter''s Vambraces'), +(7, 1, 9, 0, 290, 51243, 'Phase 5', 'Shaman', 'Enhancement', 'Hands', 'Both', 'Sanctified Frost Witch''s Grips'), +(7, 1, 10, 0, 290, 50678, 'Phase 5', 'Shaman', 'Enhancement', 'Finger1', 'Both', 'Seal of Many Mouths'), +(7, 1, 11, 0, 290, 50402, 'Phase 5', 'Shaman', 'Enhancement', 'Finger2', 'Both', 'Ashen Band of Endless Vengeance'), +(7, 1, 12, 0, 290, 50706, 'Phase 5', 'Shaman', 'Enhancement', 'Trinket1', 'Both', 'Tiny Abomination in a Jar'), +(7, 1, 13, 0, 290, 54590, 'Phase 5', 'Shaman', 'Enhancement', 'Trinket2', 'Both', 'Sharpened Twilight Scale'), +(7, 1, 14, 0, 290, 50653, 'Phase 5', 'Shaman', 'Enhancement', 'Back', 'Both', 'Shadowvault Slayer''s Cloak'), +(7, 1, 15, 0, 290, 50737, 'Phase 5', 'Shaman', 'Enhancement', 'MainHand', 'Both', 'Havoc''s Call, Blade of Lordaeron Kings'), +(7, 1, 16, 0, 290, 50737, 'Phase 5', 'Shaman', 'Enhancement', 'OffHand', 'Both', 'Havoc''s Call, Blade of Lordaeron Kings'), +(7, 1, 17, 0, 290, 50463, 'Phase 5', 'Shaman', 'Enhancement', 'Ranged', 'Both', 'Totem of the Avalanche'); + +-- Restoration (tab 2) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 66, 13216, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Head', 'Both', 'Crown of the Penitent'), +(7, 2, 1, 0, 66, 18723, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Neck', 'Both', 'Animated Chain Necklace'), +(7, 2, 2, 0, 66, 15061, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Living Shoulders'), +(7, 2, 4, 0, 66, 13346, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Chest', 'Both', 'Robes of the Exalted'), +(7, 2, 5, 0, 66, 14553, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Waist', 'Both', 'Sash of Mercy'), +(7, 2, 6, 0, 66, 11841, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Legs', 'Both', 'Senior Designer''s Pantaloons'), +(7, 2, 7, 0, 66, 13954, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Feet', 'Both', 'Verdant Footpads'), +(7, 2, 8, 0, 66, 13969, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Loomguard Armbraces'), +(7, 2, 9, 0, 66, 10787, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Hands', 'Both', 'Atal''ai Gloves'), +(7, 2, 10, 0, 66, 16058, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Fordring''s Seal'), +(7, 2, 11, 0, 66, 13178, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Rosewine Circle'), +(7, 2, 12, 0, 66, 12930, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Briarwood Reed'), +(7, 2, 13, 0, 66, 11819, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Second Wind'), +(7, 2, 14, 0, 66, 13386, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'Back', 'Both', 'Archivist Cape'), +(7, 2, 15, 0, 66, 11923, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'MainHand', 'Both', 'The Hammer of Grace'), +(7, 2, 16, 0, 66, 11928, 'Phase 1 (Pre-Raid)', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Thaurissan''s Royal Scepter'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 76, 13216, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Head', 'Both', 'Crown of the Penitent'), +(7, 2, 1, 0, 76, 18723, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Neck', 'Both', 'Animated Chain Necklace'), +(7, 2, 2, 0, 76, 18757, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Diabolic Mantle'), +(7, 2, 4, 0, 76, 13346, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Chest', 'Both', 'Robes of the Exalted'), +(7, 2, 5, 0, 76, 14553, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Waist', 'Both', 'Sash of Mercy'), +(7, 2, 6, 0, 76, 18386, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Legs', 'Both', 'Padre''s Trousers'), +(7, 2, 7, 0, 76, 13954, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Feet', 'Both', 'Verdant Footpads'), +(7, 2, 8, 0, 76, 13969, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Loomguard Armbraces'), +(7, 2, 9, 0, 76, 18527, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Hands', 'Both', 'Harmonious Gauntlets'), +(7, 2, 10, 0, 76, 16058, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Fordring''s Seal'), +(7, 2, 11, 0, 76, 13178, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Rosewine Circle'), +(7, 2, 12, 0, 76, 18371, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Mindtap Talisman'), +(7, 2, 13, 0, 76, 18371, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Mindtap Talisman'), +(7, 2, 14, 0, 76, 13386, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'Back', 'Both', 'Archivist Cape'), +(7, 2, 15, 0, 76, 11923, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'MainHand', 'Both', 'The Hammer of Grace'), +(7, 2, 16, 0, 76, 18523, 'Phase 2 (Pre-Raid)', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Brightly Glowing Stone'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 78, 16842, 'Phase 2', 'Shaman', 'Restoration', 'Head', 'Both', 'Earthfury Helmet'), +(7, 2, 1, 0, 78, 18723, 'Phase 2', 'Shaman', 'Restoration', 'Neck', 'Both', 'Animated Chain Necklace'), +(7, 2, 2, 0, 78, 18810, 'Phase 2', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Wild Growth Spaulders'), +(7, 2, 4, 0, 78, 13346, 'Phase 2', 'Shaman', 'Restoration', 'Chest', 'Both', 'Robes of the Exalted'), +(7, 2, 5, 0, 78, 19162, 'Phase 2', 'Shaman', 'Restoration', 'Waist', 'Both', 'Corehound Belt'), +(7, 2, 6, 0, 78, 18875, 'Phase 2', 'Shaman', 'Restoration', 'Legs', 'Both', 'Salamander Scale Pants'), +(7, 2, 7, 0, 78, 13954, 'Phase 2', 'Shaman', 'Restoration', 'Feet', 'Both', 'Verdant Footpads'), +(7, 2, 8, 0, 78, 13969, 'Phase 2', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Loomguard Armbraces'), +(7, 2, 9, 0, 78, 18527, 'Phase 2', 'Shaman', 'Restoration', 'Hands', 'Both', 'Harmonious Gauntlets'), +(7, 2, 10, 0, 78, 19140, 'Phase 2', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Cauterizing Band'), +(7, 2, 11, 0, 78, 13178, 'Phase 2', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Rosewine Circle'), +(7, 2, 12, 0, 78, 17064, 'Phase 2', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Shard of the Scale'), +(7, 2, 13, 0, 78, 18371, 'Phase 2', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Mindtap Talisman'), +(7, 2, 14, 0, 78, 13386, 'Phase 2', 'Shaman', 'Restoration', 'Back', 'Both', 'Archivist Cape'), +(7, 2, 15, 0, 78, 17070, 'Phase 2', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Fang of the Mystics'), +(7, 2, 16, 0, 78, 18523, 'Phase 2', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Brightly Glowing Stone'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 83, 16947, 'Phase 3', 'Shaman', 'Restoration', 'Head', 'Both', 'Helmet of Ten Storms'), +(7, 2, 1, 0, 83, 19371, 'Phase 3', 'Shaman', 'Restoration', 'Neck', 'Both', 'Pendant of the Fallen Dragon'), +(7, 2, 2, 0, 83, 18810, 'Phase 3', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Wild Growth Spaulders'), +(7, 2, 4, 0, 83, 13346, 'Phase 3', 'Shaman', 'Restoration', 'Chest', 'Both', 'Robes of the Exalted'), +(7, 2, 5, 0, 83, 19162, 'Phase 3', 'Shaman', 'Restoration', 'Waist', 'Both', 'Corehound Belt'), +(7, 2, 6, 0, 83, 18875, 'Phase 3', 'Shaman', 'Restoration', 'Legs', 'Both', 'Salamander Scale Pants'), +(7, 2, 7, 0, 83, 19391, 'Phase 3', 'Shaman', 'Restoration', 'Feet', 'Both', 'Shimmering Geta'), +(7, 2, 8, 0, 83, 16943, 'Phase 3', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Bracers of Ten Storms'), +(7, 2, 9, 0, 83, 16948, 'Phase 3', 'Shaman', 'Restoration', 'Hands', 'Both', 'Gauntlets of Ten Storms'), +(7, 2, 10, 0, 83, 19397, 'Phase 3', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Ring of Blackrock'), +(7, 2, 11, 0, 83, 19382, 'Phase 3', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Pure Elementium Band'), +(7, 2, 12, 0, 83, 17064, 'Phase 3', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Shard of the Scale'), +(7, 2, 13, 0, 83, 19395, 'Phase 3', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Rejuvenating Gem'), +(7, 2, 14, 0, 83, 19430, 'Phase 3', 'Shaman', 'Restoration', 'Back', 'Both', 'Shroud of Pure Thought'), +(7, 2, 15, 0, 83, 19347, 'Phase 3', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Claw of Chromaggus'), +(7, 2, 16, 0, 83, 19312, 'Phase 3', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Lei of the Lifegiver'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 88, 21372, 'Phase 5', 'Shaman', 'Restoration', 'Head', 'Both', 'Stormcaller''s Diadem'), +(7, 2, 1, 0, 88, 21712, 'Phase 5', 'Shaman', 'Restoration', 'Neck', 'Both', 'Amulet of the Fallen God'), +(7, 2, 2, 0, 88, 21376, 'Phase 5', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Stormcaller''s Pauldrons'), +(7, 2, 4, 0, 88, 21374, 'Phase 5', 'Shaman', 'Restoration', 'Chest', 'Both', 'Stormcaller''s Hauberk'), +(7, 2, 5, 0, 88, 16944, 'Phase 5', 'Shaman', 'Restoration', 'Waist', 'Both', 'Belt of Ten Storms'), +(7, 2, 6, 0, 88, 21375, 'Phase 5', 'Shaman', 'Restoration', 'Legs', 'Both', 'Stormcaller''s Leggings'), +(7, 2, 7, 0, 88, 21373, 'Phase 5', 'Shaman', 'Restoration', 'Feet', 'Both', 'Stormcaller''s Footguards'), +(7, 2, 8, 0, 88, 16943, 'Phase 5', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Bracers of Ten Storms'), +(7, 2, 9, 0, 88, 16948, 'Phase 5', 'Shaman', 'Restoration', 'Hands', 'Both', 'Gauntlets of Ten Storms'), +(7, 2, 10, 0, 88, 21620, 'Phase 5', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Ring of the Martyr'), +(7, 2, 11, 0, 88, 21681, 'Phase 5', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Ring of the Devoured'), +(7, 2, 12, 0, 88, 17064, 'Phase 5', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Shard of the Scale'), +(7, 2, 13, 0, 88, 19395, 'Phase 5', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Rejuvenating Gem'), +(7, 2, 14, 0, 88, 21583, 'Phase 5', 'Shaman', 'Restoration', 'Back', 'Both', 'Cloak of Clarity'), +(7, 2, 15, 0, 88, 21839, 'Phase 5', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Scepter of the False Prophet'), +(7, 2, 16, 0, 88, 21610, 'Phase 5', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Wormscale Blocker'), +(7, 2, 17, 0, 88, 22396, 'Phase 5', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of Life'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 92, 22466, 'Phase 6', 'Shaman', 'Restoration', 'Head', 'Both', 'Earthshatter Headpiece'), +(7, 2, 1, 0, 92, 21712, 'Phase 6', 'Shaman', 'Restoration', 'Neck', 'Both', 'Amulet of the Fallen God'), +(7, 2, 2, 0, 92, 22467, 'Phase 6', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Earthshatter Spaulders'), +(7, 2, 4, 0, 92, 22464, 'Phase 6', 'Shaman', 'Restoration', 'Chest', 'Both', 'Earthshatter Tunic'), +(7, 2, 5, 0, 92, 22470, 'Phase 6', 'Shaman', 'Restoration', 'Waist', 'Both', 'Earthshatter Girdle'), +(7, 2, 6, 0, 92, 22465, 'Phase 6', 'Shaman', 'Restoration', 'Legs', 'Both', 'Earthshatter Legguards'), +(7, 2, 7, 0, 92, 22468, 'Phase 6', 'Shaman', 'Restoration', 'Feet', 'Both', 'Earthshatter Boots'), +(7, 2, 8, 0, 92, 22471, 'Phase 6', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Earthshatter Wristguards'), +(7, 2, 9, 0, 92, 21619, 'Phase 6', 'Shaman', 'Restoration', 'Hands', 'Both', 'Gloves of the Messiah'), +(7, 2, 10, 0, 92, 21620, 'Phase 6', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Ring of the Martyr'), +(7, 2, 11, 0, 92, 23065, 'Phase 6', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Ring of the Earthshatterer'), +(7, 2, 12, 0, 92, 23027, 'Phase 6', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Warmth of Forgiveness'), +(7, 2, 13, 0, 92, 19395, 'Phase 6', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Rejuvenating Gem'), +(7, 2, 14, 0, 92, 21583, 'Phase 6', 'Shaman', 'Restoration', 'Back', 'Both', 'Cloak of Clarity'), +(7, 2, 15, 0, 92, 23056, 'Phase 6', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Hammer of the Twisting Nether'), +(7, 2, 16, 0, 92, 22819, 'Phase 6', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Shield of Condemnation'), +(7, 2, 17, 0, 92, 22396, 'Phase 6', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of Life'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 120, 24264, 'Pre-Raid', 'Shaman', 'Restoration', 'Head', 'Both', 'Whitemend Hood'), +(7, 2, 1, 0, 120, 31691, 'Pre-Raid', 'Shaman', 'Restoration', 'Neck', 'Both', 'Natasha''s Guardian Cord'), +(7, 2, 2, 0, 120, 35395, 'Pre-Raid', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Seer''s Ringmail Shoulderpads'), +(7, 2, 4, 0, 120, 29522, 'Pre-Raid', 'Shaman', 'Restoration', 'Chest', 'Both', 'Windhawk Hauberk'), +(7, 2, 5, 0, 120, 29524, 'Pre-Raid', 'Shaman', 'Restoration', 'Waist', 'Both', 'Windhawk Belt'), +(7, 2, 6, 0, 120, 24261, 'Pre-Raid', 'Shaman', 'Restoration', 'Legs', 'Both', 'Whitemend Pants'), +(7, 2, 7, 0, 120, 27549, 'Pre-Raid', 'Shaman', 'Restoration', 'Feet', 'Both', 'Wavefury Boots'), +(7, 2, 8, 0, 120, 29523, 'Pre-Raid', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Windhawk Bracers'), +(7, 2, 9, 0, 120, 29506, 'Pre-Raid', 'Shaman', 'Restoration', 'Hands', 'Both', 'Gloves of the Living Touch'), +(7, 2, 10, 0, 120, 28259, 'Pre-Raid', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Cosmic Lifeband'), +(7, 2, 10, 2, 120, 29168, 'Pre-Raid', 'Shaman', 'Restoration', 'Finger1', 'Horde', 'Ancestral Band'), +(7, 2, 12, 0, 120, 29376, 'Pre-Raid', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(7, 2, 13, 0, 120, 30841, 'Pre-Raid', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Lower City Prayerbook'), +(7, 2, 14, 0, 120, 24254, 'Pre-Raid', 'Shaman', 'Restoration', 'Back', 'Both', 'White Remedy Cape'), +(7, 2, 15, 0, 120, 23556, 'Pre-Raid', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Hand of Eternity'), +(7, 2, 16, 0, 120, 29267, 'Pre-Raid', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Light-Bearer''s Faith Shield'), +(7, 2, 17, 0, 120, 27544, 'Pre-Raid', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of Spontaneous Regrowth'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 125, 29035, 'Phase 1', 'Shaman', 'Restoration', 'Head', 'Both', 'Cyclone Faceguard'), +(7, 2, 1, 0, 125, 28609, 'Phase 1', 'Shaman', 'Restoration', 'Neck', 'Both', 'Emberspur Talisman'), +(7, 2, 2, 0, 125, 29037, 'Phase 1', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Cyclone Shoulderguards'), +(7, 2, 4, 0, 125, 29522, 'Phase 1', 'Shaman', 'Restoration', 'Chest', 'Both', 'Windhawk Hauberk'), +(7, 2, 5, 0, 125, 29524, 'Phase 1', 'Shaman', 'Restoration', 'Waist', 'Both', 'Windhawk Belt'), +(7, 2, 6, 0, 125, 30727, 'Phase 1', 'Shaman', 'Restoration', 'Legs', 'Both', 'Gilded Trousers of Benediction'), +(7, 2, 7, 0, 125, 30737, 'Phase 1', 'Shaman', 'Restoration', 'Feet', 'Both', 'Gold-Leaf Wildboots'), +(7, 2, 8, 0, 125, 29523, 'Phase 1', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Windhawk Bracers'), +(7, 2, 9, 0, 125, 28520, 'Phase 1', 'Shaman', 'Restoration', 'Hands', 'Both', 'Gloves of Centering'), +(7, 2, 10, 0, 125, 28763, 'Phase 1', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Jade Ring of the Everliving'), +(7, 2, 11, 0, 125, 28790, 'Phase 1', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Naaru Lightwarden''s Band'), +(7, 2, 12, 0, 125, 29376, 'Phase 1', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(7, 2, 13, 0, 125, 28590, 'Phase 1', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Ribbon of Sacrifice'), +(7, 2, 14, 0, 125, 28765, 'Phase 1', 'Shaman', 'Restoration', 'Back', 'Both', 'Stainless Cloak of the Pure Hearted'), +(7, 2, 15, 0, 125, 28771, 'Phase 1', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Light''s Justice'), +(7, 2, 16, 0, 125, 29458, 'Phase 1', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Aegis of the Vindicator'), +(7, 2, 17, 0, 125, 28523, 'Phase 1', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of Healing Rains'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 141, 32475, 'Phase 2', 'Shaman', 'Restoration', 'Head', 'Both', 'Living Replicator Specs'), +(7, 2, 1, 0, 141, 30018, 'Phase 2', 'Shaman', 'Restoration', 'Neck', 'Both', 'Lord Sanguinar''s Claim'), +(7, 2, 2, 0, 141, 30168, 'Phase 2', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Cataclysm Shoulderguards'), +(7, 2, 4, 0, 141, 30164, 'Phase 2', 'Shaman', 'Restoration', 'Chest', 'Both', 'Cataclysm Chestguard'), +(7, 2, 5, 0, 141, 21873, 'Phase 2', 'Shaman', 'Restoration', 'Waist', 'Both', 'Primal Mooncloth Belt'), +(7, 2, 6, 0, 141, 30727, 'Phase 2', 'Shaman', 'Restoration', 'Legs', 'Both', 'Gilded Trousers of Benediction'), +(7, 2, 7, 0, 141, 30737, 'Phase 2', 'Shaman', 'Restoration', 'Feet', 'Both', 'Gold-Leaf Wildboots'), +(7, 2, 8, 0, 141, 30047, 'Phase 2', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Blackfathom Warbands'), +(7, 2, 9, 0, 141, 29976, 'Phase 2', 'Shaman', 'Restoration', 'Hands', 'Both', 'Worldstorm Gauntlets'), +(7, 2, 10, 0, 141, 28763, 'Phase 2', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Jade Ring of the Everliving'), +(7, 2, 11, 0, 141, 30736, 'Phase 2', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Ring of Flowing Light'), +(7, 2, 12, 0, 141, 29376, 'Phase 2', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(7, 2, 13, 0, 141, 28590, 'Phase 2', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Ribbon of Sacrifice'), +(7, 2, 14, 0, 141, 29989, 'Phase 2', 'Shaman', 'Restoration', 'Back', 'Both', 'Sunshower Light Cloak'), +(7, 2, 15, 0, 141, 30108, 'Phase 2', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Lightfathom Scepter'), +(7, 2, 16, 0, 141, 29458, 'Phase 2', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Aegis of the Vindicator'), +(7, 2, 17, 0, 141, 28523, 'Phase 2', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of Healing Rains'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 156, 31012, 'Phase 3', 'Shaman', 'Restoration', 'Head', 'Both', 'Skyshatter Helmet'), +(7, 2, 1, 0, 156, 32370, 'Phase 3', 'Shaman', 'Restoration', 'Neck', 'Both', 'Nadina''s Pendant of Purity'), +(7, 2, 2, 0, 156, 31022, 'Phase 3', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Skyshatter Shoulderpads'), +(7, 2, 4, 0, 156, 31016, 'Phase 3', 'Shaman', 'Restoration', 'Chest', 'Both', 'Skyshatter Chestguard'), +(7, 2, 5, 0, 156, 32258, 'Phase 3', 'Shaman', 'Restoration', 'Waist', 'Both', 'Naturalist''s Preserving Cinch'), +(7, 2, 6, 0, 156, 31019, 'Phase 3', 'Shaman', 'Restoration', 'Legs', 'Both', 'Skyshatter Leggings'), +(7, 2, 7, 0, 156, 30737, 'Phase 3', 'Shaman', 'Restoration', 'Feet', 'Both', 'Gold-Leaf Wildboots'), +(7, 2, 8, 0, 156, 32584, 'Phase 3', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Swiftheal Wraps'), +(7, 2, 9, 0, 156, 32328, 'Phase 3', 'Shaman', 'Restoration', 'Hands', 'Both', 'Botanist''s Gloves of Growth'), +(7, 2, 10, 0, 156, 32528, 'Phase 3', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(7, 2, 11, 0, 156, 32528, 'Phase 3', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Blessed Band of Karabor'), +(7, 2, 12, 0, 156, 29376, 'Phase 3', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(7, 2, 13, 0, 156, 32496, 'Phase 3', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Memento of Tyrande'), +(7, 2, 14, 0, 156, 32524, 'Phase 3', 'Shaman', 'Restoration', 'Back', 'Both', 'Shroud of the Highborne'), +(7, 2, 15, 0, 156, 32500, 'Phase 3', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(7, 2, 16, 0, 156, 30882, 'Phase 3', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Bastion of Light'), +(7, 2, 17, 0, 156, 28523, 'Phase 3', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of Healing Rains'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 164, 31012, 'Phase 4', 'Shaman', 'Restoration', 'Head', 'Both', 'Skyshatter Helmet'), +(7, 2, 1, 0, 164, 33281, 'Phase 4', 'Shaman', 'Restoration', 'Neck', 'Both', 'Brooch of Nature''s Mercy'), +(7, 2, 2, 0, 164, 31022, 'Phase 4', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Skyshatter Shoulderpads'), +(7, 2, 4, 0, 164, 31016, 'Phase 4', 'Shaman', 'Restoration', 'Chest', 'Both', 'Skyshatter Chestguard'), +(7, 2, 5, 0, 164, 32258, 'Phase 4', 'Shaman', 'Restoration', 'Waist', 'Both', 'Naturalist''s Preserving Cinch'), +(7, 2, 6, 0, 164, 31019, 'Phase 4', 'Shaman', 'Restoration', 'Legs', 'Both', 'Skyshatter Leggings'), +(7, 2, 7, 0, 164, 33471, 'Phase 4', 'Shaman', 'Restoration', 'Feet', 'Both', 'Two-Toed Sandals'), +(7, 2, 8, 0, 164, 32584, 'Phase 4', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Swiftheal Wraps'), +(7, 2, 9, 0, 164, 32328, 'Phase 4', 'Shaman', 'Restoration', 'Hands', 'Both', 'Botanist''s Gloves of Growth'), +(7, 2, 10, 0, 164, 32528, 'Phase 4', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(7, 2, 11, 0, 164, 32528, 'Phase 4', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Blessed Band of Karabor'), +(7, 2, 12, 0, 164, 29376, 'Phase 4', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(7, 2, 13, 0, 164, 32496, 'Phase 4', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Memento of Tyrande'), +(7, 2, 14, 0, 164, 32524, 'Phase 4', 'Shaman', 'Restoration', 'Back', 'Both', 'Shroud of the Highborne'), +(7, 2, 15, 0, 164, 32500, 'Phase 4', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(7, 2, 16, 0, 164, 30882, 'Phase 4', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Bastion of Light'), +(7, 2, 17, 0, 164, 28523, 'Phase 4', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of Healing Rains'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 200, 42555, 'Pre-Raid', 'Shaman', 'Restoration', 'Head', 'Both', 'Electroflux Sight Enhancers'), +(7, 2, 1, 0, 200, 42647, 'Pre-Raid', 'Shaman', 'Restoration', 'Neck', 'Both', 'Titanium Spellshock Necklace'), +(7, 2, 2, 0, 200, 37875, 'Pre-Raid', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Spaulders of the Violet Hold'), +(7, 2, 4, 0, 200, 43461, 'Pre-Raid', 'Shaman', 'Restoration', 'Chest', 'Both', 'Revenant''s Breastplate'), +(7, 2, 5, 0, 200, 37855, 'Pre-Raid', 'Shaman', 'Restoration', 'Waist', 'Both', 'Mail Girdle of the Audient Earth'), +(7, 2, 6, 0, 200, 44305, 'Pre-Raid', 'Shaman', 'Restoration', 'Legs', 'Both', 'Kilt of Dark Mercy'), +(7, 2, 7, 0, 200, 43469, 'Pre-Raid', 'Shaman', 'Restoration', 'Feet', 'Both', 'Revenant''s Treads'), +(7, 2, 8, 0, 200, 37788, 'Pre-Raid', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Limb Regeneration Bracers'), +(7, 2, 9, 0, 200, 44204, 'Pre-Raid', 'Shaman', 'Restoration', 'Hands', 'Both', 'Grips of Fierce Pronouncements'), +(7, 2, 10, 0, 200, 37192, 'Pre-Raid', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Annhylde''s Ring'), +(7, 2, 12, 0, 200, 40685, 'Pre-Raid', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'The Egg of Mortal Essence'), +(7, 2, 14, 0, 200, 41609, 'Pre-Raid', 'Shaman', 'Restoration', 'Back', 'Both', 'Wispcloak'), +(7, 2, 15, 0, 200, 37169, 'Pre-Raid', 'Shaman', 'Restoration', 'MainHand', 'Both', 'War Mace of Unrequited Love'), +(7, 2, 17, 0, 200, 40709, 'Pre-Raid', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of Forest Growth'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 224, 40510, 'Phase 1', 'Shaman', 'Restoration', 'Head', 'Both', 'Valorous Earthshatter Headpiece'), +(7, 2, 1, 0, 224, 44662, 'Phase 1', 'Shaman', 'Restoration', 'Neck', 'Both', 'Life-Binder''s Locket'), +(7, 2, 2, 0, 224, 40513, 'Phase 1', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Valorous Earthshatter Spaulders'), +(7, 2, 4, 0, 224, 40508, 'Phase 1', 'Shaman', 'Restoration', 'Chest', 'Both', 'Valorous Earthshatter Tunic'), +(7, 2, 5, 0, 224, 40327, 'Phase 1', 'Shaman', 'Restoration', 'Waist', 'Both', 'Girdle of Recuperation'), +(7, 2, 6, 0, 224, 40512, 'Phase 1', 'Shaman', 'Restoration', 'Legs', 'Both', 'Valorous Earthshatter Legguards'), +(7, 2, 7, 0, 224, 40519, 'Phase 1', 'Shaman', 'Restoration', 'Feet', 'Both', 'Footsteps of Malygos'), +(7, 2, 8, 0, 224, 40324, 'Phase 1', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Bands of Mutual Respect'), +(7, 2, 9, 0, 224, 40564, 'Phase 1', 'Shaman', 'Restoration', 'Hands', 'Both', 'Winter Spectacle Gloves'), +(7, 2, 10, 0, 224, 40375, 'Phase 1', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Ring of Decaying Beauty'), +(7, 2, 12, 0, 224, 40258, 'Phase 1', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Forethought Talisman'), +(7, 2, 14, 0, 224, 44005, 'Phase 1', 'Shaman', 'Restoration', 'Back', 'Both', 'Pennant Cloak'), +(7, 2, 17, 0, 224, 39728, 'Phase 1', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of Misery'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 245, 46201, 'Phase 2', 'Shaman', 'Restoration', 'Head', 'Both', 'Conqueror''s Worldbreaker Headpiece'), +(7, 2, 1, 0, 245, 45443, 'Phase 2', 'Shaman', 'Restoration', 'Neck', 'Both', 'Charm of Meticulous Timing'), +(7, 2, 2, 0, 245, 46204, 'Phase 2', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Conqueror''s Worldbreaker Spaulders'), +(7, 2, 4, 0, 245, 45867, 'Phase 2', 'Shaman', 'Restoration', 'Chest', 'Both', 'Breastplate of the Stoneshaper'), +(7, 2, 5, 0, 245, 45151, 'Phase 2', 'Shaman', 'Restoration', 'Waist', 'Both', 'Belt of the Fallen Wyrm'), +(7, 2, 6, 0, 245, 46202, 'Phase 2', 'Shaman', 'Restoration', 'Legs', 'Both', 'Conqueror''s Worldbreaker Legguards'), +(7, 2, 7, 0, 245, 45537, 'Phase 2', 'Shaman', 'Restoration', 'Feet', 'Both', 'Treads of the False Oracle'), +(7, 2, 8, 0, 245, 45460, 'Phase 2', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Bindings of Winter Gale'), +(7, 2, 9, 0, 245, 46199, 'Phase 2', 'Shaman', 'Restoration', 'Hands', 'Both', 'Conqueror''s Worldbreaker Handguards'), +(7, 2, 10, 0, 245, 45614, 'Phase 2', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Starshine Circle'), +(7, 2, 12, 0, 245, 40432, 'Phase 2', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Illustration of the Dragon Soul'), +(7, 2, 14, 0, 245, 44005, 'Phase 2', 'Shaman', 'Restoration', 'Back', 'Both', 'Pennant Cloak'), +(7, 2, 15, 0, 245, 46017, 'Phase 2', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(7, 2, 17, 0, 245, 45114, 'Phase 2', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Steamcaller''s Totem'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 258, 46201, 'Phase 3', 'Shaman', 'Restoration', 'Head', 'Both', 'Conqueror''s Worldbreaker Headpiece'), +(7, 2, 1, 1, 258, 47144, 'Phase 3', 'Shaman', 'Restoration', 'Neck', 'Alliance', 'Wail of the Val''kyr'), +(7, 2, 1, 2, 258, 47468, 'Phase 3', 'Shaman', 'Restoration', 'Neck', 'Horde', 'Cry of the Val''kyr'), +(7, 2, 2, 0, 258, 46204, 'Phase 3', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Conqueror''s Worldbreaker Spaulders'), +(7, 2, 4, 0, 258, 46198, 'Phase 3', 'Shaman', 'Restoration', 'Chest', 'Both', 'Conqueror''s Worldbreaker Tunic'), +(7, 2, 5, 0, 258, 46991, 'Phase 3', 'Shaman', 'Restoration', 'Waist', 'Both', 'Belt of the Ice Burrower'), +(7, 2, 6, 1, 258, 47190, 'Phase 3', 'Shaman', 'Restoration', 'Legs', 'Alliance', 'Legwraps of the Awakening'), +(7, 2, 6, 2, 258, 47479, 'Phase 3', 'Shaman', 'Restoration', 'Legs', 'Horde', 'Leggings of the Awakening'), +(7, 2, 7, 1, 258, 47099, 'Phase 3', 'Shaman', 'Restoration', 'Feet', 'Alliance', 'Boots of Tremoring Earth'), +(7, 2, 7, 2, 258, 47456, 'Phase 3', 'Shaman', 'Restoration', 'Feet', 'Horde', 'Sabatons of Tremoring Earth'), +(7, 2, 8, 0, 258, 45460, 'Phase 3', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Bindings of Winter Gale'), +(7, 2, 9, 0, 258, 46199, 'Phase 3', 'Shaman', 'Restoration', 'Hands', 'Both', 'Conqueror''s Worldbreaker Handguards'), +(7, 2, 10, 1, 258, 47224, 'Phase 3', 'Shaman', 'Restoration', 'Finger1', 'Alliance', 'Ring of the Darkmender'), +(7, 2, 10, 2, 258, 47439, 'Phase 3', 'Shaman', 'Restoration', 'Finger1', 'Horde', 'Circle of the Darkmender'), +(7, 2, 12, 1, 258, 47059, 'Phase 3', 'Shaman', 'Restoration', 'Trinket1', 'Alliance', 'Solace of the Defeated'), +(7, 2, 12, 2, 258, 47432, 'Phase 3', 'Shaman', 'Restoration', 'Trinket1', 'Horde', 'Solace of the Fallen'), +(7, 2, 14, 1, 258, 47552, 'Phase 3', 'Shaman', 'Restoration', 'Back', 'Alliance', 'Jaina''s Radiance'), +(7, 2, 14, 2, 258, 47551, 'Phase 3', 'Shaman', 'Restoration', 'Back', 'Horde', 'Aethas'' Intensity'), +(7, 2, 15, 0, 258, 46017, 'Phase 3', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 264, 51247, 'Phase 4', 'Shaman', 'Restoration', 'Head', 'Both', 'Sanctified Frost Witch''s Headpiece'), +(7, 2, 1, 0, 264, 50724, 'Phase 4', 'Shaman', 'Restoration', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(7, 2, 2, 0, 264, 51245, 'Phase 4', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Sanctified Frost Witch''s Spaulders'), +(7, 2, 4, 0, 264, 51249, 'Phase 4', 'Shaman', 'Restoration', 'Chest', 'Both', 'Sanctified Frost Witch''s Tunic'), +(7, 2, 5, 0, 264, 50613, 'Phase 4', 'Shaman', 'Restoration', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(7, 2, 6, 0, 264, 51246, 'Phase 4', 'Shaman', 'Restoration', 'Legs', 'Both', 'Sanctified Frost Witch''s Legguards'), +(7, 2, 7, 0, 264, 50699, 'Phase 4', 'Shaman', 'Restoration', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(7, 2, 8, 0, 264, 50687, 'Phase 4', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Bloodsunder''s Bracers'), +(7, 2, 9, 0, 264, 50703, 'Phase 4', 'Shaman', 'Restoration', 'Hands', 'Both', 'Unclean Surgical Gloves'), +(7, 2, 10, 0, 264, 50400, 'Phase 4', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Ashen Band of Endless Wisdom'), +(7, 2, 12, 1, 264, 47059, 'Phase 4', 'Shaman', 'Restoration', 'Trinket1', 'Alliance', 'Solace of the Defeated'), +(7, 2, 12, 2, 264, 47432, 'Phase 4', 'Shaman', 'Restoration', 'Trinket1', 'Horde', 'Solace of the Fallen'), +(7, 2, 14, 0, 264, 50628, 'Phase 4', 'Shaman', 'Restoration', 'Back', 'Both', 'Frostbinder''s Shredded Cape'), +(7, 2, 15, 0, 264, 46017, 'Phase 4', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(7, 2, 17, 0, 264, 50464, 'Phase 4', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Totem of the Surging Sea'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(7, 2, 0, 0, 290, 51247, 'Phase 5', 'Shaman', 'Restoration', 'Head', 'Both', 'Sanctified Frost Witch''s Headpiece'), +(7, 2, 1, 0, 290, 50182, 'Phase 5', 'Shaman', 'Restoration', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(7, 2, 2, 0, 290, 51245, 'Phase 5', 'Shaman', 'Restoration', 'Shoulders', 'Both', 'Sanctified Frost Witch''s Spaulders'), +(7, 2, 4, 0, 290, 51249, 'Phase 5', 'Shaman', 'Restoration', 'Chest', 'Both', 'Sanctified Frost Witch''s Tunic'), +(7, 2, 5, 0, 290, 54587, 'Phase 5', 'Shaman', 'Restoration', 'Waist', 'Both', 'Split Shape Belt'), +(7, 2, 6, 0, 290, 51246, 'Phase 5', 'Shaman', 'Restoration', 'Legs', 'Both', 'Sanctified Frost Witch''s Legguards'), +(7, 2, 7, 0, 290, 50699, 'Phase 5', 'Shaman', 'Restoration', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(7, 2, 8, 0, 290, 50687, 'Phase 5', 'Shaman', 'Restoration', 'Wrists', 'Both', 'Bloodsunder''s Bracers'), +(7, 2, 9, 0, 290, 50703, 'Phase 5', 'Shaman', 'Restoration', 'Hands', 'Both', 'Unclean Surgical Gloves'), +(7, 2, 10, 0, 290, 50664, 'Phase 5', 'Shaman', 'Restoration', 'Finger1', 'Both', 'Ring of Rapid Ascent'), +(7, 2, 11, 0, 290, 50400, 'Phase 5', 'Shaman', 'Restoration', 'Finger2', 'Both', 'Ashen Band of Endless Wisdom'), +(7, 2, 12, 0, 290, 50366, 'Phase 5', 'Shaman', 'Restoration', 'Trinket1', 'Both', 'Althor''s Abacus'), +(7, 2, 13, 0, 290, 54589, 'Phase 5', 'Shaman', 'Restoration', 'Trinket2', 'Both', 'Glowing Twilight Scale'), +(7, 2, 14, 0, 290, 54583, 'Phase 5', 'Shaman', 'Restoration', 'Back', 'Both', 'Cloak of Burning Dusk'), +(7, 2, 15, 0, 290, 46017, 'Phase 5', 'Shaman', 'Restoration', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(7, 2, 16, 0, 290, 50616, 'Phase 5', 'Shaman', 'Restoration', 'OffHand', 'Both', 'Bulwark of Smouldering Steel'), +(7, 2, 17, 0, 290, 50458, 'Phase 5', 'Shaman', 'Restoration', 'Ranged', 'Both', 'Bizuri''s Totem of Shattered Ice'); + + +-- ============================================================ +-- Mage (8) +-- ============================================================ +-- Arcane (tab 0) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 66, 10504, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Head', 'Both', 'Green Lens'), +(8, 0, 1, 0, 66, 12103, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Neck', 'Both', 'Star of Mystaria'), +(8, 0, 2, 0, 66, 11782, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Boreal Mantle'), +(8, 0, 4, 0, 66, 14152, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 0, 5, 0, 66, 11662, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Waist', 'Both', 'Ban''thok Sash'), +(8, 0, 6, 0, 66, 13170, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Legs', 'Both', 'Skyshroud Leggings'), +(8, 0, 7, 0, 66, 11822, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Feet', 'Both', 'Omnicast Boots'), +(8, 0, 8, 0, 66, 11766, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Wrists', 'Both', 'Flameweave Cuffs'), +(8, 0, 9, 0, 66, 13253, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Hands', 'Both', 'Hands of Power'), +(8, 0, 10, 0, 66, 942, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Finger1', 'Both', 'Freezing Band'), +(8, 0, 11, 0, 66, 942, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Finger2', 'Both', 'Freezing Band'), +(8, 0, 12, 0, 66, 12930, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Briarwood Reed'), +(8, 0, 13, 0, 66, 13968, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Trinket2', 'Both', 'Eye of the Beast'), +(8, 0, 14, 0, 66, 13386, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Back', 'Both', 'Archivist Cape'), +(8, 0, 15, 0, 66, 13964, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'MainHand', 'Both', 'Witchblade'), +(8, 0, 16, 0, 66, 10796, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'OffHand', 'Both', 'Drakestone'), +(8, 0, 17, 0, 66, 13938, 'Phase 1 (Pre-Raid)', 'Mage', 'Arcane', 'Ranged', 'Both', 'Bonecreeper Stylus'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 76, 23318, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Head', 'Both', 'Lieutenant Commander''s Silk Cowl'), +(8, 0, 1, 0, 76, 12103, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Neck', 'Both', 'Star of Mystaria'), +(8, 0, 2, 0, 76, 23319, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Lieutenant Commander''s Silk Mantle'), +(8, 0, 4, 0, 76, 14152, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 0, 5, 0, 76, 11662, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Waist', 'Both', 'Ban''thok Sash'), +(8, 0, 6, 0, 76, 23304, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Legs', 'Both', 'Knight-Captain''s Silk Legguards'), +(8, 0, 7, 0, 76, 23291, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Feet', 'Both', 'Knight-Lieutenant''s Silk Walkers'), +(8, 0, 8, 0, 76, 11766, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Wrists', 'Both', 'Flameweave Cuffs'), +(8, 0, 9, 0, 76, 13253, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Hands', 'Both', 'Hands of Power'), +(8, 0, 10, 0, 76, 942, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Finger1', 'Both', 'Freezing Band'), +(8, 0, 11, 0, 76, 942, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Finger2', 'Both', 'Freezing Band'), +(8, 0, 12, 0, 76, 12930, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Briarwood Reed'), +(8, 0, 13, 0, 76, 13968, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Trinket2', 'Both', 'Eye of the Beast'), +(8, 0, 14, 0, 76, 13386, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Back', 'Both', 'Archivist Cape'), +(8, 0, 15, 0, 76, 13964, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'MainHand', 'Both', 'Witchblade'), +(8, 0, 16, 0, 76, 10796, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'OffHand', 'Both', 'Drakestone'), +(8, 0, 17, 0, 76, 13938, 'Phase 2 (Pre-Raid)', 'Mage', 'Arcane', 'Ranged', 'Both', 'Bonecreeper Stylus'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 78, 16914, 'Phase 2', 'Mage', 'Arcane', 'Head', 'Both', 'Netherwind Crown'), +(8, 0, 1, 0, 78, 18814, 'Phase 2', 'Mage', 'Arcane', 'Neck', 'Both', 'Choker of the Fire Lord'), +(8, 0, 2, 0, 78, 23319, 'Phase 2', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Lieutenant Commander''s Silk Mantle'), +(8, 0, 4, 0, 78, 14152, 'Phase 2', 'Mage', 'Arcane', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 0, 5, 0, 78, 19136, 'Phase 2', 'Mage', 'Arcane', 'Waist', 'Both', 'Mana Igniting Cord'), +(8, 0, 6, 0, 78, 16915, 'Phase 2', 'Mage', 'Arcane', 'Legs', 'Both', 'Netherwind Pants'), +(8, 0, 7, 0, 78, 23291, 'Phase 2', 'Mage', 'Arcane', 'Feet', 'Both', 'Knight-Lieutenant''s Silk Walkers'), +(8, 0, 8, 0, 78, 11766, 'Phase 2', 'Mage', 'Arcane', 'Wrists', 'Both', 'Flameweave Cuffs'), +(8, 0, 9, 0, 78, 13253, 'Phase 2', 'Mage', 'Arcane', 'Hands', 'Both', 'Hands of Power'), +(8, 0, 10, 0, 78, 19147, 'Phase 2', 'Mage', 'Arcane', 'Finger1', 'Both', 'Ring of Spell Power'), +(8, 0, 11, 0, 78, 19147, 'Phase 2', 'Mage', 'Arcane', 'Finger2', 'Both', 'Ring of Spell Power'), +(8, 0, 12, 0, 78, 12930, 'Phase 2', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Briarwood Reed'), +(8, 0, 13, 0, 78, 18820, 'Phase 2', 'Mage', 'Arcane', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(8, 0, 14, 0, 78, 13386, 'Phase 2', 'Mage', 'Arcane', 'Back', 'Both', 'Archivist Cape'), +(8, 0, 15, 0, 78, 17103, 'Phase 2', 'Mage', 'Arcane', 'MainHand', 'Both', 'Azuresong Mageblade'), +(8, 0, 16, 0, 78, 10796, 'Phase 2', 'Mage', 'Arcane', 'OffHand', 'Both', 'Drakestone'), +(8, 0, 17, 0, 78, 19130, 'Phase 2', 'Mage', 'Arcane', 'Ranged', 'Both', 'Cold Snap'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 83, 19375, 'Phase 3', 'Mage', 'Arcane', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(8, 0, 1, 0, 83, 18814, 'Phase 3', 'Mage', 'Arcane', 'Neck', 'Both', 'Choker of the Fire Lord'), +(8, 0, 2, 0, 83, 19370, 'Phase 3', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(8, 0, 4, 0, 83, 14152, 'Phase 3', 'Mage', 'Arcane', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 0, 5, 0, 83, 19136, 'Phase 3', 'Mage', 'Arcane', 'Waist', 'Both', 'Mana Igniting Cord'), +(8, 0, 6, 0, 83, 16915, 'Phase 3', 'Mage', 'Arcane', 'Legs', 'Both', 'Netherwind Pants'), +(8, 0, 7, 0, 83, 19438, 'Phase 3', 'Mage', 'Arcane', 'Feet', 'Both', 'Ringo''s Blizzard Boots'), +(8, 0, 8, 0, 83, 19374, 'Phase 3', 'Mage', 'Arcane', 'Wrists', 'Both', 'Bracers of Arcane Accuracy'), +(8, 0, 9, 0, 83, 16913, 'Phase 3', 'Mage', 'Arcane', 'Hands', 'Both', 'Netherwind Gloves'), +(8, 0, 11, 0, 83, 19147, 'Phase 3', 'Mage', 'Arcane', 'Finger2', 'Both', 'Ring of Spell Power'), +(8, 0, 12, 0, 83, 19379, 'Phase 3', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(8, 0, 13, 0, 83, 19339, 'Phase 3', 'Mage', 'Arcane', 'Trinket2', 'Both', 'Mind Quickening Gem'), +(8, 0, 14, 0, 83, 19378, 'Phase 3', 'Mage', 'Arcane', 'Back', 'Both', 'Cloak of the Brood Lord'), +(8, 0, 15, 0, 83, 19356, 'Phase 3', 'Mage', 'Arcane', 'MainHand', 'Both', 'Staff of the Shadow Flame'), +(8, 0, 17, 0, 83, 19130, 'Phase 3', 'Mage', 'Arcane', 'Ranged', 'Both', 'Cold Snap'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 88, 19375, 'Phase 5', 'Mage', 'Arcane', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(8, 0, 1, 0, 88, 21608, 'Phase 5', 'Mage', 'Arcane', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(8, 0, 2, 0, 88, 19370, 'Phase 5', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(8, 0, 4, 0, 88, 19145, 'Phase 5', 'Mage', 'Arcane', 'Chest', 'Both', 'Robe of Volatile Power'), +(8, 0, 5, 0, 88, 22730, 'Phase 5', 'Mage', 'Arcane', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(8, 0, 6, 0, 88, 21461, 'Phase 5', 'Mage', 'Arcane', 'Legs', 'Both', 'Leggings of the Black Blizzard'), +(8, 0, 7, 0, 88, 21344, 'Phase 5', 'Mage', 'Arcane', 'Feet', 'Both', 'Enigma Boots'), +(8, 0, 8, 0, 88, 21186, 'Phase 5', 'Mage', 'Arcane', 'Wrists', 'Both', 'Rockfury Bracers'), +(8, 0, 9, 0, 88, 21585, 'Phase 5', 'Mage', 'Arcane', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(8, 0, 10, 0, 88, 21836, 'Phase 5', 'Mage', 'Arcane', 'Finger1', 'Both', 'Ritssyn''s Ring of Chaos'), +(8, 0, 11, 0, 88, 21709, 'Phase 5', 'Mage', 'Arcane', 'Finger2', 'Both', 'Ring of the Fallen God'), +(8, 0, 12, 0, 88, 19379, 'Phase 5', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(8, 0, 13, 0, 88, 19339, 'Phase 5', 'Mage', 'Arcane', 'Trinket2', 'Both', 'Mind Quickening Gem'), +(8, 0, 14, 0, 88, 22731, 'Phase 5', 'Mage', 'Arcane', 'Back', 'Both', 'Cloak of the Devoured'), +(8, 0, 15, 0, 88, 21622, 'Phase 5', 'Mage', 'Arcane', 'MainHand', 'Both', 'Sharpened Silithid Femur'), +(8, 0, 16, 0, 88, 21597, 'Phase 5', 'Mage', 'Arcane', 'OffHand', 'Both', 'Royal Scepter of Vek''lor'), +(8, 0, 17, 0, 88, 21603, 'Phase 5', 'Mage', 'Arcane', 'Ranged', 'Both', 'Wand of Qiraji Nobility'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 92, 22498, 'Phase 6', 'Mage', 'Arcane', 'Head', 'Both', 'Frostfire Circlet'), +(8, 0, 1, 0, 92, 23057, 'Phase 6', 'Mage', 'Arcane', 'Neck', 'Both', 'Gem of Trapped Innocents'), +(8, 0, 2, 0, 92, 22983, 'Phase 6', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Rime Covered Mantle'), +(8, 0, 4, 0, 92, 22496, 'Phase 6', 'Mage', 'Arcane', 'Chest', 'Both', 'Frostfire Robe'), +(8, 0, 5, 0, 92, 22730, 'Phase 6', 'Mage', 'Arcane', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(8, 0, 6, 0, 92, 23070, 'Phase 6', 'Mage', 'Arcane', 'Legs', 'Both', 'Leggings of Polarity'), +(8, 0, 7, 0, 92, 22500, 'Phase 6', 'Mage', 'Arcane', 'Feet', 'Both', 'Frostfire Sandals'), +(8, 0, 8, 0, 92, 23021, 'Phase 6', 'Mage', 'Arcane', 'Wrists', 'Both', 'The Soul Harvester''s Bindings'), +(8, 0, 9, 0, 92, 21585, 'Phase 6', 'Mage', 'Arcane', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(8, 0, 10, 0, 92, 23237, 'Phase 6', 'Mage', 'Arcane', 'Finger1', 'Both', 'Ring of the Eternal Flame'), +(8, 0, 11, 0, 92, 23062, 'Phase 6', 'Mage', 'Arcane', 'Finger2', 'Both', 'Frostfire Ring'), +(8, 0, 12, 0, 92, 19379, 'Phase 6', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(8, 0, 13, 0, 92, 23046, 'Phase 6', 'Mage', 'Arcane', 'Trinket2', 'Both', 'The Restrained Essence of Sapphiron'), +(8, 0, 14, 0, 92, 23050, 'Phase 6', 'Mage', 'Arcane', 'Back', 'Both', 'Cloak of the Necropolis'), +(8, 0, 15, 0, 92, 22807, 'Phase 6', 'Mage', 'Arcane', 'MainHand', 'Both', 'Wraith Blade'), +(8, 0, 16, 0, 92, 23049, 'Phase 6', 'Mage', 'Arcane', 'OffHand', 'Both', 'Sapphiron''s Left Eye'), +(8, 0, 17, 0, 92, 22821, 'Phase 6', 'Mage', 'Arcane', 'Ranged', 'Both', 'Doomfinger'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 120, 28278, 'Pre-Raid', 'Mage', 'Arcane', 'Head', 'Both', 'Incanter''s Cowl'), +(8, 0, 1, 0, 120, 28134, 'Pre-Raid', 'Mage', 'Arcane', 'Neck', 'Both', 'Brooch of Heightened Potential'), +(8, 0, 2, 0, 120, 27796, 'Pre-Raid', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Mana-Etched Spaulders'), +(8, 0, 4, 0, 120, 21848, 'Pre-Raid', 'Mage', 'Arcane', 'Chest', 'Both', 'Spellfire Robe'), +(8, 0, 5, 0, 120, 21846, 'Pre-Raid', 'Mage', 'Arcane', 'Waist', 'Both', 'Spellfire Belt'), +(8, 0, 6, 0, 120, 30532, 'Pre-Raid', 'Mage', 'Arcane', 'Legs', 'Both', 'Kirin Tor Master''s Trousers'), +(8, 0, 7, 0, 120, 28406, 'Pre-Raid', 'Mage', 'Arcane', 'Feet', 'Both', 'Sigil-Laced Boots'), +(8, 0, 8, 0, 120, 29240, 'Pre-Raid', 'Mage', 'Arcane', 'Wrists', 'Both', 'Bands of Negation'), +(8, 0, 9, 0, 120, 21847, 'Pre-Raid', 'Mage', 'Arcane', 'Hands', 'Both', 'Spellfire Gloves'), +(8, 0, 10, 0, 120, 29172, 'Pre-Raid', 'Mage', 'Arcane', 'Finger1', 'Both', 'Ashyen''s Gift'), +(8, 0, 11, 0, 120, 28227, 'Pre-Raid', 'Mage', 'Arcane', 'Finger2', 'Both', 'Sparking Arcanite Ring'), +(8, 0, 12, 0, 120, 29370, 'Pre-Raid', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(8, 0, 13, 0, 120, 27683, 'Pre-Raid', 'Mage', 'Arcane', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(8, 0, 14, 0, 120, 27981, 'Pre-Raid', 'Mage', 'Arcane', 'Back', 'Both', 'Sethekk Oracle Cloak'), +(8, 0, 15, 0, 120, 23554, 'Pre-Raid', 'Mage', 'Arcane', 'MainHand', 'Both', 'Eternium Runed Blade'), +(8, 0, 16, 0, 120, 29271, 'Pre-Raid', 'Mage', 'Arcane', 'OffHand', 'Both', 'Talisman of Kalecgos'), +(8, 0, 17, 0, 120, 29350, 'Pre-Raid', 'Mage', 'Arcane', 'Ranged', 'Both', 'The Black Stalk'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 125, 29076, 'Phase 1', 'Mage', 'Arcane', 'Head', 'Both', 'Collar of the Aldor'), +(8, 0, 1, 0, 125, 28762, 'Phase 1', 'Mage', 'Arcane', 'Neck', 'Both', 'Adornment of Stolen Souls'), +(8, 0, 2, 0, 125, 29079, 'Phase 1', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Pauldrons of the Aldor'), +(8, 0, 4, 0, 125, 21848, 'Phase 1', 'Mage', 'Arcane', 'Chest', 'Both', 'Spellfire Robe'), +(8, 0, 5, 0, 125, 21846, 'Phase 1', 'Mage', 'Arcane', 'Waist', 'Both', 'Spellfire Belt'), +(8, 0, 6, 0, 125, 29078, 'Phase 1', 'Mage', 'Arcane', 'Legs', 'Both', 'Legwraps of the Aldor'), +(8, 0, 7, 0, 125, 28517, 'Phase 1', 'Mage', 'Arcane', 'Feet', 'Both', 'Boots of Foretelling'), +(8, 0, 8, 0, 125, 28515, 'Phase 1', 'Mage', 'Arcane', 'Wrists', 'Both', 'Bands of Nefarious Deeds'), +(8, 0, 9, 0, 125, 21847, 'Phase 1', 'Mage', 'Arcane', 'Hands', 'Both', 'Spellfire Gloves'), +(8, 0, 10, 0, 125, 28793, 'Phase 1', 'Mage', 'Arcane', 'Finger1', 'Both', 'Band of Crimson Fury'), +(8, 0, 11, 0, 125, 29287, 'Phase 1', 'Mage', 'Arcane', 'Finger2', 'Both', 'Violet Signet of the Archmage'), +(8, 0, 12, 0, 125, 29370, 'Phase 1', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(8, 0, 13, 0, 125, 28785, 'Phase 1', 'Mage', 'Arcane', 'Trinket2', 'Both', 'The Lightning Capacitor'), +(8, 0, 14, 0, 125, 28766, 'Phase 1', 'Mage', 'Arcane', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(8, 0, 15, 0, 125, 28770, 'Phase 1', 'Mage', 'Arcane', 'MainHand', 'Both', 'Nathrezim Mindblade'), +(8, 0, 16, 0, 125, 29271, 'Phase 1', 'Mage', 'Arcane', 'OffHand', 'Both', 'Talisman of Kalecgos'), +(8, 0, 17, 0, 125, 28783, 'Phase 1', 'Mage', 'Arcane', 'Ranged', 'Both', 'Eredar Wand of Obliteration'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 141, 30206, 'Phase 2', 'Mage', 'Arcane', 'Head', 'Both', 'Cowl of Tirisfal'), +(8, 0, 1, 0, 141, 30015, 'Phase 2', 'Mage', 'Arcane', 'Neck', 'Both', 'The Sun King''s Talisman'), +(8, 0, 2, 0, 141, 30210, 'Phase 2', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Mantle of Tirisfal'), +(8, 0, 4, 0, 141, 30196, 'Phase 2', 'Mage', 'Arcane', 'Chest', 'Both', 'Robes of Tirisfal'), +(8, 0, 5, 0, 141, 30038, 'Phase 2', 'Mage', 'Arcane', 'Waist', 'Both', 'Belt of Blasting'), +(8, 0, 6, 0, 141, 30207, 'Phase 2', 'Mage', 'Arcane', 'Legs', 'Both', 'Leggings of Tirisfal'), +(8, 0, 7, 0, 141, 30067, 'Phase 2', 'Mage', 'Arcane', 'Feet', 'Both', 'Velvet Boots of the Guardian'), +(8, 0, 8, 0, 141, 29918, 'Phase 2', 'Mage', 'Arcane', 'Wrists', 'Both', 'Mindstorm Wristbands'), +(8, 0, 9, 0, 141, 29987, 'Phase 2', 'Mage', 'Arcane', 'Hands', 'Both', 'Gauntlets of the Sun King'), +(8, 0, 10, 0, 141, 29302, 'Phase 2', 'Mage', 'Arcane', 'Finger1', 'Both', 'Band of Eternity'), +(8, 0, 11, 0, 141, 29287, 'Phase 2', 'Mage', 'Arcane', 'Finger2', 'Both', 'Violet Signet of the Archmage'), +(8, 0, 12, 0, 141, 29370, 'Phase 2', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(8, 0, 13, 0, 141, 30720, 'Phase 2', 'Mage', 'Arcane', 'Trinket2', 'Both', 'Serpent-Coil Braid'), +(8, 0, 14, 0, 141, 29992, 'Phase 2', 'Mage', 'Arcane', 'Back', 'Both', 'Royal Cloak of the Sunstriders'), +(8, 0, 15, 0, 141, 30095, 'Phase 2', 'Mage', 'Arcane', 'MainHand', 'Both', 'Fang of the Leviathan'), +(8, 0, 16, 0, 141, 30049, 'Phase 2', 'Mage', 'Arcane', 'OffHand', 'Both', 'Fathomstone'), +(8, 0, 17, 0, 141, 28783, 'Phase 2', 'Mage', 'Arcane', 'Ranged', 'Both', 'Eredar Wand of Obliteration'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 156, 30206, 'Phase 3', 'Mage', 'Arcane', 'Head', 'Both', 'Cowl of Tirisfal'), +(8, 0, 1, 0, 156, 30015, 'Phase 3', 'Mage', 'Arcane', 'Neck', 'Both', 'The Sun King''s Talisman'), +(8, 0, 2, 0, 156, 30210, 'Phase 3', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Mantle of Tirisfal'), +(8, 0, 4, 0, 156, 30196, 'Phase 3', 'Mage', 'Arcane', 'Chest', 'Both', 'Robes of Tirisfal'), +(8, 0, 5, 0, 156, 30888, 'Phase 3', 'Mage', 'Arcane', 'Waist', 'Both', 'Anetheron''s Noose'), +(8, 0, 6, 0, 156, 31058, 'Phase 3', 'Mage', 'Arcane', 'Legs', 'Both', 'Leggings of the Tempest'), +(8, 0, 7, 0, 156, 32239, 'Phase 3', 'Mage', 'Arcane', 'Feet', 'Both', 'Slippers of the Seacaller'), +(8, 0, 8, 0, 156, 30870, 'Phase 3', 'Mage', 'Arcane', 'Wrists', 'Both', 'Cuffs of Devastation'), +(8, 0, 9, 0, 156, 30205, 'Phase 3', 'Mage', 'Arcane', 'Hands', 'Both', 'Gloves of Tirisfal'), +(8, 0, 10, 0, 156, 29305, 'Phase 3', 'Mage', 'Arcane', 'Finger1', 'Both', 'Band of the Eternal Sage'), +(8, 0, 11, 0, 156, 32527, 'Phase 3', 'Mage', 'Arcane', 'Finger2', 'Both', 'Ring of Ancient Knowledge'), +(8, 0, 12, 0, 156, 32488, 'Phase 3', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Ashtongue Talisman of Insight'), +(8, 0, 13, 0, 156, 32483, 'Phase 3', 'Mage', 'Arcane', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(8, 0, 14, 0, 156, 32331, 'Phase 3', 'Mage', 'Arcane', 'Back', 'Both', 'Cloak of the Illidari Council'), +(8, 0, 15, 0, 156, 30910, 'Phase 3', 'Mage', 'Arcane', 'MainHand', 'Both', 'Tempest of Chaos'), +(8, 0, 16, 0, 156, 30872, 'Phase 3', 'Mage', 'Arcane', 'OffHand', 'Both', 'Chronicle of Dark Secrets'), +(8, 0, 17, 0, 156, 28783, 'Phase 3', 'Mage', 'Arcane', 'Ranged', 'Both', 'Eredar Wand of Obliteration'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 164, 30206, 'Phase 4', 'Mage', 'Arcane', 'Head', 'Both', 'Cowl of Tirisfal'), +(8, 0, 1, 0, 164, 30015, 'Phase 4', 'Mage', 'Arcane', 'Neck', 'Both', 'The Sun King''s Talisman'), +(8, 0, 2, 0, 164, 30210, 'Phase 4', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Mantle of Tirisfal'), +(8, 0, 4, 0, 164, 30196, 'Phase 4', 'Mage', 'Arcane', 'Chest', 'Both', 'Robes of Tirisfal'), +(8, 0, 5, 0, 164, 30888, 'Phase 4', 'Mage', 'Arcane', 'Waist', 'Both', 'Anetheron''s Noose'), +(8, 0, 6, 0, 164, 31058, 'Phase 4', 'Mage', 'Arcane', 'Legs', 'Both', 'Leggings of the Tempest'), +(8, 0, 7, 0, 164, 32239, 'Phase 4', 'Mage', 'Arcane', 'Feet', 'Both', 'Slippers of the Seacaller'), +(8, 0, 8, 0, 164, 30870, 'Phase 4', 'Mage', 'Arcane', 'Wrists', 'Both', 'Cuffs of Devastation'), +(8, 0, 9, 0, 164, 30205, 'Phase 4', 'Mage', 'Arcane', 'Hands', 'Both', 'Gloves of Tirisfal'), +(8, 0, 10, 0, 164, 29305, 'Phase 4', 'Mage', 'Arcane', 'Finger1', 'Both', 'Band of the Eternal Sage'), +(8, 0, 11, 0, 164, 32527, 'Phase 4', 'Mage', 'Arcane', 'Finger2', 'Both', 'Ring of Ancient Knowledge'), +(8, 0, 12, 0, 164, 32488, 'Phase 4', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Ashtongue Talisman of Insight'), +(8, 0, 13, 0, 164, 32483, 'Phase 4', 'Mage', 'Arcane', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(8, 0, 14, 0, 164, 32331, 'Phase 4', 'Mage', 'Arcane', 'Back', 'Both', 'Cloak of the Illidari Council'), +(8, 0, 15, 0, 164, 30910, 'Phase 4', 'Mage', 'Arcane', 'MainHand', 'Both', 'Tempest of Chaos'), +(8, 0, 16, 0, 164, 30872, 'Phase 4', 'Mage', 'Arcane', 'OffHand', 'Both', 'Chronicle of Dark Secrets'), +(8, 0, 17, 0, 164, 33192, 'Phase 4', 'Mage', 'Arcane', 'Ranged', 'Both', 'Carved Witch Doctor''s Stick'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 200, 37294, 'Pre-Raid', 'Mage', 'Arcane', 'Head', 'Both', 'Crown of Unbridled Magic'), +(8, 0, 1, 0, 200, 39472, 'Pre-Raid', 'Mage', 'Arcane', 'Neck', 'Both', 'Chain of Latent Energies'), +(8, 0, 2, 0, 200, 37673, 'Pre-Raid', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Dark Runic Mantle'), +(8, 0, 4, 0, 200, 39492, 'Pre-Raid', 'Mage', 'Arcane', 'Chest', 'Both', 'Heroes'' Frostfire Robe'), +(8, 0, 5, 0, 200, 40696, 'Pre-Raid', 'Mage', 'Arcane', 'Waist', 'Both', 'Plush Sash of Guzbah'), +(8, 0, 6, 0, 200, 37854, 'Pre-Raid', 'Mage', 'Arcane', 'Legs', 'Both', 'Woven Bracae Leggings'), +(8, 0, 7, 0, 200, 44202, 'Pre-Raid', 'Mage', 'Arcane', 'Feet', 'Both', 'Sandals of Crimson Fury'), +(8, 0, 8, 0, 200, 37361, 'Pre-Raid', 'Mage', 'Arcane', 'Wrists', 'Both', 'Cuffs of Winged Levitation'), +(8, 0, 9, 0, 200, 39495, 'Pre-Raid', 'Mage', 'Arcane', 'Hands', 'Both', 'Heroes'' Frostfire Gloves'), +(8, 0, 10, 0, 200, 37694, 'Pre-Raid', 'Mage', 'Arcane', 'Finger1', 'Both', 'Band of Guile'), +(8, 0, 12, 0, 200, 37660, 'Pre-Raid', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Forge Ember'), +(8, 0, 14, 0, 200, 41610, 'Pre-Raid', 'Mage', 'Arcane', 'Back', 'Both', 'Deathchill Cloak'), +(8, 0, 15, 0, 200, 37360, 'Pre-Raid', 'Mage', 'Arcane', 'MainHand', 'Both', 'Staff of Draconic Combat'), +(8, 0, 17, 0, 200, 37177, 'Pre-Raid', 'Mage', 'Arcane', 'Ranged', 'Both', 'Wand of the San''layn'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 1, 0, 224, 44661, 'Phase 1', 'Mage', 'Arcane', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(8, 0, 2, 0, 224, 40419, 'Phase 1', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Valorous Frostfire Shoulderpads'), +(8, 0, 4, 0, 224, 44002, 'Phase 1', 'Mage', 'Arcane', 'Chest', 'Both', 'The Sanctum''s Flowing Vestments'), +(8, 0, 5, 0, 224, 40561, 'Phase 1', 'Mage', 'Arcane', 'Waist', 'Both', 'Leash of Heedless Magic'), +(8, 0, 6, 0, 224, 40417, 'Phase 1', 'Mage', 'Arcane', 'Legs', 'Both', 'Valorous Frostfire Leggings'), +(8, 0, 7, 0, 224, 40558, 'Phase 1', 'Mage', 'Arcane', 'Feet', 'Both', 'Arcanic Tramplers'), +(8, 0, 8, 0, 224, 44008, 'Phase 1', 'Mage', 'Arcane', 'Wrists', 'Both', 'Unsullied Cuffs'), +(8, 0, 9, 0, 224, 40415, 'Phase 1', 'Mage', 'Arcane', 'Hands', 'Both', 'Valorous Frostfire Gloves'), +(8, 0, 10, 0, 224, 40719, 'Phase 1', 'Mage', 'Arcane', 'Finger1', 'Both', 'Band of Channeled Magic'), +(8, 0, 12, 0, 224, 40255, 'Phase 1', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Dying Curse'), +(8, 0, 14, 0, 224, 44005, 'Phase 1', 'Mage', 'Arcane', 'Back', 'Both', 'Pennant Cloak'), +(8, 0, 15, 0, 224, 40396, 'Phase 1', 'Mage', 'Arcane', 'MainHand', 'Both', 'The Turning Tide'), +(8, 0, 17, 0, 224, 39426, 'Phase 1', 'Mage', 'Arcane', 'Ranged', 'Both', 'Wand of the Archlich'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 245, 46129, 'Phase 2', 'Mage', 'Arcane', 'Head', 'Both', 'Conqueror''s Kirin Tor Hood'), +(8, 0, 1, 0, 245, 45243, 'Phase 2', 'Mage', 'Arcane', 'Neck', 'Both', 'Sapphire Amulet of Renewal'), +(8, 0, 2, 0, 245, 46134, 'Phase 2', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Conqueror''s Kirin Tor Shoulderpads'), +(8, 0, 4, 0, 245, 46130, 'Phase 2', 'Mage', 'Arcane', 'Chest', 'Both', 'Conqueror''s Kirin Tor Tunic'), +(8, 0, 5, 0, 245, 45557, 'Phase 2', 'Mage', 'Arcane', 'Waist', 'Both', 'Sash of Ancient Power'), +(8, 0, 6, 0, 245, 46133, 'Phase 2', 'Mage', 'Arcane', 'Legs', 'Both', 'Conqueror''s Kirin Tor Leggings'), +(8, 0, 7, 0, 245, 45135, 'Phase 2', 'Mage', 'Arcane', 'Feet', 'Both', 'Boots of Fiery Resolution'), +(8, 0, 8, 0, 245, 45446, 'Phase 2', 'Mage', 'Arcane', 'Wrists', 'Both', 'Grasps of Reason'), +(8, 0, 9, 0, 245, 45665, 'Phase 2', 'Mage', 'Arcane', 'Hands', 'Both', 'Pharos Gloves'), +(8, 0, 10, 0, 245, 46046, 'Phase 2', 'Mage', 'Arcane', 'Finger1', 'Both', 'Nebula Band'), +(8, 0, 12, 0, 245, 45518, 'Phase 2', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Flare of the Heavens'), +(8, 0, 14, 0, 245, 45618, 'Phase 2', 'Mage', 'Arcane', 'Back', 'Both', 'Sunglimmer Cloak'), +(8, 0, 15, 0, 245, 45457, 'Phase 2', 'Mage', 'Arcane', 'MainHand', 'Both', 'Staff of Endless Winter'), +(8, 0, 17, 0, 245, 45511, 'Phase 2', 'Mage', 'Arcane', 'Ranged', 'Both', 'Scepter of Lost Souls'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 258, 47761, 'Phase 3', 'Mage', 'Arcane', 'Head', 'Both', 'Hood of Triumph'), +(8, 0, 2, 0, 258, 47758, 'Phase 3', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Shoulderpads of Triumph'), +(8, 0, 4, 1, 258, 46993, 'Phase 3', 'Mage', 'Arcane', 'Chest', 'Alliance', 'Flowing Vestments of Ascent'), +(8, 0, 4, 2, 258, 47425, 'Phase 3', 'Mage', 'Arcane', 'Chest', 'Horde', 'Flowing Robes of Ascent'), +(8, 0, 5, 1, 258, 47419, 'Phase 3', 'Mage', 'Arcane', 'Waist', 'Alliance', 'Belt of the Tenebrous Mist'), +(8, 0, 5, 2, 258, 46973, 'Phase 3', 'Mage', 'Arcane', 'Waist', 'Horde', 'Cord of the Tenebrous Mist'), +(8, 0, 6, 0, 258, 47760, 'Phase 3', 'Mage', 'Arcane', 'Legs', 'Both', 'Leggings of Triumph'), +(8, 0, 7, 1, 258, 47097, 'Phase 3', 'Mage', 'Arcane', 'Feet', 'Alliance', 'Boots of the Mourning Widow'), +(8, 0, 7, 2, 258, 47454, 'Phase 3', 'Mage', 'Arcane', 'Feet', 'Horde', 'Sandals of the Mourning Widow'), +(8, 0, 8, 1, 258, 47208, 'Phase 3', 'Mage', 'Arcane', 'Wrists', 'Alliance', 'Armbands of the Ashen Saint'), +(8, 0, 8, 2, 258, 47485, 'Phase 3', 'Mage', 'Arcane', 'Wrists', 'Horde', 'Bindings of the Ashen Saint'), +(8, 0, 9, 0, 258, 47762, 'Phase 3', 'Mage', 'Arcane', 'Hands', 'Both', 'Gauntlets of Triumph'), +(8, 0, 10, 1, 258, 47489, 'Phase 3', 'Mage', 'Arcane', 'Finger1', 'Alliance', 'Lurid Manifestation'), +(8, 0, 10, 2, 258, 47237, 'Phase 3', 'Mage', 'Arcane', 'Finger1', 'Horde', 'Band of Deplorable Violence'), +(8, 0, 12, 1, 258, 47188, 'Phase 3', 'Mage', 'Arcane', 'Trinket1', 'Alliance', 'Reign of the Unliving'), +(8, 0, 12, 2, 258, 47477, 'Phase 3', 'Mage', 'Arcane', 'Trinket1', 'Horde', 'Reign of the Dead'), +(8, 0, 14, 1, 258, 47552, 'Phase 3', 'Mage', 'Arcane', 'Back', 'Alliance', 'Jaina''s Radiance'), +(8, 0, 14, 2, 258, 47551, 'Phase 3', 'Mage', 'Arcane', 'Back', 'Horde', 'Aethas'' Intensity'), +(8, 0, 17, 0, 258, 45294, 'Phase 3', 'Mage', 'Arcane', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 264, 51281, 'Phase 4', 'Mage', 'Arcane', 'Head', 'Both', 'Sanctified Bloodmage Hood'), +(8, 0, 1, 0, 264, 50724, 'Phase 4', 'Mage', 'Arcane', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(8, 0, 2, 0, 264, 51284, 'Phase 4', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Sanctified Bloodmage Shoulderpads'), +(8, 0, 4, 0, 264, 51283, 'Phase 4', 'Mage', 'Arcane', 'Chest', 'Both', 'Sanctified Bloodmage Robe'), +(8, 0, 5, 0, 264, 50613, 'Phase 4', 'Mage', 'Arcane', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(8, 0, 6, 0, 264, 51282, 'Phase 4', 'Mage', 'Arcane', 'Legs', 'Both', 'Sanctified Bloodmage Leggings'), +(8, 0, 7, 0, 264, 50699, 'Phase 4', 'Mage', 'Arcane', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(8, 0, 8, 0, 264, 50651, 'Phase 4', 'Mage', 'Arcane', 'Wrists', 'Both', 'The Lady''s Brittle Bracers'), +(8, 0, 9, 0, 264, 50722, 'Phase 4', 'Mage', 'Arcane', 'Hands', 'Both', 'San''layn Ritualist Gloves'), +(8, 0, 10, 0, 264, 50398, 'Phase 4', 'Mage', 'Arcane', 'Finger1', 'Both', 'Ashen Band of Endless Destruction'), +(8, 0, 12, 1, 264, 47188, 'Phase 4', 'Mage', 'Arcane', 'Trinket1', 'Alliance', 'Reign of the Unliving'), +(8, 0, 12, 2, 264, 47477, 'Phase 4', 'Mage', 'Arcane', 'Trinket1', 'Horde', 'Reign of the Dead'), +(8, 0, 14, 0, 264, 50628, 'Phase 4', 'Mage', 'Arcane', 'Back', 'Both', 'Frostbinder''s Shredded Cape'), +(8, 0, 15, 0, 264, 50732, 'Phase 4', 'Mage', 'Arcane', 'MainHand', 'Both', 'Bloodsurge, Kel''Thuzad''s Blade of Agony'), +(8, 0, 17, 0, 264, 50684, 'Phase 4', 'Mage', 'Arcane', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 0, 0, 0, 290, 51281, 'Phase 5', 'Mage', 'Arcane', 'Head', 'Both', 'Sanctified Bloodmage Hood'), +(8, 0, 1, 0, 290, 50182, 'Phase 5', 'Mage', 'Arcane', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(8, 0, 2, 0, 290, 51284, 'Phase 5', 'Mage', 'Arcane', 'Shoulders', 'Both', 'Sanctified Bloodmage Shoulderpads'), +(8, 0, 4, 0, 290, 51283, 'Phase 5', 'Mage', 'Arcane', 'Chest', 'Both', 'Sanctified Bloodmage Robe'), +(8, 0, 5, 0, 290, 50613, 'Phase 5', 'Mage', 'Arcane', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(8, 0, 6, 0, 290, 50694, 'Phase 5', 'Mage', 'Arcane', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(8, 0, 7, 0, 290, 50699, 'Phase 5', 'Mage', 'Arcane', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(8, 0, 8, 0, 290, 54582, 'Phase 5', 'Mage', 'Arcane', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(8, 0, 9, 0, 290, 51280, 'Phase 5', 'Mage', 'Arcane', 'Hands', 'Both', 'Sanctified Bloodmage Gloves'), +(8, 0, 10, 0, 290, 50614, 'Phase 5', 'Mage', 'Arcane', 'Finger1', 'Both', 'Loop of the Endless Labyrinth'), +(8, 0, 11, 0, 290, 50398, 'Phase 5', 'Mage', 'Arcane', 'Finger2', 'Both', 'Ashen Band of Endless Destruction'), +(8, 0, 12, 0, 290, 54588, 'Phase 5', 'Mage', 'Arcane', 'Trinket1', 'Both', 'Charred Twilight Scale'), +(8, 0, 13, 0, 290, 50348, 'Phase 5', 'Mage', 'Arcane', 'Trinket2', 'Both', 'Dislodged Foreign Object'), +(8, 0, 14, 0, 290, 54583, 'Phase 5', 'Mage', 'Arcane', 'Back', 'Both', 'Cloak of Burning Dusk'), +(8, 0, 15, 0, 290, 50732, 'Phase 5', 'Mage', 'Arcane', 'MainHand', 'Both', 'Bloodsurge, Kel''Thuzad''s Blade of Agony'), +(8, 0, 16, 0, 290, 50719, 'Phase 5', 'Mage', 'Arcane', 'OffHand', 'Both', 'Shadow Silk Spindle'), +(8, 0, 17, 0, 290, 50684, 'Phase 5', 'Mage', 'Arcane', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + +-- Fire (tab 1) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 66, 10504, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Head', 'Both', 'Green Lens'), +(8, 1, 1, 0, 66, 12103, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Neck', 'Both', 'Star of Mystaria'), +(8, 1, 2, 0, 66, 11782, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Shoulders', 'Both', 'Boreal Mantle'), +(8, 1, 4, 0, 66, 14152, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 1, 5, 0, 66, 11662, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Waist', 'Both', 'Ban''thok Sash'), +(8, 1, 6, 0, 66, 13170, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Legs', 'Both', 'Skyshroud Leggings'), +(8, 1, 7, 0, 66, 11822, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Feet', 'Both', 'Omnicast Boots'), +(8, 1, 8, 0, 66, 11766, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Wrists', 'Both', 'Flameweave Cuffs'), +(8, 1, 9, 0, 66, 13253, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Hands', 'Both', 'Hands of Power'), +(8, 1, 10, 0, 66, 942, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Finger1', 'Both', 'Freezing Band'), +(8, 1, 11, 0, 66, 942, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Finger2', 'Both', 'Freezing Band'), +(8, 1, 12, 0, 66, 12930, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Trinket1', 'Both', 'Briarwood Reed'), +(8, 1, 13, 0, 66, 13968, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Trinket2', 'Both', 'Eye of the Beast'), +(8, 1, 14, 0, 66, 13386, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Back', 'Both', 'Archivist Cape'), +(8, 1, 15, 0, 66, 13964, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'MainHand', 'Both', 'Witchblade'), +(8, 1, 16, 0, 66, 10796, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'OffHand', 'Both', 'Drakestone'), +(8, 1, 17, 0, 66, 13938, 'Phase 1 (Pre-Raid)', 'Mage', 'Fire', 'Ranged', 'Both', 'Bonecreeper Stylus'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 76, 23318, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Head', 'Both', 'Lieutenant Commander''s Silk Cowl'), +(8, 1, 1, 0, 76, 12103, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Neck', 'Both', 'Star of Mystaria'), +(8, 1, 2, 0, 76, 23319, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Shoulders', 'Both', 'Lieutenant Commander''s Silk Mantle'), +(8, 1, 4, 0, 76, 14152, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 1, 5, 0, 76, 11662, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Waist', 'Both', 'Ban''thok Sash'), +(8, 1, 6, 0, 76, 23304, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Legs', 'Both', 'Knight-Captain''s Silk Legguards'), +(8, 1, 7, 0, 76, 23291, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Feet', 'Both', 'Knight-Lieutenant''s Silk Walkers'), +(8, 1, 8, 0, 76, 11766, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Wrists', 'Both', 'Flameweave Cuffs'), +(8, 1, 9, 0, 76, 13253, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Hands', 'Both', 'Hands of Power'), +(8, 1, 10, 0, 76, 942, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Finger1', 'Both', 'Freezing Band'), +(8, 1, 11, 0, 76, 942, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Finger2', 'Both', 'Freezing Band'), +(8, 1, 12, 0, 76, 12930, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Trinket1', 'Both', 'Briarwood Reed'), +(8, 1, 13, 0, 76, 13968, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Trinket2', 'Both', 'Eye of the Beast'), +(8, 1, 14, 0, 76, 13386, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Back', 'Both', 'Archivist Cape'), +(8, 1, 15, 0, 76, 13964, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'MainHand', 'Both', 'Witchblade'), +(8, 1, 16, 0, 76, 10796, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'OffHand', 'Both', 'Drakestone'), +(8, 1, 17, 0, 76, 13938, 'Phase 2 (Pre-Raid)', 'Mage', 'Fire', 'Ranged', 'Both', 'Bonecreeper Stylus'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 78, 16914, 'Phase 2', 'Mage', 'Fire', 'Head', 'Both', 'Netherwind Crown'), +(8, 1, 1, 0, 78, 18814, 'Phase 2', 'Mage', 'Fire', 'Neck', 'Both', 'Choker of the Fire Lord'), +(8, 1, 2, 0, 78, 23319, 'Phase 2', 'Mage', 'Fire', 'Shoulders', 'Both', 'Lieutenant Commander''s Silk Mantle'), +(8, 1, 4, 0, 78, 14152, 'Phase 2', 'Mage', 'Fire', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 1, 5, 0, 78, 19136, 'Phase 2', 'Mage', 'Fire', 'Waist', 'Both', 'Mana Igniting Cord'), +(8, 1, 6, 0, 78, 16915, 'Phase 2', 'Mage', 'Fire', 'Legs', 'Both', 'Netherwind Pants'), +(8, 1, 7, 0, 78, 23291, 'Phase 2', 'Mage', 'Fire', 'Feet', 'Both', 'Knight-Lieutenant''s Silk Walkers'), +(8, 1, 8, 0, 78, 11766, 'Phase 2', 'Mage', 'Fire', 'Wrists', 'Both', 'Flameweave Cuffs'), +(8, 1, 9, 0, 78, 13253, 'Phase 2', 'Mage', 'Fire', 'Hands', 'Both', 'Hands of Power'), +(8, 1, 10, 0, 78, 19147, 'Phase 2', 'Mage', 'Fire', 'Finger1', 'Both', 'Ring of Spell Power'), +(8, 1, 11, 0, 78, 19147, 'Phase 2', 'Mage', 'Fire', 'Finger2', 'Both', 'Ring of Spell Power'), +(8, 1, 12, 0, 78, 12930, 'Phase 2', 'Mage', 'Fire', 'Trinket1', 'Both', 'Briarwood Reed'), +(8, 1, 13, 0, 78, 18820, 'Phase 2', 'Mage', 'Fire', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(8, 1, 14, 0, 78, 13386, 'Phase 2', 'Mage', 'Fire', 'Back', 'Both', 'Archivist Cape'), +(8, 1, 15, 0, 78, 17103, 'Phase 2', 'Mage', 'Fire', 'MainHand', 'Both', 'Azuresong Mageblade'), +(8, 1, 16, 0, 78, 10796, 'Phase 2', 'Mage', 'Fire', 'OffHand', 'Both', 'Drakestone'), +(8, 1, 17, 0, 78, 19130, 'Phase 2', 'Mage', 'Fire', 'Ranged', 'Both', 'Cold Snap'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 83, 19375, 'Phase 3', 'Mage', 'Fire', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(8, 1, 1, 0, 83, 18814, 'Phase 3', 'Mage', 'Fire', 'Neck', 'Both', 'Choker of the Fire Lord'), +(8, 1, 2, 0, 83, 19370, 'Phase 3', 'Mage', 'Fire', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(8, 1, 4, 0, 83, 14152, 'Phase 3', 'Mage', 'Fire', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 1, 5, 0, 83, 19136, 'Phase 3', 'Mage', 'Fire', 'Waist', 'Both', 'Mana Igniting Cord'), +(8, 1, 6, 0, 83, 16915, 'Phase 3', 'Mage', 'Fire', 'Legs', 'Both', 'Netherwind Pants'), +(8, 1, 7, 0, 83, 19438, 'Phase 3', 'Mage', 'Fire', 'Feet', 'Both', 'Ringo''s Blizzard Boots'), +(8, 1, 8, 0, 83, 19374, 'Phase 3', 'Mage', 'Fire', 'Wrists', 'Both', 'Bracers of Arcane Accuracy'), +(8, 1, 9, 0, 83, 16913, 'Phase 3', 'Mage', 'Fire', 'Hands', 'Both', 'Netherwind Gloves'), +(8, 1, 11, 0, 83, 19147, 'Phase 3', 'Mage', 'Fire', 'Finger2', 'Both', 'Ring of Spell Power'), +(8, 1, 12, 0, 83, 19379, 'Phase 3', 'Mage', 'Fire', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(8, 1, 13, 0, 83, 19339, 'Phase 3', 'Mage', 'Fire', 'Trinket2', 'Both', 'Mind Quickening Gem'), +(8, 1, 14, 0, 83, 19378, 'Phase 3', 'Mage', 'Fire', 'Back', 'Both', 'Cloak of the Brood Lord'), +(8, 1, 15, 0, 83, 19356, 'Phase 3', 'Mage', 'Fire', 'MainHand', 'Both', 'Staff of the Shadow Flame'), +(8, 1, 17, 0, 83, 19130, 'Phase 3', 'Mage', 'Fire', 'Ranged', 'Both', 'Cold Snap'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 88, 19375, 'Phase 5', 'Mage', 'Fire', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(8, 1, 1, 0, 88, 21608, 'Phase 5', 'Mage', 'Fire', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(8, 1, 2, 0, 88, 19370, 'Phase 5', 'Mage', 'Fire', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(8, 1, 4, 0, 88, 19145, 'Phase 5', 'Mage', 'Fire', 'Chest', 'Both', 'Robe of Volatile Power'), +(8, 1, 5, 0, 88, 22730, 'Phase 5', 'Mage', 'Fire', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(8, 1, 6, 0, 88, 21461, 'Phase 5', 'Mage', 'Fire', 'Legs', 'Both', 'Leggings of the Black Blizzard'), +(8, 1, 7, 0, 88, 21344, 'Phase 5', 'Mage', 'Fire', 'Feet', 'Both', 'Enigma Boots'), +(8, 1, 8, 0, 88, 21186, 'Phase 5', 'Mage', 'Fire', 'Wrists', 'Both', 'Rockfury Bracers'), +(8, 1, 9, 0, 88, 21585, 'Phase 5', 'Mage', 'Fire', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(8, 1, 10, 0, 88, 21836, 'Phase 5', 'Mage', 'Fire', 'Finger1', 'Both', 'Ritssyn''s Ring of Chaos'), +(8, 1, 11, 0, 88, 21709, 'Phase 5', 'Mage', 'Fire', 'Finger2', 'Both', 'Ring of the Fallen God'), +(8, 1, 12, 0, 88, 19379, 'Phase 5', 'Mage', 'Fire', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(8, 1, 13, 0, 88, 19339, 'Phase 5', 'Mage', 'Fire', 'Trinket2', 'Both', 'Mind Quickening Gem'), +(8, 1, 14, 0, 88, 22731, 'Phase 5', 'Mage', 'Fire', 'Back', 'Both', 'Cloak of the Devoured'), +(8, 1, 15, 0, 88, 21622, 'Phase 5', 'Mage', 'Fire', 'MainHand', 'Both', 'Sharpened Silithid Femur'), +(8, 1, 16, 0, 88, 21597, 'Phase 5', 'Mage', 'Fire', 'OffHand', 'Both', 'Royal Scepter of Vek''lor'), +(8, 1, 17, 0, 88, 21603, 'Phase 5', 'Mage', 'Fire', 'Ranged', 'Both', 'Wand of Qiraji Nobility'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 92, 22498, 'Phase 6', 'Mage', 'Fire', 'Head', 'Both', 'Frostfire Circlet'), +(8, 1, 1, 0, 92, 23057, 'Phase 6', 'Mage', 'Fire', 'Neck', 'Both', 'Gem of Trapped Innocents'), +(8, 1, 2, 0, 92, 22983, 'Phase 6', 'Mage', 'Fire', 'Shoulders', 'Both', 'Rime Covered Mantle'), +(8, 1, 4, 0, 92, 22496, 'Phase 6', 'Mage', 'Fire', 'Chest', 'Both', 'Frostfire Robe'), +(8, 1, 5, 0, 92, 22730, 'Phase 6', 'Mage', 'Fire', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(8, 1, 6, 0, 92, 23070, 'Phase 6', 'Mage', 'Fire', 'Legs', 'Both', 'Leggings of Polarity'), +(8, 1, 7, 0, 92, 22500, 'Phase 6', 'Mage', 'Fire', 'Feet', 'Both', 'Frostfire Sandals'), +(8, 1, 8, 0, 92, 23021, 'Phase 6', 'Mage', 'Fire', 'Wrists', 'Both', 'The Soul Harvester''s Bindings'), +(8, 1, 9, 0, 92, 21585, 'Phase 6', 'Mage', 'Fire', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(8, 1, 10, 0, 92, 23237, 'Phase 6', 'Mage', 'Fire', 'Finger1', 'Both', 'Ring of the Eternal Flame'), +(8, 1, 11, 0, 92, 23062, 'Phase 6', 'Mage', 'Fire', 'Finger2', 'Both', 'Frostfire Ring'), +(8, 1, 12, 0, 92, 19379, 'Phase 6', 'Mage', 'Fire', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(8, 1, 13, 0, 92, 23046, 'Phase 6', 'Mage', 'Fire', 'Trinket2', 'Both', 'The Restrained Essence of Sapphiron'), +(8, 1, 14, 0, 92, 23050, 'Phase 6', 'Mage', 'Fire', 'Back', 'Both', 'Cloak of the Necropolis'), +(8, 1, 15, 0, 92, 22807, 'Phase 6', 'Mage', 'Fire', 'MainHand', 'Both', 'Wraith Blade'), +(8, 1, 16, 0, 92, 23049, 'Phase 6', 'Mage', 'Fire', 'OffHand', 'Both', 'Sapphiron''s Left Eye'), +(8, 1, 17, 0, 92, 22821, 'Phase 6', 'Mage', 'Fire', 'Ranged', 'Both', 'Doomfinger'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 120, 24266, 'Pre-Raid', 'Mage', 'Fire', 'Head', 'Both', 'Spellstrike Hood'), +(8, 1, 1, 0, 120, 28134, 'Pre-Raid', 'Mage', 'Fire', 'Neck', 'Both', 'Brooch of Heightened Potential'), +(8, 1, 2, 0, 120, 27994, 'Pre-Raid', 'Mage', 'Fire', 'Shoulders', 'Both', 'Mantle of Three Terrors'), +(8, 1, 4, 0, 120, 21848, 'Pre-Raid', 'Mage', 'Fire', 'Chest', 'Both', 'Spellfire Robe'), +(8, 1, 5, 0, 120, 21846, 'Pre-Raid', 'Mage', 'Fire', 'Waist', 'Both', 'Spellfire Belt'), +(8, 1, 6, 0, 120, 24262, 'Pre-Raid', 'Mage', 'Fire', 'Legs', 'Both', 'Spellstrike Pants'), +(8, 1, 7, 0, 120, 28406, 'Pre-Raid', 'Mage', 'Fire', 'Feet', 'Both', 'Sigil-Laced Boots'), +(8, 1, 8, 0, 120, 29240, 'Pre-Raid', 'Mage', 'Fire', 'Wrists', 'Both', 'Bands of Negation'), +(8, 1, 9, 0, 120, 21847, 'Pre-Raid', 'Mage', 'Fire', 'Hands', 'Both', 'Spellfire Gloves'), +(8, 1, 10, 0, 120, 29172, 'Pre-Raid', 'Mage', 'Fire', 'Finger1', 'Both', 'Ashyen''s Gift'), +(8, 1, 11, 0, 120, 28227, 'Pre-Raid', 'Mage', 'Fire', 'Finger2', 'Both', 'Sparking Arcanite Ring'), +(8, 1, 12, 0, 120, 29370, 'Pre-Raid', 'Mage', 'Fire', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(8, 1, 13, 0, 120, 27683, 'Pre-Raid', 'Mage', 'Fire', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(8, 1, 14, 0, 120, 27981, 'Pre-Raid', 'Mage', 'Fire', 'Back', 'Both', 'Sethekk Oracle Cloak'), +(8, 1, 15, 0, 120, 23554, 'Pre-Raid', 'Mage', 'Fire', 'MainHand', 'Both', 'Eternium Runed Blade'), +(8, 1, 16, 0, 120, 29270, 'Pre-Raid', 'Mage', 'Fire', 'OffHand', 'Both', 'Flametongue Seal'), +(8, 1, 17, 0, 120, 28386, 'Pre-Raid', 'Mage', 'Fire', 'Ranged', 'Both', 'Nether Core''s Control Rod'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 125, 29076, 'Phase 1', 'Mage', 'Fire', 'Head', 'Both', 'Collar of the Aldor'), +(8, 1, 1, 0, 125, 28530, 'Phase 1', 'Mage', 'Fire', 'Neck', 'Both', 'Brooch of Unquenchable Fury'), +(8, 1, 2, 0, 125, 29079, 'Phase 1', 'Mage', 'Fire', 'Shoulders', 'Both', 'Pauldrons of the Aldor'), +(8, 1, 4, 0, 125, 21848, 'Phase 1', 'Mage', 'Fire', 'Chest', 'Both', 'Spellfire Robe'), +(8, 1, 5, 0, 125, 21846, 'Phase 1', 'Mage', 'Fire', 'Waist', 'Both', 'Spellfire Belt'), +(8, 1, 6, 0, 125, 29078, 'Phase 1', 'Mage', 'Fire', 'Legs', 'Both', 'Legwraps of the Aldor'), +(8, 1, 7, 0, 125, 28517, 'Phase 1', 'Mage', 'Fire', 'Feet', 'Both', 'Boots of Foretelling'), +(8, 1, 8, 0, 125, 28515, 'Phase 1', 'Mage', 'Fire', 'Wrists', 'Both', 'Bands of Nefarious Deeds'), +(8, 1, 9, 0, 125, 21847, 'Phase 1', 'Mage', 'Fire', 'Hands', 'Both', 'Spellfire Gloves'), +(8, 1, 10, 0, 125, 28793, 'Phase 1', 'Mage', 'Fire', 'Finger1', 'Both', 'Band of Crimson Fury'), +(8, 1, 11, 0, 125, 29287, 'Phase 1', 'Mage', 'Fire', 'Finger2', 'Both', 'Violet Signet of the Archmage'), +(8, 1, 12, 0, 125, 29370, 'Phase 1', 'Mage', 'Fire', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(8, 1, 13, 0, 125, 27683, 'Phase 1', 'Mage', 'Fire', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(8, 1, 14, 0, 125, 28766, 'Phase 1', 'Mage', 'Fire', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(8, 1, 15, 0, 125, 28770, 'Phase 1', 'Mage', 'Fire', 'MainHand', 'Both', 'Nathrezim Mindblade'), +(8, 1, 16, 0, 125, 28734, 'Phase 1', 'Mage', 'Fire', 'OffHand', 'Both', 'Jewel of Infinite Possibilities'), +(8, 1, 17, 0, 125, 28673, 'Phase 1', 'Mage', 'Fire', 'Ranged', 'Both', 'Tirisfal Wand of Ascendancy'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 141, 32494, 'Phase 2', 'Mage', 'Fire', 'Head', 'Both', 'Destruction Holo-gogs'), +(8, 1, 1, 0, 141, 30015, 'Phase 2', 'Mage', 'Fire', 'Neck', 'Both', 'The Sun King''s Talisman'), +(8, 1, 2, 0, 141, 30024, 'Phase 2', 'Mage', 'Fire', 'Shoulders', 'Both', 'Mantle of the Elven Kings'), +(8, 1, 4, 0, 141, 30107, 'Phase 2', 'Mage', 'Fire', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(8, 1, 5, 0, 141, 30038, 'Phase 2', 'Mage', 'Fire', 'Waist', 'Both', 'Belt of Blasting'), +(8, 1, 6, 0, 141, 30207, 'Phase 2', 'Mage', 'Fire', 'Legs', 'Both', 'Leggings of Tirisfal'), +(8, 1, 7, 0, 141, 30067, 'Phase 2', 'Mage', 'Fire', 'Feet', 'Both', 'Velvet Boots of the Guardian'), +(8, 1, 8, 0, 141, 29918, 'Phase 2', 'Mage', 'Fire', 'Wrists', 'Both', 'Mindstorm Wristbands'), +(8, 1, 9, 0, 141, 29987, 'Phase 2', 'Mage', 'Fire', 'Hands', 'Both', 'Gauntlets of the Sun King'), +(8, 1, 10, 0, 141, 29302, 'Phase 2', 'Mage', 'Fire', 'Finger1', 'Both', 'Band of Eternity'), +(8, 1, 11, 0, 141, 29287, 'Phase 2', 'Mage', 'Fire', 'Finger2', 'Both', 'Violet Signet of the Archmage'), +(8, 1, 12, 0, 141, 27683, 'Phase 2', 'Mage', 'Fire', 'Trinket1', 'Both', 'Quagmirran''s Eye'), +(8, 1, 13, 0, 141, 30626, 'Phase 2', 'Mage', 'Fire', 'Trinket2', 'Both', 'Sextant of Unstable Currents'), +(8, 1, 14, 0, 141, 28766, 'Phase 2', 'Mage', 'Fire', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(8, 1, 15, 0, 141, 30095, 'Phase 2', 'Mage', 'Fire', 'MainHand', 'Both', 'Fang of the Leviathan'), +(8, 1, 16, 0, 141, 29270, 'Phase 2', 'Mage', 'Fire', 'OffHand', 'Both', 'Flametongue Seal'), +(8, 1, 17, 0, 141, 29982, 'Phase 2', 'Mage', 'Fire', 'Ranged', 'Both', 'Wand of the Forgotten Star'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 156, 32525, 'Phase 3', 'Mage', 'Fire', 'Head', 'Both', 'Cowl of the Illidari High Lord'), +(8, 1, 1, 0, 156, 32589, 'Phase 3', 'Mage', 'Fire', 'Neck', 'Both', 'Hellfire-Encased Pendant'), +(8, 1, 2, 0, 156, 31059, 'Phase 3', 'Mage', 'Fire', 'Shoulders', 'Both', 'Mantle of the Tempest'), +(8, 1, 4, 0, 156, 31057, 'Phase 3', 'Mage', 'Fire', 'Chest', 'Both', 'Robes of the Tempest'), +(8, 1, 5, 0, 156, 30038, 'Phase 3', 'Mage', 'Fire', 'Waist', 'Both', 'Belt of Blasting'), +(8, 1, 6, 0, 156, 31058, 'Phase 3', 'Mage', 'Fire', 'Legs', 'Both', 'Leggings of the Tempest'), +(8, 1, 7, 0, 156, 32239, 'Phase 3', 'Mage', 'Fire', 'Feet', 'Both', 'Slippers of the Seacaller'), +(8, 1, 8, 0, 156, 32586, 'Phase 3', 'Mage', 'Fire', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(8, 1, 9, 0, 156, 31055, 'Phase 3', 'Mage', 'Fire', 'Hands', 'Both', 'Gloves of the Tempest'), +(8, 1, 10, 0, 156, 32527, 'Phase 3', 'Mage', 'Fire', 'Finger1', 'Both', 'Ring of Ancient Knowledge'), +(8, 1, 11, 0, 156, 32527, 'Phase 3', 'Mage', 'Fire', 'Finger2', 'Both', 'Ring of Ancient Knowledge'), +(8, 1, 12, 0, 156, 32488, 'Phase 3', 'Mage', 'Fire', 'Trinket1', 'Both', 'Ashtongue Talisman of Insight'), +(8, 1, 13, 0, 156, 32483, 'Phase 3', 'Mage', 'Fire', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(8, 1, 14, 0, 156, 32331, 'Phase 3', 'Mage', 'Fire', 'Back', 'Both', 'Cloak of the Illidari Council'), +(8, 1, 15, 0, 156, 30910, 'Phase 3', 'Mage', 'Fire', 'MainHand', 'Both', 'Tempest of Chaos'), +(8, 1, 16, 0, 156, 30872, 'Phase 3', 'Mage', 'Fire', 'OffHand', 'Both', 'Chronicle of Dark Secrets'), +(8, 1, 17, 0, 156, 29982, 'Phase 3', 'Mage', 'Fire', 'Ranged', 'Both', 'Wand of the Forgotten Star'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 164, 32525, 'Phase 4', 'Mage', 'Fire', 'Head', 'Both', 'Cowl of the Illidari High Lord'), +(8, 1, 1, 0, 164, 32589, 'Phase 4', 'Mage', 'Fire', 'Neck', 'Both', 'Hellfire-Encased Pendant'), +(8, 1, 2, 0, 164, 31059, 'Phase 4', 'Mage', 'Fire', 'Shoulders', 'Both', 'Mantle of the Tempest'), +(8, 1, 4, 0, 164, 31057, 'Phase 4', 'Mage', 'Fire', 'Chest', 'Both', 'Robes of the Tempest'), +(8, 1, 5, 0, 164, 30038, 'Phase 4', 'Mage', 'Fire', 'Waist', 'Both', 'Belt of Blasting'), +(8, 1, 6, 0, 164, 31058, 'Phase 4', 'Mage', 'Fire', 'Legs', 'Both', 'Leggings of the Tempest'), +(8, 1, 7, 0, 164, 32239, 'Phase 4', 'Mage', 'Fire', 'Feet', 'Both', 'Slippers of the Seacaller'), +(8, 1, 8, 0, 164, 32586, 'Phase 4', 'Mage', 'Fire', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(8, 1, 9, 0, 164, 31055, 'Phase 4', 'Mage', 'Fire', 'Hands', 'Both', 'Gloves of the Tempest'), +(8, 1, 10, 0, 164, 32527, 'Phase 4', 'Mage', 'Fire', 'Finger1', 'Both', 'Ring of Ancient Knowledge'), +(8, 1, 11, 0, 164, 32527, 'Phase 4', 'Mage', 'Fire', 'Finger2', 'Both', 'Ring of Ancient Knowledge'), +(8, 1, 12, 0, 164, 33829, 'Phase 4', 'Mage', 'Fire', 'Trinket1', 'Both', 'Hex Shrunken Head'), +(8, 1, 13, 0, 164, 32483, 'Phase 4', 'Mage', 'Fire', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(8, 1, 14, 0, 164, 32331, 'Phase 4', 'Mage', 'Fire', 'Back', 'Both', 'Cloak of the Illidari Council'), +(8, 1, 15, 0, 164, 30910, 'Phase 4', 'Mage', 'Fire', 'MainHand', 'Both', 'Tempest of Chaos'), +(8, 1, 16, 0, 164, 30872, 'Phase 4', 'Mage', 'Fire', 'OffHand', 'Both', 'Chronicle of Dark Secrets'), +(8, 1, 17, 0, 164, 29982, 'Phase 4', 'Mage', 'Fire', 'Ranged', 'Both', 'Wand of the Forgotten Star'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 200, 42553, 'Pre-Raid', 'Mage', 'Fire', 'Head', 'Both', 'Visage Liquification Goggles'), +(8, 1, 1, 0, 200, 39472, 'Pre-Raid', 'Mage', 'Fire', 'Neck', 'Both', 'Chain of Latent Energies'), +(8, 1, 2, 0, 200, 37673, 'Pre-Raid', 'Mage', 'Fire', 'Shoulders', 'Both', 'Dark Runic Mantle'), +(8, 1, 4, 0, 200, 39492, 'Pre-Raid', 'Mage', 'Fire', 'Chest', 'Both', 'Heroes'' Frostfire Robe'), +(8, 1, 5, 0, 200, 40696, 'Pre-Raid', 'Mage', 'Fire', 'Waist', 'Both', 'Plush Sash of Guzbah'), +(8, 1, 6, 0, 200, 37854, 'Pre-Raid', 'Mage', 'Fire', 'Legs', 'Both', 'Woven Bracae Leggings'), +(8, 1, 7, 0, 200, 44202, 'Pre-Raid', 'Mage', 'Fire', 'Feet', 'Both', 'Sandals of Crimson Fury'), +(8, 1, 8, 0, 200, 37884, 'Pre-Raid', 'Mage', 'Fire', 'Wrists', 'Both', 'Azure Cloth Bindings'), +(8, 1, 9, 0, 200, 39495, 'Pre-Raid', 'Mage', 'Fire', 'Hands', 'Both', 'Heroes'' Frostfire Gloves'), +(8, 1, 10, 0, 200, 42644, 'Pre-Raid', 'Mage', 'Fire', 'Finger1', 'Both', 'Titanium Spellshock Ring'), +(8, 1, 12, 0, 200, 37873, 'Pre-Raid', 'Mage', 'Fire', 'Trinket1', 'Both', 'Mark of the War Prisoner'), +(8, 1, 14, 0, 200, 41610, 'Pre-Raid', 'Mage', 'Fire', 'Back', 'Both', 'Deathchill Cloak'), +(8, 1, 15, 0, 200, 45085, 'Pre-Raid', 'Mage', 'Fire', 'MainHand', 'Both', 'Titansteel Spellblade'), +(8, 1, 17, 0, 200, 37238, 'Pre-Raid', 'Mage', 'Fire', 'Ranged', 'Both', 'Rod of the Fallen Monarch'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 224, 40416, 'Phase 1', 'Mage', 'Fire', 'Head', 'Both', 'Valorous Frostfire Circlet'), +(8, 1, 1, 0, 224, 44661, 'Phase 1', 'Mage', 'Fire', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(8, 1, 2, 0, 224, 40419, 'Phase 1', 'Mage', 'Fire', 'Shoulders', 'Both', 'Valorous Frostfire Shoulderpads'), +(8, 1, 4, 0, 224, 40418, 'Phase 1', 'Mage', 'Fire', 'Chest', 'Both', 'Valorous Frostfire Robe'), +(8, 1, 5, 0, 224, 40301, 'Phase 1', 'Mage', 'Fire', 'Waist', 'Both', 'Cincture of Polarity'), +(8, 1, 6, 0, 224, 40560, 'Phase 1', 'Mage', 'Fire', 'Legs', 'Both', 'Leggings of the Wanton Spellcaster'), +(8, 1, 7, 0, 224, 40246, 'Phase 1', 'Mage', 'Fire', 'Feet', 'Both', 'Boots of Impetuous Ideals'), +(8, 1, 8, 0, 224, 44008, 'Phase 1', 'Mage', 'Fire', 'Wrists', 'Both', 'Unsullied Cuffs'), +(8, 1, 9, 0, 224, 40415, 'Phase 1', 'Mage', 'Fire', 'Hands', 'Both', 'Valorous Frostfire Gloves'), +(8, 1, 10, 0, 224, 40399, 'Phase 1', 'Mage', 'Fire', 'Finger1', 'Both', 'Signet of Manifested Pain'), +(8, 1, 12, 0, 224, 40255, 'Phase 1', 'Mage', 'Fire', 'Trinket1', 'Both', 'Dying Curse'), +(8, 1, 14, 0, 224, 44005, 'Phase 1', 'Mage', 'Fire', 'Back', 'Both', 'Pennant Cloak'), +(8, 1, 15, 0, 224, 40396, 'Phase 1', 'Mage', 'Fire', 'MainHand', 'Both', 'The Turning Tide'), +(8, 1, 17, 0, 224, 39712, 'Phase 1', 'Mage', 'Fire', 'Ranged', 'Both', 'Gemmed Wand of the Nerubians'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 245, 46129, 'Phase 2', 'Mage', 'Fire', 'Head', 'Both', 'Conqueror''s Kirin Tor Hood'), +(8, 1, 1, 0, 245, 45133, 'Phase 2', 'Mage', 'Fire', 'Neck', 'Both', 'Pendant of Fiery Havoc'), +(8, 1, 2, 0, 245, 46134, 'Phase 2', 'Mage', 'Fire', 'Shoulders', 'Both', 'Conqueror''s Kirin Tor Shoulderpads'), +(8, 1, 4, 0, 245, 46130, 'Phase 2', 'Mage', 'Fire', 'Chest', 'Both', 'Conqueror''s Kirin Tor Tunic'), +(8, 1, 5, 0, 245, 45619, 'Phase 2', 'Mage', 'Fire', 'Waist', 'Both', 'Starwatcher''s Binding'), +(8, 1, 6, 0, 245, 46133, 'Phase 2', 'Mage', 'Fire', 'Legs', 'Both', 'Conqueror''s Kirin Tor Leggings'), +(8, 1, 7, 0, 245, 45135, 'Phase 2', 'Mage', 'Fire', 'Feet', 'Both', 'Boots of Fiery Resolution'), +(8, 1, 8, 0, 245, 45446, 'Phase 2', 'Mage', 'Fire', 'Wrists', 'Both', 'Grasps of Reason'), +(8, 1, 9, 0, 245, 45665, 'Phase 2', 'Mage', 'Fire', 'Hands', 'Both', 'Pharos Gloves'), +(8, 1, 10, 0, 245, 46046, 'Phase 2', 'Mage', 'Fire', 'Finger1', 'Both', 'Nebula Band'), +(8, 1, 12, 0, 245, 45518, 'Phase 2', 'Mage', 'Fire', 'Trinket1', 'Both', 'Flare of the Heavens'), +(8, 1, 14, 0, 245, 45242, 'Phase 2', 'Mage', 'Fire', 'Back', 'Both', 'Drape of Mortal Downfall'), +(8, 1, 15, 0, 245, 45620, 'Phase 2', 'Mage', 'Fire', 'MainHand', 'Both', 'Starshard Edge'), +(8, 1, 17, 0, 245, 45511, 'Phase 2', 'Mage', 'Fire', 'Ranged', 'Both', 'Scepter of Lost Souls'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 258, 47761, 'Phase 3', 'Mage', 'Fire', 'Head', 'Both', 'Hood of Triumph'), +(8, 1, 2, 0, 258, 47758, 'Phase 3', 'Mage', 'Fire', 'Shoulders', 'Both', 'Shoulderpads of Triumph'), +(8, 1, 4, 1, 258, 47129, 'Phase 3', 'Mage', 'Fire', 'Chest', 'Alliance', 'Skyweaver Robes'), +(8, 1, 4, 2, 258, 47462, 'Phase 3', 'Mage', 'Fire', 'Chest', 'Horde', 'Skyweaver Vestments'), +(8, 1, 5, 1, 258, 47084, 'Phase 3', 'Mage', 'Fire', 'Waist', 'Alliance', 'Cord of Biting Cold'), +(8, 1, 5, 2, 258, 47447, 'Phase 3', 'Mage', 'Fire', 'Waist', 'Horde', 'Belt of Biting Cold'), +(8, 1, 6, 0, 258, 47760, 'Phase 3', 'Mage', 'Fire', 'Legs', 'Both', 'Leggings of Triumph'), +(8, 1, 7, 1, 258, 47097, 'Phase 3', 'Mage', 'Fire', 'Feet', 'Alliance', 'Boots of the Mourning Widow'), +(8, 1, 7, 2, 258, 47454, 'Phase 3', 'Mage', 'Fire', 'Feet', 'Horde', 'Sandals of the Mourning Widow'), +(8, 1, 8, 1, 258, 47143, 'Phase 3', 'Mage', 'Fire', 'Wrists', 'Alliance', 'Bindings of Dark Essence'), +(8, 1, 8, 2, 258, 47467, 'Phase 3', 'Mage', 'Fire', 'Wrists', 'Horde', 'Dark Essence Bindings'), +(8, 1, 9, 0, 258, 47762, 'Phase 3', 'Mage', 'Fire', 'Hands', 'Both', 'Gauntlets of Triumph'), +(8, 1, 10, 1, 258, 47489, 'Phase 3', 'Mage', 'Fire', 'Finger1', 'Alliance', 'Lurid Manifestation'), +(8, 1, 10, 2, 258, 47237, 'Phase 3', 'Mage', 'Fire', 'Finger1', 'Horde', 'Band of Deplorable Violence'), +(8, 1, 12, 1, 258, 47188, 'Phase 3', 'Mage', 'Fire', 'Trinket1', 'Alliance', 'Reign of the Unliving'), +(8, 1, 12, 2, 258, 47477, 'Phase 3', 'Mage', 'Fire', 'Trinket1', 'Horde', 'Reign of the Dead'), +(8, 1, 14, 1, 258, 47552, 'Phase 3', 'Mage', 'Fire', 'Back', 'Alliance', 'Jaina''s Radiance'), +(8, 1, 14, 2, 258, 47551, 'Phase 3', 'Mage', 'Fire', 'Back', 'Horde', 'Aethas'' Intensity'), +(8, 1, 17, 0, 258, 45294, 'Phase 3', 'Mage', 'Fire', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 264, 51281, 'Phase 4', 'Mage', 'Fire', 'Head', 'Both', 'Sanctified Bloodmage Hood'), +(8, 1, 1, 0, 264, 50724, 'Phase 4', 'Mage', 'Fire', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(8, 1, 2, 0, 264, 51284, 'Phase 4', 'Mage', 'Fire', 'Shoulders', 'Both', 'Sanctified Bloodmage Shoulderpads'), +(8, 1, 4, 0, 264, 50629, 'Phase 4', 'Mage', 'Fire', 'Chest', 'Both', 'Robe of the Waking Nightmare'), +(8, 1, 5, 0, 264, 50613, 'Phase 4', 'Mage', 'Fire', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(8, 1, 6, 0, 264, 51282, 'Phase 4', 'Mage', 'Fire', 'Legs', 'Both', 'Sanctified Bloodmage Leggings'), +(8, 1, 7, 0, 264, 50699, 'Phase 4', 'Mage', 'Fire', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(8, 1, 8, 0, 264, 50651, 'Phase 4', 'Mage', 'Fire', 'Wrists', 'Both', 'The Lady''s Brittle Bracers'), +(8, 1, 9, 0, 264, 51280, 'Phase 4', 'Mage', 'Fire', 'Hands', 'Both', 'Sanctified Bloodmage Gloves'), +(8, 1, 10, 0, 264, 50398, 'Phase 4', 'Mage', 'Fire', 'Finger1', 'Both', 'Ashen Band of Endless Destruction'), +(8, 1, 12, 0, 264, 50365, 'Phase 4', 'Mage', 'Fire', 'Trinket1', 'Both', 'Phylactery of the Nameless Lich'), +(8, 1, 14, 0, 264, 50628, 'Phase 4', 'Mage', 'Fire', 'Back', 'Both', 'Frostbinder''s Shredded Cape'), +(8, 1, 15, 0, 264, 50732, 'Phase 4', 'Mage', 'Fire', 'MainHand', 'Both', 'Bloodsurge, Kel''Thuzad''s Blade of Agony'), +(8, 1, 17, 0, 264, 50684, 'Phase 4', 'Mage', 'Fire', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 1, 0, 0, 290, 51281, 'Phase 5', 'Mage', 'Fire', 'Head', 'Both', 'Sanctified Bloodmage Hood'), +(8, 1, 1, 0, 290, 50182, 'Phase 5', 'Mage', 'Fire', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(8, 1, 2, 0, 290, 51284, 'Phase 5', 'Mage', 'Fire', 'Shoulders', 'Both', 'Sanctified Bloodmage Shoulderpads'), +(8, 1, 4, 0, 290, 51283, 'Phase 5', 'Mage', 'Fire', 'Chest', 'Both', 'Sanctified Bloodmage Robe'), +(8, 1, 5, 0, 290, 50613, 'Phase 5', 'Mage', 'Fire', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(8, 1, 6, 0, 290, 50694, 'Phase 5', 'Mage', 'Fire', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(8, 1, 7, 0, 290, 50699, 'Phase 5', 'Mage', 'Fire', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(8, 1, 8, 0, 290, 54582, 'Phase 5', 'Mage', 'Fire', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(8, 1, 9, 0, 290, 51280, 'Phase 5', 'Mage', 'Fire', 'Hands', 'Both', 'Sanctified Bloodmage Gloves'), +(8, 1, 10, 0, 290, 50614, 'Phase 5', 'Mage', 'Fire', 'Finger1', 'Both', 'Loop of the Endless Labyrinth'), +(8, 1, 11, 0, 290, 50398, 'Phase 5', 'Mage', 'Fire', 'Finger2', 'Both', 'Ashen Band of Endless Destruction'), +(8, 1, 12, 0, 290, 54588, 'Phase 5', 'Mage', 'Fire', 'Trinket1', 'Both', 'Charred Twilight Scale'), +(8, 1, 13, 0, 290, 50365, 'Phase 5', 'Mage', 'Fire', 'Trinket2', 'Both', 'Phylactery of the Nameless Lich'), +(8, 1, 14, 0, 290, 54583, 'Phase 5', 'Mage', 'Fire', 'Back', 'Both', 'Cloak of Burning Dusk'), +(8, 1, 15, 0, 290, 50732, 'Phase 5', 'Mage', 'Fire', 'MainHand', 'Both', 'Bloodsurge, Kel''Thuzad''s Blade of Agony'), +(8, 1, 16, 0, 290, 50719, 'Phase 5', 'Mage', 'Fire', 'OffHand', 'Both', 'Shadow Silk Spindle'), +(8, 1, 17, 0, 290, 50684, 'Phase 5', 'Mage', 'Fire', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + +-- Frost (tab 2) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 66, 10504, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Head', 'Both', 'Green Lens'), +(8, 2, 1, 0, 66, 12103, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Neck', 'Both', 'Star of Mystaria'), +(8, 2, 2, 0, 66, 11782, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Shoulders', 'Both', 'Boreal Mantle'), +(8, 2, 4, 0, 66, 14152, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 2, 5, 0, 66, 11662, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Waist', 'Both', 'Ban''thok Sash'), +(8, 2, 6, 0, 66, 13170, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Legs', 'Both', 'Skyshroud Leggings'), +(8, 2, 7, 0, 66, 11822, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Feet', 'Both', 'Omnicast Boots'), +(8, 2, 8, 0, 66, 11766, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Wrists', 'Both', 'Flameweave Cuffs'), +(8, 2, 9, 0, 66, 13253, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Hands', 'Both', 'Hands of Power'), +(8, 2, 10, 0, 66, 942, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Finger1', 'Both', 'Freezing Band'), +(8, 2, 11, 0, 66, 942, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Finger2', 'Both', 'Freezing Band'), +(8, 2, 12, 0, 66, 12930, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Trinket1', 'Both', 'Briarwood Reed'), +(8, 2, 13, 0, 66, 13968, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Trinket2', 'Both', 'Eye of the Beast'), +(8, 2, 14, 0, 66, 13386, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Back', 'Both', 'Archivist Cape'), +(8, 2, 15, 0, 66, 13964, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'MainHand', 'Both', 'Witchblade'), +(8, 2, 16, 0, 66, 10796, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'OffHand', 'Both', 'Drakestone'), +(8, 2, 17, 0, 66, 13938, 'Phase 1 (Pre-Raid)', 'Mage', 'Frost', 'Ranged', 'Both', 'Bonecreeper Stylus'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 76, 23318, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Head', 'Both', 'Lieutenant Commander''s Silk Cowl'), +(8, 2, 1, 0, 76, 12103, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Neck', 'Both', 'Star of Mystaria'), +(8, 2, 2, 0, 76, 23319, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Shoulders', 'Both', 'Lieutenant Commander''s Silk Mantle'), +(8, 2, 4, 0, 76, 14152, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 2, 5, 0, 76, 11662, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Waist', 'Both', 'Ban''thok Sash'), +(8, 2, 6, 0, 76, 23304, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Legs', 'Both', 'Knight-Captain''s Silk Legguards'), +(8, 2, 7, 0, 76, 23291, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Feet', 'Both', 'Knight-Lieutenant''s Silk Walkers'), +(8, 2, 8, 0, 76, 11766, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Wrists', 'Both', 'Flameweave Cuffs'), +(8, 2, 9, 0, 76, 13253, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Hands', 'Both', 'Hands of Power'), +(8, 2, 10, 0, 76, 942, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Finger1', 'Both', 'Freezing Band'), +(8, 2, 11, 0, 76, 942, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Finger2', 'Both', 'Freezing Band'), +(8, 2, 12, 0, 76, 12930, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Trinket1', 'Both', 'Briarwood Reed'), +(8, 2, 13, 0, 76, 13968, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Trinket2', 'Both', 'Eye of the Beast'), +(8, 2, 14, 0, 76, 13386, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Back', 'Both', 'Archivist Cape'), +(8, 2, 15, 0, 76, 13964, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'MainHand', 'Both', 'Witchblade'), +(8, 2, 16, 0, 76, 10796, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'OffHand', 'Both', 'Drakestone'), +(8, 2, 17, 0, 76, 13938, 'Phase 2 (Pre-Raid)', 'Mage', 'Frost', 'Ranged', 'Both', 'Bonecreeper Stylus'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 78, 16914, 'Phase 2', 'Mage', 'Frost', 'Head', 'Both', 'Netherwind Crown'), +(8, 2, 1, 0, 78, 18814, 'Phase 2', 'Mage', 'Frost', 'Neck', 'Both', 'Choker of the Fire Lord'), +(8, 2, 2, 0, 78, 23319, 'Phase 2', 'Mage', 'Frost', 'Shoulders', 'Both', 'Lieutenant Commander''s Silk Mantle'), +(8, 2, 4, 0, 78, 14152, 'Phase 2', 'Mage', 'Frost', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 2, 5, 0, 78, 19136, 'Phase 2', 'Mage', 'Frost', 'Waist', 'Both', 'Mana Igniting Cord'), +(8, 2, 6, 0, 78, 16915, 'Phase 2', 'Mage', 'Frost', 'Legs', 'Both', 'Netherwind Pants'), +(8, 2, 7, 0, 78, 23291, 'Phase 2', 'Mage', 'Frost', 'Feet', 'Both', 'Knight-Lieutenant''s Silk Walkers'), +(8, 2, 8, 0, 78, 11766, 'Phase 2', 'Mage', 'Frost', 'Wrists', 'Both', 'Flameweave Cuffs'), +(8, 2, 9, 0, 78, 13253, 'Phase 2', 'Mage', 'Frost', 'Hands', 'Both', 'Hands of Power'), +(8, 2, 10, 0, 78, 19147, 'Phase 2', 'Mage', 'Frost', 'Finger1', 'Both', 'Ring of Spell Power'), +(8, 2, 11, 0, 78, 19147, 'Phase 2', 'Mage', 'Frost', 'Finger2', 'Both', 'Ring of Spell Power'), +(8, 2, 12, 0, 78, 12930, 'Phase 2', 'Mage', 'Frost', 'Trinket1', 'Both', 'Briarwood Reed'), +(8, 2, 13, 0, 78, 18820, 'Phase 2', 'Mage', 'Frost', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(8, 2, 14, 0, 78, 13386, 'Phase 2', 'Mage', 'Frost', 'Back', 'Both', 'Archivist Cape'), +(8, 2, 15, 0, 78, 17103, 'Phase 2', 'Mage', 'Frost', 'MainHand', 'Both', 'Azuresong Mageblade'), +(8, 2, 16, 0, 78, 10796, 'Phase 2', 'Mage', 'Frost', 'OffHand', 'Both', 'Drakestone'), +(8, 2, 17, 0, 78, 19130, 'Phase 2', 'Mage', 'Frost', 'Ranged', 'Both', 'Cold Snap'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 83, 19375, 'Phase 3', 'Mage', 'Frost', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(8, 2, 1, 0, 83, 18814, 'Phase 3', 'Mage', 'Frost', 'Neck', 'Both', 'Choker of the Fire Lord'), +(8, 2, 2, 0, 83, 19370, 'Phase 3', 'Mage', 'Frost', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(8, 2, 4, 0, 83, 14152, 'Phase 3', 'Mage', 'Frost', 'Chest', 'Both', 'Robe of the Archmage'), +(8, 2, 5, 0, 83, 19136, 'Phase 3', 'Mage', 'Frost', 'Waist', 'Both', 'Mana Igniting Cord'), +(8, 2, 6, 0, 83, 16915, 'Phase 3', 'Mage', 'Frost', 'Legs', 'Both', 'Netherwind Pants'), +(8, 2, 7, 0, 83, 19438, 'Phase 3', 'Mage', 'Frost', 'Feet', 'Both', 'Ringo''s Blizzard Boots'), +(8, 2, 8, 0, 83, 19374, 'Phase 3', 'Mage', 'Frost', 'Wrists', 'Both', 'Bracers of Arcane Accuracy'), +(8, 2, 9, 0, 83, 16913, 'Phase 3', 'Mage', 'Frost', 'Hands', 'Both', 'Netherwind Gloves'), +(8, 2, 11, 0, 83, 19147, 'Phase 3', 'Mage', 'Frost', 'Finger2', 'Both', 'Ring of Spell Power'), +(8, 2, 12, 0, 83, 19379, 'Phase 3', 'Mage', 'Frost', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(8, 2, 13, 0, 83, 19339, 'Phase 3', 'Mage', 'Frost', 'Trinket2', 'Both', 'Mind Quickening Gem'), +(8, 2, 14, 0, 83, 19378, 'Phase 3', 'Mage', 'Frost', 'Back', 'Both', 'Cloak of the Brood Lord'), +(8, 2, 15, 0, 83, 19356, 'Phase 3', 'Mage', 'Frost', 'MainHand', 'Both', 'Staff of the Shadow Flame'), +(8, 2, 17, 0, 83, 19130, 'Phase 3', 'Mage', 'Frost', 'Ranged', 'Both', 'Cold Snap'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 88, 19375, 'Phase 5', 'Mage', 'Frost', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(8, 2, 1, 0, 88, 21608, 'Phase 5', 'Mage', 'Frost', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(8, 2, 2, 0, 88, 19370, 'Phase 5', 'Mage', 'Frost', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(8, 2, 4, 0, 88, 19145, 'Phase 5', 'Mage', 'Frost', 'Chest', 'Both', 'Robe of Volatile Power'), +(8, 2, 5, 0, 88, 22730, 'Phase 5', 'Mage', 'Frost', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(8, 2, 6, 0, 88, 21461, 'Phase 5', 'Mage', 'Frost', 'Legs', 'Both', 'Leggings of the Black Blizzard'), +(8, 2, 7, 0, 88, 21344, 'Phase 5', 'Mage', 'Frost', 'Feet', 'Both', 'Enigma Boots'), +(8, 2, 8, 0, 88, 21186, 'Phase 5', 'Mage', 'Frost', 'Wrists', 'Both', 'Rockfury Bracers'), +(8, 2, 9, 0, 88, 21585, 'Phase 5', 'Mage', 'Frost', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(8, 2, 10, 0, 88, 21836, 'Phase 5', 'Mage', 'Frost', 'Finger1', 'Both', 'Ritssyn''s Ring of Chaos'), +(8, 2, 11, 0, 88, 21709, 'Phase 5', 'Mage', 'Frost', 'Finger2', 'Both', 'Ring of the Fallen God'), +(8, 2, 12, 0, 88, 19379, 'Phase 5', 'Mage', 'Frost', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(8, 2, 13, 0, 88, 19339, 'Phase 5', 'Mage', 'Frost', 'Trinket2', 'Both', 'Mind Quickening Gem'), +(8, 2, 14, 0, 88, 22731, 'Phase 5', 'Mage', 'Frost', 'Back', 'Both', 'Cloak of the Devoured'), +(8, 2, 15, 0, 88, 21622, 'Phase 5', 'Mage', 'Frost', 'MainHand', 'Both', 'Sharpened Silithid Femur'), +(8, 2, 16, 0, 88, 21597, 'Phase 5', 'Mage', 'Frost', 'OffHand', 'Both', 'Royal Scepter of Vek''lor'), +(8, 2, 17, 0, 88, 21603, 'Phase 5', 'Mage', 'Frost', 'Ranged', 'Both', 'Wand of Qiraji Nobility'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 92, 22498, 'Phase 6', 'Mage', 'Frost', 'Head', 'Both', 'Frostfire Circlet'), +(8, 2, 1, 0, 92, 23057, 'Phase 6', 'Mage', 'Frost', 'Neck', 'Both', 'Gem of Trapped Innocents'), +(8, 2, 2, 0, 92, 22983, 'Phase 6', 'Mage', 'Frost', 'Shoulders', 'Both', 'Rime Covered Mantle'), +(8, 2, 4, 0, 92, 22496, 'Phase 6', 'Mage', 'Frost', 'Chest', 'Both', 'Frostfire Robe'), +(8, 2, 5, 0, 92, 22730, 'Phase 6', 'Mage', 'Frost', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(8, 2, 6, 0, 92, 23070, 'Phase 6', 'Mage', 'Frost', 'Legs', 'Both', 'Leggings of Polarity'), +(8, 2, 7, 0, 92, 22500, 'Phase 6', 'Mage', 'Frost', 'Feet', 'Both', 'Frostfire Sandals'), +(8, 2, 8, 0, 92, 23021, 'Phase 6', 'Mage', 'Frost', 'Wrists', 'Both', 'The Soul Harvester''s Bindings'), +(8, 2, 9, 0, 92, 21585, 'Phase 6', 'Mage', 'Frost', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(8, 2, 10, 0, 92, 23237, 'Phase 6', 'Mage', 'Frost', 'Finger1', 'Both', 'Ring of the Eternal Flame'), +(8, 2, 11, 0, 92, 23062, 'Phase 6', 'Mage', 'Frost', 'Finger2', 'Both', 'Frostfire Ring'), +(8, 2, 12, 0, 92, 19379, 'Phase 6', 'Mage', 'Frost', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(8, 2, 13, 0, 92, 23046, 'Phase 6', 'Mage', 'Frost', 'Trinket2', 'Both', 'The Restrained Essence of Sapphiron'), +(8, 2, 14, 0, 92, 23050, 'Phase 6', 'Mage', 'Frost', 'Back', 'Both', 'Cloak of the Necropolis'), +(8, 2, 15, 0, 92, 22807, 'Phase 6', 'Mage', 'Frost', 'MainHand', 'Both', 'Wraith Blade'), +(8, 2, 16, 0, 92, 23049, 'Phase 6', 'Mage', 'Frost', 'OffHand', 'Both', 'Sapphiron''s Left Eye'), +(8, 2, 17, 0, 92, 22821, 'Phase 6', 'Mage', 'Frost', 'Ranged', 'Both', 'Doomfinger'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 120, 24266, 'Pre-Raid', 'Mage', 'Frost', 'Head', 'Both', 'Spellstrike Hood'), +(8, 2, 1, 0, 120, 28134, 'Pre-Raid', 'Mage', 'Frost', 'Neck', 'Both', 'Brooch of Heightened Potential'), +(8, 2, 2, 0, 120, 21869, 'Pre-Raid', 'Mage', 'Frost', 'Shoulders', 'Both', 'Frozen Shadoweave Shoulders'), +(8, 2, 4, 0, 120, 21871, 'Pre-Raid', 'Mage', 'Frost', 'Chest', 'Both', 'Frozen Shadoweave Robe'), +(8, 2, 5, 0, 120, 24256, 'Pre-Raid', 'Mage', 'Frost', 'Waist', 'Both', 'Girdle of Ruination'), +(8, 2, 6, 0, 120, 24262, 'Pre-Raid', 'Mage', 'Frost', 'Legs', 'Both', 'Spellstrike Pants'), +(8, 2, 7, 0, 120, 21870, 'Pre-Raid', 'Mage', 'Frost', 'Feet', 'Both', 'Frozen Shadoweave Boots'), +(8, 2, 8, 0, 120, 24250, 'Pre-Raid', 'Mage', 'Frost', 'Wrists', 'Both', 'Bracers of Havok'), +(8, 2, 9, 0, 120, 30725, 'Pre-Raid', 'Mage', 'Frost', 'Hands', 'Both', 'Anger-Spark Gloves'), +(8, 2, 10, 0, 120, 29172, 'Pre-Raid', 'Mage', 'Frost', 'Finger1', 'Both', 'Ashyen''s Gift'), +(8, 2, 11, 0, 120, 28227, 'Pre-Raid', 'Mage', 'Frost', 'Finger2', 'Both', 'Sparking Arcanite Ring'), +(8, 2, 12, 0, 120, 29370, 'Pre-Raid', 'Mage', 'Frost', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(8, 2, 13, 0, 120, 27683, 'Pre-Raid', 'Mage', 'Frost', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(8, 2, 14, 0, 120, 27981, 'Pre-Raid', 'Mage', 'Frost', 'Back', 'Both', 'Sethekk Oracle Cloak'), +(8, 2, 15, 0, 120, 23554, 'Pre-Raid', 'Mage', 'Frost', 'MainHand', 'Both', 'Eternium Runed Blade'), +(8, 2, 16, 0, 120, 29273, 'Pre-Raid', 'Mage', 'Frost', 'OffHand', 'Both', 'Khadgar''s Knapsack'), +(8, 2, 17, 0, 120, 29350, 'Pre-Raid', 'Mage', 'Frost', 'Ranged', 'Both', 'The Black Stalk'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 125, 29076, 'Phase 1', 'Mage', 'Frost', 'Head', 'Both', 'Collar of the Aldor'), +(8, 2, 1, 0, 125, 28530, 'Phase 1', 'Mage', 'Frost', 'Neck', 'Both', 'Brooch of Unquenchable Fury'), +(8, 2, 2, 0, 125, 21869, 'Phase 1', 'Mage', 'Frost', 'Shoulders', 'Both', 'Frozen Shadoweave Shoulders'), +(8, 2, 4, 0, 125, 21871, 'Phase 1', 'Mage', 'Frost', 'Chest', 'Both', 'Frozen Shadoweave Robe'), +(8, 2, 5, 0, 125, 24256, 'Phase 1', 'Mage', 'Frost', 'Waist', 'Both', 'Girdle of Ruination'), +(8, 2, 6, 0, 125, 29078, 'Phase 1', 'Mage', 'Frost', 'Legs', 'Both', 'Legwraps of the Aldor'), +(8, 2, 7, 0, 125, 21870, 'Phase 1', 'Mage', 'Frost', 'Feet', 'Both', 'Frozen Shadoweave Boots'), +(8, 2, 8, 0, 125, 28515, 'Phase 1', 'Mage', 'Frost', 'Wrists', 'Both', 'Bands of Nefarious Deeds'), +(8, 2, 9, 0, 125, 30725, 'Phase 1', 'Mage', 'Frost', 'Hands', 'Both', 'Anger-Spark Gloves'), +(8, 2, 10, 0, 125, 28793, 'Phase 1', 'Mage', 'Frost', 'Finger1', 'Both', 'Band of Crimson Fury'), +(8, 2, 11, 0, 125, 29287, 'Phase 1', 'Mage', 'Frost', 'Finger2', 'Both', 'Violet Signet of the Archmage'), +(8, 2, 12, 0, 125, 29370, 'Phase 1', 'Mage', 'Frost', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(8, 2, 13, 0, 125, 27683, 'Phase 1', 'Mage', 'Frost', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(8, 2, 14, 0, 125, 28766, 'Phase 1', 'Mage', 'Frost', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(8, 2, 15, 0, 125, 28770, 'Phase 1', 'Mage', 'Frost', 'MainHand', 'Both', 'Nathrezim Mindblade'), +(8, 2, 16, 0, 125, 29269, 'Phase 1', 'Mage', 'Frost', 'OffHand', 'Both', 'Sapphiron''s Wing Bone'), +(8, 2, 17, 0, 125, 28783, 'Phase 1', 'Mage', 'Frost', 'Ranged', 'Both', 'Eredar Wand of Obliteration'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 200, 37294, 'Pre-Raid', 'Mage', 'Frost', 'Head', 'Both', 'Crown of Unbridled Magic'), +(8, 2, 1, 0, 200, 39472, 'Pre-Raid', 'Mage', 'Frost', 'Neck', 'Both', 'Chain of Latent Energies'), +(8, 2, 2, 0, 200, 37673, 'Pre-Raid', 'Mage', 'Frost', 'Shoulders', 'Both', 'Dark Runic Mantle'), +(8, 2, 4, 0, 200, 39492, 'Pre-Raid', 'Mage', 'Frost', 'Chest', 'Both', 'Heroes'' Frostfire Robe'), +(8, 2, 5, 0, 200, 40696, 'Pre-Raid', 'Mage', 'Frost', 'Waist', 'Both', 'Plush Sash of Guzbah'), +(8, 2, 6, 0, 200, 37854, 'Pre-Raid', 'Mage', 'Frost', 'Legs', 'Both', 'Woven Bracae Leggings'), +(8, 2, 7, 0, 200, 44202, 'Pre-Raid', 'Mage', 'Frost', 'Feet', 'Both', 'Sandals of Crimson Fury'), +(8, 2, 8, 0, 200, 37361, 'Pre-Raid', 'Mage', 'Frost', 'Wrists', 'Both', 'Cuffs of Winged Levitation'), +(8, 2, 9, 0, 200, 39495, 'Pre-Raid', 'Mage', 'Frost', 'Hands', 'Both', 'Heroes'' Frostfire Gloves'), +(8, 2, 10, 0, 200, 40585, 'Pre-Raid', 'Mage', 'Frost', 'Finger1', 'Both', 'Signet of the Kirin Tor'), +(8, 2, 12, 0, 200, 37660, 'Pre-Raid', 'Mage', 'Frost', 'Trinket1', 'Both', 'Forge Ember'), +(8, 2, 14, 0, 200, 41610, 'Pre-Raid', 'Mage', 'Frost', 'Back', 'Both', 'Deathchill Cloak'), +(8, 2, 15, 0, 200, 45085, 'Pre-Raid', 'Mage', 'Frost', 'MainHand', 'Both', 'Titansteel Spellblade'), +(8, 2, 17, 0, 200, 37177, 'Pre-Raid', 'Mage', 'Frost', 'Ranged', 'Both', 'Wand of the San''layn'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 1, 0, 224, 44661, 'Phase 1', 'Mage', 'Frost', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(8, 2, 2, 0, 224, 40419, 'Phase 1', 'Mage', 'Frost', 'Shoulders', 'Both', 'Valorous Frostfire Shoulderpads'), +(8, 2, 4, 0, 224, 40418, 'Phase 1', 'Mage', 'Frost', 'Chest', 'Both', 'Valorous Frostfire Robe'), +(8, 2, 5, 0, 224, 40561, 'Phase 1', 'Mage', 'Frost', 'Waist', 'Both', 'Leash of Heedless Magic'), +(8, 2, 6, 0, 224, 40560, 'Phase 1', 'Mage', 'Frost', 'Legs', 'Both', 'Leggings of the Wanton Spellcaster'), +(8, 2, 7, 0, 224, 40558, 'Phase 1', 'Mage', 'Frost', 'Feet', 'Both', 'Arcanic Tramplers'), +(8, 2, 8, 0, 224, 44008, 'Phase 1', 'Mage', 'Frost', 'Wrists', 'Both', 'Unsullied Cuffs'), +(8, 2, 9, 0, 224, 40415, 'Phase 1', 'Mage', 'Frost', 'Hands', 'Both', 'Valorous Frostfire Gloves'), +(8, 2, 10, 0, 224, 40719, 'Phase 1', 'Mage', 'Frost', 'Finger1', 'Both', 'Band of Channeled Magic'), +(8, 2, 12, 0, 224, 40255, 'Phase 1', 'Mage', 'Frost', 'Trinket1', 'Both', 'Dying Curse'), +(8, 2, 14, 0, 224, 44005, 'Phase 1', 'Mage', 'Frost', 'Back', 'Both', 'Pennant Cloak'), +(8, 2, 15, 0, 224, 40396, 'Phase 1', 'Mage', 'Frost', 'MainHand', 'Both', 'The Turning Tide'), +(8, 2, 17, 0, 224, 39426, 'Phase 1', 'Mage', 'Frost', 'Ranged', 'Both', 'Wand of the Archlich'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 245, 46129, 'Phase 2', 'Mage', 'Frost', 'Head', 'Both', 'Conqueror''s Kirin Tor Hood'), +(8, 2, 1, 0, 245, 45133, 'Phase 2', 'Mage', 'Frost', 'Neck', 'Both', 'Pendant of Fiery Havoc'), +(8, 2, 2, 0, 245, 46134, 'Phase 2', 'Mage', 'Frost', 'Shoulders', 'Both', 'Conqueror''s Kirin Tor Shoulderpads'), +(8, 2, 4, 0, 245, 46130, 'Phase 2', 'Mage', 'Frost', 'Chest', 'Both', 'Conqueror''s Kirin Tor Tunic'), +(8, 2, 5, 0, 245, 45619, 'Phase 2', 'Mage', 'Frost', 'Waist', 'Both', 'Starwatcher''s Binding'), +(8, 2, 6, 0, 245, 46133, 'Phase 2', 'Mage', 'Frost', 'Legs', 'Both', 'Conqueror''s Kirin Tor Leggings'), +(8, 2, 7, 0, 245, 45135, 'Phase 2', 'Mage', 'Frost', 'Feet', 'Both', 'Boots of Fiery Resolution'), +(8, 2, 8, 0, 245, 45446, 'Phase 2', 'Mage', 'Frost', 'Wrists', 'Both', 'Grasps of Reason'), +(8, 2, 9, 0, 245, 45665, 'Phase 2', 'Mage', 'Frost', 'Hands', 'Both', 'Pharos Gloves'), +(8, 2, 10, 0, 245, 46046, 'Phase 2', 'Mage', 'Frost', 'Finger1', 'Both', 'Nebula Band'), +(8, 2, 12, 0, 245, 45466, 'Phase 2', 'Mage', 'Frost', 'Trinket1', 'Both', 'Scale of Fates'), +(8, 2, 14, 0, 245, 45242, 'Phase 2', 'Mage', 'Frost', 'Back', 'Both', 'Drape of Mortal Downfall'), +(8, 2, 15, 0, 245, 45620, 'Phase 2', 'Mage', 'Frost', 'MainHand', 'Both', 'Starshard Edge'), +(8, 2, 17, 0, 245, 45511, 'Phase 2', 'Mage', 'Frost', 'Ranged', 'Both', 'Scepter of Lost Souls'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 1, 258, 47761, 'Phase 3', 'Mage', 'Frost', 'Head', 'Alliance', 'Khadgar''s Hood of Triumph'), +(8, 2, 2, 1, 258, 47758, 'Phase 3', 'Mage', 'Frost', 'Shoulders', 'Alliance', 'Khadgar''s Shoulderpads of Triumph'), +(8, 2, 4, 1, 258, 47129, 'Phase 3', 'Mage', 'Frost', 'Chest', 'Alliance', 'Skyweaver Robes'), +(8, 2, 5, 1, 258, 47419, 'Phase 3', 'Mage', 'Frost', 'Waist', 'Alliance', 'Belt of the Tenebrous Mist'), +(8, 2, 6, 1, 258, 47760, 'Phase 3', 'Mage', 'Frost', 'Legs', 'Alliance', 'Khadgar''s Leggings of Triumph'), +(8, 2, 7, 1, 258, 47097, 'Phase 3', 'Mage', 'Frost', 'Feet', 'Alliance', 'Boots of the Mourning Widow'), +(8, 2, 8, 1, 258, 47143, 'Phase 3', 'Mage', 'Frost', 'Wrists', 'Alliance', 'Bindings of Dark Essence'), +(8, 2, 9, 1, 258, 47762, 'Phase 3', 'Mage', 'Frost', 'Hands', 'Alliance', 'Khadgar''s Gauntlets of Triumph'), +(8, 2, 10, 1, 258, 47489, 'Phase 3', 'Mage', 'Frost', 'Finger1', 'Alliance', 'Lurid Manifestation'), +(8, 2, 12, 1, 258, 47188, 'Phase 3', 'Mage', 'Frost', 'Trinket1', 'Alliance', 'Reign of the Unliving'), +(8, 2, 14, 1, 258, 47552, 'Phase 3', 'Mage', 'Frost', 'Back', 'Alliance', 'Jaina''s Radiance'), +(8, 2, 17, 0, 258, 45294, 'Phase 3', 'Mage', 'Frost', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 264, 51281, 'Phase 4', 'Mage', 'Frost', 'Head', 'Both', 'Sanctified Bloodmage Hood'), +(8, 2, 1, 0, 264, 50724, 'Phase 4', 'Mage', 'Frost', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(8, 2, 2, 0, 264, 51284, 'Phase 4', 'Mage', 'Frost', 'Shoulders', 'Both', 'Sanctified Bloodmage Shoulderpads'), +(8, 2, 4, 0, 264, 50717, 'Phase 4', 'Mage', 'Frost', 'Chest', 'Both', 'Sanguine Silk Robes'), +(8, 2, 5, 0, 264, 50613, 'Phase 4', 'Mage', 'Frost', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(8, 2, 6, 0, 264, 51282, 'Phase 4', 'Mage', 'Frost', 'Legs', 'Both', 'Sanctified Bloodmage Leggings'), +(8, 2, 7, 0, 264, 50699, 'Phase 4', 'Mage', 'Frost', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(8, 2, 8, 0, 264, 50651, 'Phase 4', 'Mage', 'Frost', 'Wrists', 'Both', 'The Lady''s Brittle Bracers'), +(8, 2, 9, 0, 264, 51280, 'Phase 4', 'Mage', 'Frost', 'Hands', 'Both', 'Sanctified Bloodmage Gloves'), +(8, 2, 10, 0, 264, 50398, 'Phase 4', 'Mage', 'Frost', 'Finger1', 'Both', 'Ashen Band of Endless Destruction'), +(8, 2, 12, 0, 264, 50365, 'Phase 4', 'Mage', 'Frost', 'Trinket1', 'Both', 'Phylactery of the Nameless Lich'), +(8, 2, 14, 0, 264, 50628, 'Phase 4', 'Mage', 'Frost', 'Back', 'Both', 'Frostbinder''s Shredded Cape'), +(8, 2, 15, 0, 264, 50732, 'Phase 4', 'Mage', 'Frost', 'MainHand', 'Both', 'Bloodsurge, Kel''Thuzad''s Blade of Agony'), +(8, 2, 17, 0, 264, 50684, 'Phase 4', 'Mage', 'Frost', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(8, 2, 0, 0, 290, 51281, 'Phase 5', 'Mage', 'Frost', 'Head', 'Both', 'Sanctified Bloodmage Hood'), +(8, 2, 1, 0, 290, 50182, 'Phase 5', 'Mage', 'Frost', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(8, 2, 2, 0, 290, 51284, 'Phase 5', 'Mage', 'Frost', 'Shoulders', 'Both', 'Sanctified Bloodmage Shoulderpads'), +(8, 2, 4, 0, 290, 51283, 'Phase 5', 'Mage', 'Frost', 'Chest', 'Both', 'Sanctified Bloodmage Robe'), +(8, 2, 5, 0, 290, 50613, 'Phase 5', 'Mage', 'Frost', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(8, 2, 6, 0, 290, 50694, 'Phase 5', 'Mage', 'Frost', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(8, 2, 7, 0, 290, 50699, 'Phase 5', 'Mage', 'Frost', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(8, 2, 8, 0, 290, 54582, 'Phase 5', 'Mage', 'Frost', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(8, 2, 9, 0, 290, 51280, 'Phase 5', 'Mage', 'Frost', 'Hands', 'Both', 'Sanctified Bloodmage Gloves'), +(8, 2, 10, 0, 290, 50614, 'Phase 5', 'Mage', 'Frost', 'Finger1', 'Both', 'Loop of the Endless Labyrinth'), +(8, 2, 11, 0, 290, 50398, 'Phase 5', 'Mage', 'Frost', 'Finger2', 'Both', 'Ashen Band of Endless Destruction'), +(8, 2, 12, 0, 290, 54588, 'Phase 5', 'Mage', 'Frost', 'Trinket1', 'Both', 'Charred Twilight Scale'), +(8, 2, 13, 0, 290, 50348, 'Phase 5', 'Mage', 'Frost', 'Trinket2', 'Both', 'Dislodged Foreign Object'), +(8, 2, 14, 0, 290, 54583, 'Phase 5', 'Mage', 'Frost', 'Back', 'Both', 'Cloak of Burning Dusk'), +(8, 2, 15, 0, 290, 50732, 'Phase 5', 'Mage', 'Frost', 'MainHand', 'Both', 'Bloodsurge, Kel''Thuzad''s Blade of Agony'), +(8, 2, 16, 0, 290, 50719, 'Phase 5', 'Mage', 'Frost', 'OffHand', 'Both', 'Shadow Silk Spindle'), +(8, 2, 17, 0, 290, 50684, 'Phase 5', 'Mage', 'Frost', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + + +-- ============================================================ +-- Warlock (9) +-- ============================================================ +-- Affliction (tab 0) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 1, 66, 10504, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Head', 'Alliance', 'Green Lens'), +(9, 0, 0, 2, 66, 10504, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Head', 'Horde', 'Green Lens'), +(9, 0, 1, 1, 66, 18691, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Neck', 'Alliance', 'Dark Advisor''s Pendant'), +(9, 0, 1, 2, 66, 18691, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Neck', 'Horde', 'Dark Advisor''s Pendant'), +(9, 0, 2, 1, 66, 14112, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Shoulders', 'Alliance', 'Felcloth Shoulders'), +(9, 0, 2, 2, 66, 14112, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Shoulders', 'Horde', 'Felcloth Shoulders'), +(9, 0, 4, 1, 66, 14153, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Chest', 'Alliance', 'Robe of the Void'), +(9, 0, 4, 2, 66, 14153, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Chest', 'Horde', 'Robe of the Void'), +(9, 0, 5, 1, 66, 11662, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Waist', 'Alliance', 'Ban''thok Sash'), +(9, 0, 5, 2, 66, 11662, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Waist', 'Horde', 'Ban''thok Sash'), +(9, 0, 6, 1, 66, 13170, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(9, 0, 6, 2, 66, 13170, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Legs', 'Horde', 'Skyshroud Leggings'), +(9, 0, 7, 1, 66, 18735, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Feet', 'Alliance', 'Maleki''s Footwraps'), +(9, 0, 7, 2, 66, 18735, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Feet', 'Horde', 'Maleki''s Footwraps'), +(9, 0, 8, 1, 66, 11766, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(9, 0, 8, 2, 66, 11766, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(9, 0, 9, 1, 66, 13253, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Hands', 'Alliance', 'Hands of Power'), +(9, 0, 9, 2, 66, 13253, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Hands', 'Horde', 'Hands of Power'), +(9, 0, 10, 1, 66, 12543, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(9, 0, 10, 2, 66, 12545, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(9, 0, 11, 1, 66, 13001, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(9, 0, 11, 2, 66, 13001, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Finger2', 'Horde', 'Maiden''s Circle'), +(9, 0, 12, 1, 66, 12930, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(9, 0, 12, 2, 66, 12930, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Trinket1', 'Horde', 'Briarwood Reed'), +(9, 0, 13, 1, 66, 13968, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Trinket2', 'Alliance', 'Eye of the Beast'), +(9, 0, 13, 2, 66, 13968, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Trinket2', 'Horde', 'Eye of the Beast'), +(9, 0, 14, 1, 66, 13386, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Back', 'Alliance', 'Archivist Cape'), +(9, 0, 14, 2, 66, 13386, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Back', 'Horde', 'Archivist Cape'), +(9, 0, 15, 1, 66, 13964, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'MainHand', 'Alliance', 'Witchblade'), +(9, 0, 15, 2, 66, 13964, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'MainHand', 'Horde', 'Witchblade'), +(9, 0, 16, 1, 66, 10796, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'OffHand', 'Alliance', 'Drakestone'), +(9, 0, 16, 2, 66, 10796, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'OffHand', 'Horde', 'Drakestone'), +(9, 0, 17, 1, 66, 13396, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Ranged', 'Alliance', 'Skul''s Ghastly Touch'), +(9, 0, 17, 2, 66, 13396, 'Phase 1 (Pre-Raid)', 'Warlock', 'Affliction', 'Ranged', 'Horde', 'Skul''s Ghastly Touch'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 1, 76, 23310, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Head', 'Alliance', 'Lieutenant Commander''s Dreadweave Cowl'), +(9, 0, 0, 2, 76, 23310, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Head', 'Horde', 'Lieutenant Commander''s Dreadweave Cowl'), +(9, 0, 1, 1, 76, 18691, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Neck', 'Alliance', 'Dark Advisor''s Pendant'), +(9, 0, 1, 2, 76, 18691, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Neck', 'Horde', 'Dark Advisor''s Pendant'), +(9, 0, 2, 1, 76, 23311, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Shoulders', 'Alliance', 'Lieutenant Commander''s Dreadweave Spaulders'), +(9, 0, 2, 2, 76, 23311, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Shoulders', 'Horde', 'Lieutenant Commander''s Dreadweave Spaulders'), +(9, 0, 4, 1, 76, 14153, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Chest', 'Alliance', 'Robe of the Void'), +(9, 0, 4, 2, 76, 14153, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Chest', 'Horde', 'Robe of the Void'), +(9, 0, 5, 1, 76, 11662, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Waist', 'Alliance', 'Ban''thok Sash'), +(9, 0, 5, 2, 76, 11662, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Waist', 'Horde', 'Ban''thok Sash'), +(9, 0, 6, 1, 76, 13170, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(9, 0, 6, 2, 76, 13170, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Legs', 'Horde', 'Skyshroud Leggings'), +(9, 0, 7, 1, 76, 18735, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Feet', 'Alliance', 'Maleki''s Footwraps'), +(9, 0, 7, 2, 76, 18735, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Feet', 'Horde', 'Maleki''s Footwraps'), +(9, 0, 8, 1, 76, 11766, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(9, 0, 8, 2, 76, 11766, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(9, 0, 9, 1, 76, 18407, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Hands', 'Alliance', 'Felcloth Gloves'), +(9, 0, 9, 2, 76, 18407, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Hands', 'Horde', 'Felcloth Gloves'), +(9, 0, 10, 1, 76, 12543, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(9, 0, 10, 2, 76, 12545, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(9, 0, 11, 1, 76, 13001, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(9, 0, 11, 2, 76, 13001, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Finger2', 'Horde', 'Maiden''s Circle'), +(9, 0, 12, 1, 76, 12930, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(9, 0, 12, 2, 76, 12930, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Trinket1', 'Horde', 'Briarwood Reed'), +(9, 0, 13, 1, 76, 18467, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Trinket2', 'Alliance', 'Royal Seal of Eldre''Thalas'), +(9, 0, 13, 2, 76, 18467, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Trinket2', 'Horde', 'Royal Seal of Eldre''Thalas'), +(9, 0, 14, 1, 76, 13386, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Back', 'Alliance', 'Archivist Cape'), +(9, 0, 14, 2, 76, 13386, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Back', 'Horde', 'Archivist Cape'), +(9, 0, 15, 1, 76, 18372, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'MainHand', 'Alliance', 'Blade of the New Moon'), +(9, 0, 15, 2, 76, 18372, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'MainHand', 'Horde', 'Blade of the New Moon'), +(9, 0, 16, 1, 76, 10796, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'OffHand', 'Alliance', 'Drakestone'), +(9, 0, 16, 2, 76, 10796, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'OffHand', 'Horde', 'Drakestone'), +(9, 0, 17, 1, 76, 13396, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Ranged', 'Alliance', 'Skul''s Ghastly Touch'), +(9, 0, 17, 2, 76, 13396, 'Phase 2 (Pre-Raid)', 'Warlock', 'Affliction', 'Ranged', 'Horde', 'Skul''s Ghastly Touch'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 78, 23310, 'Phase 2', 'Warlock', 'Affliction', 'Head', 'Both', 'Lieutenant Commander''s Dreadweave Cowl'), +(9, 0, 1, 0, 78, 18814, 'Phase 2', 'Warlock', 'Affliction', 'Neck', 'Both', 'Choker of the Fire Lord'), +(9, 0, 2, 0, 78, 23311, 'Phase 2', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Lieutenant Commander''s Dreadweave Spaulders'), +(9, 0, 4, 0, 78, 19145, 'Phase 2', 'Warlock', 'Affliction', 'Chest', 'Both', 'Robe of Volatile Power'), +(9, 0, 5, 0, 78, 18809, 'Phase 2', 'Warlock', 'Affliction', 'Waist', 'Both', 'Sash of Whispered Secrets'), +(9, 0, 6, 0, 78, 19133, 'Phase 2', 'Warlock', 'Affliction', 'Legs', 'Both', 'Fel Infused Leggings'), +(9, 0, 7, 0, 78, 19131, 'Phase 2', 'Warlock', 'Affliction', 'Feet', 'Both', 'Snowblind Shoes'), +(9, 0, 8, 0, 78, 11766, 'Phase 2', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Flameweave Cuffs'), +(9, 0, 9, 0, 78, 18407, 'Phase 2', 'Warlock', 'Affliction', 'Hands', 'Both', 'Felcloth Gloves'), +(9, 0, 10, 0, 78, 19147, 'Phase 2', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Ring of Spell Power'), +(9, 0, 11, 0, 78, 19147, 'Phase 2', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Ring of Spell Power'), +(9, 0, 12, 0, 78, 12930, 'Phase 2', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Briarwood Reed'), +(9, 0, 13, 0, 78, 18820, 'Phase 2', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(9, 0, 14, 0, 78, 13386, 'Phase 2', 'Warlock', 'Affliction', 'Back', 'Both', 'Archivist Cape'), +(9, 0, 15, 0, 78, 17103, 'Phase 2', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Azuresong Mageblade'), +(9, 0, 16, 0, 78, 10796, 'Phase 2', 'Warlock', 'Affliction', 'OffHand', 'Both', 'Drakestone'), +(9, 0, 17, 0, 78, 13396, 'Phase 2', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Skul''s Ghastly Touch'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 83, 19375, 'Phase 3', 'Warlock', 'Affliction', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(9, 0, 1, 0, 83, 18814, 'Phase 3', 'Warlock', 'Affliction', 'Neck', 'Both', 'Choker of the Fire Lord'), +(9, 0, 2, 0, 83, 19370, 'Phase 3', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(9, 0, 4, 0, 83, 19145, 'Phase 3', 'Warlock', 'Affliction', 'Chest', 'Both', 'Robe of Volatile Power'), +(9, 0, 5, 0, 83, 18809, 'Phase 3', 'Warlock', 'Affliction', 'Waist', 'Both', 'Sash of Whispered Secrets'), +(9, 0, 6, 0, 83, 19133, 'Phase 3', 'Warlock', 'Affliction', 'Legs', 'Both', 'Fel Infused Leggings'), +(9, 0, 7, 0, 83, 19131, 'Phase 3', 'Warlock', 'Affliction', 'Feet', 'Both', 'Snowblind Shoes'), +(9, 0, 8, 0, 83, 19374, 'Phase 3', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Bracers of Arcane Accuracy'), +(9, 0, 9, 0, 83, 19407, 'Phase 3', 'Warlock', 'Affliction', 'Hands', 'Both', 'Ebony Flame Gloves'), +(9, 0, 11, 0, 83, 19434, 'Phase 3', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Band of Dark Dominion'), +(9, 0, 12, 0, 83, 19379, 'Phase 3', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(9, 0, 13, 0, 83, 18820, 'Phase 3', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(9, 0, 14, 0, 83, 19378, 'Phase 3', 'Warlock', 'Affliction', 'Back', 'Both', 'Cloak of the Brood Lord'), +(9, 0, 15, 0, 83, 19356, 'Phase 3', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Staff of the Shadow Flame'), +(9, 0, 17, 0, 83, 13396, 'Phase 3', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Skul''s Ghastly Touch'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 88, 21337, 'Phase 5', 'Warlock', 'Affliction', 'Head', 'Both', 'Doomcaller''s Circlet'), +(9, 0, 1, 0, 88, 21608, 'Phase 5', 'Warlock', 'Affliction', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(9, 0, 2, 0, 88, 21335, 'Phase 5', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Doomcaller''s Mantle'), +(9, 0, 4, 0, 88, 19682, 'Phase 5', 'Warlock', 'Affliction', 'Chest', 'Both', 'Bloodvine Vest'), +(9, 0, 5, 0, 88, 22730, 'Phase 5', 'Warlock', 'Affliction', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(9, 0, 6, 0, 88, 19683, 'Phase 5', 'Warlock', 'Affliction', 'Legs', 'Both', 'Bloodvine Leggings'), +(9, 0, 7, 0, 88, 19684, 'Phase 5', 'Warlock', 'Affliction', 'Feet', 'Both', 'Bloodvine Boots'), +(9, 0, 8, 0, 88, 21186, 'Phase 5', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Rockfury Bracers'), +(9, 0, 9, 0, 88, 21585, 'Phase 5', 'Warlock', 'Affliction', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(9, 0, 10, 0, 88, 21417, 'Phase 5', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Ring of Unspoken Names'), +(9, 0, 11, 0, 88, 21709, 'Phase 5', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Ring of the Fallen God'), +(9, 0, 12, 0, 88, 19379, 'Phase 5', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(9, 0, 13, 0, 88, 18820, 'Phase 5', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(9, 0, 14, 0, 88, 22731, 'Phase 5', 'Warlock', 'Affliction', 'Back', 'Both', 'Cloak of the Devoured'), +(9, 0, 15, 0, 88, 21622, 'Phase 5', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Sharpened Silithid Femur'), +(9, 0, 16, 0, 88, 21597, 'Phase 5', 'Warlock', 'Affliction', 'OffHand', 'Both', 'Royal Scepter of Vek''lor'), +(9, 0, 17, 0, 88, 21603, 'Phase 5', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Wand of Qiraji Nobility'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 92, 22506, 'Phase 6', 'Warlock', 'Affliction', 'Head', 'Both', 'Plagueheart Circlet'), +(9, 0, 1, 0, 92, 21608, 'Phase 6', 'Warlock', 'Affliction', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(9, 0, 2, 0, 92, 22507, 'Phase 6', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Plagueheart Shoulderpads'), +(9, 0, 4, 0, 92, 22504, 'Phase 6', 'Warlock', 'Affliction', 'Chest', 'Both', 'Plagueheart Robe'), +(9, 0, 5, 0, 92, 22730, 'Phase 6', 'Warlock', 'Affliction', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(9, 0, 6, 0, 92, 23070, 'Phase 6', 'Warlock', 'Affliction', 'Legs', 'Both', 'Leggings of Polarity'), +(9, 0, 7, 0, 92, 22508, 'Phase 6', 'Warlock', 'Affliction', 'Feet', 'Both', 'Plagueheart Sandals'), +(9, 0, 8, 0, 92, 21186, 'Phase 6', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Rockfury Bracers'), +(9, 0, 9, 0, 92, 21585, 'Phase 6', 'Warlock', 'Affliction', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(9, 0, 10, 0, 92, 23031, 'Phase 6', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Band of the Inevitable'), +(9, 0, 11, 0, 92, 21709, 'Phase 6', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Ring of the Fallen God'), +(9, 0, 12, 0, 92, 19379, 'Phase 6', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(9, 0, 13, 0, 92, 23046, 'Phase 6', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'The Restrained Essence of Sapphiron'), +(9, 0, 14, 0, 92, 23050, 'Phase 6', 'Warlock', 'Affliction', 'Back', 'Both', 'Cloak of the Necropolis'), +(9, 0, 15, 0, 92, 22807, 'Phase 6', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Wraith Blade'), +(9, 0, 16, 0, 92, 23049, 'Phase 6', 'Warlock', 'Affliction', 'OffHand', 'Both', 'Sapphiron''s Left Eye'), +(9, 0, 17, 0, 92, 22820, 'Phase 6', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Wand of Fates'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 120, 24266, 'Pre-Raid', 'Warlock', 'Affliction', 'Head', 'Both', 'Spellstrike Hood'), +(9, 0, 1, 0, 120, 28134, 'Pre-Raid', 'Warlock', 'Affliction', 'Neck', 'Both', 'Brooch of Heightened Potential'), +(9, 0, 2, 0, 120, 21869, 'Pre-Raid', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Frozen Shadoweave Shoulders'), +(9, 0, 4, 0, 120, 21871, 'Pre-Raid', 'Warlock', 'Affliction', 'Chest', 'Both', 'Frozen Shadoweave Robe'), +(9, 0, 5, 0, 120, 24256, 'Pre-Raid', 'Warlock', 'Affliction', 'Waist', 'Both', 'Girdle of Ruination'), +(9, 0, 6, 0, 120, 24262, 'Pre-Raid', 'Warlock', 'Affliction', 'Legs', 'Both', 'Spellstrike Pants'), +(9, 0, 7, 0, 120, 21870, 'Pre-Raid', 'Warlock', 'Affliction', 'Feet', 'Both', 'Frozen Shadoweave Boots'), +(9, 0, 8, 0, 120, 24250, 'Pre-Raid', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Bracers of Havok'), +(9, 0, 9, 0, 120, 24450, 'Pre-Raid', 'Warlock', 'Affliction', 'Hands', 'Both', 'Manaspark Gloves'), +(9, 0, 10, 0, 120, 29172, 'Pre-Raid', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Ashyen''s Gift'), +(9, 0, 11, 0, 120, 28227, 'Pre-Raid', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Sparking Arcanite Ring'), +(9, 0, 12, 0, 120, 29370, 'Pre-Raid', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 0, 13, 0, 120, 29132, 'Pre-Raid', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'Scryer''s Bloodgem'), +(9, 0, 14, 0, 120, 27981, 'Pre-Raid', 'Warlock', 'Affliction', 'Back', 'Both', 'Sethekk Oracle Cloak'), +(9, 0, 15, 0, 120, 23554, 'Pre-Raid', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Eternium Runed Blade'), +(9, 0, 16, 0, 120, 29273, 'Pre-Raid', 'Warlock', 'Affliction', 'OffHand', 'Both', 'Khadgar''s Knapsack'), +(9, 0, 17, 0, 120, 29350, 'Pre-Raid', 'Warlock', 'Affliction', 'Ranged', 'Both', 'The Black Stalk'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 125, 28963, 'Phase 1', 'Warlock', 'Affliction', 'Head', 'Both', 'Voidheart Crown'), +(9, 0, 1, 0, 125, 28530, 'Phase 1', 'Warlock', 'Affliction', 'Neck', 'Both', 'Brooch of Unquenchable Fury'), +(9, 0, 2, 0, 125, 28967, 'Phase 1', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Voidheart Mantle'), +(9, 0, 4, 0, 125, 28964, 'Phase 1', 'Warlock', 'Affliction', 'Chest', 'Both', 'Voidheart Robe'), +(9, 0, 5, 0, 125, 24256, 'Phase 1', 'Warlock', 'Affliction', 'Waist', 'Both', 'Girdle of Ruination'), +(9, 0, 6, 0, 125, 30734, 'Phase 1', 'Warlock', 'Affliction', 'Legs', 'Both', 'Leggings of the Seventh Circle'), +(9, 0, 7, 0, 125, 21870, 'Phase 1', 'Warlock', 'Affliction', 'Feet', 'Both', 'Frozen Shadoweave Boots'), +(9, 0, 8, 0, 125, 24250, 'Phase 1', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Bracers of Havok'), +(9, 0, 9, 0, 125, 28968, 'Phase 1', 'Warlock', 'Affliction', 'Hands', 'Both', 'Voidheart Gloves'), +(9, 0, 10, 0, 125, 29172, 'Phase 1', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Ashyen''s Gift'), +(9, 0, 11, 0, 125, 28793, 'Phase 1', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Band of Crimson Fury'), +(9, 0, 12, 0, 125, 29370, 'Phase 1', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 0, 13, 0, 125, 27683, 'Phase 1', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(9, 0, 14, 0, 125, 28766, 'Phase 1', 'Warlock', 'Affliction', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(9, 0, 15, 0, 125, 30723, 'Phase 1', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Talon of the Tempest'), +(9, 0, 16, 0, 125, 29272, 'Phase 1', 'Warlock', 'Affliction', 'OffHand', 'Both', 'Orb of the Soul-Eater'), +(9, 0, 17, 0, 125, 28783, 'Phase 1', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Eredar Wand of Obliteration'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 141, 32494, 'Phase 2', 'Warlock', 'Affliction', 'Head', 'Both', 'Destruction Holo-gogs'), +(9, 0, 1, 0, 141, 30015, 'Phase 2', 'Warlock', 'Affliction', 'Neck', 'Both', 'The Sun King''s Talisman'), +(9, 0, 2, 0, 141, 28967, 'Phase 2', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Voidheart Mantle'), +(9, 0, 4, 0, 141, 30107, 'Phase 2', 'Warlock', 'Affliction', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(9, 0, 5, 0, 141, 30038, 'Phase 2', 'Warlock', 'Affliction', 'Waist', 'Both', 'Belt of Blasting'), +(9, 0, 6, 0, 141, 30213, 'Phase 2', 'Warlock', 'Affliction', 'Legs', 'Both', 'Leggings of the Corruptor'), +(9, 0, 7, 0, 141, 30037, 'Phase 2', 'Warlock', 'Affliction', 'Feet', 'Both', 'Boots of Blasting'), +(9, 0, 8, 0, 141, 29918, 'Phase 2', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Mindstorm Wristbands'), +(9, 0, 9, 0, 141, 28968, 'Phase 2', 'Warlock', 'Affliction', 'Hands', 'Both', 'Voidheart Gloves'), +(9, 0, 10, 0, 141, 29302, 'Phase 2', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Band of Eternity'), +(9, 0, 11, 0, 141, 30109, 'Phase 2', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Ring of Endless Coils'), +(9, 0, 12, 0, 141, 29370, 'Phase 2', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 0, 13, 0, 141, 27683, 'Phase 2', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(9, 0, 14, 0, 141, 28766, 'Phase 2', 'Warlock', 'Affliction', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(9, 0, 15, 0, 141, 30095, 'Phase 2', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Fang of the Leviathan'), +(9, 0, 16, 0, 141, 30049, 'Phase 2', 'Warlock', 'Affliction', 'OffHand', 'Both', 'Fathomstone'), +(9, 0, 17, 0, 141, 29982, 'Phase 2', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Wand of the Forgotten Star'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 156, 31051, 'Phase 3', 'Warlock', 'Affliction', 'Head', 'Both', 'Hood of the Malefic'), +(9, 0, 1, 0, 156, 30015, 'Phase 3', 'Warlock', 'Affliction', 'Neck', 'Both', 'Sun King''s Talisman'), +(9, 0, 2, 0, 156, 31054, 'Phase 3', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Mantle of the Malefic'), +(9, 0, 4, 0, 156, 30107, 'Phase 3', 'Warlock', 'Affliction', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(9, 0, 5, 0, 156, 30888, 'Phase 3', 'Warlock', 'Affliction', 'Waist', 'Both', 'Anetheron''s Noose'), +(9, 0, 6, 0, 156, 31053, 'Phase 3', 'Warlock', 'Affliction', 'Legs', 'Both', 'Leggings of the Malefic'), +(9, 0, 7, 0, 156, 32239, 'Phase 3', 'Warlock', 'Affliction', 'Feet', 'Both', 'Slippers of the Seacaller'), +(9, 0, 8, 0, 156, 32586, 'Phase 3', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(9, 0, 9, 0, 156, 31050, 'Phase 3', 'Warlock', 'Affliction', 'Hands', 'Both', 'Gloves of the Malefic'), +(9, 0, 10, 0, 156, 32527, 'Phase 3', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Ring of Ancient Knowledge'), +(9, 0, 11, 0, 156, 32247, 'Phase 3', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Ring of Captured Storms'), +(9, 0, 12, 0, 156, 29370, 'Phase 3', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 0, 13, 0, 156, 32483, 'Phase 3', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(9, 0, 14, 0, 156, 32590, 'Phase 3', 'Warlock', 'Affliction', 'Back', 'Both', 'Nethervoid Cloak'), +(9, 0, 15, 0, 156, 32374, 'Phase 3', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Zhar''doom, Greatstaff of the Devourer'), +(9, 0, 17, 0, 156, 29982, 'Phase 3', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Wand of the Forgotten Star'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 164, 31051, 'Phase 4', 'Warlock', 'Affliction', 'Head', 'Both', 'Hood of the Malefic'), +(9, 0, 1, 0, 164, 30015, 'Phase 4', 'Warlock', 'Affliction', 'Neck', 'Both', 'Sun King''s Talisman'), +(9, 0, 2, 0, 164, 31054, 'Phase 4', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Mantle of the Malefic'), +(9, 0, 4, 0, 164, 30107, 'Phase 4', 'Warlock', 'Affliction', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(9, 0, 5, 0, 164, 30888, 'Phase 4', 'Warlock', 'Affliction', 'Waist', 'Both', 'Anetheron''s Noose'), +(9, 0, 6, 0, 164, 31053, 'Phase 4', 'Warlock', 'Affliction', 'Legs', 'Both', 'Leggings of the Malefic'), +(9, 0, 7, 0, 164, 32239, 'Phase 4', 'Warlock', 'Affliction', 'Feet', 'Both', 'Slippers of the Seacaller'), +(9, 0, 8, 0, 164, 32586, 'Phase 4', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(9, 0, 9, 0, 164, 31050, 'Phase 4', 'Warlock', 'Affliction', 'Hands', 'Both', 'Gloves of the Malefic'), +(9, 0, 10, 0, 164, 33497, 'Phase 4', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Mana Attuned Band'), +(9, 0, 11, 0, 164, 32247, 'Phase 4', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Ring of Captured Storms'), +(9, 0, 12, 0, 164, 33829, 'Phase 4', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Hex Shrunken Head'), +(9, 0, 13, 0, 164, 32483, 'Phase 4', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(9, 0, 14, 0, 164, 32590, 'Phase 4', 'Warlock', 'Affliction', 'Back', 'Both', 'Nethervoid Cloak'), +(9, 0, 15, 0, 164, 32374, 'Phase 4', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Zhar''doom, Greatstaff of the Devourer'), +(9, 0, 16, 0, 164, 34179, 'Phase 5', 'Warlock', 'Affliction', 'OffHand', 'Both', 'Heart of the Pit'), +(9, 0, 17, 0, 164, 33192, 'Phase 4', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Carved Witch Doctor''s Stick'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 200, 44910, 'Pre-Raid', 'Warlock', 'Affliction', 'Head', 'Both', 'Titan-Forged Hood of Dominance'), +(9, 0, 1, 0, 200, 40680, 'Pre-Raid', 'Warlock', 'Affliction', 'Neck', 'Both', 'Encircling Burnished Gold Chains'), +(9, 0, 2, 0, 200, 34210, 'Pre-Raid', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Amice of the Convoker'), +(9, 0, 4, 0, 200, 39497, 'Pre-Raid', 'Warlock', 'Affliction', 'Chest', 'Both', 'Heroes'' Plagueheart Robe'), +(9, 0, 5, 0, 200, 40696, 'Pre-Raid', 'Warlock', 'Affliction', 'Waist', 'Both', 'Plush Sash of Guzbah'), +(9, 0, 6, 0, 200, 34181, 'Pre-Raid', 'Warlock', 'Affliction', 'Legs', 'Both', 'Leggings of Calamity'), +(9, 0, 7, 0, 200, 44202, 'Pre-Raid', 'Warlock', 'Affliction', 'Feet', 'Both', 'Sandals of Crimson Fury'), +(9, 0, 8, 0, 200, 37361, 'Pre-Raid', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Cuffs of Winged Levitation'), +(9, 0, 9, 0, 200, 42113, 'Pre-Raid', 'Warlock', 'Affliction', 'Hands', 'Both', 'Spellweave Gloves'), +(9, 0, 10, 0, 200, 43253, 'Pre-Raid', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Ring of Northern Tears'), +(9, 0, 12, 0, 200, 40682, 'Pre-Raid', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Sundial of the Exiled'), +(9, 0, 14, 0, 200, 41610, 'Pre-Raid', 'Warlock', 'Affliction', 'Back', 'Both', 'Deathchill Cloak'), +(9, 0, 17, 0, 200, 37177, 'Pre-Raid', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Wand of the San''layn'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 224, 40421, 'Phase 1', 'Warlock', 'Affliction', 'Head', 'Both', 'Valorous Plagueheart Circlet'), +(9, 0, 1, 0, 224, 44661, 'Phase 1', 'Warlock', 'Affliction', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(9, 0, 2, 0, 224, 40424, 'Phase 1', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Valorous Plagueheart Shoulderpads'), +(9, 0, 4, 0, 224, 40423, 'Phase 1', 'Warlock', 'Affliction', 'Chest', 'Both', 'Valorous Plagueheart Robe'), +(9, 0, 5, 0, 224, 40561, 'Phase 1', 'Warlock', 'Affliction', 'Waist', 'Both', 'Leash of Heedless Magic'), +(9, 0, 6, 0, 224, 40560, 'Phase 1', 'Warlock', 'Affliction', 'Legs', 'Both', 'Leggings of the Wanton Spellcaster'), +(9, 0, 7, 0, 224, 40558, 'Phase 1', 'Warlock', 'Affliction', 'Feet', 'Both', 'Arcanic Tramplers'), +(9, 0, 8, 0, 224, 44008, 'Phase 1', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Unsullied Cuffs'), +(9, 0, 9, 0, 224, 40420, 'Phase 1', 'Warlock', 'Affliction', 'Hands', 'Both', 'Valorous Plagueheart Gloves'), +(9, 0, 10, 0, 224, 40399, 'Phase 1', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Signet of Manifested Pain'), +(9, 0, 12, 0, 224, 40432, 'Phase 1', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Illustration of the Dragon Soul'), +(9, 0, 14, 0, 224, 44005, 'Phase 1', 'Warlock', 'Affliction', 'Back', 'Both', 'Pennant Cloak'), +(9, 0, 15, 0, 224, 40396, 'Phase 1', 'Warlock', 'Affliction', 'MainHand', 'Both', 'The Turning Tide'), +(9, 0, 17, 0, 224, 39712, 'Phase 1', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Gemmed Wand of the Nerubians'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 245, 45497, 'Phase 2', 'Warlock', 'Affliction', 'Head', 'Both', 'Crown of Luminescence'), +(9, 0, 1, 0, 245, 45133, 'Phase 2', 'Warlock', 'Affliction', 'Neck', 'Both', 'Pendant of Fiery Havoc'), +(9, 0, 2, 0, 245, 46068, 'Phase 2', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Amice of Inconceivable Horror'), +(9, 0, 4, 0, 245, 46137, 'Phase 2', 'Warlock', 'Affliction', 'Chest', 'Both', 'Conqueror''s Deathbringer Robe'), +(9, 0, 9, 0, 245, 45665, 'Phase 2', 'Warlock', 'Affliction', 'Hands', 'Both', 'Pharos Gloves'), +(9, 0, 10, 0, 245, 45495, 'Phase 2', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Conductive Seal'), +(9, 0, 14, 0, 245, 45618, 'Phase 2', 'Warlock', 'Affliction', 'Back', 'Both', 'Sunglimmer Cloak'), +(9, 0, 15, 0, 245, 45620, 'Phase 2', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Starshard Edge'), +(9, 0, 17, 0, 245, 45294, 'Phase 2', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 10, 0, 258, 45495, 'Phase 3', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Conductive Seal'), +(9, 0, 17, 0, 258, 45294, 'Phase 3', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 0, 0, 0, 290, 51231, 'Phase 5', 'Warlock', 'Affliction', 'Head', 'Both', 'Sanctified Dark Coven Hood'), +(9, 0, 1, 0, 290, 50182, 'Phase 5', 'Warlock', 'Affliction', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(9, 0, 2, 0, 290, 51234, 'Phase 5', 'Warlock', 'Affliction', 'Shoulders', 'Both', 'Sanctified Dark Coven Shoulderpads'), +(9, 0, 4, 0, 290, 51233, 'Phase 5', 'Warlock', 'Affliction', 'Chest', 'Both', 'Sanctified Dark Coven Robe'), +(9, 0, 5, 0, 290, 50613, 'Phase 5', 'Warlock', 'Affliction', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(9, 0, 6, 0, 290, 50694, 'Phase 5', 'Warlock', 'Affliction', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(9, 0, 7, 0, 290, 50699, 'Phase 5', 'Warlock', 'Affliction', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(9, 0, 8, 0, 290, 54582, 'Phase 5', 'Warlock', 'Affliction', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(9, 0, 9, 0, 290, 51230, 'Phase 5', 'Warlock', 'Affliction', 'Hands', 'Both', 'Sanctified Dark Coven Gloves'), +(9, 0, 10, 0, 290, 50614, 'Phase 5', 'Warlock', 'Affliction', 'Finger1', 'Both', 'Loop of the Endless Labyrinth'), +(9, 0, 11, 0, 290, 50664, 'Phase 5', 'Warlock', 'Affliction', 'Finger2', 'Both', 'Ring of Rapid Ascent'), +(9, 0, 12, 0, 290, 54588, 'Phase 5', 'Warlock', 'Affliction', 'Trinket1', 'Both', 'Charred Twilight Scale'), +(9, 0, 13, 0, 290, 50365, 'Phase 5', 'Warlock', 'Affliction', 'Trinket2', 'Both', 'Phylactery of the Nameless Lich'), +(9, 0, 14, 0, 290, 54583, 'Phase 5', 'Warlock', 'Affliction', 'Back', 'Both', 'Cloak of Burning Dusk'), +(9, 0, 15, 0, 290, 50732, 'Phase 5', 'Warlock', 'Affliction', 'MainHand', 'Both', 'Bloodsurge, Kel''Thuzad''s Blade of Agony'), +(9, 0, 16, 0, 290, 50719, 'Phase 5', 'Warlock', 'Affliction', 'OffHand', 'Both', 'Shadow Silk Spindle'), +(9, 0, 17, 0, 290, 50684, 'Phase 5', 'Warlock', 'Affliction', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + +-- Demonology (tab 1) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 1, 66, 10504, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Head', 'Alliance', 'Green Lens'), +(9, 1, 0, 2, 66, 10504, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Head', 'Horde', 'Green Lens'), +(9, 1, 1, 1, 66, 18691, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Neck', 'Alliance', 'Dark Advisor''s Pendant'), +(9, 1, 1, 2, 66, 18691, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Neck', 'Horde', 'Dark Advisor''s Pendant'), +(9, 1, 2, 1, 66, 14112, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Shoulders', 'Alliance', 'Felcloth Shoulders'), +(9, 1, 2, 2, 66, 14112, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Shoulders', 'Horde', 'Felcloth Shoulders'), +(9, 1, 4, 1, 66, 14153, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Chest', 'Alliance', 'Robe of the Void'), +(9, 1, 4, 2, 66, 14153, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Chest', 'Horde', 'Robe of the Void'), +(9, 1, 5, 1, 66, 11662, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Waist', 'Alliance', 'Ban''thok Sash'), +(9, 1, 5, 2, 66, 11662, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Waist', 'Horde', 'Ban''thok Sash'), +(9, 1, 6, 1, 66, 13170, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(9, 1, 6, 2, 66, 13170, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Legs', 'Horde', 'Skyshroud Leggings'), +(9, 1, 7, 1, 66, 18735, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Feet', 'Alliance', 'Maleki''s Footwraps'), +(9, 1, 7, 2, 66, 18735, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Feet', 'Horde', 'Maleki''s Footwraps'), +(9, 1, 8, 1, 66, 11766, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(9, 1, 8, 2, 66, 11766, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(9, 1, 9, 1, 66, 13253, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Hands', 'Alliance', 'Hands of Power'), +(9, 1, 9, 2, 66, 13253, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Hands', 'Horde', 'Hands of Power'), +(9, 1, 10, 1, 66, 12543, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(9, 1, 10, 2, 66, 12545, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(9, 1, 11, 1, 66, 13001, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(9, 1, 11, 2, 66, 13001, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Finger2', 'Horde', 'Maiden''s Circle'), +(9, 1, 12, 1, 66, 12930, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(9, 1, 12, 2, 66, 12930, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Trinket1', 'Horde', 'Briarwood Reed'), +(9, 1, 13, 1, 66, 13968, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Trinket2', 'Alliance', 'Eye of the Beast'), +(9, 1, 13, 2, 66, 13968, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Trinket2', 'Horde', 'Eye of the Beast'), +(9, 1, 14, 1, 66, 13386, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Back', 'Alliance', 'Archivist Cape'), +(9, 1, 14, 2, 66, 13386, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Back', 'Horde', 'Archivist Cape'), +(9, 1, 15, 1, 66, 13964, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'MainHand', 'Alliance', 'Witchblade'), +(9, 1, 15, 2, 66, 13964, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'MainHand', 'Horde', 'Witchblade'), +(9, 1, 16, 1, 66, 10796, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'OffHand', 'Alliance', 'Drakestone'), +(9, 1, 16, 2, 66, 10796, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'OffHand', 'Horde', 'Drakestone'), +(9, 1, 17, 1, 66, 13396, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Ranged', 'Alliance', 'Skul''s Ghastly Touch'), +(9, 1, 17, 2, 66, 13396, 'Phase 1 (Pre-Raid)', 'Warlock', 'Demonology', 'Ranged', 'Horde', 'Skul''s Ghastly Touch'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 1, 76, 23310, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Head', 'Alliance', 'Lieutenant Commander''s Dreadweave Cowl'), +(9, 1, 0, 2, 76, 23310, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Head', 'Horde', 'Lieutenant Commander''s Dreadweave Cowl'), +(9, 1, 1, 1, 76, 18691, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Neck', 'Alliance', 'Dark Advisor''s Pendant'), +(9, 1, 1, 2, 76, 18691, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Neck', 'Horde', 'Dark Advisor''s Pendant'), +(9, 1, 2, 1, 76, 23311, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Shoulders', 'Alliance', 'Lieutenant Commander''s Dreadweave Spaulders'), +(9, 1, 2, 2, 76, 23311, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Shoulders', 'Horde', 'Lieutenant Commander''s Dreadweave Spaulders'), +(9, 1, 4, 1, 76, 14153, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Chest', 'Alliance', 'Robe of the Void'), +(9, 1, 4, 2, 76, 14153, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Chest', 'Horde', 'Robe of the Void'), +(9, 1, 5, 1, 76, 11662, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Waist', 'Alliance', 'Ban''thok Sash'), +(9, 1, 5, 2, 76, 11662, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Waist', 'Horde', 'Ban''thok Sash'), +(9, 1, 6, 1, 76, 13170, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(9, 1, 6, 2, 76, 13170, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Legs', 'Horde', 'Skyshroud Leggings'), +(9, 1, 7, 1, 76, 18735, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Feet', 'Alliance', 'Maleki''s Footwraps'), +(9, 1, 7, 2, 76, 18735, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Feet', 'Horde', 'Maleki''s Footwraps'), +(9, 1, 8, 1, 76, 11766, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(9, 1, 8, 2, 76, 11766, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(9, 1, 9, 1, 76, 18407, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Hands', 'Alliance', 'Felcloth Gloves'), +(9, 1, 9, 2, 76, 18407, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Hands', 'Horde', 'Felcloth Gloves'), +(9, 1, 10, 1, 76, 12543, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(9, 1, 10, 2, 76, 12545, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(9, 1, 11, 1, 76, 13001, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(9, 1, 11, 2, 76, 13001, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Finger2', 'Horde', 'Maiden''s Circle'), +(9, 1, 12, 1, 76, 12930, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(9, 1, 12, 2, 76, 12930, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Trinket1', 'Horde', 'Briarwood Reed'), +(9, 1, 13, 1, 76, 18467, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Trinket2', 'Alliance', 'Royal Seal of Eldre''Thalas'), +(9, 1, 13, 2, 76, 18467, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Trinket2', 'Horde', 'Royal Seal of Eldre''Thalas'), +(9, 1, 14, 1, 76, 13386, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Back', 'Alliance', 'Archivist Cape'), +(9, 1, 14, 2, 76, 13386, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Back', 'Horde', 'Archivist Cape'), +(9, 1, 15, 1, 76, 18372, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'MainHand', 'Alliance', 'Blade of the New Moon'), +(9, 1, 15, 2, 76, 18372, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'MainHand', 'Horde', 'Blade of the New Moon'), +(9, 1, 16, 1, 76, 10796, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'OffHand', 'Alliance', 'Drakestone'), +(9, 1, 16, 2, 76, 10796, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'OffHand', 'Horde', 'Drakestone'), +(9, 1, 17, 1, 76, 13396, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Ranged', 'Alliance', 'Skul''s Ghastly Touch'), +(9, 1, 17, 2, 76, 13396, 'Phase 2 (Pre-Raid)', 'Warlock', 'Demonology', 'Ranged', 'Horde', 'Skul''s Ghastly Touch'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 78, 23310, 'Phase 2', 'Warlock', 'Demonology', 'Head', 'Both', 'Lieutenant Commander''s Dreadweave Cowl'), +(9, 1, 1, 0, 78, 18814, 'Phase 2', 'Warlock', 'Demonology', 'Neck', 'Both', 'Choker of the Fire Lord'), +(9, 1, 2, 0, 78, 23311, 'Phase 2', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Lieutenant Commander''s Dreadweave Spaulders'), +(9, 1, 4, 0, 78, 19145, 'Phase 2', 'Warlock', 'Demonology', 'Chest', 'Both', 'Robe of Volatile Power'), +(9, 1, 5, 0, 78, 18809, 'Phase 2', 'Warlock', 'Demonology', 'Waist', 'Both', 'Sash of Whispered Secrets'), +(9, 1, 6, 0, 78, 19133, 'Phase 2', 'Warlock', 'Demonology', 'Legs', 'Both', 'Fel Infused Leggings'), +(9, 1, 7, 0, 78, 19131, 'Phase 2', 'Warlock', 'Demonology', 'Feet', 'Both', 'Snowblind Shoes'), +(9, 1, 8, 0, 78, 11766, 'Phase 2', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Flameweave Cuffs'), +(9, 1, 9, 0, 78, 18407, 'Phase 2', 'Warlock', 'Demonology', 'Hands', 'Both', 'Felcloth Gloves'), +(9, 1, 10, 0, 78, 19147, 'Phase 2', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Ring of Spell Power'), +(9, 1, 11, 0, 78, 19147, 'Phase 2', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Ring of Spell Power'), +(9, 1, 12, 0, 78, 12930, 'Phase 2', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Briarwood Reed'), +(9, 1, 13, 0, 78, 18820, 'Phase 2', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(9, 1, 14, 0, 78, 13386, 'Phase 2', 'Warlock', 'Demonology', 'Back', 'Both', 'Archivist Cape'), +(9, 1, 15, 0, 78, 17103, 'Phase 2', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Azuresong Mageblade'), +(9, 1, 16, 0, 78, 10796, 'Phase 2', 'Warlock', 'Demonology', 'OffHand', 'Both', 'Drakestone'), +(9, 1, 17, 0, 78, 13396, 'Phase 2', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Skul''s Ghastly Touch'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 83, 19375, 'Phase 3', 'Warlock', 'Demonology', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(9, 1, 1, 0, 83, 18814, 'Phase 3', 'Warlock', 'Demonology', 'Neck', 'Both', 'Choker of the Fire Lord'), +(9, 1, 2, 0, 83, 19370, 'Phase 3', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(9, 1, 4, 0, 83, 19145, 'Phase 3', 'Warlock', 'Demonology', 'Chest', 'Both', 'Robe of Volatile Power'), +(9, 1, 5, 0, 83, 18809, 'Phase 3', 'Warlock', 'Demonology', 'Waist', 'Both', 'Sash of Whispered Secrets'), +(9, 1, 6, 0, 83, 19133, 'Phase 3', 'Warlock', 'Demonology', 'Legs', 'Both', 'Fel Infused Leggings'), +(9, 1, 7, 0, 83, 19131, 'Phase 3', 'Warlock', 'Demonology', 'Feet', 'Both', 'Snowblind Shoes'), +(9, 1, 8, 0, 83, 19374, 'Phase 3', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Bracers of Arcane Accuracy'), +(9, 1, 9, 0, 83, 19407, 'Phase 3', 'Warlock', 'Demonology', 'Hands', 'Both', 'Ebony Flame Gloves'), +(9, 1, 11, 0, 83, 19434, 'Phase 3', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Band of Dark Dominion'), +(9, 1, 12, 0, 83, 19379, 'Phase 3', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(9, 1, 13, 0, 83, 18820, 'Phase 3', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(9, 1, 14, 0, 83, 19378, 'Phase 3', 'Warlock', 'Demonology', 'Back', 'Both', 'Cloak of the Brood Lord'), +(9, 1, 15, 0, 83, 19356, 'Phase 3', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Staff of the Shadow Flame'), +(9, 1, 17, 0, 83, 13396, 'Phase 3', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Skul''s Ghastly Touch'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 88, 21337, 'Phase 5', 'Warlock', 'Demonology', 'Head', 'Both', 'Doomcaller''s Circlet'), +(9, 1, 1, 0, 88, 21608, 'Phase 5', 'Warlock', 'Demonology', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(9, 1, 2, 0, 88, 21335, 'Phase 5', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Doomcaller''s Mantle'), +(9, 1, 4, 0, 88, 19682, 'Phase 5', 'Warlock', 'Demonology', 'Chest', 'Both', 'Bloodvine Vest'), +(9, 1, 5, 0, 88, 22730, 'Phase 5', 'Warlock', 'Demonology', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(9, 1, 6, 0, 88, 19683, 'Phase 5', 'Warlock', 'Demonology', 'Legs', 'Both', 'Bloodvine Leggings'), +(9, 1, 7, 0, 88, 19684, 'Phase 5', 'Warlock', 'Demonology', 'Feet', 'Both', 'Bloodvine Boots'), +(9, 1, 8, 0, 88, 21186, 'Phase 5', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Rockfury Bracers'), +(9, 1, 9, 0, 88, 21585, 'Phase 5', 'Warlock', 'Demonology', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(9, 1, 10, 0, 88, 21417, 'Phase 5', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Ring of Unspoken Names'), +(9, 1, 11, 0, 88, 21709, 'Phase 5', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Ring of the Fallen God'), +(9, 1, 12, 0, 88, 19379, 'Phase 5', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(9, 1, 13, 0, 88, 18820, 'Phase 5', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(9, 1, 14, 0, 88, 22731, 'Phase 5', 'Warlock', 'Demonology', 'Back', 'Both', 'Cloak of the Devoured'), +(9, 1, 15, 0, 88, 21622, 'Phase 5', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Sharpened Silithid Femur'), +(9, 1, 16, 0, 88, 21597, 'Phase 5', 'Warlock', 'Demonology', 'OffHand', 'Both', 'Royal Scepter of Vek''lor'), +(9, 1, 17, 0, 88, 21603, 'Phase 5', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Wand of Qiraji Nobility'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 92, 22506, 'Phase 6', 'Warlock', 'Demonology', 'Head', 'Both', 'Plagueheart Circlet'), +(9, 1, 1, 0, 92, 21608, 'Phase 6', 'Warlock', 'Demonology', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(9, 1, 2, 0, 92, 22507, 'Phase 6', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Plagueheart Shoulderpads'), +(9, 1, 4, 0, 92, 22504, 'Phase 6', 'Warlock', 'Demonology', 'Chest', 'Both', 'Plagueheart Robe'), +(9, 1, 5, 0, 92, 22730, 'Phase 6', 'Warlock', 'Demonology', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(9, 1, 6, 0, 92, 23070, 'Phase 6', 'Warlock', 'Demonology', 'Legs', 'Both', 'Leggings of Polarity'), +(9, 1, 7, 0, 92, 22508, 'Phase 6', 'Warlock', 'Demonology', 'Feet', 'Both', 'Plagueheart Sandals'), +(9, 1, 8, 0, 92, 21186, 'Phase 6', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Rockfury Bracers'), +(9, 1, 9, 0, 92, 21585, 'Phase 6', 'Warlock', 'Demonology', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(9, 1, 10, 0, 92, 23031, 'Phase 6', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Band of the Inevitable'), +(9, 1, 11, 0, 92, 21709, 'Phase 6', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Ring of the Fallen God'), +(9, 1, 12, 0, 92, 19379, 'Phase 6', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(9, 1, 13, 0, 92, 23046, 'Phase 6', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'The Restrained Essence of Sapphiron'), +(9, 1, 14, 0, 92, 23050, 'Phase 6', 'Warlock', 'Demonology', 'Back', 'Both', 'Cloak of the Necropolis'), +(9, 1, 15, 0, 92, 22807, 'Phase 6', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Wraith Blade'), +(9, 1, 16, 0, 92, 23049, 'Phase 6', 'Warlock', 'Demonology', 'OffHand', 'Both', 'Sapphiron''s Left Eye'), +(9, 1, 17, 0, 92, 22820, 'Phase 6', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Wand of Fates'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 120, 24266, 'Pre-Raid', 'Warlock', 'Demonology', 'Head', 'Both', 'Spellstrike Hood'), +(9, 1, 1, 0, 120, 28134, 'Pre-Raid', 'Warlock', 'Demonology', 'Neck', 'Both', 'Brooch of Heightened Potential'), +(9, 1, 2, 0, 120, 21869, 'Pre-Raid', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Frozen Shadoweave Shoulders'), +(9, 1, 4, 0, 120, 21871, 'Pre-Raid', 'Warlock', 'Demonology', 'Chest', 'Both', 'Frozen Shadoweave Robe'), +(9, 1, 5, 0, 120, 24256, 'Pre-Raid', 'Warlock', 'Demonology', 'Waist', 'Both', 'Girdle of Ruination'), +(9, 1, 6, 0, 120, 24262, 'Pre-Raid', 'Warlock', 'Demonology', 'Legs', 'Both', 'Spellstrike Pants'), +(9, 1, 7, 0, 120, 21870, 'Pre-Raid', 'Warlock', 'Demonology', 'Feet', 'Both', 'Frozen Shadoweave Boots'), +(9, 1, 8, 0, 120, 24250, 'Pre-Raid', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Bracers of Havok'), +(9, 1, 9, 0, 120, 24450, 'Pre-Raid', 'Warlock', 'Demonology', 'Hands', 'Both', 'Manaspark Gloves'), +(9, 1, 10, 0, 120, 29172, 'Pre-Raid', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Ashyen''s Gift'), +(9, 1, 11, 0, 120, 28227, 'Pre-Raid', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Sparking Arcanite Ring'), +(9, 1, 12, 0, 120, 29370, 'Pre-Raid', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 1, 13, 0, 120, 29132, 'Pre-Raid', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'Scryer''s Bloodgem'), +(9, 1, 14, 0, 120, 27981, 'Pre-Raid', 'Warlock', 'Demonology', 'Back', 'Both', 'Sethekk Oracle Cloak'), +(9, 1, 15, 0, 120, 23554, 'Pre-Raid', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Eternium Runed Blade'), +(9, 1, 16, 0, 120, 29273, 'Pre-Raid', 'Warlock', 'Demonology', 'OffHand', 'Both', 'Khadgar''s Knapsack'), +(9, 1, 17, 0, 120, 29350, 'Pre-Raid', 'Warlock', 'Demonology', 'Ranged', 'Both', 'The Black Stalk'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 125, 28963, 'Phase 1', 'Warlock', 'Demonology', 'Head', 'Both', 'Voidheart Crown'), +(9, 1, 1, 0, 125, 28530, 'Phase 1', 'Warlock', 'Demonology', 'Neck', 'Both', 'Brooch of Unquenchable Fury'), +(9, 1, 2, 0, 125, 28967, 'Phase 1', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Voidheart Mantle'), +(9, 1, 4, 0, 125, 28964, 'Phase 1', 'Warlock', 'Demonology', 'Chest', 'Both', 'Voidheart Robe'), +(9, 1, 5, 0, 125, 24256, 'Phase 1', 'Warlock', 'Demonology', 'Waist', 'Both', 'Girdle of Ruination'), +(9, 1, 6, 0, 125, 30734, 'Phase 1', 'Warlock', 'Demonology', 'Legs', 'Both', 'Leggings of the Seventh Circle'), +(9, 1, 7, 0, 125, 21870, 'Phase 1', 'Warlock', 'Demonology', 'Feet', 'Both', 'Frozen Shadoweave Boots'), +(9, 1, 8, 0, 125, 24250, 'Phase 1', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Bracers of Havok'), +(9, 1, 9, 0, 125, 28968, 'Phase 1', 'Warlock', 'Demonology', 'Hands', 'Both', 'Voidheart Gloves'), +(9, 1, 10, 0, 125, 29172, 'Phase 1', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Ashyen''s Gift'), +(9, 1, 11, 0, 125, 28793, 'Phase 1', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Band of Crimson Fury'), +(9, 1, 12, 0, 125, 29370, 'Phase 1', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 1, 13, 0, 125, 27683, 'Phase 1', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(9, 1, 14, 0, 125, 28766, 'Phase 1', 'Warlock', 'Demonology', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(9, 1, 15, 0, 125, 30723, 'Phase 1', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Talon of the Tempest'), +(9, 1, 16, 0, 125, 29272, 'Phase 1', 'Warlock', 'Demonology', 'OffHand', 'Both', 'Orb of the Soul-Eater'), +(9, 1, 17, 0, 125, 28783, 'Phase 1', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Eredar Wand of Obliteration'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 141, 32494, 'Phase 2', 'Warlock', 'Demonology', 'Head', 'Both', 'Destruction Holo-gogs'), +(9, 1, 1, 0, 141, 30015, 'Phase 2', 'Warlock', 'Demonology', 'Neck', 'Both', 'The Sun King''s Talisman'), +(9, 1, 2, 0, 141, 28967, 'Phase 2', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Voidheart Mantle'), +(9, 1, 4, 0, 141, 30107, 'Phase 2', 'Warlock', 'Demonology', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(9, 1, 5, 0, 141, 30038, 'Phase 2', 'Warlock', 'Demonology', 'Waist', 'Both', 'Belt of Blasting'), +(9, 1, 6, 0, 141, 30213, 'Phase 2', 'Warlock', 'Demonology', 'Legs', 'Both', 'Leggings of the Corruptor'), +(9, 1, 7, 0, 141, 30037, 'Phase 2', 'Warlock', 'Demonology', 'Feet', 'Both', 'Boots of Blasting'), +(9, 1, 8, 0, 141, 29918, 'Phase 2', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Mindstorm Wristbands'), +(9, 1, 9, 0, 141, 28968, 'Phase 2', 'Warlock', 'Demonology', 'Hands', 'Both', 'Voidheart Gloves'), +(9, 1, 10, 0, 141, 29302, 'Phase 2', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Band of Eternity'), +(9, 1, 11, 0, 141, 30109, 'Phase 2', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Ring of Endless Coils'), +(9, 1, 12, 0, 141, 29370, 'Phase 2', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 1, 13, 0, 141, 27683, 'Phase 2', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(9, 1, 14, 0, 141, 28766, 'Phase 2', 'Warlock', 'Demonology', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(9, 1, 15, 0, 141, 30095, 'Phase 2', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Fang of the Leviathan'), +(9, 1, 16, 0, 141, 30049, 'Phase 2', 'Warlock', 'Demonology', 'OffHand', 'Both', 'Fathomstone'), +(9, 1, 17, 0, 141, 29982, 'Phase 2', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Wand of the Forgotten Star'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 156, 31051, 'Phase 3', 'Warlock', 'Demonology', 'Head', 'Both', 'Hood of the Malefic'), +(9, 1, 1, 0, 156, 30015, 'Phase 3', 'Warlock', 'Demonology', 'Neck', 'Both', 'Sun King''s Talisman'), +(9, 1, 2, 0, 156, 31054, 'Phase 3', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Mantle of the Malefic'), +(9, 1, 4, 0, 156, 30107, 'Phase 3', 'Warlock', 'Demonology', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(9, 1, 5, 0, 156, 30888, 'Phase 3', 'Warlock', 'Demonology', 'Waist', 'Both', 'Anetheron''s Noose'), +(9, 1, 6, 0, 156, 31053, 'Phase 3', 'Warlock', 'Demonology', 'Legs', 'Both', 'Leggings of the Malefic'), +(9, 1, 7, 0, 156, 32239, 'Phase 3', 'Warlock', 'Demonology', 'Feet', 'Both', 'Slippers of the Seacaller'), +(9, 1, 8, 0, 156, 32586, 'Phase 3', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(9, 1, 9, 0, 156, 31050, 'Phase 3', 'Warlock', 'Demonology', 'Hands', 'Both', 'Gloves of the Malefic'), +(9, 1, 10, 0, 156, 32527, 'Phase 3', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Ring of Ancient Knowledge'), +(9, 1, 11, 0, 156, 32247, 'Phase 3', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Ring of Captured Storms'), +(9, 1, 12, 0, 156, 29370, 'Phase 3', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 1, 13, 0, 156, 32483, 'Phase 3', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(9, 1, 14, 0, 156, 32590, 'Phase 3', 'Warlock', 'Demonology', 'Back', 'Both', 'Nethervoid Cloak'), +(9, 1, 15, 0, 156, 32374, 'Phase 3', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Zhar''doom, Greatstaff of the Devourer'), +(9, 1, 17, 0, 156, 29982, 'Phase 3', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Wand of the Forgotten Star'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 164, 31051, 'Phase 4', 'Warlock', 'Demonology', 'Head', 'Both', 'Hood of the Malefic'), +(9, 1, 1, 0, 164, 30015, 'Phase 4', 'Warlock', 'Demonology', 'Neck', 'Both', 'Sun King''s Talisman'), +(9, 1, 2, 0, 164, 31054, 'Phase 4', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Mantle of the Malefic'), +(9, 1, 4, 0, 164, 30107, 'Phase 4', 'Warlock', 'Demonology', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(9, 1, 5, 0, 164, 30888, 'Phase 4', 'Warlock', 'Demonology', 'Waist', 'Both', 'Anetheron''s Noose'), +(9, 1, 6, 0, 164, 31053, 'Phase 4', 'Warlock', 'Demonology', 'Legs', 'Both', 'Leggings of the Malefic'), +(9, 1, 7, 0, 164, 32239, 'Phase 4', 'Warlock', 'Demonology', 'Feet', 'Both', 'Slippers of the Seacaller'), +(9, 1, 8, 0, 164, 32586, 'Phase 4', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(9, 1, 9, 0, 164, 31050, 'Phase 4', 'Warlock', 'Demonology', 'Hands', 'Both', 'Gloves of the Malefic'), +(9, 1, 10, 0, 164, 33497, 'Phase 4', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Mana Attuned Band'), +(9, 1, 11, 0, 164, 32247, 'Phase 4', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Ring of Captured Storms'), +(9, 1, 12, 0, 164, 33829, 'Phase 4', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Hex Shrunken Head'), +(9, 1, 13, 0, 164, 32483, 'Phase 4', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(9, 1, 14, 0, 164, 32590, 'Phase 4', 'Warlock', 'Demonology', 'Back', 'Both', 'Nethervoid Cloak'), +(9, 1, 15, 0, 164, 32374, 'Phase 4', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Zhar''doom, Greatstaff of the Devourer'), +(9, 1, 16, 0, 164, 34179, 'Phase 5', 'Warlock', 'Demonology', 'OffHand', 'Both', 'Heart of the Pit'), +(9, 1, 17, 0, 164, 33192, 'Phase 4', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Carved Witch Doctor''s Stick'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 200, 37684, 'Pre-Raid', 'Warlock', 'Demonology', 'Head', 'Both', 'Forgotten Shadow Hood'), +(9, 1, 1, 0, 200, 40680, 'Pre-Raid', 'Warlock', 'Demonology', 'Neck', 'Both', 'Encircling Burnished Gold Chains'), +(9, 1, 2, 0, 200, 37196, 'Pre-Raid', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Runecaster''s Mantle'), +(9, 1, 4, 0, 200, 39497, 'Pre-Raid', 'Warlock', 'Demonology', 'Chest', 'Both', 'Heroes'' Plagueheart Robe'), +(9, 1, 5, 0, 200, 37408, 'Pre-Raid', 'Warlock', 'Demonology', 'Waist', 'Both', 'Girdle of Bane'), +(9, 1, 6, 0, 200, 34170, 'Pre-Raid', 'Warlock', 'Demonology', 'Legs', 'Both', 'Pantaloons of Calming Strife'), +(9, 1, 7, 0, 200, 44202, 'Pre-Raid', 'Warlock', 'Demonology', 'Feet', 'Both', 'Sandals of Crimson Fury'), +(9, 1, 8, 0, 200, 37361, 'Pre-Raid', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Cuffs of Winged Levitation'), +(9, 1, 9, 0, 200, 42113, 'Pre-Raid', 'Warlock', 'Demonology', 'Hands', 'Both', 'Spellweave Gloves'), +(9, 1, 10, 0, 200, 40585, 'Pre-Raid', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Signet of the Kirin Tor'), +(9, 1, 12, 0, 200, 40682, 'Pre-Raid', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Sundial of the Exiled'), +(9, 1, 14, 0, 200, 41610, 'Pre-Raid', 'Warlock', 'Demonology', 'Back', 'Both', 'Deathchill Cloak'), +(9, 1, 17, 0, 200, 34348, 'Pre-Raid', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Wand of Cleansing Light'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 224, 40421, 'Phase 1', 'Warlock', 'Demonology', 'Head', 'Both', 'Valorous Plagueheart Circlet'), +(9, 1, 1, 0, 224, 44661, 'Phase 1', 'Warlock', 'Demonology', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(9, 1, 2, 0, 224, 40424, 'Phase 1', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Valorous Plagueheart Shoulderpads'), +(9, 1, 4, 0, 224, 40423, 'Phase 1', 'Warlock', 'Demonology', 'Chest', 'Both', 'Valorous Plagueheart Robe'), +(9, 1, 5, 0, 224, 40561, 'Phase 1', 'Warlock', 'Demonology', 'Waist', 'Both', 'Leash of Heedless Magic'), +(9, 1, 6, 0, 224, 40560, 'Phase 1', 'Warlock', 'Demonology', 'Legs', 'Both', 'Leggings of the Wanton Spellcaster'), +(9, 1, 7, 0, 224, 40558, 'Phase 1', 'Warlock', 'Demonology', 'Feet', 'Both', 'Arcanic Tramplers'), +(9, 1, 8, 0, 224, 44008, 'Phase 1', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Unsullied Cuffs'), +(9, 1, 9, 0, 224, 40420, 'Phase 1', 'Warlock', 'Demonology', 'Hands', 'Both', 'Valorous Plagueheart Gloves'), +(9, 1, 10, 0, 224, 40399, 'Phase 1', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Signet of Manifested Pain'), +(9, 1, 12, 0, 224, 40432, 'Phase 1', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Illustration of the Dragon Soul'), +(9, 1, 14, 0, 224, 44005, 'Phase 1', 'Warlock', 'Demonology', 'Back', 'Both', 'Pennant Cloak'), +(9, 1, 15, 0, 224, 40396, 'Phase 1', 'Warlock', 'Demonology', 'MainHand', 'Both', 'The Turning Tide'), +(9, 1, 17, 0, 224, 39426, 'Phase 1', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Wand of the Archlich'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 245, 45497, 'Phase 2', 'Warlock', 'Demonology', 'Head', 'Both', 'Crown of Luminescence'), +(9, 1, 1, 0, 245, 45133, 'Phase 2', 'Warlock', 'Demonology', 'Neck', 'Both', 'Pendant of Fiery Havoc'), +(9, 1, 2, 0, 245, 46068, 'Phase 2', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Amice of Inconceivable Horror'), +(9, 1, 4, 0, 245, 46137, 'Phase 2', 'Warlock', 'Demonology', 'Chest', 'Both', 'Conqueror''s Deathbringer Robe'), +(9, 1, 9, 0, 245, 45665, 'Phase 2', 'Warlock', 'Demonology', 'Hands', 'Both', 'Pharos Gloves'), +(9, 1, 10, 0, 245, 45495, 'Phase 2', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Conductive Seal'), +(9, 1, 14, 0, 245, 45618, 'Phase 2', 'Warlock', 'Demonology', 'Back', 'Both', 'Sunglimmer Cloak'), +(9, 1, 15, 0, 245, 45620, 'Phase 2', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Starshard Edge'), +(9, 1, 17, 0, 245, 45294, 'Phase 2', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 10, 0, 258, 45495, 'Phase 3', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Conductive Seal'), +(9, 1, 17, 0, 258, 45294, 'Phase 3', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 1, 0, 0, 290, 51231, 'Phase 5', 'Warlock', 'Demonology', 'Head', 'Both', 'Sanctified Dark Coven Hood'), +(9, 1, 1, 0, 290, 50182, 'Phase 5', 'Warlock', 'Demonology', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(9, 1, 2, 0, 290, 51234, 'Phase 5', 'Warlock', 'Demonology', 'Shoulders', 'Both', 'Sanctified Dark Coven Shoulderpads'), +(9, 1, 4, 0, 290, 51233, 'Phase 5', 'Warlock', 'Demonology', 'Chest', 'Both', 'Sanctified Dark Coven Robe'), +(9, 1, 5, 0, 290, 50613, 'Phase 5', 'Warlock', 'Demonology', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(9, 1, 6, 0, 290, 50694, 'Phase 5', 'Warlock', 'Demonology', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(9, 1, 7, 0, 290, 50699, 'Phase 5', 'Warlock', 'Demonology', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(9, 1, 8, 0, 290, 54582, 'Phase 5', 'Warlock', 'Demonology', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(9, 1, 9, 0, 290, 51230, 'Phase 5', 'Warlock', 'Demonology', 'Hands', 'Both', 'Sanctified Dark Coven Gloves'), +(9, 1, 10, 0, 290, 50614, 'Phase 5', 'Warlock', 'Demonology', 'Finger1', 'Both', 'Loop of the Endless Labyrinth'), +(9, 1, 11, 0, 290, 50664, 'Phase 5', 'Warlock', 'Demonology', 'Finger2', 'Both', 'Ring of Rapid Ascent'), +(9, 1, 12, 0, 290, 54588, 'Phase 5', 'Warlock', 'Demonology', 'Trinket1', 'Both', 'Charred Twilight Scale'), +(9, 1, 13, 0, 290, 50365, 'Phase 5', 'Warlock', 'Demonology', 'Trinket2', 'Both', 'Phylactery of the Nameless Lich'), +(9, 1, 14, 0, 290, 54583, 'Phase 5', 'Warlock', 'Demonology', 'Back', 'Both', 'Cloak of Burning Dusk'), +(9, 1, 15, 0, 290, 50732, 'Phase 5', 'Warlock', 'Demonology', 'MainHand', 'Both', 'Bloodsurge, Kel''Thuzad''s Blade of Agony'), +(9, 1, 16, 0, 290, 50719, 'Phase 5', 'Warlock', 'Demonology', 'OffHand', 'Both', 'Shadow Silk Spindle'), +(9, 1, 17, 0, 290, 50684, 'Phase 5', 'Warlock', 'Demonology', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + +-- Destruction (tab 2) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 1, 66, 10504, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Head', 'Alliance', 'Green Lens'), +(9, 2, 0, 2, 66, 10504, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Head', 'Horde', 'Green Lens'), +(9, 2, 1, 1, 66, 18691, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Neck', 'Alliance', 'Dark Advisor''s Pendant'), +(9, 2, 1, 2, 66, 18691, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Neck', 'Horde', 'Dark Advisor''s Pendant'), +(9, 2, 2, 1, 66, 14112, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Shoulders', 'Alliance', 'Felcloth Shoulders'), +(9, 2, 2, 2, 66, 14112, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Shoulders', 'Horde', 'Felcloth Shoulders'), +(9, 2, 4, 1, 66, 14153, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Chest', 'Alliance', 'Robe of the Void'), +(9, 2, 4, 2, 66, 14153, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Chest', 'Horde', 'Robe of the Void'), +(9, 2, 5, 1, 66, 11662, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Waist', 'Alliance', 'Ban''thok Sash'), +(9, 2, 5, 2, 66, 11662, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Waist', 'Horde', 'Ban''thok Sash'), +(9, 2, 6, 1, 66, 13170, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(9, 2, 6, 2, 66, 13170, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Legs', 'Horde', 'Skyshroud Leggings'), +(9, 2, 7, 1, 66, 18735, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Feet', 'Alliance', 'Maleki''s Footwraps'), +(9, 2, 7, 2, 66, 18735, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Feet', 'Horde', 'Maleki''s Footwraps'), +(9, 2, 8, 1, 66, 11766, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(9, 2, 8, 2, 66, 11766, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(9, 2, 9, 1, 66, 13253, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Hands', 'Alliance', 'Hands of Power'), +(9, 2, 9, 2, 66, 13253, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Hands', 'Horde', 'Hands of Power'), +(9, 2, 10, 1, 66, 12543, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(9, 2, 10, 2, 66, 12545, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(9, 2, 11, 1, 66, 13001, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(9, 2, 11, 2, 66, 13001, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Finger2', 'Horde', 'Maiden''s Circle'), +(9, 2, 12, 1, 66, 12930, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(9, 2, 12, 2, 66, 12930, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Trinket1', 'Horde', 'Briarwood Reed'), +(9, 2, 13, 1, 66, 13968, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Trinket2', 'Alliance', 'Eye of the Beast'), +(9, 2, 13, 2, 66, 13968, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Trinket2', 'Horde', 'Eye of the Beast'), +(9, 2, 14, 1, 66, 13386, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Back', 'Alliance', 'Archivist Cape'), +(9, 2, 14, 2, 66, 13386, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Back', 'Horde', 'Archivist Cape'), +(9, 2, 15, 1, 66, 13964, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'MainHand', 'Alliance', 'Witchblade'), +(9, 2, 15, 2, 66, 13964, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'MainHand', 'Horde', 'Witchblade'), +(9, 2, 16, 1, 66, 10796, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'OffHand', 'Alliance', 'Drakestone'), +(9, 2, 16, 2, 66, 10796, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'OffHand', 'Horde', 'Drakestone'), +(9, 2, 17, 1, 66, 13396, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Ranged', 'Alliance', 'Skul''s Ghastly Touch'), +(9, 2, 17, 2, 66, 13396, 'Phase 1 (Pre-Raid)', 'Warlock', 'Destruction', 'Ranged', 'Horde', 'Skul''s Ghastly Touch'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 1, 76, 23310, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Head', 'Alliance', 'Lieutenant Commander''s Dreadweave Cowl'), +(9, 2, 0, 2, 76, 23310, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Head', 'Horde', 'Lieutenant Commander''s Dreadweave Cowl'), +(9, 2, 1, 1, 76, 18691, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Neck', 'Alliance', 'Dark Advisor''s Pendant'), +(9, 2, 1, 2, 76, 18691, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Neck', 'Horde', 'Dark Advisor''s Pendant'), +(9, 2, 2, 1, 76, 23311, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Shoulders', 'Alliance', 'Lieutenant Commander''s Dreadweave Spaulders'), +(9, 2, 2, 2, 76, 23311, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Shoulders', 'Horde', 'Lieutenant Commander''s Dreadweave Spaulders'), +(9, 2, 4, 1, 76, 14153, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Chest', 'Alliance', 'Robe of the Void'), +(9, 2, 4, 2, 76, 14153, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Chest', 'Horde', 'Robe of the Void'), +(9, 2, 5, 1, 76, 11662, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Waist', 'Alliance', 'Ban''thok Sash'), +(9, 2, 5, 2, 76, 11662, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Waist', 'Horde', 'Ban''thok Sash'), +(9, 2, 6, 1, 76, 13170, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(9, 2, 6, 2, 76, 13170, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Legs', 'Horde', 'Skyshroud Leggings'), +(9, 2, 7, 1, 76, 18735, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Feet', 'Alliance', 'Maleki''s Footwraps'), +(9, 2, 7, 2, 76, 18735, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Feet', 'Horde', 'Maleki''s Footwraps'), +(9, 2, 8, 1, 76, 11766, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(9, 2, 8, 2, 76, 11766, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(9, 2, 9, 1, 76, 18407, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Hands', 'Alliance', 'Felcloth Gloves'), +(9, 2, 9, 2, 76, 18407, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Hands', 'Horde', 'Felcloth Gloves'), +(9, 2, 10, 1, 76, 12543, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(9, 2, 10, 2, 76, 12545, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(9, 2, 11, 1, 76, 13001, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(9, 2, 11, 2, 76, 13001, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Finger2', 'Horde', 'Maiden''s Circle'), +(9, 2, 12, 1, 76, 12930, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(9, 2, 12, 2, 76, 12930, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Trinket1', 'Horde', 'Briarwood Reed'), +(9, 2, 13, 1, 76, 18467, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Trinket2', 'Alliance', 'Royal Seal of Eldre''Thalas'), +(9, 2, 13, 2, 76, 18467, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Trinket2', 'Horde', 'Royal Seal of Eldre''Thalas'), +(9, 2, 14, 1, 76, 13386, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Back', 'Alliance', 'Archivist Cape'), +(9, 2, 14, 2, 76, 13386, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Back', 'Horde', 'Archivist Cape'), +(9, 2, 15, 1, 76, 18372, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'MainHand', 'Alliance', 'Blade of the New Moon'), +(9, 2, 15, 2, 76, 18372, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'MainHand', 'Horde', 'Blade of the New Moon'), +(9, 2, 16, 1, 76, 10796, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'OffHand', 'Alliance', 'Drakestone'), +(9, 2, 16, 2, 76, 10796, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'OffHand', 'Horde', 'Drakestone'), +(9, 2, 17, 1, 76, 13396, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Ranged', 'Alliance', 'Skul''s Ghastly Touch'), +(9, 2, 17, 2, 76, 13396, 'Phase 2 (Pre-Raid)', 'Warlock', 'Destruction', 'Ranged', 'Horde', 'Skul''s Ghastly Touch'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 78, 23310, 'Phase 2', 'Warlock', 'Destruction', 'Head', 'Both', 'Lieutenant Commander''s Dreadweave Cowl'), +(9, 2, 1, 0, 78, 18814, 'Phase 2', 'Warlock', 'Destruction', 'Neck', 'Both', 'Choker of the Fire Lord'), +(9, 2, 2, 0, 78, 23311, 'Phase 2', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Lieutenant Commander''s Dreadweave Spaulders'), +(9, 2, 4, 0, 78, 19145, 'Phase 2', 'Warlock', 'Destruction', 'Chest', 'Both', 'Robe of Volatile Power'), +(9, 2, 5, 0, 78, 18809, 'Phase 2', 'Warlock', 'Destruction', 'Waist', 'Both', 'Sash of Whispered Secrets'), +(9, 2, 6, 0, 78, 19133, 'Phase 2', 'Warlock', 'Destruction', 'Legs', 'Both', 'Fel Infused Leggings'), +(9, 2, 7, 0, 78, 19131, 'Phase 2', 'Warlock', 'Destruction', 'Feet', 'Both', 'Snowblind Shoes'), +(9, 2, 8, 0, 78, 11766, 'Phase 2', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Flameweave Cuffs'), +(9, 2, 9, 0, 78, 18407, 'Phase 2', 'Warlock', 'Destruction', 'Hands', 'Both', 'Felcloth Gloves'), +(9, 2, 10, 0, 78, 19147, 'Phase 2', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Ring of Spell Power'), +(9, 2, 11, 0, 78, 19147, 'Phase 2', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Ring of Spell Power'), +(9, 2, 12, 0, 78, 12930, 'Phase 2', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Briarwood Reed'), +(9, 2, 13, 0, 78, 18820, 'Phase 2', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(9, 2, 14, 0, 78, 13386, 'Phase 2', 'Warlock', 'Destruction', 'Back', 'Both', 'Archivist Cape'), +(9, 2, 15, 0, 78, 17103, 'Phase 2', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Azuresong Mageblade'), +(9, 2, 16, 0, 78, 10796, 'Phase 2', 'Warlock', 'Destruction', 'OffHand', 'Both', 'Drakestone'), +(9, 2, 17, 0, 78, 13396, 'Phase 2', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Skul''s Ghastly Touch'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 83, 19375, 'Phase 3', 'Warlock', 'Destruction', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(9, 2, 1, 0, 83, 18814, 'Phase 3', 'Warlock', 'Destruction', 'Neck', 'Both', 'Choker of the Fire Lord'), +(9, 2, 2, 0, 83, 19370, 'Phase 3', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(9, 2, 4, 0, 83, 19145, 'Phase 3', 'Warlock', 'Destruction', 'Chest', 'Both', 'Robe of Volatile Power'), +(9, 2, 5, 0, 83, 18809, 'Phase 3', 'Warlock', 'Destruction', 'Waist', 'Both', 'Sash of Whispered Secrets'), +(9, 2, 6, 0, 83, 19133, 'Phase 3', 'Warlock', 'Destruction', 'Legs', 'Both', 'Fel Infused Leggings'), +(9, 2, 7, 0, 83, 19131, 'Phase 3', 'Warlock', 'Destruction', 'Feet', 'Both', 'Snowblind Shoes'), +(9, 2, 8, 0, 83, 19374, 'Phase 3', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Bracers of Arcane Accuracy'), +(9, 2, 9, 0, 83, 19407, 'Phase 3', 'Warlock', 'Destruction', 'Hands', 'Both', 'Ebony Flame Gloves'), +(9, 2, 11, 0, 83, 19434, 'Phase 3', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Band of Dark Dominion'), +(9, 2, 12, 0, 83, 19379, 'Phase 3', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(9, 2, 13, 0, 83, 18820, 'Phase 3', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(9, 2, 14, 0, 83, 19378, 'Phase 3', 'Warlock', 'Destruction', 'Back', 'Both', 'Cloak of the Brood Lord'), +(9, 2, 15, 0, 83, 19356, 'Phase 3', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Staff of the Shadow Flame'), +(9, 2, 17, 0, 83, 13396, 'Phase 3', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Skul''s Ghastly Touch'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 88, 21337, 'Phase 5', 'Warlock', 'Destruction', 'Head', 'Both', 'Doomcaller''s Circlet'), +(9, 2, 1, 0, 88, 21608, 'Phase 5', 'Warlock', 'Destruction', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(9, 2, 2, 0, 88, 21335, 'Phase 5', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Doomcaller''s Mantle'), +(9, 2, 4, 0, 88, 19682, 'Phase 5', 'Warlock', 'Destruction', 'Chest', 'Both', 'Bloodvine Vest'), +(9, 2, 5, 0, 88, 22730, 'Phase 5', 'Warlock', 'Destruction', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(9, 2, 6, 0, 88, 19683, 'Phase 5', 'Warlock', 'Destruction', 'Legs', 'Both', 'Bloodvine Leggings'), +(9, 2, 7, 0, 88, 19684, 'Phase 5', 'Warlock', 'Destruction', 'Feet', 'Both', 'Bloodvine Boots'), +(9, 2, 8, 0, 88, 21186, 'Phase 5', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Rockfury Bracers'), +(9, 2, 9, 0, 88, 21585, 'Phase 5', 'Warlock', 'Destruction', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(9, 2, 10, 0, 88, 21417, 'Phase 5', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Ring of Unspoken Names'), +(9, 2, 11, 0, 88, 21709, 'Phase 5', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Ring of the Fallen God'), +(9, 2, 12, 0, 88, 19379, 'Phase 5', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(9, 2, 13, 0, 88, 18820, 'Phase 5', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(9, 2, 14, 0, 88, 22731, 'Phase 5', 'Warlock', 'Destruction', 'Back', 'Both', 'Cloak of the Devoured'), +(9, 2, 15, 0, 88, 21622, 'Phase 5', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Sharpened Silithid Femur'), +(9, 2, 16, 0, 88, 21597, 'Phase 5', 'Warlock', 'Destruction', 'OffHand', 'Both', 'Royal Scepter of Vek''lor'), +(9, 2, 17, 0, 88, 21603, 'Phase 5', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Wand of Qiraji Nobility'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 92, 22506, 'Phase 6', 'Warlock', 'Destruction', 'Head', 'Both', 'Plagueheart Circlet'), +(9, 2, 1, 0, 92, 21608, 'Phase 6', 'Warlock', 'Destruction', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(9, 2, 2, 0, 92, 22507, 'Phase 6', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Plagueheart Shoulderpads'), +(9, 2, 4, 0, 92, 22504, 'Phase 6', 'Warlock', 'Destruction', 'Chest', 'Both', 'Plagueheart Robe'), +(9, 2, 5, 0, 92, 22730, 'Phase 6', 'Warlock', 'Destruction', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(9, 2, 6, 0, 92, 23070, 'Phase 6', 'Warlock', 'Destruction', 'Legs', 'Both', 'Leggings of Polarity'), +(9, 2, 7, 0, 92, 22508, 'Phase 6', 'Warlock', 'Destruction', 'Feet', 'Both', 'Plagueheart Sandals'), +(9, 2, 8, 0, 92, 21186, 'Phase 6', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Rockfury Bracers'), +(9, 2, 9, 0, 92, 21585, 'Phase 6', 'Warlock', 'Destruction', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(9, 2, 10, 0, 92, 23031, 'Phase 6', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Band of the Inevitable'), +(9, 2, 11, 0, 92, 21709, 'Phase 6', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Ring of the Fallen God'), +(9, 2, 12, 0, 92, 19379, 'Phase 6', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(9, 2, 13, 0, 92, 23046, 'Phase 6', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'The Restrained Essence of Sapphiron'), +(9, 2, 14, 0, 92, 23050, 'Phase 6', 'Warlock', 'Destruction', 'Back', 'Both', 'Cloak of the Necropolis'), +(9, 2, 15, 0, 92, 22807, 'Phase 6', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Wraith Blade'), +(9, 2, 16, 0, 92, 23049, 'Phase 6', 'Warlock', 'Destruction', 'OffHand', 'Both', 'Sapphiron''s Left Eye'), +(9, 2, 17, 0, 92, 22820, 'Phase 6', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Wand of Fates'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 120, 24266, 'Pre-Raid', 'Warlock', 'Destruction', 'Head', 'Both', 'Spellstrike Hood'), +(9, 2, 1, 0, 120, 28134, 'Pre-Raid', 'Warlock', 'Destruction', 'Neck', 'Both', 'Brooch of Heightened Potential'), +(9, 2, 2, 0, 120, 21869, 'Pre-Raid', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Frozen Shadoweave Shoulders'), +(9, 2, 4, 0, 120, 21871, 'Pre-Raid', 'Warlock', 'Destruction', 'Chest', 'Both', 'Frozen Shadoweave Robe'), +(9, 2, 5, 0, 120, 24256, 'Pre-Raid', 'Warlock', 'Destruction', 'Waist', 'Both', 'Girdle of Ruination'), +(9, 2, 6, 0, 120, 24262, 'Pre-Raid', 'Warlock', 'Destruction', 'Legs', 'Both', 'Spellstrike Pants'), +(9, 2, 7, 0, 120, 21870, 'Pre-Raid', 'Warlock', 'Destruction', 'Feet', 'Both', 'Frozen Shadoweave Boots'), +(9, 2, 8, 0, 120, 24250, 'Pre-Raid', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Bracers of Havok'), +(9, 2, 9, 0, 120, 24450, 'Pre-Raid', 'Warlock', 'Destruction', 'Hands', 'Both', 'Manaspark Gloves'), +(9, 2, 10, 0, 120, 29172, 'Pre-Raid', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Ashyen''s Gift'), +(9, 2, 11, 0, 120, 28227, 'Pre-Raid', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Sparking Arcanite Ring'), +(9, 2, 12, 0, 120, 29370, 'Pre-Raid', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 2, 13, 0, 120, 29132, 'Pre-Raid', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'Scryer''s Bloodgem'), +(9, 2, 14, 0, 120, 27981, 'Pre-Raid', 'Warlock', 'Destruction', 'Back', 'Both', 'Sethekk Oracle Cloak'), +(9, 2, 15, 0, 120, 23554, 'Pre-Raid', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Eternium Runed Blade'), +(9, 2, 16, 0, 120, 29272, 'Pre-Raid', 'Warlock', 'Destruction', 'OffHand', 'Both', 'Orb of the Soul-Eater'), +(9, 2, 17, 0, 120, 29350, 'Pre-Raid', 'Warlock', 'Destruction', 'Ranged', 'Both', 'The Black Stalk'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 125, 28963, 'Phase 1', 'Warlock', 'Destruction', 'Head', 'Both', 'Voidheart Crown'), +(9, 2, 1, 0, 125, 28530, 'Phase 1', 'Warlock', 'Destruction', 'Neck', 'Both', 'Brooch of Unquenchable Fury'), +(9, 2, 2, 0, 125, 28967, 'Phase 1', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Voidheart Mantle'), +(9, 2, 4, 0, 125, 28964, 'Phase 1', 'Warlock', 'Destruction', 'Chest', 'Both', 'Voidheart Robe'), +(9, 2, 5, 0, 125, 24256, 'Phase 1', 'Warlock', 'Destruction', 'Waist', 'Both', 'Girdle of Ruination'), +(9, 2, 6, 0, 125, 30734, 'Phase 1', 'Warlock', 'Destruction', 'Legs', 'Both', 'Leggings of the Seventh Circle'), +(9, 2, 7, 0, 125, 21870, 'Phase 1', 'Warlock', 'Destruction', 'Feet', 'Both', 'Frozen Shadoweave Boots'), +(9, 2, 8, 0, 125, 24250, 'Phase 1', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Bracers of Havok'), +(9, 2, 9, 0, 125, 28968, 'Phase 1', 'Warlock', 'Destruction', 'Hands', 'Both', 'Voidheart Gloves'), +(9, 2, 10, 0, 125, 29172, 'Phase 1', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Ashyen''s Gift'), +(9, 2, 11, 0, 125, 28793, 'Phase 1', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Band of Crimson Fury'), +(9, 2, 12, 0, 125, 29370, 'Phase 1', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 2, 13, 0, 125, 27683, 'Phase 1', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(9, 2, 14, 0, 125, 28766, 'Phase 1', 'Warlock', 'Destruction', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(9, 2, 15, 0, 125, 30723, 'Phase 1', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Talon of the Tempest'), +(9, 2, 16, 0, 125, 29272, 'Phase 1', 'Warlock', 'Destruction', 'OffHand', 'Both', 'Orb of the Soul-Eater'), +(9, 2, 17, 0, 125, 28783, 'Phase 1', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Eredar Wand of Obliteration'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 141, 32494, 'Phase 2', 'Warlock', 'Destruction', 'Head', 'Both', 'Destruction Holo-gogs'), +(9, 2, 1, 0, 141, 30015, 'Phase 2', 'Warlock', 'Destruction', 'Neck', 'Both', 'The Sun King''s Talisman'), +(9, 2, 2, 0, 141, 28967, 'Phase 2', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Voidheart Mantle'), +(9, 2, 4, 0, 141, 30107, 'Phase 2', 'Warlock', 'Destruction', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(9, 2, 5, 0, 141, 30038, 'Phase 2', 'Warlock', 'Destruction', 'Waist', 'Both', 'Belt of Blasting'), +(9, 2, 6, 0, 141, 30213, 'Phase 2', 'Warlock', 'Destruction', 'Legs', 'Both', 'Leggings of the Corruptor'), +(9, 2, 7, 0, 141, 30037, 'Phase 2', 'Warlock', 'Destruction', 'Feet', 'Both', 'Boots of Blasting'), +(9, 2, 8, 0, 141, 29918, 'Phase 2', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Mindstorm Wristbands'), +(9, 2, 9, 0, 141, 28968, 'Phase 2', 'Warlock', 'Destruction', 'Hands', 'Both', 'Voidheart Gloves'), +(9, 2, 10, 0, 141, 29302, 'Phase 2', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Band of Eternity'), +(9, 2, 11, 0, 141, 30109, 'Phase 2', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Ring of Endless Coils'), +(9, 2, 12, 0, 141, 29370, 'Phase 2', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 2, 13, 0, 141, 27683, 'Phase 2', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(9, 2, 14, 0, 141, 28766, 'Phase 2', 'Warlock', 'Destruction', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(9, 2, 15, 0, 141, 30095, 'Phase 2', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Fang of the Leviathan'), +(9, 2, 16, 0, 141, 30049, 'Phase 2', 'Warlock', 'Destruction', 'OffHand', 'Both', 'Fathomstone'), +(9, 2, 17, 0, 141, 29982, 'Phase 2', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Wand of the Forgotten Star'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 156, 31051, 'Phase 3', 'Warlock', 'Destruction', 'Head', 'Both', 'Hood of the Malefic'), +(9, 2, 1, 0, 156, 32589, 'Phase 3', 'Warlock', 'Destruction', 'Neck', 'Both', 'Hellfire-Encased Pendant'), +(9, 2, 2, 0, 156, 31054, 'Phase 3', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Mantle of the Malefic'), +(9, 2, 4, 0, 156, 30107, 'Phase 3', 'Warlock', 'Destruction', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(9, 2, 5, 0, 156, 30888, 'Phase 3', 'Warlock', 'Destruction', 'Waist', 'Both', 'Anetheron''s Noose'), +(9, 2, 6, 0, 156, 31053, 'Phase 3', 'Warlock', 'Destruction', 'Legs', 'Both', 'Leggings of the Malefic'), +(9, 2, 7, 0, 156, 32239, 'Phase 3', 'Warlock', 'Destruction', 'Feet', 'Both', 'Slippers of the Seacaller'), +(9, 2, 8, 0, 156, 32586, 'Phase 3', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(9, 2, 9, 0, 156, 31050, 'Phase 3', 'Warlock', 'Destruction', 'Hands', 'Both', 'Gloves of the Malefic'), +(9, 2, 10, 0, 156, 32527, 'Phase 3', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Ring of Ancient Knowledge'), +(9, 2, 11, 0, 156, 32247, 'Phase 3', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Ring of Captured Storms'), +(9, 2, 12, 0, 156, 29370, 'Phase 3', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(9, 2, 13, 0, 156, 32483, 'Phase 3', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(9, 2, 14, 0, 156, 32590, 'Phase 3', 'Warlock', 'Destruction', 'Back', 'Both', 'Nethervoid Cloak'), +(9, 2, 15, 0, 156, 32374, 'Phase 3', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Zhar''doom, Greatstaff of the Devourer'), +(9, 2, 17, 0, 156, 29982, 'Phase 3', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Wand of the Forgotten Star'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 164, 31051, 'Phase 4', 'Warlock', 'Destruction', 'Head', 'Both', 'Hood of the Malefic'), +(9, 2, 1, 0, 164, 32589, 'Phase 4', 'Warlock', 'Destruction', 'Neck', 'Both', 'Hellfire-Encased Pendant'), +(9, 2, 2, 0, 164, 31054, 'Phase 4', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Mantle of the Malefic'), +(9, 2, 4, 0, 164, 30107, 'Phase 4', 'Warlock', 'Destruction', 'Chest', 'Both', 'Vestments of the Sea-Witch'), +(9, 2, 5, 0, 164, 30888, 'Phase 4', 'Warlock', 'Destruction', 'Waist', 'Both', 'Anetheron''s Noose'), +(9, 2, 6, 0, 164, 31053, 'Phase 4', 'Warlock', 'Destruction', 'Legs', 'Both', 'Leggings of the Malefic'), +(9, 2, 7, 0, 164, 32239, 'Phase 4', 'Warlock', 'Destruction', 'Feet', 'Both', 'Slippers of the Seacaller'), +(9, 2, 8, 0, 164, 32586, 'Phase 4', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Bracers of Nimble Thought'), +(9, 2, 9, 0, 164, 31050, 'Phase 4', 'Warlock', 'Destruction', 'Hands', 'Both', 'Gloves of the Malefic'), +(9, 2, 10, 0, 164, 33497, 'Phase 4', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Mana Attuned Band'), +(9, 2, 11, 0, 164, 32247, 'Phase 4', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Ring of Captured Storms'), +(9, 2, 12, 0, 164, 33829, 'Phase 4', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Hex Shrunken Head'), +(9, 2, 13, 0, 164, 32483, 'Phase 4', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'The Skull of Gul''dan'), +(9, 2, 14, 0, 164, 32590, 'Phase 4', 'Warlock', 'Destruction', 'Back', 'Both', 'Nethervoid Cloak'), +(9, 2, 15, 0, 164, 32374, 'Phase 4', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Zhar''doom, Greatstaff of the Devourer'), +(9, 2, 16, 0, 164, 34179, 'Phase 5', 'Warlock', 'Destruction', 'OffHand', 'Both', 'Heart of the Pit'), +(9, 2, 17, 0, 164, 33192, 'Phase 4', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Carved Witch Doctor''s Stick'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 200, 42553, 'Pre-Raid', 'Warlock', 'Destruction', 'Head', 'Both', 'Visage Liquification Goggles'), +(9, 2, 1, 0, 200, 39472, 'Pre-Raid', 'Warlock', 'Destruction', 'Neck', 'Both', 'Chain of Latent Energies'), +(9, 2, 2, 0, 200, 34393, 'Pre-Raid', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Shoulderpads of Knowledge''s Pursuit'), +(9, 2, 4, 0, 200, 39497, 'Pre-Raid', 'Warlock', 'Destruction', 'Chest', 'Both', 'Heroes'' Plagueheart Robe'), +(9, 2, 5, 0, 200, 40696, 'Pre-Raid', 'Warlock', 'Destruction', 'Waist', 'Both', 'Plush Sash of Guzbah'), +(9, 2, 6, 0, 200, 34170, 'Pre-Raid', 'Warlock', 'Destruction', 'Legs', 'Both', 'Pantaloons of Calming Strife'), +(9, 2, 7, 0, 200, 40558, 'Pre-Raid', 'Warlock', 'Destruction', 'Feet', 'Both', 'Arcanic Tramplers'), +(9, 2, 8, 0, 200, 37361, 'Pre-Raid', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Cuffs of Winged Levitation'), +(9, 2, 9, 0, 200, 39500, 'Pre-Raid', 'Warlock', 'Destruction', 'Hands', 'Both', 'Heroes'' Plagueheart Gloves'), +(9, 2, 10, 0, 200, 43253, 'Pre-Raid', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Ring of Northern Tears'), +(9, 2, 12, 0, 200, 40682, 'Pre-Raid', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Sundial of the Exiled'), +(9, 2, 14, 0, 200, 41610, 'Pre-Raid', 'Warlock', 'Destruction', 'Back', 'Both', 'Deathchill Cloak'), +(9, 2, 17, 0, 200, 34348, 'Pre-Raid', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Wand of Cleansing Light'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 224, 40421, 'Phase 1', 'Warlock', 'Destruction', 'Head', 'Both', 'Valorous Plagueheart Circlet'), +(9, 2, 1, 0, 224, 44661, 'Phase 1', 'Warlock', 'Destruction', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(9, 2, 2, 0, 224, 40424, 'Phase 1', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Valorous Plagueheart Shoulderpads'), +(9, 2, 4, 0, 224, 40423, 'Phase 1', 'Warlock', 'Destruction', 'Chest', 'Both', 'Valorous Plagueheart Robe'), +(9, 2, 5, 0, 224, 40561, 'Phase 1', 'Warlock', 'Destruction', 'Waist', 'Both', 'Leash of Heedless Magic'), +(9, 2, 6, 0, 224, 40560, 'Phase 1', 'Warlock', 'Destruction', 'Legs', 'Both', 'Leggings of the Wanton Spellcaster'), +(9, 2, 7, 0, 224, 40558, 'Phase 1', 'Warlock', 'Destruction', 'Feet', 'Both', 'Arcanic Tramplers'), +(9, 2, 8, 0, 224, 44008, 'Phase 1', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Unsullied Cuffs'), +(9, 2, 9, 0, 224, 40420, 'Phase 1', 'Warlock', 'Destruction', 'Hands', 'Both', 'Valorous Plagueheart Gloves'), +(9, 2, 10, 0, 224, 40399, 'Phase 1', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Signet of Manifested Pain'), +(9, 2, 12, 0, 224, 40432, 'Phase 1', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Illustration of the Dragon Soul'), +(9, 2, 14, 0, 224, 44005, 'Phase 1', 'Warlock', 'Destruction', 'Back', 'Both', 'Pennant Cloak'), +(9, 2, 15, 0, 224, 40396, 'Phase 1', 'Warlock', 'Destruction', 'MainHand', 'Both', 'The Turning Tide'), +(9, 2, 17, 0, 224, 39712, 'Phase 1', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Gemmed Wand of the Nerubians'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 245, 45497, 'Phase 2', 'Warlock', 'Destruction', 'Head', 'Both', 'Crown of Luminescence'), +(9, 2, 1, 0, 245, 45133, 'Phase 2', 'Warlock', 'Destruction', 'Neck', 'Both', 'Pendant of Fiery Havoc'), +(9, 2, 2, 0, 245, 46068, 'Phase 2', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Amice of Inconceivable Horror'), +(9, 2, 4, 0, 245, 46137, 'Phase 2', 'Warlock', 'Destruction', 'Chest', 'Both', 'Conqueror''s Deathbringer Robe'), +(9, 2, 9, 0, 245, 45665, 'Phase 2', 'Warlock', 'Destruction', 'Hands', 'Both', 'Pharos Gloves'), +(9, 2, 10, 0, 245, 45495, 'Phase 2', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Conductive Seal'), +(9, 2, 14, 0, 245, 45618, 'Phase 2', 'Warlock', 'Destruction', 'Back', 'Both', 'Sunglimmer Cloak'), +(9, 2, 15, 0, 245, 45620, 'Phase 2', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Starshard Edge'), +(9, 2, 17, 0, 245, 45294, 'Phase 2', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 10, 0, 258, 45495, 'Phase 3', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Conductive Seal'), +(9, 2, 17, 0, 258, 45294, 'Phase 3', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Petrified Ivy Sprig'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(9, 2, 0, 0, 290, 51231, 'Phase 5', 'Warlock', 'Destruction', 'Head', 'Both', 'Sanctified Dark Coven Hood'), +(9, 2, 1, 0, 290, 50182, 'Phase 5', 'Warlock', 'Destruction', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(9, 2, 2, 0, 290, 51234, 'Phase 5', 'Warlock', 'Destruction', 'Shoulders', 'Both', 'Sanctified Dark Coven Shoulderpads'), +(9, 2, 4, 0, 290, 51233, 'Phase 5', 'Warlock', 'Destruction', 'Chest', 'Both', 'Sanctified Dark Coven Robe'), +(9, 2, 5, 0, 290, 50613, 'Phase 5', 'Warlock', 'Destruction', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(9, 2, 6, 0, 290, 50694, 'Phase 5', 'Warlock', 'Destruction', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(9, 2, 7, 0, 290, 50699, 'Phase 5', 'Warlock', 'Destruction', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(9, 2, 8, 0, 290, 54582, 'Phase 5', 'Warlock', 'Destruction', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(9, 2, 9, 0, 290, 51230, 'Phase 5', 'Warlock', 'Destruction', 'Hands', 'Both', 'Sanctified Dark Coven Gloves'), +(9, 2, 10, 0, 290, 50614, 'Phase 5', 'Warlock', 'Destruction', 'Finger1', 'Both', 'Loop of the Endless Labyrinth'), +(9, 2, 11, 0, 290, 50664, 'Phase 5', 'Warlock', 'Destruction', 'Finger2', 'Both', 'Ring of Rapid Ascent'), +(9, 2, 12, 0, 290, 54588, 'Phase 5', 'Warlock', 'Destruction', 'Trinket1', 'Both', 'Charred Twilight Scale'), +(9, 2, 13, 0, 290, 50365, 'Phase 5', 'Warlock', 'Destruction', 'Trinket2', 'Both', 'Phylactery of the Nameless Lich'), +(9, 2, 14, 0, 290, 54583, 'Phase 5', 'Warlock', 'Destruction', 'Back', 'Both', 'Cloak of Burning Dusk'), +(9, 2, 15, 0, 290, 50732, 'Phase 5', 'Warlock', 'Destruction', 'MainHand', 'Both', 'Bloodsurge, Kel''Thuzad''s Blade of Agony'), +(9, 2, 16, 0, 290, 50719, 'Phase 5', 'Warlock', 'Destruction', 'OffHand', 'Both', 'Shadow Silk Spindle'), +(9, 2, 17, 0, 290, 50684, 'Phase 5', 'Warlock', 'Destruction', 'Ranged', 'Both', 'Corpse-Impaling Spike'); + + +-- ============================================================ +-- Druid (11) +-- ============================================================ +-- Balance (tab 0) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 1, 66, 10504, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Head', 'Alliance', 'Green Lens'), +(11, 0, 0, 2, 66, 10504, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Head', 'Horde', 'Green Lens'), +(11, 0, 1, 1, 66, 12103, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Neck', 'Alliance', 'Star of Mystaria'), +(11, 0, 1, 2, 66, 12103, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Neck', 'Horde', 'Star of Mystaria'), +(11, 0, 2, 1, 66, 13013, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Shoulders', 'Alliance', 'Elder Wizard''s Mantle'), +(11, 0, 2, 2, 66, 13013, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Shoulders', 'Horde', 'Elder Wizard''s Mantle'), +(11, 0, 4, 1, 66, 11924, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Chest', 'Alliance', 'Robes of the Royal Crown'), +(11, 0, 4, 2, 66, 11924, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Chest', 'Horde', 'Robes of the Royal Crown'), +(11, 0, 5, 1, 66, 11662, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Waist', 'Alliance', 'Ban''thok Sash'), +(11, 0, 5, 2, 66, 11662, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Waist', 'Horde', 'Ban''thok Sash'), +(11, 0, 6, 1, 66, 13170, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(11, 0, 6, 2, 66, 13170, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Legs', 'Horde', 'Skyshroud Leggings'), +(11, 0, 7, 1, 66, 11822, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Feet', 'Alliance', 'Omnicast Boots'), +(11, 0, 7, 2, 66, 11822, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Feet', 'Horde', 'Omnicast Boots'), +(11, 0, 8, 1, 66, 11766, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(11, 0, 8, 2, 66, 11766, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(11, 0, 9, 1, 66, 13258, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Hands', 'Alliance', 'Slaghide Gauntlets'), +(11, 0, 9, 2, 66, 13258, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Hands', 'Horde', 'Slaghide Gauntlets'), +(11, 0, 10, 1, 66, 12543, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(11, 0, 10, 2, 66, 12545, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(11, 0, 11, 1, 66, 13001, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(11, 0, 11, 2, 66, 13001, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Finger2', 'Horde', 'Maiden''s Circle'), +(11, 0, 12, 1, 66, 12930, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(11, 0, 12, 2, 66, 12930, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Trinket1', 'Horde', 'Briarwood Reed'), +(11, 0, 13, 1, 66, 13968, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Trinket2', 'Alliance', 'Eye of the Beast'), +(11, 0, 13, 2, 66, 13968, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Trinket2', 'Horde', 'Eye of the Beast'), +(11, 0, 14, 1, 66, 13386, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Back', 'Alliance', 'Archivist Cape'), +(11, 0, 14, 2, 66, 13386, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'Back', 'Horde', 'Archivist Cape'), +(11, 0, 15, 1, 66, 13964, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'MainHand', 'Alliance', 'Witchblade'), +(11, 0, 15, 2, 66, 13964, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'MainHand', 'Horde', 'Witchblade'), +(11, 0, 16, 1, 66, 10796, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'OffHand', 'Alliance', 'Drakestone'), +(11, 0, 16, 2, 66, 10796, 'Phase 1 (Pre-Raid)', 'Druid', 'Balance', 'OffHand', 'Horde', 'Drakestone'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 1, 76, 10504, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Head', 'Alliance', 'Green Lens'), +(11, 0, 0, 2, 76, 10504, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Head', 'Horde', 'Green Lens'), +(11, 0, 1, 1, 76, 12103, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Neck', 'Alliance', 'Star of Mystaria'), +(11, 0, 1, 2, 76, 12103, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Neck', 'Horde', 'Star of Mystaria'), +(11, 0, 2, 1, 76, 13013, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Shoulders', 'Alliance', 'Elder Wizard''s Mantle'), +(11, 0, 2, 2, 76, 13013, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Shoulders', 'Horde', 'Elder Wizard''s Mantle'), +(11, 0, 4, 1, 76, 18385, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Chest', 'Alliance', 'Robe of Everlasting Night'), +(11, 0, 4, 2, 76, 18385, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Chest', 'Horde', 'Robe of Everlasting Night'), +(11, 0, 5, 1, 76, 11662, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Waist', 'Alliance', 'Ban''thok Sash'), +(11, 0, 5, 2, 76, 11662, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Waist', 'Horde', 'Ban''thok Sash'), +(11, 0, 6, 1, 76, 13170, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Legs', 'Alliance', 'Skyshroud Leggings'), +(11, 0, 6, 2, 76, 13170, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Legs', 'Horde', 'Skyshroud Leggings'), +(11, 0, 7, 1, 76, 11822, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Feet', 'Alliance', 'Omnicast Boots'), +(11, 0, 7, 2, 76, 11822, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Feet', 'Horde', 'Omnicast Boots'), +(11, 0, 8, 1, 76, 11766, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Wrists', 'Alliance', 'Flameweave Cuffs'), +(11, 0, 8, 2, 76, 11766, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Wrists', 'Horde', 'Flameweave Cuffs'), +(11, 0, 9, 1, 76, 13258, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Hands', 'Alliance', 'Slaghide Gauntlets'), +(11, 0, 9, 2, 76, 13258, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Hands', 'Horde', 'Slaghide Gauntlets'), +(11, 0, 10, 1, 76, 12543, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Finger1', 'Alliance', 'Songstone of Ironforge'), +(11, 0, 10, 2, 76, 12545, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Finger1', 'Horde', 'Eye of Orgrimmar'), +(11, 0, 11, 1, 76, 13001, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Finger2', 'Alliance', 'Maiden''s Circle'), +(11, 0, 11, 2, 76, 13001, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Finger2', 'Horde', 'Maiden''s Circle'), +(11, 0, 12, 1, 76, 12930, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Trinket1', 'Alliance', 'Briarwood Reed'), +(11, 0, 12, 2, 76, 12930, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Trinket1', 'Horde', 'Briarwood Reed'), +(11, 0, 13, 1, 76, 13968, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Trinket2', 'Alliance', 'Eye of the Beast'), +(11, 0, 13, 2, 76, 13968, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Trinket2', 'Horde', 'Eye of the Beast'), +(11, 0, 14, 1, 76, 13386, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Back', 'Alliance', 'Archivist Cape'), +(11, 0, 14, 2, 76, 13386, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'Back', 'Horde', 'Archivist Cape'), +(11, 0, 15, 1, 76, 18534, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'MainHand', 'Alliance', 'Rod of the Ogre Magi'), +(11, 0, 15, 2, 76, 18534, 'Phase 2 (Pre-Raid)', 'Druid', 'Balance', 'MainHand', 'Horde', 'Rod of the Ogre Magi'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 78, 10504, 'Phase 2', 'Druid', 'Balance', 'Head', 'Both', 'Green Lens'), +(11, 0, 1, 0, 78, 18814, 'Phase 2', 'Druid', 'Balance', 'Neck', 'Both', 'Choker of the Fire Lord'), +(11, 0, 2, 0, 78, 16836, 'Phase 2', 'Druid', 'Balance', 'Shoulders', 'Both', 'Cenarion Spaulders'), +(11, 0, 4, 0, 78, 19145, 'Phase 2', 'Druid', 'Balance', 'Chest', 'Both', 'Robe of Volatile Power'), +(11, 0, 5, 0, 78, 19136, 'Phase 2', 'Druid', 'Balance', 'Waist', 'Both', 'Mana Igniting Cord'), +(11, 0, 6, 0, 78, 13170, 'Phase 2', 'Druid', 'Balance', 'Legs', 'Both', 'Skyshroud Leggings'), +(11, 0, 7, 0, 78, 11822, 'Phase 2', 'Druid', 'Balance', 'Feet', 'Both', 'Omnicast Boots'), +(11, 0, 8, 0, 78, 11766, 'Phase 2', 'Druid', 'Balance', 'Wrists', 'Both', 'Flameweave Cuffs'), +(11, 0, 9, 0, 78, 13258, 'Phase 2', 'Druid', 'Balance', 'Hands', 'Both', 'Slaghide Gauntlets'), +(11, 0, 10, 0, 78, 19147, 'Phase 2', 'Druid', 'Balance', 'Finger1', 'Both', 'Ring of Spell Power'), +(11, 0, 11, 0, 78, 19147, 'Phase 2', 'Druid', 'Balance', 'Finger2', 'Both', 'Ring of Spell Power'), +(11, 0, 12, 0, 78, 12930, 'Phase 2', 'Druid', 'Balance', 'Trinket1', 'Both', 'Briarwood Reed'), +(11, 0, 13, 0, 78, 18820, 'Phase 2', 'Druid', 'Balance', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(11, 0, 14, 0, 78, 13386, 'Phase 2', 'Druid', 'Balance', 'Back', 'Both', 'Archivist Cape'), +(11, 0, 15, 0, 78, 18842, 'Phase 2', 'Druid', 'Balance', 'MainHand', 'Both', 'Staff of Dominance'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 83, 19375, 'Phase 3', 'Druid', 'Balance', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(11, 0, 1, 0, 83, 18814, 'Phase 3', 'Druid', 'Balance', 'Neck', 'Both', 'Choker of the Fire Lord'), +(11, 0, 2, 0, 83, 19370, 'Phase 3', 'Druid', 'Balance', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(11, 0, 4, 0, 83, 19145, 'Phase 3', 'Druid', 'Balance', 'Chest', 'Both', 'Robe of Volatile Power'), +(11, 0, 5, 0, 83, 19400, 'Phase 3', 'Druid', 'Balance', 'Waist', 'Both', 'Firemaw''s Clutch'), +(11, 0, 6, 0, 83, 13170, 'Phase 3', 'Druid', 'Balance', 'Legs', 'Both', 'Skyshroud Leggings'), +(11, 0, 7, 0, 83, 11822, 'Phase 3', 'Druid', 'Balance', 'Feet', 'Both', 'Omnicast Boots'), +(11, 0, 8, 0, 83, 19374, 'Phase 3', 'Druid', 'Balance', 'Wrists', 'Both', 'Bracers of Arcane Accuracy'), +(11, 0, 9, 0, 83, 13258, 'Phase 3', 'Druid', 'Balance', 'Hands', 'Both', 'Slaghide Gauntlets'), +(11, 0, 11, 0, 83, 19147, 'Phase 3', 'Druid', 'Balance', 'Finger2', 'Both', 'Ring of Spell Power'), +(11, 0, 12, 0, 83, 19379, 'Phase 3', 'Druid', 'Balance', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(11, 0, 13, 0, 83, 18820, 'Phase 3', 'Druid', 'Balance', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(11, 0, 14, 0, 83, 19378, 'Phase 3', 'Druid', 'Balance', 'Back', 'Both', 'Cloak of the Brood Lord'), +(11, 0, 15, 0, 83, 19360, 'Phase 3', 'Druid', 'Balance', 'MainHand', 'Both', 'Lok''amir il Romathis'), +(11, 0, 16, 0, 83, 19308, 'Phase 3', 'Druid', 'Balance', 'OffHand', 'Both', 'Tome of Arcane Domination'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 88, 19375, 'Phase 5', 'Druid', 'Balance', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(11, 0, 1, 0, 88, 21608, 'Phase 5', 'Druid', 'Balance', 'Neck', 'Both', 'Amulet of Vek''nilash'), +(11, 0, 2, 0, 88, 19370, 'Phase 5', 'Druid', 'Balance', 'Shoulders', 'Both', 'Mantle of the Blackwing Cabal'), +(11, 0, 4, 0, 88, 19682, 'Phase 5', 'Druid', 'Balance', 'Chest', 'Both', 'Bloodvine Vest'), +(11, 0, 5, 0, 88, 22730, 'Phase 5', 'Druid', 'Balance', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(11, 0, 6, 0, 88, 19683, 'Phase 5', 'Druid', 'Balance', 'Legs', 'Both', 'Bloodvine Leggings'), +(11, 0, 7, 0, 88, 19684, 'Phase 5', 'Druid', 'Balance', 'Feet', 'Both', 'Bloodvine Boots'), +(11, 0, 8, 0, 88, 21186, 'Phase 5', 'Druid', 'Balance', 'Wrists', 'Both', 'Rockfury Bracers'), +(11, 0, 9, 0, 88, 21585, 'Phase 5', 'Druid', 'Balance', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(11, 0, 11, 0, 88, 21709, 'Phase 5', 'Druid', 'Balance', 'Finger2', 'Both', 'Ring of the Fallen God'), +(11, 0, 12, 0, 88, 19379, 'Phase 5', 'Druid', 'Balance', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(11, 0, 13, 0, 88, 18820, 'Phase 5', 'Druid', 'Balance', 'Trinket2', 'Both', 'Talisman of Ephemeral Power'), +(11, 0, 14, 0, 88, 22731, 'Phase 5', 'Druid', 'Balance', 'Back', 'Both', 'Cloak of the Devoured'), +(11, 0, 15, 0, 88, 19360, 'Phase 5', 'Druid', 'Balance', 'MainHand', 'Both', 'Lok''amir il Romathis'), +(11, 0, 16, 0, 88, 21597, 'Phase 5', 'Druid', 'Balance', 'OffHand', 'Both', 'Royal Scepter of Vek''lor'), +(11, 0, 17, 0, 88, 23197, 'Phase 5', 'Druid', 'Balance', 'Ranged', 'Both', 'Idol of the Moon'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 92, 19375, 'Phase 6', 'Druid', 'Balance', 'Head', 'Both', 'Mish''undare, Circlet of the Mind Flayer'), +(11, 0, 1, 0, 92, 23057, 'Phase 6', 'Druid', 'Balance', 'Neck', 'Both', 'Gem of Trapped Innocents'), +(11, 0, 2, 0, 92, 22983, 'Phase 6', 'Druid', 'Balance', 'Shoulders', 'Both', 'Rime Covered Mantle'), +(11, 0, 4, 0, 92, 19682, 'Phase 6', 'Druid', 'Balance', 'Chest', 'Both', 'Bloodvine Vest'), +(11, 0, 5, 0, 92, 22730, 'Phase 6', 'Druid', 'Balance', 'Waist', 'Both', 'Eyestalk Waist Cord'), +(11, 0, 6, 0, 92, 19683, 'Phase 6', 'Druid', 'Balance', 'Legs', 'Both', 'Bloodvine Leggings'), +(11, 0, 7, 0, 92, 19684, 'Phase 6', 'Druid', 'Balance', 'Feet', 'Both', 'Bloodvine Boots'), +(11, 0, 8, 0, 92, 23021, 'Phase 6', 'Druid', 'Balance', 'Wrists', 'Both', 'The Soul Harvester''s Bindings'), +(11, 0, 9, 0, 92, 21585, 'Phase 6', 'Druid', 'Balance', 'Hands', 'Both', 'Dark Storm Gauntlets'), +(11, 0, 10, 0, 92, 23025, 'Phase 6', 'Druid', 'Balance', 'Finger1', 'Both', 'Seal of the Damned'), +(11, 0, 11, 0, 92, 21709, 'Phase 6', 'Druid', 'Balance', 'Finger2', 'Both', 'Ring of the Fallen God'), +(11, 0, 12, 0, 92, 19379, 'Phase 6', 'Druid', 'Balance', 'Trinket1', 'Both', 'Neltharion''s Tear'), +(11, 0, 13, 0, 92, 23046, 'Phase 6', 'Druid', 'Balance', 'Trinket2', 'Both', 'The Restrained Essence of Sapphiron'), +(11, 0, 14, 0, 92, 23050, 'Phase 6', 'Druid', 'Balance', 'Back', 'Both', 'Cloak of the Necropolis'), +(11, 0, 15, 0, 92, 22988, 'Phase 6', 'Druid', 'Balance', 'MainHand', 'Both', 'The End of Dreams'), +(11, 0, 16, 0, 92, 23049, 'Phase 6', 'Druid', 'Balance', 'OffHand', 'Both', 'Sapphiron''s Left Eye'), +(11, 0, 17, 0, 92, 23197, 'Phase 6', 'Druid', 'Balance', 'Ranged', 'Both', 'Idol of the Moon'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 120, 24266, 'Pre-Raid', 'Druid', 'Balance', 'Head', 'Both', 'Spellstrike Hood'), +(11, 0, 1, 0, 120, 28134, 'Pre-Raid', 'Druid', 'Balance', 'Neck', 'Both', 'Brooch of Heightened Potential'), +(11, 0, 2, 0, 120, 27796, 'Pre-Raid', 'Druid', 'Balance', 'Shoulders', 'Both', 'Mana-Etched Spaulders'), +(11, 0, 4, 0, 120, 21848, 'Pre-Raid', 'Druid', 'Balance', 'Chest', 'Both', 'Spellfire Robe'), +(11, 0, 5, 0, 120, 21846, 'Pre-Raid', 'Druid', 'Balance', 'Waist', 'Both', 'Spellfire Belt'), +(11, 0, 6, 0, 120, 24262, 'Pre-Raid', 'Druid', 'Balance', 'Legs', 'Both', 'Spellstrike Pants'), +(11, 0, 7, 0, 120, 28406, 'Pre-Raid', 'Druid', 'Balance', 'Feet', 'Both', 'Sigil-Laced Boots'), +(11, 0, 8, 0, 120, 24250, 'Pre-Raid', 'Druid', 'Balance', 'Wrists', 'Both', 'Bracers of Havok'), +(11, 0, 9, 0, 120, 21847, 'Pre-Raid', 'Druid', 'Balance', 'Hands', 'Both', 'Spellfire Gloves'), +(11, 0, 10, 0, 120, 29172, 'Pre-Raid', 'Druid', 'Balance', 'Finger1', 'Both', 'Ashyen''s Gift'), +(11, 0, 11, 0, 120, 28227, 'Pre-Raid', 'Druid', 'Balance', 'Finger2', 'Both', 'Sparking Arcanite Ring'), +(11, 0, 12, 0, 120, 29370, 'Pre-Raid', 'Druid', 'Balance', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(11, 0, 13, 0, 120, 27683, 'Pre-Raid', 'Druid', 'Balance', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(11, 0, 14, 0, 120, 27981, 'Pre-Raid', 'Druid', 'Balance', 'Back', 'Both', 'Sethekk Oracle Cloak'), +(11, 0, 15, 0, 120, 30832, 'Pre-Raid', 'Druid', 'Balance', 'MainHand', 'Both', 'Gavel of Unearthed Secrets'), +(11, 0, 16, 0, 120, 29271, 'Pre-Raid', 'Druid', 'Balance', 'OffHand', 'Both', 'Talisman of Kalecgos'), +(11, 0, 17, 0, 120, 27518, 'Pre-Raid', 'Druid', 'Balance', 'Ranged', 'Both', 'Ivory Idol of the Moongoddess'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 125, 29093, 'Phase 1', 'Druid', 'Balance', 'Head', 'Both', 'Antlers of Malorne'), +(11, 0, 1, 0, 125, 28530, 'Phase 1', 'Druid', 'Balance', 'Neck', 'Both', 'Brooch of Unquenchable Fury'), +(11, 0, 2, 0, 125, 29095, 'Phase 1', 'Druid', 'Balance', 'Shoulders', 'Both', 'Pauldrons of Malorne'), +(11, 0, 4, 0, 125, 21848, 'Phase 1', 'Druid', 'Balance', 'Chest', 'Both', 'Spellfire Robe'), +(11, 0, 5, 0, 125, 21846, 'Phase 1', 'Druid', 'Balance', 'Waist', 'Both', 'Spellfire Belt'), +(11, 0, 6, 0, 125, 24262, 'Phase 1', 'Druid', 'Balance', 'Legs', 'Both', 'Spellstrike Pants'), +(11, 0, 7, 0, 125, 28517, 'Phase 1', 'Druid', 'Balance', 'Feet', 'Both', 'Boots of Foretelling'), +(11, 0, 8, 0, 125, 24250, 'Phase 1', 'Druid', 'Balance', 'Wrists', 'Both', 'Bracers of Havok'), +(11, 0, 9, 0, 125, 21847, 'Phase 1', 'Druid', 'Balance', 'Hands', 'Both', 'Spellfire Gloves'), +(11, 0, 10, 0, 125, 28793, 'Phase 1', 'Druid', 'Balance', 'Finger1', 'Both', 'Band of Crimson Fury'), +(11, 0, 11, 0, 125, 28753, 'Phase 1', 'Druid', 'Balance', 'Finger2', 'Both', 'Ring of Recurrence'), +(11, 0, 12, 0, 125, 29370, 'Phase 1', 'Druid', 'Balance', 'Trinket1', 'Both', 'Icon of the Silver Crescent'), +(11, 0, 13, 0, 125, 27683, 'Phase 1', 'Druid', 'Balance', 'Trinket2', 'Both', 'Quagmirran''s Eye'), +(11, 0, 14, 0, 125, 28766, 'Phase 1', 'Druid', 'Balance', 'Back', 'Both', 'Ruby Drape of the Mysticant'), +(11, 0, 15, 0, 125, 28770, 'Phase 1', 'Druid', 'Balance', 'MainHand', 'Both', 'Nathrezim Mindblade'), +(11, 0, 16, 0, 125, 29271, 'Phase 1', 'Druid', 'Balance', 'OffHand', 'Both', 'Talisman of Kalecgos'), +(11, 0, 17, 0, 125, 27518, 'Phase 1', 'Druid', 'Balance', 'Ranged', 'Both', 'Ivory Idol of the Moongoddess'); + +-- ilvl 200 (Fresh 80) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 200, 39545, 'Fresh 80', 'Druid', 'Balance', 'Head', 'Both', 'Heroes'' Dreamwalker Cover'), +(11, 0, 1, 0, 200, 44658, 'Fresh 80', 'Druid', 'Balance', 'Neck', 'Both', 'Chain of the Ancient Wyrm'), +(11, 0, 2, 0, 200, 39548, 'Fresh 80', 'Druid', 'Balance', 'Shoulders', 'Both', 'Heroes'' Dreamwalker Mantle'), +(11, 0, 4, 0, 200, 39547, 'Fresh 80', 'Druid', 'Balance', 'Chest', 'Both', 'Heroes'' Dreamwalker Vestments'), +(11, 0, 5, 0, 200, 40696, 'Fresh 80', 'Druid', 'Balance', 'Waist', 'Both', 'Plush Sash of Guzbah'), +(11, 0, 6, 0, 200, 39546, 'Fresh 80', 'Druid', 'Balance', 'Legs', 'Both', 'Heroes'' Dreamwalker Trousers'), +(11, 0, 7, 0, 200, 40519, 'Fresh 80', 'Druid', 'Balance', 'Feet', 'Both', 'Footsteps of Malygos'), +(11, 0, 8, 0, 200, 40740, 'Fresh 80', 'Druid', 'Balance', 'Wrists', 'Both', 'Wraps of the Astral Traveler'), +(11, 0, 9, 0, 200, 39544, 'Fresh 80', 'Druid', 'Balance', 'Hands', 'Both', 'Heroes'' Dreamwalker Gloves'), +(11, 0, 10, 0, 200, 40719, 'Fresh 80', 'Druid', 'Balance', 'Finger1', 'Both', 'Band of Channeled Magic'), +(11, 0, 12, 0, 200, 39229, 'Fresh 80', 'Druid', 'Balance', 'Trinket1', 'Both', 'Embrace of the Spider'), +(11, 0, 14, 0, 200, 40723, 'Fresh 80', 'Druid', 'Balance', 'Back', 'Both', 'Disguise of the Kumiho'), +(11, 0, 15, 0, 200, 39423, 'Fresh 80', 'Druid', 'Balance', 'MainHand', 'Both', 'Hammer of the Astral Plane'), +(11, 0, 17, 0, 200, 40321, 'Fresh 80', 'Druid', 'Balance', 'Ranged', 'Both', 'Idol of the Shooting Star'); + +-- ilvl 213 (Pre-Ulduar) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 213, 40467, 'Pre-Ulduar', 'Druid', 'Balance', 'Head', 'Both', 'Valorous Dreamwalker Cover'), +(11, 0, 1, 0, 213, 44661, 'Pre-Ulduar', 'Druid', 'Balance', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(11, 0, 2, 0, 213, 40470, 'Pre-Ulduar', 'Druid', 'Balance', 'Shoulders', 'Both', 'Valorous Dreamwalker Mantle'), +(11, 0, 4, 0, 213, 40469, 'Pre-Ulduar', 'Druid', 'Balance', 'Chest', 'Both', 'Valorous Dreamwalker Vestments'), +(11, 0, 5, 0, 213, 40561, 'Pre-Ulduar', 'Druid', 'Balance', 'Waist', 'Both', 'Leash of Heedless Magic'), +(11, 0, 6, 0, 213, 40560, 'Pre-Ulduar', 'Druid', 'Balance', 'Legs', 'Both', 'Leggings of the Wanton Spellcaster'), +(11, 0, 7, 0, 213, 40519, 'Pre-Ulduar', 'Druid', 'Balance', 'Feet', 'Both', 'Footsteps of Malygos'), +(11, 0, 8, 0, 213, 40323, 'Pre-Ulduar', 'Druid', 'Balance', 'Wrists', 'Both', 'Esteemed Bindings'), +(11, 0, 9, 0, 213, 40460, 'Pre-Ulduar', 'Druid', 'Balance', 'Hands', 'Both', 'Valorous Dreamwalker Handguards'), +(11, 0, 10, 0, 213, 40080, 'Pre-Ulduar', 'Druid', 'Balance', 'Finger1', 'Both', 'Lost Jewel'), +(11, 0, 12, 0, 213, 40682, 'Pre-Ulduar', 'Druid', 'Balance', 'Trinket1', 'Both', 'Sundial of the Exiled'), +(11, 0, 14, 0, 213, 40405, 'Pre-Ulduar', 'Druid', 'Balance', 'Back', 'Both', 'Cape of the Unworthy Wizard'), +(11, 0, 17, 0, 213, 40321, 'Pre-Ulduar', 'Druid', 'Balance', 'Ranged', 'Both', 'Idol of the Shooting Star'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 245, 46191, 'Phase 2', 'Druid', 'Balance', 'Head', 'Both', 'Conqueror''s Nightsong Cover'), +(11, 0, 1, 0, 245, 45933, 'Phase 2', 'Druid', 'Balance', 'Neck', 'Both', 'Pendant of the Shallow Grave'), +(11, 0, 2, 0, 245, 45136, 'Phase 2', 'Druid', 'Balance', 'Shoulders', 'Both', 'Shoulderpads of Dormant Energies'), +(11, 0, 4, 0, 245, 45519, 'Phase 2', 'Druid', 'Balance', 'Chest', 'Both', 'Vestments of the Blind Denizen'), +(11, 0, 5, 0, 245, 45619, 'Phase 2', 'Druid', 'Balance', 'Waist', 'Both', 'Starwatcher''s Binding'), +(11, 0, 6, 0, 245, 46192, 'Phase 2', 'Druid', 'Balance', 'Legs', 'Both', 'Conqueror''s Nightsong Trousers'), +(11, 0, 7, 0, 245, 45537, 'Phase 2', 'Druid', 'Balance', 'Feet', 'Both', 'Treads of the False Oracle'), +(11, 0, 8, 0, 245, 45446, 'Phase 2', 'Druid', 'Balance', 'Wrists', 'Both', 'Grasps of Reason'), +(11, 0, 9, 0, 245, 45665, 'Phase 2', 'Druid', 'Balance', 'Hands', 'Both', 'Pharos Gloves'), +(11, 0, 10, 0, 245, 45495, 'Phase 2', 'Druid', 'Balance', 'Finger1', 'Both', 'Conductive Seal'), +(11, 0, 12, 0, 245, 45466, 'Phase 2', 'Druid', 'Balance', 'Trinket1', 'Both', 'Scale of Fates'), +(11, 0, 14, 0, 245, 45242, 'Phase 2', 'Druid', 'Balance', 'Back', 'Both', 'Drape of Mortal Downfall'), +(11, 0, 17, 0, 245, 40321, 'Phase 2', 'Druid', 'Balance', 'Ranged', 'Both', 'Idol of the Shooting Star'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 258, 48171, 'Phase 3', 'Druid', 'Balance', 'Head', 'Both', 'Cover of Triumph'), +(11, 0, 1, 1, 258, 47144, 'Phase 3', 'Druid', 'Balance', 'Neck', 'Alliance', 'Wail of the Val''kyr'), +(11, 0, 1, 2, 258, 47468, 'Phase 3', 'Druid', 'Balance', 'Neck', 'Horde', 'Cry of the Val''kyr'), +(11, 0, 2, 0, 258, 48168, 'Phase 3', 'Druid', 'Balance', 'Shoulders', 'Both', 'Mantle of Triumph'), +(11, 0, 4, 0, 258, 48169, 'Phase 3', 'Druid', 'Balance', 'Chest', 'Both', 'Vestments of Triumph'), +(11, 0, 5, 1, 258, 47084, 'Phase 3', 'Druid', 'Balance', 'Waist', 'Alliance', 'Cord of Biting Cold'), +(11, 0, 5, 2, 258, 47447, 'Phase 3', 'Druid', 'Balance', 'Waist', 'Horde', 'Belt of Biting Cold'), +(11, 0, 6, 1, 258, 47190, 'Phase 3', 'Druid', 'Balance', 'Legs', 'Alliance', 'Legwraps of the Awakening'), +(11, 0, 6, 2, 258, 47479, 'Phase 3', 'Druid', 'Balance', 'Legs', 'Horde', 'Leggings of the Awakening'), +(11, 0, 7, 1, 258, 47097, 'Phase 3', 'Druid', 'Balance', 'Feet', 'Alliance', 'Boots of the Mourning Widow'), +(11, 0, 7, 2, 258, 47454, 'Phase 3', 'Druid', 'Balance', 'Feet', 'Horde', 'Sandals of the Mourning Widow'), +(11, 0, 8, 1, 258, 47066, 'Phase 3', 'Druid', 'Balance', 'Wrists', 'Alliance', 'Bracers of the Autumn Willow'), +(11, 0, 8, 2, 258, 47438, 'Phase 3', 'Druid', 'Balance', 'Wrists', 'Horde', 'Bindings of the Autumn Willow'), +(11, 0, 9, 0, 258, 48172, 'Phase 3', 'Druid', 'Balance', 'Hands', 'Both', 'Gloves of Triumph'), +(11, 0, 10, 1, 258, 47237, 'Phase 3', 'Druid', 'Balance', 'Finger1', 'Alliance', 'Band of Deplorable Violence'), +(11, 0, 10, 2, 258, 47489, 'Phase 3', 'Druid', 'Balance', 'Finger1', 'Horde', 'Lurid Manifestation'), +(11, 0, 12, 2, 258, 45518, 'Phase 3', 'Druid', 'Balance', 'Trinket1', 'Horde', 'Flare of the Heavens'), +(11, 0, 14, 1, 258, 47552, 'Phase 3', 'Druid', 'Balance', 'Back', 'Alliance', 'Jaina''s Radiance'), +(11, 0, 14, 2, 258, 47551, 'Phase 3', 'Druid', 'Balance', 'Back', 'Horde', 'Aethas'' Intensity'), +(11, 0, 17, 0, 258, 47670, 'Phase 3', 'Druid', 'Balance', 'Ranged', 'Both', 'Idol of Lunar Fury'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 264, 51290, 'Phase 4', 'Druid', 'Balance', 'Head', 'Both', 'Sanctified Lasherweave Cover'), +(11, 0, 1, 0, 264, 50724, 'Phase 4', 'Druid', 'Balance', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(11, 0, 2, 0, 264, 51292, 'Phase 4', 'Druid', 'Balance', 'Shoulders', 'Both', 'Sanctified Lasherweave Mantle'), +(11, 0, 4, 0, 264, 51294, 'Phase 4', 'Druid', 'Balance', 'Chest', 'Both', 'Sanctified Lasherweave Vestment'), +(11, 0, 5, 0, 264, 50613, 'Phase 4', 'Druid', 'Balance', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(11, 0, 6, 0, 264, 50694, 'Phase 4', 'Druid', 'Balance', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(11, 0, 7, 0, 264, 50699, 'Phase 4', 'Druid', 'Balance', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(11, 0, 8, 0, 264, 50630, 'Phase 4', 'Druid', 'Balance', 'Wrists', 'Both', 'Bracers of Eternal Dreaming'), +(11, 0, 9, 0, 264, 51291, 'Phase 4', 'Druid', 'Balance', 'Hands', 'Both', 'Sanctified Lasherweave Gloves'), +(11, 0, 10, 0, 264, 50398, 'Phase 4', 'Druid', 'Balance', 'Finger1', 'Both', 'Ashen Band of Endless Destruction'), +(11, 0, 12, 0, 264, 50348, 'Phase 4', 'Druid', 'Balance', 'Trinket1', 'Both', 'Dislodged Foreign Object'), +(11, 0, 14, 0, 264, 50628, 'Phase 4', 'Druid', 'Balance', 'Back', 'Both', 'Frostbinder''s Shredded Cape'), +(11, 0, 17, 0, 264, 50719, 'Phase 4', 'Druid', 'Balance', 'Ranged', 'Both', 'Shadow Silk Spindle'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 0, 0, 0, 290, 51290, 'Phase 5', 'Druid', 'Balance', 'Head', 'Both', 'Sanctified Lasherweave Cover'), +(11, 0, 1, 0, 290, 50182, 'Phase 5', 'Druid', 'Balance', 'Neck', 'Both', 'Blood Queen''s Crimson Choker'), +(11, 0, 2, 0, 290, 51292, 'Phase 5', 'Druid', 'Balance', 'Shoulders', 'Both', 'Sanctified Lasherweave Mantle'), +(11, 0, 4, 0, 290, 51294, 'Phase 5', 'Druid', 'Balance', 'Chest', 'Both', 'Sanctified Lasherweave Vestment'), +(11, 0, 5, 0, 290, 50613, 'Phase 5', 'Druid', 'Balance', 'Waist', 'Both', 'Crushing Coldwraith Belt'), +(11, 0, 6, 0, 290, 50694, 'Phase 5', 'Druid', 'Balance', 'Legs', 'Both', 'Plaguebringer''s Stained Pants'), +(11, 0, 7, 0, 290, 50699, 'Phase 5', 'Druid', 'Balance', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(11, 0, 8, 0, 290, 54582, 'Phase 5', 'Druid', 'Balance', 'Wrists', 'Both', 'Bracers of Fiery Night'), +(11, 0, 9, 0, 290, 51291, 'Phase 5', 'Druid', 'Balance', 'Hands', 'Both', 'Sanctified Lasherweave Gloves'), +(11, 0, 10, 0, 290, 50398, 'Phase 5', 'Druid', 'Balance', 'Finger1', 'Both', 'Ashen Band of Endless Destruction'), +(11, 0, 11, 0, 290, 50714, 'Phase 5', 'Druid', 'Balance', 'Finger2', 'Both', 'Valanar''s Other Signet Ring'), +(11, 0, 12, 0, 290, 54588, 'Phase 5', 'Druid', 'Balance', 'Trinket1', 'Both', 'Charred Twilight Scale'), +(11, 0, 13, 0, 290, 50365, 'Phase 5', 'Druid', 'Balance', 'Trinket2', 'Both', 'Phylactery of the Nameless Lich'), +(11, 0, 14, 0, 290, 54583, 'Phase 5', 'Druid', 'Balance', 'Back', 'Both', 'Cloak of Burning Dusk'), +(11, 0, 15, 0, 290, 50734, 'Phase 5', 'Druid', 'Balance', 'MainHand', 'Both', 'Royal Scepter of Terenas II'), +(11, 0, 16, 0, 290, 50719, 'Phase 5', 'Druid', 'Balance', 'OffHand', 'Both', 'Shadow Silk Spindle'); + +-- Feral Cat (tab 1) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 66, 8345, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Head', 'Both', 'Wolfshead Helm'), +(11, 1, 1, 0, 66, 15411, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Neck', 'Both', 'Mark of Fordring'), +(11, 1, 2, 0, 66, 12927, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(11, 1, 4, 0, 66, 14637, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Cadaverous Armor'), +(11, 1, 5, 0, 66, 13252, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Cloudrunner Girdle'), +(11, 1, 6, 0, 66, 15062, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Devilsaur Leggings'), +(11, 1, 7, 0, 66, 12553, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Swiftwalker Boots'), +(11, 1, 8, 0, 66, 16710, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Shadowcraft Bracers'), +(11, 1, 9, 0, 66, 15063, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(11, 1, 10, 0, 66, 17713, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Blackstone Ring'), +(11, 1, 11, 0, 66, 2246, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Finger2', 'Both', 'Myrmidon''s Signet'), +(11, 1, 12, 0, 66, 13965, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(11, 1, 13, 0, 66, 11815, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Trinket2', 'Both', 'Hand of Justice'), +(11, 1, 14, 0, 66, 13340, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'Back', 'Both', 'Cape of the Black Baron'), +(11, 1, 15, 0, 66, 11921, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'Impervious Giant'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 76, 8345, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Head', 'Both', 'Wolfshead Helm'), +(11, 1, 1, 0, 76, 15411, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Neck', 'Both', 'Mark of Fordring'), +(11, 1, 2, 0, 76, 12927, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(11, 1, 4, 0, 76, 14637, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Cadaverous Armor'), +(11, 1, 5, 0, 76, 13252, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Cloudrunner Girdle'), +(11, 1, 6, 0, 76, 15062, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Devilsaur Leggings'), +(11, 1, 7, 0, 76, 12553, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Swiftwalker Boots'), +(11, 1, 8, 0, 76, 18375, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Bracers of the Eclipse'), +(11, 1, 9, 0, 76, 15063, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Devilsaur Gauntlets'), +(11, 1, 10, 0, 76, 17713, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Blackstone Ring'), +(11, 1, 11, 0, 76, 18500, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Finger2', 'Both', 'Tarnished Elven Ring'), +(11, 1, 12, 0, 76, 13965, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(11, 1, 13, 0, 76, 11815, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Trinket2', 'Both', 'Hand of Justice'), +(11, 1, 14, 0, 76, 13340, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'Back', 'Both', 'Cape of the Black Baron'), +(11, 1, 15, 0, 76, 18420, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'Bonecrusher'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 78, 8345, 'Phase 2', 'Druid', 'Feral Cat', 'Head', 'Both', 'Wolfshead Helm'), +(11, 1, 1, 0, 78, 18404, 'Phase 2', 'Druid', 'Feral Cat', 'Neck', 'Both', 'Onyxia Tooth Pendant'), +(11, 1, 2, 0, 78, 12927, 'Phase 2', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(11, 1, 4, 0, 78, 14637, 'Phase 2', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Cadaverous Armor'), +(11, 1, 5, 0, 78, 13252, 'Phase 2', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Cloudrunner Girdle'), +(11, 1, 6, 0, 78, 15062, 'Phase 2', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Devilsaur Leggings'), +(11, 1, 7, 0, 78, 12553, 'Phase 2', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Swiftwalker Boots'), +(11, 1, 8, 0, 78, 19146, 'Phase 2', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Wristguards of Stability'), +(11, 1, 9, 0, 78, 18823, 'Phase 2', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Aged Core Leather Gloves'), +(11, 1, 10, 0, 78, 17063, 'Phase 2', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Band of Accuria'), +(11, 1, 11, 0, 78, 18500, 'Phase 2', 'Druid', 'Feral Cat', 'Finger2', 'Both', 'Tarnished Elven Ring'), +(11, 1, 12, 0, 78, 13965, 'Phase 2', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Blackhand''s Breadth'), +(11, 1, 13, 0, 78, 11815, 'Phase 2', 'Druid', 'Feral Cat', 'Trinket2', 'Both', 'Hand of Justice'), +(11, 1, 14, 0, 78, 13340, 'Phase 2', 'Druid', 'Feral Cat', 'Back', 'Both', 'Cape of the Black Baron'), +(11, 1, 15, 0, 78, 18420, 'Phase 2', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'Bonecrusher'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 83, 8345, 'Phase 3', 'Druid', 'Feral Cat', 'Head', 'Both', 'Wolfshead Helm'), +(11, 1, 1, 0, 83, 19377, 'Phase 3', 'Druid', 'Feral Cat', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(11, 1, 2, 0, 83, 12927, 'Phase 3', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Truestrike Shoulders'), +(11, 1, 4, 0, 83, 19405, 'Phase 3', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Malfurion''s Blessed Bulwark'), +(11, 1, 5, 0, 83, 19396, 'Phase 3', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Taut Dragonhide Belt'), +(11, 1, 6, 0, 83, 15062, 'Phase 3', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Devilsaur Leggings'), +(11, 1, 7, 0, 83, 19381, 'Phase 3', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Boots of the Shadow Flame'), +(11, 1, 8, 0, 83, 19146, 'Phase 3', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Wristguards of Stability'), +(11, 1, 9, 0, 83, 18823, 'Phase 3', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Aged Core Leather Gloves'), +(11, 1, 10, 0, 83, 19384, 'Phase 3', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Master Dragonslayer''s Ring'), +(11, 1, 12, 0, 83, 19406, 'Phase 3', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Drake Fang Talisman'), +(11, 1, 13, 0, 83, 11815, 'Phase 3', 'Druid', 'Feral Cat', 'Trinket2', 'Both', 'Hand of Justice'), +(11, 1, 14, 0, 83, 19436, 'Phase 3', 'Druid', 'Feral Cat', 'Back', 'Both', 'Cloak of Draconic Might'), +(11, 1, 15, 0, 83, 19358, 'Phase 3', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'Draconic Maul'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 88, 8345, 'Phase 5', 'Druid', 'Feral Cat', 'Head', 'Both', 'Wolfshead Helm'), +(11, 1, 1, 0, 88, 19377, 'Phase 5', 'Druid', 'Feral Cat', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(11, 1, 2, 0, 88, 21665, 'Phase 5', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Mantle of Wicked Revenge'), +(11, 1, 4, 0, 88, 21680, 'Phase 5', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Vest of Swift Execution'), +(11, 1, 5, 0, 88, 21586, 'Phase 5', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Belt of Never-ending Agony'), +(11, 1, 6, 0, 88, 20665, 'Phase 5', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Abyssal Leather Leggings'), +(11, 1, 7, 0, 88, 21493, 'Phase 5', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Boots of the Vanguard'), +(11, 1, 8, 0, 88, 21602, 'Phase 5', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Qiraji Execution Bracers'), +(11, 1, 10, 0, 88, 21205, 'Phase 5', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Signet Ring of the Bronze Dragonflight'), +(11, 1, 11, 0, 88, 17063, 'Phase 5', 'Druid', 'Feral Cat', 'Finger2', 'Both', 'Band of Accuria'), +(11, 1, 12, 0, 88, 19406, 'Phase 5', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Drake Fang Talisman'), +(11, 1, 13, 0, 88, 13965, 'Phase 5', 'Druid', 'Feral Cat', 'Trinket2', 'Both', 'Blackhand''s Breadth'), +(11, 1, 14, 0, 88, 21701, 'Phase 5', 'Druid', 'Feral Cat', 'Back', 'Both', 'Cloak of Concentrated Hatred'), +(11, 1, 15, 0, 88, 21268, 'Phase 5', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'Blessed Qiraji War Hammer'), +(11, 1, 16, 0, 88, 13385, 'Phase 5', 'Druid', 'Feral Cat', 'OffHand', 'Both', 'Tome of Knowledge'), +(11, 1, 17, 0, 88, 22397, 'Phase 5', 'Druid', 'Feral Cat', 'Ranged', 'Both', 'Idol of Ferocity'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 92, 8345, 'Phase 6', 'Druid', 'Feral Cat', 'Head', 'Both', 'Wolfshead Helm'), +(11, 1, 1, 0, 92, 19377, 'Phase 6', 'Druid', 'Feral Cat', 'Neck', 'Both', 'Prestor''s Talisman of Connivery'), +(11, 1, 2, 0, 92, 21665, 'Phase 6', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Mantle of Wicked Revenge'), +(11, 1, 4, 0, 92, 23226, 'Phase 6', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Ghoul Skin Tunic'), +(11, 1, 5, 0, 92, 21586, 'Phase 6', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Belt of Never-ending Agony'), +(11, 1, 6, 0, 92, 23071, 'Phase 6', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Leggings of Apocalypse'), +(11, 1, 7, 0, 92, 21493, 'Phase 6', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Boots of the Vanguard'), +(11, 1, 8, 0, 92, 21602, 'Phase 6', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Qiraji Execution Bracers'), +(11, 1, 12, 0, 92, 19406, 'Phase 6', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Drake Fang Talisman'), +(11, 1, 13, 0, 92, 23041, 'Phase 6', 'Druid', 'Feral Cat', 'Trinket2', 'Both', 'Slayer''s Crest'), +(11, 1, 14, 0, 92, 21710, 'Phase 6', 'Druid', 'Feral Cat', 'Back', 'Both', 'Cloak of the Fallen God'), +(11, 1, 15, 0, 92, 22988, 'Phase 6', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'The End of Dreams'), +(11, 1, 16, 0, 92, 13385, 'Phase 6', 'Druid', 'Feral Cat', 'OffHand', 'Both', 'Tome of Knowledge'), +(11, 1, 17, 0, 92, 22397, 'Phase 6', 'Druid', 'Feral Cat', 'Ranged', 'Both', 'Idol of Ferocity'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 120, 8345, 'Pre-Raid', 'Druid', 'Feral Cat', 'Head', 'Both', 'Wolfshead Helm'), +(11, 1, 1, 0, 120, 29381, 'Pre-Raid', 'Druid', 'Feral Cat', 'Neck', 'Both', 'Choker of Vile Intent'), +(11, 1, 2, 0, 120, 27797, 'Pre-Raid', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Wastewalker Shoulderpads'), +(11, 1, 4, 0, 120, 29525, 'Pre-Raid', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Primalstrike Vest'), +(11, 1, 5, 0, 120, 29247, 'Pre-Raid', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Girdle of the Deathdealer'), +(11, 1, 6, 0, 120, 31544, 'Pre-Raid', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Clefthoof Hide Leggings'), +(11, 1, 7, 0, 120, 25686, 'Pre-Raid', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Fel Leather Boots'), +(11, 1, 8, 0, 120, 29246, 'Pre-Raid', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Nightfall Wristguards'), +(11, 1, 9, 0, 120, 29507, 'Pre-Raid', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Windslayer Wraps'), +(11, 1, 10, 0, 120, 30365, 'Pre-Raid', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Overseer''s Signet'), +(11, 1, 11, 0, 120, 31920, 'Pre-Raid', 'Druid', 'Feral Cat', 'Finger2', 'Both', 'Shaffar''s Band of Brutality'), +(11, 1, 12, 0, 120, 29383, 'Pre-Raid', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(11, 1, 13, 0, 120, 28034, 'Pre-Raid', 'Druid', 'Feral Cat', 'Trinket2', 'Both', 'Hourglass of the Unraveller'), +(11, 1, 14, 0, 120, 31255, 'Pre-Raid', 'Druid', 'Feral Cat', 'Back', 'Both', 'Cloak of the Craft'), +(11, 1, 15, 0, 120, 31334, 'Pre-Raid', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'Staff of Natural Fury'), +(11, 1, 17, 0, 120, 28372, 'Pre-Raid', 'Druid', 'Feral Cat', 'Ranged', 'Both', 'Idol of Feral Shadows'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 125, 8345, 'Phase 1', 'Druid', 'Feral Cat', 'Head', 'Both', 'Wolfshead Helm'), +(11, 1, 1, 0, 125, 29381, 'Phase 1', 'Druid', 'Feral Cat', 'Neck', 'Both', 'Choker of Vile Intent'), +(11, 1, 2, 0, 125, 29100, 'Phase 1', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Mantle of Malorne'), +(11, 1, 4, 0, 125, 29096, 'Phase 1', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Breastplate of Malorne'), +(11, 1, 5, 0, 125, 29247, 'Phase 1', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Girdle of the Deathdealer'), +(11, 1, 6, 0, 125, 28741, 'Phase 1', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Skulker''s Greaves'), +(11, 1, 7, 0, 125, 28545, 'Phase 1', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Edgewalker Longboots'), +(11, 1, 8, 0, 125, 29246, 'Phase 1', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Nightfall Wristguards'), +(11, 1, 9, 0, 125, 28506, 'Phase 1', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Gloves of Dexterous Manipulation'), +(11, 1, 10, 0, 125, 28757, 'Phase 1', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Ring of a Thousand Marks'), +(11, 1, 11, 0, 125, 30834, 'Phase 1', 'Druid', 'Feral Cat', 'Finger2', 'Both', 'Shapeshifter''s Signet'), +(11, 1, 12, 0, 125, 28830, 'Phase 1', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Dragonspine Trophy'), +(11, 1, 13, 0, 125, 29383, 'Phase 1', 'Druid', 'Feral Cat', 'Trinket2', 'Both', 'Bloodlust Brooch'), +(11, 1, 14, 0, 125, 28660, 'Phase 1', 'Druid', 'Feral Cat', 'Back', 'Both', 'Gilded Thorium Cloak'), +(11, 1, 15, 0, 125, 28658, 'Phase 1', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'Terestian''s Stranglestaff'), +(11, 1, 17, 0, 125, 29390, 'Phase 1', 'Druid', 'Feral Cat', 'Ranged', 'Both', 'Everbloom Idol'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 200, 42550, 'Pre-Raid', 'Druid', 'Feral Cat', 'Head', 'Both', 'Weakness Spectralizers'), +(11, 1, 2, 0, 200, 43481, 'Pre-Raid', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Trollwoven Spaulders'), +(11, 1, 4, 0, 200, 39554, 'Pre-Raid', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Heroes'' Dreamwalker Raiments'), +(11, 1, 5, 0, 200, 40694, 'Pre-Raid', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Jorach''s Crocolisk Skin Belt'), +(11, 1, 6, 0, 200, 37644, 'Pre-Raid', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Gored Hide Legguards'), +(11, 1, 7, 0, 200, 44297, 'Pre-Raid', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Boots of the Neverending Path'), +(11, 1, 8, 0, 200, 44203, 'Pre-Raid', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Dragonfriend Bracers'), +(11, 1, 9, 0, 200, 37409, 'Pre-Raid', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Gilt-Edged Leather Gauntlets'), +(11, 1, 10, 0, 200, 40586, 'Pre-Raid', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Band of the Kirin Tor'), +(11, 1, 12, 0, 200, 44253, 'Pre-Raid', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(11, 1, 14, 0, 200, 43406, 'Pre-Raid', 'Druid', 'Feral Cat', 'Back', 'Both', 'Cloak of the Gushing Wound'), +(11, 1, 15, 0, 200, 41257, 'Pre-Raid', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'Titansteel Destroyer'), +(11, 1, 17, 0, 200, 40713, 'Pre-Raid', 'Druid', 'Feral Cat', 'Ranged', 'Both', 'Idol of the Ravenous Beast'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 224, 40473, 'Phase 1', 'Druid', 'Feral Cat', 'Head', 'Both', 'Valorous Dreamwalker Headguard'), +(11, 1, 2, 0, 224, 40494, 'Phase 1', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Valorous Dreamwalker Shoulderpads'), +(11, 1, 4, 0, 224, 40539, 'Phase 1', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Chestguard of the Recluse'), +(11, 1, 5, 0, 224, 40205, 'Phase 1', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Stalk-Skin Belt'), +(11, 1, 6, 0, 224, 44011, 'Phase 1', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Leggings of the Honored'), +(11, 1, 7, 0, 224, 40243, 'Phase 1', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Footwraps of Vile Deceit'), +(11, 1, 8, 0, 224, 40738, 'Phase 1', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Wristwraps of the Cutthroat'), +(11, 1, 9, 0, 224, 40541, 'Phase 1', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Frosted Adroit Handguards'), +(11, 1, 10, 0, 224, 40474, 'Phase 1', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Surge Needle Ring'), +(11, 1, 12, 0, 224, 42987, 'Phase 1', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(11, 1, 14, 0, 224, 40403, 'Phase 1', 'Druid', 'Feral Cat', 'Back', 'Both', 'Drape of the Deadly Foe'), +(11, 1, 17, 0, 224, 40713, 'Phase 1', 'Druid', 'Feral Cat', 'Ranged', 'Both', 'Idol of the Ravenous Beast'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 245, 46161, 'Phase 2', 'Druid', 'Feral Cat', 'Head', 'Both', 'Conqueror''s Nightsong Headguard'), +(11, 1, 2, 0, 245, 45245, 'Phase 2', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Shoulderpads of the Intruder'), +(11, 1, 4, 0, 245, 45473, 'Phase 2', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Embrace of the Gladiator'), +(11, 1, 5, 0, 245, 46095, 'Phase 2', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Soul-Devouring Cinch'), +(11, 1, 6, 0, 245, 45536, 'Phase 2', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Legguards of Cunning Deception'), +(11, 1, 7, 0, 245, 45564, 'Phase 2', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Footpads of Silence'), +(11, 1, 8, 0, 245, 45869, 'Phase 2', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Fluxing Energy Coils'), +(11, 1, 9, 0, 245, 46158, 'Phase 2', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Conqueror''s Nightsong Handgrips'), +(11, 1, 10, 0, 245, 45608, 'Phase 2', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Brann''s Signet Ring'), +(11, 1, 12, 0, 245, 45931, 'Phase 2', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Mjolnir Runestone'), +(11, 1, 14, 0, 245, 46032, 'Phase 2', 'Druid', 'Feral Cat', 'Back', 'Both', 'Drape of the Faceless General'), +(11, 1, 17, 0, 245, 40713, 'Phase 2', 'Druid', 'Feral Cat', 'Ranged', 'Both', 'Idol of the Ravenous Beast'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 258, 48201, 'Phase 3', 'Druid', 'Feral Cat', 'Head', 'Both', 'Headguard of Triumph'), +(11, 1, 2, 0, 258, 48198, 'Phase 3', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Shoulderpads of Triumph'), +(11, 1, 4, 0, 258, 48139, 'Phase 3', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Robe of Triumph'), +(11, 1, 5, 1, 258, 47112, 'Phase 3', 'Druid', 'Feral Cat', 'Waist', 'Alliance', 'Belt of the Merciless Killer'), +(11, 1, 5, 2, 258, 47460, 'Phase 3', 'Druid', 'Feral Cat', 'Waist', 'Horde', 'Belt of the Pitiless Killer'), +(11, 1, 6, 0, 258, 45536, 'Phase 3', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Legguards of Cunning Deception'), +(11, 1, 7, 1, 258, 47077, 'Phase 3', 'Druid', 'Feral Cat', 'Feet', 'Alliance', 'Treads of the Icewalker'), +(11, 1, 7, 2, 258, 47445, 'Phase 3', 'Druid', 'Feral Cat', 'Feet', 'Horde', 'Icewalker Treads'), +(11, 1, 8, 1, 258, 47155, 'Phase 3', 'Druid', 'Feral Cat', 'Wrists', 'Alliance', 'Bracers of Dark Determination'), +(11, 1, 8, 2, 258, 47474, 'Phase 3', 'Druid', 'Feral Cat', 'Wrists', 'Horde', 'Armbands of Dark Determination'), +(11, 1, 9, 0, 258, 48202, 'Phase 3', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Handgrips of Triumph'), +(11, 1, 10, 1, 258, 47075, 'Phase 3', 'Druid', 'Feral Cat', 'Finger1', 'Alliance', 'Ring of Callous Aggression'), +(11, 1, 10, 2, 258, 47443, 'Phase 3', 'Druid', 'Feral Cat', 'Finger1', 'Horde', 'Band of Callous Aggression'), +(11, 1, 12, 1, 258, 47131, 'Phase 3', 'Druid', 'Feral Cat', 'Trinket1', 'Alliance', 'Death''s Verdict'), +(11, 1, 12, 2, 258, 47464, 'Phase 3', 'Druid', 'Feral Cat', 'Trinket1', 'Horde', 'Death''s Choice'), +(11, 1, 14, 1, 258, 47545, 'Phase 3', 'Druid', 'Feral Cat', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(11, 1, 14, 2, 258, 47546, 'Phase 3', 'Druid', 'Feral Cat', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(11, 1, 15, 1, 258, 47130, 'Phase 3', 'Druid', 'Feral Cat', 'MainHand', 'Alliance', 'Lupine Longstaff'), +(11, 1, 15, 2, 258, 47463, 'Phase 3', 'Druid', 'Feral Cat', 'MainHand', 'Horde', 'Twin''s Pact'), +(11, 1, 17, 0, 258, 47668, 'Phase 3', 'Druid', 'Feral Cat', 'Ranged', 'Both', 'Idol of Mutilation'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 264, 51296, 'Phase 4', 'Druid', 'Feral Cat', 'Head', 'Both', 'Sanctified Lasherweave Headguard'), +(11, 1, 2, 0, 264, 51299, 'Phase 4', 'Druid', 'Feral Cat', 'Shoulders', 'Both', 'Sanctified Lasherweave Shoulderpads'), +(11, 1, 4, 0, 264, 51298, 'Phase 4', 'Druid', 'Feral Cat', 'Chest', 'Both', 'Sanctified Lasherweave Raiment'), +(11, 1, 5, 0, 264, 50707, 'Phase 4', 'Druid', 'Feral Cat', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(11, 1, 6, 0, 264, 51297, 'Phase 4', 'Druid', 'Feral Cat', 'Legs', 'Both', 'Sanctified Lasherweave Legguards'), +(11, 1, 7, 0, 264, 50607, 'Phase 4', 'Druid', 'Feral Cat', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(11, 1, 8, 0, 264, 50670, 'Phase 4', 'Druid', 'Feral Cat', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(11, 1, 9, 0, 264, 50675, 'Phase 4', 'Druid', 'Feral Cat', 'Hands', 'Both', 'Aldriana''s Gloves of Secrecy'), +(11, 1, 10, 0, 264, 50402, 'Phase 4', 'Druid', 'Feral Cat', 'Finger1', 'Both', 'Ashen Band of Endless Vengeance'), +(11, 1, 12, 0, 264, 50363, 'Phase 4', 'Druid', 'Feral Cat', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(11, 1, 14, 1, 264, 47545, 'Phase 4', 'Druid', 'Feral Cat', 'Back', 'Alliance', 'Vereesa''s Dexterity'), +(11, 1, 14, 2, 264, 47546, 'Phase 4', 'Druid', 'Feral Cat', 'Back', 'Horde', 'Sylvanas'' Cunning'), +(11, 1, 15, 0, 264, 50735, 'Phase 4', 'Druid', 'Feral Cat', 'MainHand', 'Both', 'Oathbinder, Charge of the Ranger-General'), +(11, 1, 17, 0, 264, 50456, 'Phase 4', 'Druid', 'Feral Cat', 'Ranged', 'Both', 'Idol of the Crying Moon'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 1, 0, 0, 290, 51296, 'Phase 5', 'Druid', 'FeralCat', 'Head', 'Both', 'Sanctified Lasherweave Headguard'), +(11, 1, 1, 0, 290, 50633, 'Phase 5', 'Druid', 'FeralCat', 'Neck', 'Both', 'Sindragosa''s Cruel Claw'), +(11, 1, 2, 0, 290, 51299, 'Phase 5', 'Druid', 'FeralCat', 'Shoulders', 'Both', 'Sanctified Lasherweave Shoulderpads'), +(11, 1, 4, 0, 290, 51298, 'Phase 5', 'Druid', 'FeralCat', 'Chest', 'Both', 'Sanctified Lasherweave Raiment'), +(11, 1, 5, 0, 290, 50707, 'Phase 5', 'Druid', 'FeralCat', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(11, 1, 6, 0, 290, 51297, 'Phase 5', 'Druid', 'FeralCat', 'Legs', 'Both', 'Sanctified Lasherweave Legguards'), +(11, 1, 7, 0, 290, 50607, 'Phase 5', 'Druid', 'FeralCat', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(11, 1, 8, 0, 290, 50670, 'Phase 5', 'Druid', 'FeralCat', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(11, 1, 9, 0, 290, 50675, 'Phase 5', 'Druid', 'FeralCat', 'Hands', 'Both', 'Aldriana''s Gloves of Secrecy'), +(11, 1, 10, 0, 290, 50618, 'Phase 5', 'Druid', 'FeralCat', 'Finger1', 'Both', 'Frostbrood Sapphire Ring'), +(11, 1, 11, 0, 290, 50402, 'Phase 5', 'Druid', 'FeralCat', 'Finger2', 'Both', 'Ashen Band of Endless Vengeance'), +(11, 1, 12, 0, 290, 50363, 'Phase 5', 'Druid', 'FeralCat', 'Trinket1', 'Both', 'Deathbringer''s Will'), +(11, 1, 13, 0, 290, 54590, 'Phase 5', 'Druid', 'FeralCat', 'Trinket2', 'Both', 'Sharpened Twilight Scale'), +(11, 1, 14, 0, 290, 50653, 'Phase 5', 'Druid', 'FeralCat', 'Back', 'Both', 'Shadowvault Slayer''s Cloak'), +(11, 1, 15, 0, 290, 50735, 'Phase 5', 'Druid', 'FeralCat', 'MainHand', 'Both', 'Oathbinder, Charge of the Ranger-General'), +(11, 1, 17, 0, 290, 50456, 'Phase 5', 'Druid', 'FeralCat', 'Ranged', 'Both', 'Idol of the Crying Moon'); + +-- Restoration (tab 2) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 66, 13102, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Head', 'Both', 'Cassandra''s Grace'), +(11, 2, 1, 0, 66, 18723, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Neck', 'Both', 'Animated Chain Necklace'), +(11, 2, 2, 0, 66, 15061, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Living Shoulders'), +(11, 2, 4, 0, 66, 13346, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Chest', 'Both', 'Robes of the Exalted'), +(11, 2, 5, 0, 66, 14553, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Waist', 'Both', 'Sash of Mercy'), +(11, 2, 6, 0, 66, 11841, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Legs', 'Both', 'Senior Designer''s Pantaloons'), +(11, 2, 7, 0, 66, 13954, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Feet', 'Both', 'Verdant Footpads'), +(11, 2, 8, 0, 66, 13208, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Wrists', 'Both', 'Bleak Howler Armguards'), +(11, 2, 9, 0, 66, 10787, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Hands', 'Both', 'Atal''ai Gloves'), +(11, 2, 10, 0, 66, 13178, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Finger1', 'Both', 'Rosewine Circle'), +(11, 2, 11, 0, 66, 16058, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Finger2', 'Both', 'Fordring''s Seal'), +(11, 2, 12, 0, 66, 12930, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Briarwood Reed'), +(11, 2, 13, 0, 66, 11819, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Second Wind'), +(11, 2, 14, 0, 66, 13386, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'Back', 'Both', 'Archivist Cape'), +(11, 2, 15, 0, 66, 11923, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'MainHand', 'Both', 'The Hammer of Grace'), +(11, 2, 16, 0, 66, 11928, 'Phase 1 (Pre-Raid)', 'Druid', 'Restoration', 'OffHand', 'Both', 'Thaurissan''s Royal Scepter'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 76, 13102, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Head', 'Both', 'Cassandra''s Grace'), +(11, 2, 1, 0, 76, 18723, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Neck', 'Both', 'Animated Chain Necklace'), +(11, 2, 2, 0, 76, 15061, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Living Shoulders'), +(11, 2, 4, 0, 76, 13346, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Chest', 'Both', 'Robes of the Exalted'), +(11, 2, 5, 0, 76, 14553, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Waist', 'Both', 'Sash of Mercy'), +(11, 2, 6, 0, 76, 18386, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Legs', 'Both', 'Padre''s Trousers'), +(11, 2, 7, 0, 76, 13954, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Feet', 'Both', 'Verdant Footpads'), +(11, 2, 8, 0, 76, 18525, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Wrists', 'Both', 'Bracers of Prosperity'), +(11, 2, 9, 0, 76, 10787, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Hands', 'Both', 'Atal''ai Gloves'), +(11, 2, 10, 0, 76, 13178, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Finger1', 'Both', 'Rosewine Circle'), +(11, 2, 11, 0, 76, 16058, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Finger2', 'Both', 'Fordring''s Seal'), +(11, 2, 12, 0, 76, 18470, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Royal Seal of Eldre''Thalas'), +(11, 2, 13, 0, 76, 18371, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Mindtap Talisman'), +(11, 2, 14, 0, 76, 18510, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'Back', 'Both', 'Hide of the Wild'), +(11, 2, 15, 0, 76, 11923, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'MainHand', 'Both', 'The Hammer of Grace'), +(11, 2, 16, 0, 76, 18523, 'Phase 2 (Pre-Raid)', 'Druid', 'Restoration', 'OffHand', 'Both', 'Brightly Glowing Stone'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 78, 19132, 'Phase 2', 'Druid', 'Restoration', 'Head', 'Both', 'Crystal Adorned Crown'), +(11, 2, 1, 0, 78, 18723, 'Phase 2', 'Druid', 'Restoration', 'Neck', 'Both', 'Animated Chain Necklace'), +(11, 2, 2, 0, 78, 18810, 'Phase 2', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Wild Growth Spaulders'), +(11, 2, 4, 0, 78, 13346, 'Phase 2', 'Druid', 'Restoration', 'Chest', 'Both', 'Robes of the Exalted'), +(11, 2, 5, 0, 78, 19162, 'Phase 2', 'Druid', 'Restoration', 'Waist', 'Both', 'Corehound Belt'), +(11, 2, 6, 0, 78, 18875, 'Phase 2', 'Druid', 'Restoration', 'Legs', 'Both', 'Salamander Scale Pants'), +(11, 2, 7, 0, 78, 16829, 'Phase 2', 'Druid', 'Restoration', 'Feet', 'Both', 'Cenarion Boots'), +(11, 2, 8, 0, 78, 18263, 'Phase 2', 'Druid', 'Restoration', 'Wrists', 'Both', 'Flarecore Wraps'), +(11, 2, 9, 0, 78, 10787, 'Phase 2', 'Druid', 'Restoration', 'Hands', 'Both', 'Atal''ai Gloves'), +(11, 2, 10, 0, 78, 19140, 'Phase 2', 'Druid', 'Restoration', 'Finger1', 'Both', 'Cauterizing Band'), +(11, 2, 11, 0, 78, 19140, 'Phase 2', 'Druid', 'Restoration', 'Finger2', 'Both', 'Cauterizing Band'), +(11, 2, 12, 0, 78, 18470, 'Phase 2', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Royal Seal of Eldre''Thalas'), +(11, 2, 13, 0, 78, 17064, 'Phase 2', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Shard of the Scale'), +(11, 2, 14, 0, 78, 18510, 'Phase 2', 'Druid', 'Restoration', 'Back', 'Both', 'Hide of the Wild'), +(11, 2, 15, 0, 78, 17070, 'Phase 2', 'Druid', 'Restoration', 'MainHand', 'Both', 'Fang of the Mystics'), +(11, 2, 16, 0, 78, 18523, 'Phase 2', 'Druid', 'Restoration', 'OffHand', 'Both', 'Brightly Glowing Stone'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 83, 19132, 'Phase 3', 'Druid', 'Restoration', 'Head', 'Both', 'Crystal Adorned Crown'), +(11, 2, 1, 0, 83, 18723, 'Phase 3', 'Druid', 'Restoration', 'Neck', 'Both', 'Animated Chain Necklace'), +(11, 2, 2, 0, 83, 18810, 'Phase 3', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Wild Growth Spaulders'), +(11, 2, 4, 0, 83, 13346, 'Phase 3', 'Druid', 'Restoration', 'Chest', 'Both', 'Robes of the Exalted'), +(11, 2, 5, 0, 83, 19162, 'Phase 3', 'Druid', 'Restoration', 'Waist', 'Both', 'Corehound Belt'), +(11, 2, 6, 0, 83, 19385, 'Phase 3', 'Druid', 'Restoration', 'Legs', 'Both', 'Empowered Leggings'), +(11, 2, 7, 0, 83, 16898, 'Phase 3', 'Druid', 'Restoration', 'Feet', 'Both', 'Stormrage Boots'), +(11, 2, 8, 0, 83, 16904, 'Phase 3', 'Druid', 'Restoration', 'Wrists', 'Both', 'Stormrage Bracers'), +(11, 2, 9, 0, 83, 16899, 'Phase 3', 'Druid', 'Restoration', 'Hands', 'Both', 'Stormrage Handguards'), +(11, 2, 10, 0, 83, 19140, 'Phase 3', 'Druid', 'Restoration', 'Finger1', 'Both', 'Cauterizing Band'), +(11, 2, 11, 0, 83, 19382, 'Phase 3', 'Druid', 'Restoration', 'Finger2', 'Both', 'Pure Elementium Band'), +(11, 2, 12, 0, 83, 19395, 'Phase 3', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Rejuvenating Gem'), +(11, 2, 13, 0, 83, 17064, 'Phase 3', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Shard of the Scale'), +(11, 2, 14, 0, 83, 19430, 'Phase 3', 'Druid', 'Restoration', 'Back', 'Both', 'Shroud of Pure Thought'), +(11, 2, 15, 0, 83, 19360, 'Phase 3', 'Druid', 'Restoration', 'MainHand', 'Both', 'Lok''amir il Romathis'), +(11, 2, 16, 0, 83, 19312, 'Phase 3', 'Druid', 'Restoration', 'OffHand', 'Both', 'Lei of the Lifegiver'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 88, 20628, 'Phase 5', 'Druid', 'Restoration', 'Head', 'Both', 'Deviate Growth Cap'), +(11, 2, 1, 0, 88, 21712, 'Phase 5', 'Druid', 'Restoration', 'Neck', 'Both', 'Amulet of the Fallen God'), +(11, 2, 2, 0, 88, 18810, 'Phase 5', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Wild Growth Spaulders'), +(11, 2, 4, 0, 88, 21663, 'Phase 5', 'Druid', 'Restoration', 'Chest', 'Both', 'Robes of the Guardian Saint'), +(11, 2, 5, 0, 88, 21582, 'Phase 5', 'Druid', 'Restoration', 'Waist', 'Both', 'Grasp of the Old God'), +(11, 2, 6, 0, 88, 19385, 'Phase 5', 'Druid', 'Restoration', 'Legs', 'Both', 'Empowered Leggings'), +(11, 2, 7, 0, 88, 19437, 'Phase 5', 'Druid', 'Restoration', 'Feet', 'Both', 'Boots of Pure Thought'), +(11, 2, 8, 0, 88, 21604, 'Phase 5', 'Druid', 'Restoration', 'Wrists', 'Both', 'Bracelets of Royal Redemption'), +(11, 2, 9, 0, 88, 21617, 'Phase 5', 'Druid', 'Restoration', 'Hands', 'Both', 'Wasphide Gauntlets'), +(11, 2, 10, 0, 88, 21620, 'Phase 5', 'Druid', 'Restoration', 'Finger1', 'Both', 'Ring of the Martyr'), +(11, 2, 11, 0, 88, 19382, 'Phase 5', 'Druid', 'Restoration', 'Finger2', 'Both', 'Pure Elementium Band'), +(11, 2, 12, 0, 88, 19395, 'Phase 5', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Rejuvenating Gem'), +(11, 2, 13, 0, 88, 17064, 'Phase 5', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Shard of the Scale'), +(11, 2, 14, 0, 88, 21583, 'Phase 5', 'Druid', 'Restoration', 'Back', 'Both', 'Cloak of Clarity'), +(11, 2, 15, 0, 88, 21839, 'Phase 5', 'Druid', 'Restoration', 'MainHand', 'Both', 'Scepter of the False Prophet'), +(11, 2, 16, 0, 88, 21666, 'Phase 5', 'Druid', 'Restoration', 'OffHand', 'Both', 'Sartura''s Might'), +(11, 2, 17, 0, 88, 22399, 'Phase 5', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of Health'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 92, 20628, 'Phase 6', 'Druid', 'Restoration', 'Head', 'Both', 'Deviate Growth Cap'), +(11, 2, 1, 0, 92, 21712, 'Phase 6', 'Druid', 'Restoration', 'Neck', 'Both', 'Amulet of the Fallen God'), +(11, 2, 2, 0, 92, 22491, 'Phase 6', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Dreamwalker Spaulders'), +(11, 2, 4, 0, 92, 22488, 'Phase 6', 'Druid', 'Restoration', 'Chest', 'Both', 'Dreamwalker Tunic'), +(11, 2, 5, 0, 92, 21582, 'Phase 6', 'Druid', 'Restoration', 'Waist', 'Both', 'Grasp of the Old God'), +(11, 2, 6, 0, 92, 22489, 'Phase 6', 'Druid', 'Restoration', 'Legs', 'Both', 'Dreamwalker Legguards'), +(11, 2, 7, 0, 92, 22492, 'Phase 6', 'Druid', 'Restoration', 'Feet', 'Both', 'Dreamwalker Boots'), +(11, 2, 8, 0, 92, 21604, 'Phase 6', 'Druid', 'Restoration', 'Wrists', 'Both', 'Bracelets of Royal Redemption'), +(11, 2, 9, 0, 92, 22493, 'Phase 6', 'Druid', 'Restoration', 'Hands', 'Both', 'Dreamwalker Handguards'), +(11, 2, 10, 0, 92, 21620, 'Phase 6', 'Druid', 'Restoration', 'Finger1', 'Both', 'Ring of the Martyr'), +(11, 2, 11, 0, 92, 22939, 'Phase 6', 'Druid', 'Restoration', 'Finger2', 'Both', 'Band of Unanswered Prayers'), +(11, 2, 12, 0, 92, 23047, 'Phase 6', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Eye of the Dead'), +(11, 2, 13, 0, 92, 23027, 'Phase 6', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Warmth of Forgiveness'), +(11, 2, 14, 0, 92, 22960, 'Phase 6', 'Druid', 'Restoration', 'Back', 'Both', 'Cloak of Suturing'), +(11, 2, 15, 0, 92, 23056, 'Phase 6', 'Druid', 'Restoration', 'MainHand', 'Both', 'Hammer of the Twisting Nether'), +(11, 2, 16, 0, 92, 23048, 'Phase 6', 'Druid', 'Restoration', 'OffHand', 'Both', 'Sapphiron''s Right Eye'), +(11, 2, 17, 0, 92, 22399, 'Phase 6', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of Health'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 120, 24264, 'Pre-Raid', 'Druid', 'Restoration', 'Head', 'Both', 'Whitemend Hood'), +(11, 2, 1, 0, 120, 30377, 'Pre-Raid', 'Druid', 'Restoration', 'Neck', 'Both', 'Karja''s Medallion'), +(11, 2, 2, 0, 120, 21874, 'Pre-Raid', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Primal Mooncloth Shoulders'), +(11, 2, 4, 0, 120, 21875, 'Pre-Raid', 'Druid', 'Restoration', 'Chest', 'Both', 'Primal Mooncloth Robe'), +(11, 2, 5, 0, 120, 21873, 'Pre-Raid', 'Druid', 'Restoration', 'Waist', 'Both', 'Primal Mooncloth Belt'), +(11, 2, 6, 0, 120, 24261, 'Pre-Raid', 'Druid', 'Restoration', 'Legs', 'Both', 'Whitemend Pants'), +(11, 2, 7, 0, 120, 27411, 'Pre-Raid', 'Druid', 'Restoration', 'Feet', 'Both', 'Slippers of Serenity'), +(11, 2, 8, 0, 120, 29183, 'Pre-Raid', 'Druid', 'Restoration', 'Wrists', 'Both', 'Bindings of the Timewalker'), +(11, 2, 9, 0, 120, 29506, 'Pre-Raid', 'Druid', 'Restoration', 'Hands', 'Both', 'Gloves of the Living Touch'), +(11, 2, 10, 0, 120, 27780, 'Pre-Raid', 'Druid', 'Restoration', 'Finger1', 'Both', 'Ring of Fabled Hope'), +(11, 2, 11, 0, 120, 31383, 'Pre-Raid', 'Druid', 'Restoration', 'Finger2', 'Both', 'Spiritualist''s Mark of the Sha''tar'), +(11, 2, 12, 0, 120, 29376, 'Pre-Raid', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(11, 2, 13, 0, 120, 30841, 'Pre-Raid', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Lower City Prayerbook'), +(11, 2, 14, 0, 120, 24254, 'Pre-Raid', 'Druid', 'Restoration', 'Back', 'Both', 'White Remedy Cape'), +(11, 2, 15, 0, 120, 23556, 'Pre-Raid', 'Druid', 'Restoration', 'MainHand', 'Both', 'Hand of Eternity'), +(11, 2, 16, 0, 120, 29274, 'Pre-Raid', 'Druid', 'Restoration', 'OffHand', 'Both', 'Tears of Heaven'), +(11, 2, 17, 0, 120, 27886, 'Pre-Raid', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of the Emerald Queen'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 125, 24264, 'Phase 1', 'Druid', 'Restoration', 'Head', 'Both', 'Whitemend Hood'), +(11, 2, 1, 0, 125, 30726, 'Phase 1', 'Druid', 'Restoration', 'Neck', 'Both', 'Archaic Charm of Presence'), +(11, 2, 2, 0, 125, 21874, 'Phase 1', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Primal Mooncloth Shoulders'), +(11, 2, 4, 0, 125, 21875, 'Phase 1', 'Druid', 'Restoration', 'Chest', 'Both', 'Primal Mooncloth Robe'), +(11, 2, 5, 0, 125, 21873, 'Phase 1', 'Druid', 'Restoration', 'Waist', 'Both', 'Primal Mooncloth Belt'), +(11, 2, 6, 0, 125, 30727, 'Phase 1', 'Druid', 'Restoration', 'Legs', 'Both', 'Gilded Trousers of Benediction'), +(11, 2, 7, 0, 125, 30737, 'Phase 1', 'Druid', 'Restoration', 'Feet', 'Both', 'Gold-Leaf Wildboots'), +(11, 2, 8, 0, 125, 29183, 'Phase 1', 'Druid', 'Restoration', 'Wrists', 'Both', 'Bindings of the Timewalker'), +(11, 2, 9, 0, 125, 28521, 'Phase 1', 'Druid', 'Restoration', 'Hands', 'Both', 'Mitts of the Treemender'), +(11, 2, 10, 0, 125, 28763, 'Phase 1', 'Druid', 'Restoration', 'Finger1', 'Both', 'Jade Ring of the Everliving'), +(11, 2, 11, 0, 125, 30736, 'Phase 1', 'Druid', 'Restoration', 'Finger2', 'Both', 'Ring of Flowing Light'), +(11, 2, 12, 0, 125, 29376, 'Phase 1', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(11, 2, 13, 0, 125, 30841, 'Phase 1', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Lower City Prayerbook'), +(11, 2, 14, 0, 125, 31329, 'Phase 1', 'Druid', 'Restoration', 'Back', 'Both', 'Lifegiving Cloak'), +(11, 2, 15, 0, 125, 28771, 'Phase 1', 'Druid', 'Restoration', 'MainHand', 'Both', 'Light''s Justice'), +(11, 2, 16, 0, 125, 29274, 'Phase 1', 'Druid', 'Restoration', 'OffHand', 'Both', 'Tears of Heaven'), +(11, 2, 17, 0, 125, 27886, 'Phase 1', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of the Emerald Queen'); + +-- ilvl 141 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 141, 30219, 'Phase 2', 'Druid', 'Restoration', 'Head', 'Both', 'Nordrassil Headguard'), +(11, 2, 1, 0, 141, 30018, 'Phase 2', 'Druid', 'Restoration', 'Neck', 'Both', 'Lord Sanguinar''s Claim'), +(11, 2, 2, 0, 141, 30221, 'Phase 2', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Nordrassil Life-Mantle'), +(11, 2, 4, 0, 141, 30216, 'Phase 2', 'Druid', 'Restoration', 'Chest', 'Both', 'Nordrassil Chestguard'), +(11, 2, 5, 0, 141, 21873, 'Phase 2', 'Druid', 'Restoration', 'Waist', 'Both', 'Primal Mooncloth Belt'), +(11, 2, 6, 0, 141, 30727, 'Phase 2', 'Druid', 'Restoration', 'Legs', 'Both', 'Gilded Trousers of Benediction'), +(11, 2, 7, 0, 141, 30737, 'Phase 2', 'Druid', 'Restoration', 'Feet', 'Both', 'Gold-Leaf Wildboots'), +(11, 2, 8, 0, 141, 30062, 'Phase 2', 'Druid', 'Restoration', 'Wrists', 'Both', 'Grove-Bands of Remulos'), +(11, 2, 9, 0, 141, 28521, 'Phase 2', 'Druid', 'Restoration', 'Hands', 'Both', 'Mitts of the Treemender'), +(11, 2, 10, 0, 141, 30110, 'Phase 2', 'Druid', 'Restoration', 'Finger1', 'Both', 'Coral Band of the Revived'), +(11, 2, 11, 0, 141, 28763, 'Phase 2', 'Druid', 'Restoration', 'Finger2', 'Both', 'Jade Ring of the Everliving'), +(11, 2, 12, 0, 141, 29376, 'Phase 2', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(11, 2, 13, 0, 141, 30841, 'Phase 2', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Lower City Prayerbook'), +(11, 2, 14, 0, 141, 29989, 'Phase 2', 'Druid', 'Restoration', 'Back', 'Both', 'Sunshower Light Cloak'), +(11, 2, 15, 0, 141, 30108, 'Phase 2', 'Druid', 'Restoration', 'MainHand', 'Both', 'Lightfathom Scepter'), +(11, 2, 16, 0, 141, 29274, 'Phase 2', 'Druid', 'Restoration', 'OffHand', 'Both', 'Tears of Heaven'), +(11, 2, 17, 0, 141, 27886, 'Phase 2', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of the Emerald Queen'); + +-- ilvl 156 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 156, 31037, 'Phase 3', 'Druid', 'Restoration', 'Head', 'Both', 'Thunderheart Helmet'), +(11, 2, 1, 0, 156, 30018, 'Phase 3', 'Druid', 'Restoration', 'Neck', 'Both', 'Lord Sanguinar''s Claim'), +(11, 2, 2, 0, 156, 32583, 'Phase 3', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Shoulderpads of Renewed Life'), +(11, 2, 4, 0, 156, 31041, 'Phase 3', 'Druid', 'Restoration', 'Chest', 'Both', 'Thunderheart Tunic'), +(11, 2, 5, 0, 156, 30895, 'Phase 3', 'Druid', 'Restoration', 'Waist', 'Both', 'Angelista''s Sash'), +(11, 2, 6, 0, 156, 30912, 'Phase 3', 'Druid', 'Restoration', 'Legs', 'Both', 'Leggings of Eternity'), +(11, 2, 7, 0, 156, 30737, 'Phase 3', 'Druid', 'Restoration', 'Feet', 'Both', 'Gold-Leaf Wildboots'), +(11, 2, 8, 0, 156, 32584, 'Phase 3', 'Druid', 'Restoration', 'Wrists', 'Both', 'Swiftheal Wraps'), +(11, 2, 9, 0, 156, 32328, 'Phase 3', 'Druid', 'Restoration', 'Hands', 'Both', 'Botanist''s Gloves of Growth'), +(11, 2, 10, 0, 156, 32528, 'Phase 3', 'Druid', 'Restoration', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(11, 2, 11, 0, 156, 32528, 'Phase 3', 'Druid', 'Restoration', 'Finger2', 'Both', 'Blessed Band of Karabor'), +(11, 2, 12, 0, 156, 29376, 'Phase 3', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(11, 2, 13, 0, 156, 32496, 'Phase 3', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Memento of Tyrande'), +(11, 2, 14, 0, 156, 32524, 'Phase 3', 'Druid', 'Restoration', 'Back', 'Both', 'Shroud of the Highborne'), +(11, 2, 15, 0, 156, 32500, 'Phase 3', 'Druid', 'Restoration', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(11, 2, 16, 0, 156, 30911, 'Phase 3', 'Druid', 'Restoration', 'OffHand', 'Both', 'Scepter of Purification'), +(11, 2, 17, 0, 156, 27886, 'Phase 3', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of the Emerald Queen'); + +-- ilvl 164 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 164, 31037, 'Phase 4', 'Druid', 'Restoration', 'Head', 'Both', 'Thunderheart Helmet'), +(11, 2, 1, 0, 164, 33281, 'Phase 4', 'Druid', 'Restoration', 'Neck', 'Both', 'Brooch of Nature''s Mercy'), +(11, 2, 2, 0, 164, 32583, 'Phase 4', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Shoulderpads of Renewed Life'), +(11, 2, 4, 0, 164, 31041, 'Phase 4', 'Druid', 'Restoration', 'Chest', 'Both', 'Thunderheart Tunic'), +(11, 2, 5, 0, 164, 30895, 'Phase 4', 'Druid', 'Restoration', 'Waist', 'Both', 'Angelista''s Sash'), +(11, 2, 6, 0, 164, 30912, 'Phase 4', 'Druid', 'Restoration', 'Legs', 'Both', 'Leggings of Eternity'), +(11, 2, 7, 0, 164, 30737, 'Phase 4', 'Druid', 'Restoration', 'Feet', 'Both', 'Gold-Leaf Wildboots'), +(11, 2, 8, 0, 164, 32584, 'Phase 4', 'Druid', 'Restoration', 'Wrists', 'Both', 'Swiftheal Wraps'), +(11, 2, 9, 0, 164, 32328, 'Phase 4', 'Druid', 'Restoration', 'Hands', 'Both', 'Botanist''s Gloves of Growth'), +(11, 2, 10, 0, 164, 32528, 'Phase 4', 'Druid', 'Restoration', 'Finger1', 'Both', 'Blessed Band of Karabor'), +(11, 2, 11, 0, 164, 32528, 'Phase 4', 'Druid', 'Restoration', 'Finger2', 'Both', 'Blessed Band of Karabor'), +(11, 2, 12, 0, 164, 29376, 'Phase 4', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Essence of the Martyr'), +(11, 2, 13, 0, 164, 32496, 'Phase 4', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Memento of Tyrande'), +(11, 2, 14, 0, 164, 32524, 'Phase 4', 'Druid', 'Restoration', 'Back', 'Both', 'Shroud of the Highborne'), +(11, 2, 15, 0, 164, 32500, 'Phase 4', 'Druid', 'Restoration', 'MainHand', 'Both', 'Crystal Spire of Karabor'), +(11, 2, 16, 0, 164, 30911, 'Phase 4', 'Druid', 'Restoration', 'OffHand', 'Both', 'Scepter of Purification'), +(11, 2, 17, 0, 164, 27886, 'Phase 4', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of the Emerald Queen'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 200, 42554, 'Pre-Raid', 'Druid', 'Restoration', 'Head', 'Both', 'Greensight Gogs'), +(11, 2, 1, 0, 200, 42647, 'Pre-Raid', 'Druid', 'Restoration', 'Neck', 'Both', 'Titanium Spellshock Necklace'), +(11, 2, 2, 0, 200, 37368, 'Pre-Raid', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Silent Spectator Shoulderpads'), +(11, 2, 4, 0, 200, 42102, 'Pre-Raid', 'Druid', 'Restoration', 'Chest', 'Both', 'Spellweave Robe'), +(11, 2, 5, 0, 200, 37643, 'Pre-Raid', 'Druid', 'Restoration', 'Waist', 'Both', 'Sash of Blood Removal'), +(11, 2, 6, 0, 200, 37791, 'Pre-Raid', 'Druid', 'Restoration', 'Legs', 'Both', 'Leggings of the Winged Serpent'), +(11, 2, 7, 0, 200, 43502, 'Pre-Raid', 'Druid', 'Restoration', 'Feet', 'Both', 'Earthgiving Boots'), +(11, 2, 8, 0, 200, 37696, 'Pre-Raid', 'Druid', 'Restoration', 'Wrists', 'Both', 'Plague-Infected Bracers'), +(11, 2, 9, 0, 200, 37230, 'Pre-Raid', 'Druid', 'Restoration', 'Hands', 'Both', 'Grotto Mist Gloves'), +(11, 2, 10, 0, 200, 37694, 'Pre-Raid', 'Druid', 'Restoration', 'Finger1', 'Both', 'Band of Guile'), +(11, 2, 12, 0, 200, 40685, 'Pre-Raid', 'Druid', 'Restoration', 'Trinket1', 'Both', 'The Egg of Mortal Essence'), +(11, 2, 14, 0, 200, 41609, 'Pre-Raid', 'Druid', 'Restoration', 'Back', 'Both', 'Wispcloak'), +(11, 2, 17, 0, 200, 40711, 'Pre-Raid', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of Lush Moss'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 224, 44007, 'Phase 1', 'Druid', 'Restoration', 'Head', 'Both', 'Headpiece of Reconciliation'), +(11, 2, 1, 0, 224, 44661, 'Phase 1', 'Druid', 'Restoration', 'Neck', 'Both', 'Wyrmrest Necklace of Power'), +(11, 2, 2, 0, 224, 39719, 'Phase 1', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Mantle of the Locusts'), +(11, 2, 4, 0, 224, 44002, 'Phase 1', 'Druid', 'Restoration', 'Chest', 'Both', 'The Sanctum''s Flowing Vestments'), +(11, 2, 5, 0, 224, 40561, 'Phase 1', 'Druid', 'Restoration', 'Waist', 'Both', 'Leash of Heedless Magic'), +(11, 2, 6, 0, 224, 40379, 'Phase 1', 'Druid', 'Restoration', 'Legs', 'Both', 'Legguards of the Boneyard'), +(11, 2, 7, 0, 224, 40558, 'Phase 1', 'Druid', 'Restoration', 'Feet', 'Both', 'Arcanic Tramplers'), +(11, 2, 8, 0, 224, 44008, 'Phase 1', 'Druid', 'Restoration', 'Wrists', 'Both', 'Unsullied Cuffs'), +(11, 2, 9, 0, 224, 40460, 'Phase 1', 'Druid', 'Restoration', 'Hands', 'Both', 'Valorous Dreamwalker Handguards'), +(11, 2, 10, 0, 224, 40375, 'Phase 1', 'Druid', 'Restoration', 'Finger1', 'Both', 'Ring of Decaying Beauty'), +(11, 2, 12, 0, 224, 40432, 'Phase 1', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Illustration of the Dragon Soul'), +(11, 2, 14, 0, 224, 44005, 'Phase 1', 'Druid', 'Restoration', 'Back', 'Both', 'Pennant Cloak'), +(11, 2, 17, 0, 224, 40342, 'Phase 1', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of Awakening'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 245, 46184, 'Phase 2', 'Druid', 'Restoration', 'Head', 'Both', 'Conqueror''s Nightsong Headpiece'), +(11, 2, 1, 0, 245, 45243, 'Phase 2', 'Druid', 'Restoration', 'Neck', 'Both', 'Sapphire Amulet of Renewal'), +(11, 2, 2, 0, 245, 46187, 'Phase 2', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Conqueror''s Nightsong Spaulders'), +(11, 2, 4, 0, 245, 45519, 'Phase 2', 'Druid', 'Restoration', 'Chest', 'Both', 'Vestments of the Blind Denizen'), +(11, 2, 5, 0, 245, 45616, 'Phase 2', 'Druid', 'Restoration', 'Waist', 'Both', 'Star-beaded Clutch'), +(11, 2, 6, 0, 245, 46185, 'Phase 2', 'Druid', 'Restoration', 'Legs', 'Both', 'Conqueror''s Nightsong Leggings'), +(11, 2, 7, 0, 245, 45135, 'Phase 2', 'Druid', 'Restoration', 'Feet', 'Both', 'Boots of Fiery Resolution'), +(11, 2, 8, 0, 245, 45446, 'Phase 2', 'Druid', 'Restoration', 'Wrists', 'Both', 'Grasps of Reason'), +(11, 2, 9, 0, 245, 46183, 'Phase 2', 'Druid', 'Restoration', 'Hands', 'Both', 'Conqueror''s Nightsong Handguards'), +(11, 2, 10, 0, 245, 45495, 'Phase 2', 'Druid', 'Restoration', 'Finger1', 'Both', 'Conductive Seal'), +(11, 2, 12, 0, 245, 45535, 'Phase 2', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Show of Faith'), +(11, 2, 14, 0, 245, 45618, 'Phase 2', 'Druid', 'Restoration', 'Back', 'Both', 'Sunglimmer Cloak'), +(11, 2, 17, 0, 245, 40342, 'Phase 2', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of Awakening'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 1, 258, 48141, 'Phase 3', 'Druid', 'Restoration', 'Head', 'Alliance', 'Malfurion''s Headpiece of Triumph'), +(11, 2, 1, 1, 258, 47144, 'Phase 3', 'Druid', 'Restoration', 'Neck', 'Alliance', 'Wail of the Val''kyr'), +(11, 2, 1, 2, 258, 47468, 'Phase 3', 'Druid', 'Restoration', 'Neck', 'Horde', 'Cry of the Val''kyr'), +(11, 2, 2, 1, 258, 48138, 'Phase 3', 'Druid', 'Restoration', 'Shoulders', 'Alliance', 'Malfurion''s Spaulders of Triumph'), +(11, 2, 4, 0, 258, 48169, 'Phase 3', 'Druid', 'Restoration', 'Chest', 'Both', 'Vestments of Triumph'), +(11, 2, 5, 1, 258, 47145, 'Phase 3', 'Druid', 'Restoration', 'Waist', 'Alliance', 'Cord of Pale Thorns'), +(11, 2, 5, 2, 258, 47469, 'Phase 3', 'Druid', 'Restoration', 'Waist', 'Horde', 'Belt of Pale Thorns'), +(11, 2, 6, 0, 258, 48140, 'Phase 3', 'Druid', 'Restoration', 'Legs', 'Both', 'Leggings of Triumph'), +(11, 2, 7, 1, 258, 47097, 'Phase 3', 'Druid', 'Restoration', 'Feet', 'Alliance', 'Boots of the Mourning Widow'), +(11, 2, 7, 2, 258, 47454, 'Phase 3', 'Druid', 'Restoration', 'Feet', 'Horde', 'Sandals of the Mourning Widow'), +(11, 2, 8, 1, 258, 47066, 'Phase 3', 'Druid', 'Restoration', 'Wrists', 'Alliance', 'Bracers of the Autumn Willow'), +(11, 2, 8, 2, 258, 47438, 'Phase 3', 'Druid', 'Restoration', 'Wrists', 'Horde', 'Bindings of the Autumn Willow'), +(11, 2, 9, 0, 258, 48142, 'Phase 3', 'Druid', 'Restoration', 'Hands', 'Both', 'Handguards of Triumph'), +(11, 2, 10, 1, 258, 47224, 'Phase 3', 'Druid', 'Restoration', 'Finger1', 'Alliance', 'Ring of the Darkmender'), +(11, 2, 10, 2, 258, 47439, 'Phase 3', 'Druid', 'Restoration', 'Finger1', 'Horde', 'Circle of the Darkmender'), +(11, 2, 12, 1, 258, 47059, 'Phase 3', 'Druid', 'Restoration', 'Trinket1', 'Alliance', 'Solace of the Defeated'), +(11, 2, 12, 2, 258, 47432, 'Phase 3', 'Druid', 'Restoration', 'Trinket1', 'Horde', 'Solace of the Fallen'), +(11, 2, 14, 1, 258, 47553, 'Phase 3', 'Druid', 'Restoration', 'Back', 'Alliance', 'Bolvar''s Devotion'), +(11, 2, 14, 2, 258, 47554, 'Phase 3', 'Druid', 'Restoration', 'Back', 'Horde', 'Lady Liadrin''s Conviction'), +(11, 2, 17, 0, 258, 47671, 'Phase 3', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of Flaring Growth'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 264, 51302, 'Phase 4', 'Druid', 'Restoration', 'Head', 'Both', 'Sanctified Lasherweave Helmet'), +(11, 2, 1, 0, 264, 50609, 'Phase 4', 'Druid', 'Restoration', 'Neck', 'Both', 'Bone Sentinel''s Amulet'), +(11, 2, 2, 0, 264, 51304, 'Phase 4', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Sanctified Lasherweave Pauldrons'), +(11, 2, 4, 0, 264, 50717, 'Phase 4', 'Druid', 'Restoration', 'Chest', 'Both', 'Sanguine Silk Robes'), +(11, 2, 5, 0, 264, 50705, 'Phase 4', 'Druid', 'Restoration', 'Waist', 'Both', 'Professor''s Bloodied Smock'), +(11, 2, 6, 0, 264, 51303, 'Phase 4', 'Druid', 'Restoration', 'Legs', 'Both', 'Sanctified Lasherweave Legplates'), +(11, 2, 7, 0, 264, 50699, 'Phase 4', 'Druid', 'Restoration', 'Feet', 'Both', 'Plague Scientist''s Boots'), +(11, 2, 8, 0, 264, 50630, 'Phase 4', 'Druid', 'Restoration', 'Wrists', 'Both', 'Bracers of Eternal Dreaming'), +(11, 2, 9, 0, 264, 51301, 'Phase 4', 'Druid', 'Restoration', 'Hands', 'Both', 'Sanctified Lasherweave Gauntlets'), +(11, 2, 10, 0, 264, 50636, 'Phase 4', 'Druid', 'Restoration', 'Finger1', 'Both', 'Memory of Malygos'), +(11, 2, 12, 1, 264, 47059, 'Phase 4', 'Druid', 'Restoration', 'Trinket1', 'Alliance', 'Solace of the Defeated'), +(11, 2, 12, 2, 264, 47432, 'Phase 4', 'Druid', 'Restoration', 'Trinket1', 'Horde', 'Solace of the Fallen'), +(11, 2, 14, 0, 264, 50668, 'Phase 4', 'Druid', 'Restoration', 'Back', 'Both', 'Greatcloak of the Turned Champion'), +(11, 2, 15, 0, 264, 46017, 'Phase 4', 'Druid', 'Restoration', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(11, 2, 17, 0, 264, 50454, 'Phase 4', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of the Black Willow'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 2, 0, 0, 290, 51302, 'Phase 5', 'Druid', 'Restoration', 'Head', 'Both', 'Sanctified Lasherweave Helmet'), +(11, 2, 1, 0, 290, 50609, 'Phase 5', 'Druid', 'Restoration', 'Neck', 'Both', 'Bone Sentinel''s Amulet'), +(11, 2, 2, 0, 290, 51304, 'Phase 5', 'Druid', 'Restoration', 'Shoulders', 'Both', 'Sanctified Lasherweave Pauldrons'), +(11, 2, 4, 0, 290, 50717, 'Phase 5', 'Druid', 'Restoration', 'Chest', 'Both', 'Sanguine Silk Robes'), +(11, 2, 5, 0, 290, 50705, 'Phase 5', 'Druid', 'Restoration', 'Waist', 'Both', 'Professor''s Bloodied Smock'), +(11, 2, 6, 0, 290, 51303, 'Phase 5', 'Druid', 'Restoration', 'Legs', 'Both', 'Sanctified Lasherweave Legplates'), +(11, 2, 7, 0, 290, 50665, 'Phase 5', 'Druid', 'Restoration', 'Feet', 'Both', 'Boots Of Unnatural Growth'), +(11, 2, 8, 0, 290, 54584, 'Phase 5', 'Druid', 'Restoration', 'Wrists', 'Both', 'Phaseshifter Bracers'), +(11, 2, 9, 0, 290, 51301, 'Phase 5', 'Druid', 'Restoration', 'Hands', 'Both', 'Sanctified Lasherweave Gauntlets'), +(11, 2, 10, 0, 290, 50400, 'Phase 5', 'Druid', 'Restoration', 'Finger1', 'Both', 'Ashen Band of Endless Wisdom'), +(11, 2, 11, 0, 290, 50636, 'Phase 5', 'Druid', 'Restoration', 'Finger2', 'Both', 'Memory of Malygos'), +(11, 2, 12, 0, 290, 47059, 'Phase 5', 'Druid', 'Restoration', 'Trinket1', 'Both', 'Solace of the Defeated'), +(11, 2, 13, 0, 290, 54589, 'Phase 5', 'Druid', 'Restoration', 'Trinket2', 'Both', 'Glowing Twilight Scale'), +(11, 2, 14, 0, 290, 50668, 'Phase 5', 'Druid', 'Restoration', 'Back', 'Both', 'Greatcloak of the Turned Champion'), +(11, 2, 15, 0, 290, 46017, 'Phase 5', 'Druid', 'Restoration', 'MainHand', 'Both', 'Val''anyr, Hammer of Ancient Kings'), +(11, 2, 16, 0, 290, 50635, 'Phase 5', 'Druid', 'Restoration', 'OffHand', 'Both', 'Sundial of Eternal Dusk'), +(11, 2, 17, 0, 290, 50454, 'Phase 5', 'Druid', 'Restoration', 'Ranged', 'Both', 'Idol of the Black Willow'); + +-- Feral Bear (tab 10) +-- ilvl 66 (Phase 1 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 66, 14539, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Head', 'Both', 'Bone Ring Helm'), +(11, 10, 1, 0, 66, 13177, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Neck', 'Both', 'Talisman of Evasion'), +(11, 10, 2, 0, 66, 10783, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Atal''ai Spaulders'), +(11, 10, 4, 0, 66, 15064, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Warbear Harness'), +(11, 10, 5, 0, 66, 13252, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Cloudrunner Girdle'), +(11, 10, 6, 0, 66, 11821, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Warstrife Leggings'), +(11, 10, 7, 0, 66, 16711, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Shadowcraft Boots'), +(11, 10, 8, 0, 66, 12966, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Blackmist Armguards'), +(11, 10, 9, 0, 66, 13258, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Slaghide Gauntlets'), +(11, 10, 10, 0, 66, 15855, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Ring of Protection'), +(11, 10, 11, 0, 66, 11669, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Finger2', 'Both', 'Naglering'), +(11, 10, 12, 0, 66, 13966, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Trinket1', 'Both', 'Mark of Tyranny'), +(11, 10, 13, 0, 66, 11811, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Trinket2', 'Both', 'Smoking Heart of the Mountain'), +(11, 10, 14, 0, 66, 12551, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'Back', 'Both', 'Stoneshield Cloak'), +(11, 10, 15, 0, 66, 943, 'Phase 1 (Pre-Raid)', 'Druid', 'Feral Bear', 'MainHand', 'Both', 'Warden Staff'); + +-- ilvl 76 (Phase 2 (Pre-Raid)) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 76, 14539, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Head', 'Both', 'Bone Ring Helm'), +(11, 10, 1, 0, 76, 13177, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Neck', 'Both', 'Talisman of Evasion'), +(11, 10, 2, 0, 76, 10783, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Atal''ai Spaulders'), +(11, 10, 4, 0, 76, 15064, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Warbear Harness'), +(11, 10, 5, 0, 76, 13252, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Cloudrunner Girdle'), +(11, 10, 6, 0, 76, 11821, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Warstrife Leggings'), +(11, 10, 7, 0, 76, 16711, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Shadowcraft Boots'), +(11, 10, 8, 0, 76, 12966, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Blackmist Armguards'), +(11, 10, 9, 0, 76, 13258, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Slaghide Gauntlets'), +(11, 10, 10, 0, 76, 15855, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Ring of Protection'), +(11, 10, 11, 0, 76, 11669, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Finger2', 'Both', 'Naglering'), +(11, 10, 12, 0, 76, 13966, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Trinket1', 'Both', 'Mark of Tyranny'), +(11, 10, 13, 0, 76, 11811, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Trinket2', 'Both', 'Smoking Heart of the Mountain'), +(11, 10, 14, 0, 76, 12551, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'Back', 'Both', 'Stoneshield Cloak'), +(11, 10, 15, 0, 76, 943, 'Phase 2 (Pre-Raid)', 'Druid', 'Feral Bear', 'MainHand', 'Both', 'Warden Staff'); + +-- ilvl 78 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 78, 14539, 'Phase 2', 'Druid', 'Feral Bear', 'Head', 'Both', 'Bone Ring Helm'), +(11, 10, 1, 0, 78, 17065, 'Phase 2', 'Druid', 'Feral Bear', 'Neck', 'Both', 'Medallion of Steadfast Might'), +(11, 10, 2, 0, 78, 19139, 'Phase 2', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Fireguard Shoulders'), +(11, 10, 4, 0, 78, 15064, 'Phase 2', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Warbear Harness'), +(11, 10, 5, 0, 78, 19149, 'Phase 2', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Lava Belt'), +(11, 10, 6, 0, 78, 11821, 'Phase 2', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Warstrife Leggings'), +(11, 10, 7, 0, 78, 16711, 'Phase 2', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Shadowcraft Boots'), +(11, 10, 8, 0, 78, 12966, 'Phase 2', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Blackmist Armguards'), +(11, 10, 9, 0, 78, 13258, 'Phase 2', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Slaghide Gauntlets'), +(11, 10, 10, 0, 78, 15855, 'Phase 2', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Ring of Protection'), +(11, 10, 11, 0, 78, 18879, 'Phase 2', 'Druid', 'Feral Bear', 'Finger2', 'Both', 'Heavy Dark Iron Ring'), +(11, 10, 12, 0, 78, 13966, 'Phase 2', 'Druid', 'Feral Bear', 'Trinket1', 'Both', 'Mark of Tyranny'), +(11, 10, 13, 0, 78, 11811, 'Phase 2', 'Druid', 'Feral Bear', 'Trinket2', 'Both', 'Smoking Heart of the Mountain'), +(11, 10, 14, 0, 78, 17107, 'Phase 2', 'Druid', 'Feral Bear', 'Back', 'Both', 'Dragon''s Blood Cape'), +(11, 10, 15, 0, 78, 943, 'Phase 2', 'Druid', 'Feral Bear', 'MainHand', 'Both', 'Warden Staff'); + +-- ilvl 83 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 83, 14539, 'Phase 3', 'Druid', 'Feral Bear', 'Head', 'Both', 'Bone Ring Helm'), +(11, 10, 1, 0, 83, 17065, 'Phase 3', 'Druid', 'Feral Bear', 'Neck', 'Both', 'Medallion of Steadfast Might'), +(11, 10, 2, 0, 83, 19389, 'Phase 3', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Taut Dragonhide Shoulderpads'), +(11, 10, 4, 0, 83, 19405, 'Phase 3', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Malfurion''s Blessed Bulwark'), +(11, 10, 5, 0, 83, 19149, 'Phase 3', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Lava Belt'), +(11, 10, 6, 0, 83, 11821, 'Phase 3', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Warstrife Leggings'), +(11, 10, 7, 0, 83, 19381, 'Phase 3', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Boots of the Shadow Flame'), +(11, 10, 8, 0, 83, 12966, 'Phase 3', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Blackmist Armguards'), +(11, 10, 9, 0, 83, 13258, 'Phase 3', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Slaghide Gauntlets'), +(11, 10, 10, 0, 83, 19376, 'Phase 3', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Archimtiros'' Ring of Reckoning'), +(11, 10, 11, 0, 83, 18879, 'Phase 3', 'Druid', 'Feral Bear', 'Finger2', 'Both', 'Heavy Dark Iron Ring'), +(11, 10, 12, 0, 83, 13966, 'Phase 3', 'Druid', 'Feral Bear', 'Trinket1', 'Both', 'Mark of Tyranny'), +(11, 10, 13, 0, 83, 11811, 'Phase 3', 'Druid', 'Feral Bear', 'Trinket2', 'Both', 'Smoking Heart of the Mountain'), +(11, 10, 14, 0, 83, 19386, 'Phase 3', 'Druid', 'Feral Bear', 'Back', 'Both', 'Elementium Threaded Cloak'), +(11, 10, 15, 0, 83, 943, 'Phase 3', 'Druid', 'Feral Bear', 'MainHand', 'Both', 'Warden Staff'); + +-- ilvl 88 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 1, 88, 21693, 'Phase 5', 'Druid', 'Feral Bear', 'Head', 'Alliance', 'Guise of the Devourer'), +(11, 10, 0, 2, 88, 21693, 'Phase 5', 'Druid', 'Feral Bear', 'Head', 'Horde', 'Guise of the Devourer'), +(11, 10, 1, 1, 88, 22732, 'Phase 5', 'Druid', 'Feral Bear', 'Neck', 'Alliance', 'Mark of C''Thun'), +(11, 10, 1, 2, 88, 22732, 'Phase 5', 'Druid', 'Feral Bear', 'Neck', 'Horde', 'Mark of C''Thun'), +(11, 10, 2, 1, 88, 20059, 'Phase 5', 'Druid', 'Feral Bear', 'Shoulders', 'Alliance', 'Highlander''s Leather Shoulders'), +(11, 10, 2, 2, 88, 20194, 'Phase 5', 'Druid', 'Feral Bear', 'Shoulders', 'Horde', 'Defiler''s Leather Shoulders'), +(11, 10, 4, 1, 88, 19405, 'Phase 5', 'Druid', 'Feral Bear', 'Chest', 'Alliance', 'Malfurion''s Blessed Bulwark'), +(11, 10, 4, 2, 88, 19405, 'Phase 5', 'Druid', 'Feral Bear', 'Chest', 'Horde', 'Malfurion''s Blessed Bulwark'), +(11, 10, 5, 1, 88, 21675, 'Phase 5', 'Druid', 'Feral Bear', 'Waist', 'Alliance', 'Thick Qirajihide Belt'), +(11, 10, 5, 2, 88, 21675, 'Phase 5', 'Druid', 'Feral Bear', 'Waist', 'Horde', 'Thick Qirajihide Belt'), +(11, 10, 6, 1, 88, 22749, 'Phase 5', 'Druid', 'Feral Bear', 'Legs', 'Alliance', 'Sentinel''s Leather Pants'), +(11, 10, 6, 2, 88, 22740, 'Phase 5', 'Druid', 'Feral Bear', 'Legs', 'Horde', 'Outrider''s Leather Pants'), +(11, 10, 7, 1, 88, 19381, 'Phase 5', 'Druid', 'Feral Bear', 'Feet', 'Alliance', 'Boots of the Shadow Flame'), +(11, 10, 7, 2, 88, 19381, 'Phase 5', 'Druid', 'Feral Bear', 'Feet', 'Horde', 'Boots of the Shadow Flame'), +(11, 10, 8, 1, 88, 21602, 'Phase 5', 'Druid', 'Feral Bear', 'Wrists', 'Alliance', 'Qiraji Execution Bracers'), +(11, 10, 8, 2, 88, 21602, 'Phase 5', 'Druid', 'Feral Bear', 'Wrists', 'Horde', 'Qiraji Execution Bracers'), +(11, 10, 9, 1, 88, 21605, 'Phase 5', 'Druid', 'Feral Bear', 'Hands', 'Alliance', 'Gloves of the Hidden Temple'), +(11, 10, 9, 2, 88, 21605, 'Phase 5', 'Druid', 'Feral Bear', 'Hands', 'Horde', 'Gloves of the Hidden Temple'), +(11, 10, 10, 1, 88, 21601, 'Phase 5', 'Druid', 'Feral Bear', 'Finger1', 'Alliance', 'Ring of Emperor Vek''lor'), +(11, 10, 10, 2, 88, 21601, 'Phase 5', 'Druid', 'Feral Bear', 'Finger1', 'Horde', 'Ring of Emperor Vek''lor'), +(11, 10, 11, 1, 88, 18879, 'Phase 5', 'Druid', 'Feral Bear', 'Finger2', 'Alliance', 'Heavy Dark Iron Ring'), +(11, 10, 11, 2, 88, 18879, 'Phase 5', 'Druid', 'Feral Bear', 'Finger2', 'Horde', 'Heavy Dark Iron Ring'), +(11, 10, 12, 1, 88, 13966, 'Phase 5', 'Druid', 'Feral Bear', 'Trinket1', 'Alliance', 'Mark of Tyranny'), +(11, 10, 12, 2, 88, 13966, 'Phase 5', 'Druid', 'Feral Bear', 'Trinket1', 'Horde', 'Mark of Tyranny'), +(11, 10, 13, 1, 88, 11811, 'Phase 5', 'Druid', 'Feral Bear', 'Trinket2', 'Alliance', 'Smoking Heart of the Mountain'), +(11, 10, 13, 2, 88, 11811, 'Phase 5', 'Druid', 'Feral Bear', 'Trinket2', 'Horde', 'Smoking Heart of the Mountain'), +(11, 10, 14, 1, 88, 19386, 'Phase 5', 'Druid', 'Feral Bear', 'Back', 'Alliance', 'Elementium Threaded Cloak'), +(11, 10, 14, 2, 88, 19386, 'Phase 5', 'Druid', 'Feral Bear', 'Back', 'Horde', 'Elementium Threaded Cloak'), +(11, 10, 15, 1, 88, 943, 'Phase 5', 'Druid', 'Feral Bear', 'MainHand', 'Alliance', 'Warden Staff'), +(11, 10, 15, 2, 88, 943, 'Phase 5', 'Druid', 'Feral Bear', 'MainHand', 'Horde', 'Warden Staff'), +(11, 10, 17, 1, 88, 23198, 'Phase 5', 'Druid', 'Feral Bear', 'Ranged', 'Alliance', 'Idol of Brutality'), +(11, 10, 17, 2, 88, 23198, 'Phase 5', 'Druid', 'Feral Bear', 'Ranged', 'Horde', 'Idol of Brutality'); + +-- ilvl 92 (Phase 6) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 1, 92, 21693, 'Phase 6', 'Druid', 'Feral Bear', 'Head', 'Alliance', 'Guise of the Devourer'), +(11, 10, 0, 2, 92, 21693, 'Phase 6', 'Druid', 'Feral Bear', 'Head', 'Horde', 'Guise of the Devourer'), +(11, 10, 1, 1, 92, 22732, 'Phase 6', 'Druid', 'Feral Bear', 'Neck', 'Alliance', 'Mark of C''Thun'), +(11, 10, 1, 2, 92, 22732, 'Phase 6', 'Druid', 'Feral Bear', 'Neck', 'Horde', 'Mark of C''Thun'), +(11, 10, 2, 1, 92, 20059, 'Phase 6', 'Druid', 'Feral Bear', 'Shoulders', 'Alliance', 'Highlander''s Leather Shoulders'), +(11, 10, 2, 2, 92, 20194, 'Phase 6', 'Druid', 'Feral Bear', 'Shoulders', 'Horde', 'Defiler''s Leather Shoulders'), +(11, 10, 4, 1, 92, 23226, 'Phase 6', 'Druid', 'Feral Bear', 'Chest', 'Alliance', 'Ghoul Skin Tunic'), +(11, 10, 4, 2, 92, 23226, 'Phase 6', 'Druid', 'Feral Bear', 'Chest', 'Horde', 'Ghoul Skin Tunic'), +(11, 10, 5, 1, 92, 21675, 'Phase 6', 'Druid', 'Feral Bear', 'Waist', 'Alliance', 'Thick Qirajihide Belt'), +(11, 10, 5, 2, 92, 21675, 'Phase 6', 'Druid', 'Feral Bear', 'Waist', 'Horde', 'Thick Qirajihide Belt'), +(11, 10, 6, 1, 92, 22749, 'Phase 6', 'Druid', 'Feral Bear', 'Legs', 'Alliance', 'Sentinel''s Leather Pants'), +(11, 10, 6, 2, 92, 22740, 'Phase 6', 'Druid', 'Feral Bear', 'Legs', 'Horde', 'Outrider''s Leather Pants'), +(11, 10, 7, 1, 92, 19381, 'Phase 6', 'Druid', 'Feral Bear', 'Feet', 'Alliance', 'Boots of the Shadow Flame'), +(11, 10, 7, 2, 92, 19381, 'Phase 6', 'Druid', 'Feral Bear', 'Feet', 'Horde', 'Boots of the Shadow Flame'), +(11, 10, 8, 1, 92, 22663, 'Phase 6', 'Druid', 'Feral Bear', 'Wrists', 'Alliance', 'Polar Bracers'), +(11, 10, 8, 2, 92, 22663, 'Phase 6', 'Druid', 'Feral Bear', 'Wrists', 'Horde', 'Polar Bracers'), +(11, 10, 9, 1, 92, 21605, 'Phase 6', 'Druid', 'Feral Bear', 'Hands', 'Alliance', 'Gloves of the Hidden Temple'), +(11, 10, 9, 2, 92, 21605, 'Phase 6', 'Druid', 'Feral Bear', 'Hands', 'Horde', 'Gloves of the Hidden Temple'), +(11, 10, 10, 1, 92, 21601, 'Phase 6', 'Druid', 'Feral Bear', 'Finger1', 'Alliance', 'Ring of Emperor Vek''lor'), +(11, 10, 10, 2, 92, 21601, 'Phase 6', 'Druid', 'Feral Bear', 'Finger1', 'Horde', 'Ring of Emperor Vek''lor'), +(11, 10, 11, 1, 92, 23018, 'Phase 6', 'Druid', 'Feral Bear', 'Finger2', 'Alliance', 'Signet of the Fallen Defender'), +(11, 10, 11, 2, 92, 23018, 'Phase 6', 'Druid', 'Feral Bear', 'Finger2', 'Horde', 'Signet of the Fallen Defender'), +(11, 10, 12, 1, 92, 13966, 'Phase 6', 'Druid', 'Feral Bear', 'Trinket1', 'Alliance', 'Mark of Tyranny'), +(11, 10, 12, 2, 92, 13966, 'Phase 6', 'Druid', 'Feral Bear', 'Trinket1', 'Horde', 'Mark of Tyranny'), +(11, 10, 13, 1, 92, 11811, 'Phase 6', 'Druid', 'Feral Bear', 'Trinket2', 'Alliance', 'Smoking Heart of the Mountain'), +(11, 10, 13, 2, 92, 11811, 'Phase 6', 'Druid', 'Feral Bear', 'Trinket2', 'Horde', 'Smoking Heart of the Mountain'), +(11, 10, 14, 1, 92, 22938, 'Phase 6', 'Druid', 'Feral Bear', 'Back', 'Alliance', 'Cryptfiend Silk Cloak'), +(11, 10, 14, 2, 92, 22938, 'Phase 6', 'Druid', 'Feral Bear', 'Back', 'Horde', 'Cryptfiend Silk Cloak'), +(11, 10, 15, 1, 92, 943, 'Phase 6', 'Druid', 'Feral Bear', 'MainHand', 'Alliance', 'Warden Staff'), +(11, 10, 15, 2, 92, 943, 'Phase 6', 'Druid', 'Feral Bear', 'MainHand', 'Horde', 'Warden Staff'), +(11, 10, 17, 1, 92, 23198, 'Phase 6', 'Druid', 'Feral Bear', 'Ranged', 'Alliance', 'Idol of Brutality'), +(11, 10, 17, 2, 92, 23198, 'Phase 6', 'Druid', 'Feral Bear', 'Ranged', 'Horde', 'Idol of Brutality'); + +-- ilvl 120 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 120, 28182, 'Pre-Raid', 'Druid', 'Feral Bear', 'Head', 'Both', 'Helm of the Claw'), +(11, 10, 1, 0, 120, 27779, 'Pre-Raid', 'Druid', 'Feral Bear', 'Neck', 'Both', 'Bone Chain Necklace'), +(11, 10, 2, 0, 120, 27434, 'Pre-Raid', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Mantle of Perenolde'), +(11, 10, 4, 0, 120, 25689, 'Pre-Raid', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Heavy Clefthoof Vest'), +(11, 10, 5, 0, 120, 30942, 'Pre-Raid', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Manimal''s Cinch'), +(11, 10, 6, 0, 120, 31544, 'Pre-Raid', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Clefthoof Hide Leggings'), +(11, 10, 7, 0, 120, 25691, 'Pre-Raid', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Heavy Clefthoof Boots'), +(11, 10, 8, 0, 120, 29263, 'Pre-Raid', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Forestheart Bracers'), +(11, 10, 9, 0, 120, 30341, 'Pre-Raid', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Flesh Handler''s Gauntlets'), +(11, 10, 10, 0, 120, 30834, 'Pre-Raid', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Shapeshifter''s Signet'), +(11, 10, 12, 0, 120, 19406, 'Pre-Raid', 'Druid', 'Feral Bear', 'Trinket1', 'Both', 'Drake Fang Talisman'), +(11, 10, 13, 0, 120, 29383, 'Pre-Raid', 'Druid', 'Feral Bear', 'Trinket2', 'Both', 'Bloodlust Brooch'), +(11, 10, 14, 0, 120, 24258, 'Pre-Raid', 'Druid', 'Feral Bear', 'Back', 'Both', 'Resolute Cape'), +(11, 10, 15, 0, 120, 29171, 'Pre-Raid', 'Druid', 'Feral Bear', 'MainHand', 'Both', 'Earthwarden'), +(11, 10, 17, 0, 120, 28064, 'Pre-Raid', 'Druid', 'Feral Bear', 'Ranged', 'Both', 'Idol of the Wild'); + +-- ilvl 125 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 125, 29098, 'Phase 1', 'Druid', 'Feral Bear', 'Head', 'Both', 'Stag-Helm of Malorne'), +(11, 10, 1, 0, 125, 28509, 'Phase 1', 'Druid', 'Feral Bear', 'Neck', 'Both', 'Worgen Claw Necklace'), +(11, 10, 2, 0, 125, 29100, 'Phase 1', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Mantle of Malorne'), +(11, 10, 4, 0, 125, 29096, 'Phase 1', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Breastplate of Malorne'), +(11, 10, 5, 0, 125, 29264, 'Phase 1', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Tree-Mender''s Belt'), +(11, 10, 6, 0, 125, 29099, 'Phase 1', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Greaves of Malorne'), +(11, 10, 7, 0, 125, 28545, 'Phase 1', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Edgewalker Longboots'), +(11, 10, 8, 0, 125, 29263, 'Phase 1', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Forestheart Bracers'), +(11, 10, 9, 0, 125, 29097, 'Phase 1', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Gauntlets of Malorne'), +(11, 10, 10, 0, 125, 30834, 'Phase 1', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Shapeshifter''s Signet'), +(11, 10, 11, 0, 125, 29279, 'Phase 1', 'Druid', 'Feral Bear', 'Finger2', 'Both', 'Violet Signet of the Great Protector'), +(11, 10, 12, 0, 125, 29383, 'Phase 1', 'Druid', 'Feral Bear', 'Trinket1', 'Both', 'Bloodlust Brooch'), +(11, 10, 13, 0, 125, 28830, 'Phase 1', 'Druid', 'Feral Bear', 'Trinket2', 'Both', 'Dragonspine Trophy'), +(11, 10, 14, 0, 125, 28660, 'Phase 1', 'Druid', 'Feral Bear', 'Back', 'Both', 'Gilded Thorium Cloak'), +(11, 10, 15, 0, 125, 28658, 'Phase 1', 'Druid', 'Feral Bear', 'MainHand', 'Both', 'Terestian''s Stranglestaff'), +(11, 10, 17, 0, 125, 23198, 'Phase 1', 'Druid', 'Feral Bear', 'Ranged', 'Both', 'Idol of Brutality'); + +-- ilvl 200 (Pre-Raid) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 200, 42550, 'Pre-Raid', 'Druid', 'Feral Bear', 'Head', 'Both', 'Weakness Spectralizers'), +(11, 10, 2, 0, 200, 43481, 'Pre-Raid', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Trollwoven Spaulders'), +(11, 10, 4, 0, 200, 39554, 'Pre-Raid', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Heroes'' Dreamwalker Raiments'), +(11, 10, 5, 0, 200, 40694, 'Pre-Raid', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Jorach''s Crocolisk Skin Belt'), +(11, 10, 6, 0, 200, 37644, 'Pre-Raid', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Gored Hide Legguards'), +(11, 10, 7, 0, 200, 44297, 'Pre-Raid', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Boots of the Neverending Path'), +(11, 10, 8, 0, 200, 44203, 'Pre-Raid', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Dragonfriend Bracers'), +(11, 10, 9, 0, 200, 37409, 'Pre-Raid', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Gilt-Edged Leather Gauntlets'), +(11, 10, 10, 0, 200, 40586, 'Pre-Raid', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Band of the Kirin Tor'), +(11, 10, 12, 0, 200, 42987, 'Pre-Raid', 'Druid', 'Feral Bear', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(11, 10, 14, 0, 200, 43406, 'Pre-Raid', 'Druid', 'Feral Bear', 'Back', 'Both', 'Cloak of the Gushing Wound'), +(11, 10, 17, 0, 200, 40713, 'Pre-Raid', 'Druid', 'Feral Bear', 'Ranged', 'Both', 'Idol of the Ravenous Beast'); + +-- ilvl 224 (Phase 1) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 224, 40329, 'Phase 1', 'Druid', 'Feral Bear', 'Head', 'Both', 'Hood of the Exodus'), +(11, 10, 2, 0, 224, 40494, 'Phase 1', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Valorous Dreamwalker Shoulderpads'), +(11, 10, 4, 0, 224, 40471, 'Phase 1', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Valorous Dreamwalker Raiments'), +(11, 10, 5, 0, 224, 43591, 'Phase 1', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Polar Cord'), +(11, 10, 6, 0, 224, 44011, 'Phase 1', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Leggings of the Honored'), +(11, 10, 7, 0, 224, 40243, 'Phase 1', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Footwraps of Vile Deceit'), +(11, 10, 8, 0, 224, 40186, 'Phase 1', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Thrusting Bands'), +(11, 10, 9, 0, 224, 40472, 'Phase 1', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Valorous Dreamwalker Handgrips'), +(11, 10, 10, 0, 224, 40370, 'Phase 1', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Gatekeeper'), +(11, 10, 12, 0, 224, 44253, 'Phase 1', 'Druid', 'Feral Bear', 'Trinket1', 'Both', 'Darkmoon Card: Greatness'), +(11, 10, 14, 0, 224, 40252, 'Phase 1', 'Druid', 'Feral Bear', 'Back', 'Both', 'Cloak of the Shadowed Sun'), +(11, 10, 17, 0, 224, 38365, 'Phase 1', 'Druid', 'Feral Bear', 'Ranged', 'Both', 'Idol of Perspicacious Attacks'); + +-- ilvl 245 (Phase 2) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 245, 41678, 'Phase 2', 'Druid', 'Feral Bear', 'Head', 'Both', 'Furious Gladiator''s Dragonhide Helm'), +(11, 10, 2, 0, 245, 45245, 'Phase 2', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Shoulderpads of the Intruder'), +(11, 10, 4, 0, 245, 45473, 'Phase 2', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Embrace of the Gladiator'), +(11, 10, 5, 0, 245, 46095, 'Phase 2', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Soul-Devouring Cinch'), +(11, 10, 6, 0, 245, 45536, 'Phase 2', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Legguards of Cunning Deception'), +(11, 10, 7, 0, 245, 45232, 'Phase 2', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Runed Ironhide Boots'), +(11, 10, 8, 0, 245, 45611, 'Phase 2', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Solar Bindings'), +(11, 10, 9, 0, 245, 46043, 'Phase 2', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Gloves of the Endless Dark'), +(11, 10, 10, 0, 245, 45471, 'Phase 2', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Fate''s Clutch'), +(11, 10, 12, 0, 245, 45158, 'Phase 2', 'Druid', 'Feral Bear', 'Trinket1', 'Both', 'Heart of Iron'), +(11, 10, 14, 0, 245, 45496, 'Phase 2', 'Druid', 'Feral Bear', 'Back', 'Both', 'Titanskin Cloak'), +(11, 10, 17, 0, 245, 45509, 'Phase 2', 'Druid', 'Feral Bear', 'Ranged', 'Both', 'Idol of the Corruptor'); + +-- ilvl 258 (Phase 3) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 258, 48201, 'Phase 3', 'Druid', 'Feral Bear', 'Head', 'Both', 'Headguard of Triumph'), +(11, 10, 2, 0, 258, 45245, 'Phase 3', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Shoulderpads of the Intruder'), +(11, 10, 4, 1, 258, 47004, 'Phase 3', 'Druid', 'Feral Bear', 'Chest', 'Alliance', 'Cuirass of Calamitous Fate'), +(11, 10, 4, 2, 258, 47431, 'Phase 3', 'Druid', 'Feral Bear', 'Chest', 'Horde', 'Vest of Calamitous Fate'), +(11, 10, 5, 1, 258, 47112, 'Phase 3', 'Druid', 'Feral Bear', 'Waist', 'Alliance', 'Belt of the Merciless Killer'), +(11, 10, 5, 2, 258, 47460, 'Phase 3', 'Druid', 'Feral Bear', 'Waist', 'Horde', 'Belt of the Pitiless Killer'), +(11, 10, 6, 1, 258, 46975, 'Phase 3', 'Druid', 'Feral Bear', 'Legs', 'Alliance', 'Leggings of the Broken Beast'), +(11, 10, 6, 2, 258, 47420, 'Phase 3', 'Druid', 'Feral Bear', 'Legs', 'Horde', 'Legwraps of the Broken Beast'), +(11, 10, 7, 1, 258, 47077, 'Phase 3', 'Druid', 'Feral Bear', 'Feet', 'Alliance', 'Treads of the Icewalker'), +(11, 10, 7, 2, 258, 47445, 'Phase 3', 'Druid', 'Feral Bear', 'Feet', 'Horde', 'Icewalker Treads'), +(11, 10, 8, 1, 258, 47155, 'Phase 3', 'Druid', 'Feral Bear', 'Wrists', 'Alliance', 'Bracers of Dark Determination'), +(11, 10, 8, 2, 258, 47474, 'Phase 3', 'Druid', 'Feral Bear', 'Wrists', 'Horde', 'Armbands of Dark Determination'), +(11, 10, 9, 0, 258, 48202, 'Phase 3', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Handgrips of Triumph'), +(11, 10, 10, 1, 258, 47955, 'Phase 3', 'Druid', 'Feral Bear', 'Finger1', 'Alliance', 'Loop of the Twin Val''kyr'), +(11, 10, 10, 2, 258, 48027, 'Phase 3', 'Druid', 'Feral Bear', 'Finger1', 'Horde', 'Band of the Twin Val''kyr'), +(11, 10, 12, 1, 258, 47088, 'Phase 3', 'Druid', 'Feral Bear', 'Trinket1', 'Alliance', 'Satrina''s Impeding Scarab'), +(11, 10, 12, 2, 258, 47451, 'Phase 3', 'Druid', 'Feral Bear', 'Trinket1', 'Horde', 'Juggernaut''s Vitality'), +(11, 10, 14, 1, 258, 47549, 'Phase 3', 'Druid', 'Feral Bear', 'Back', 'Alliance', 'Magni''s Resolution'), +(11, 10, 14, 2, 258, 47550, 'Phase 3', 'Druid', 'Feral Bear', 'Back', 'Horde', 'Cairne''s Endurance'), +(11, 10, 15, 0, 258, 48523, 'Phase 3', 'Druid', 'Feral Bear', 'MainHand', 'Both', 'Relentless Gladiator''s Greatstaff'), +(11, 10, 17, 0, 258, 45509, 'Phase 3', 'Druid', 'Feral Bear', 'Ranged', 'Both', 'Idol of the Corruptor'); + +-- ilvl 264 (Phase 4) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 264, 51296, 'Phase 4', 'Druid', 'Feral Bear', 'Head', 'Both', 'Sanctified Lasherweave Headguard'), +(11, 10, 2, 0, 264, 51299, 'Phase 4', 'Druid', 'Feral Bear', 'Shoulders', 'Both', 'Sanctified Lasherweave Shoulderpads'), +(11, 10, 4, 0, 264, 51298, 'Phase 4', 'Druid', 'Feral Bear', 'Chest', 'Both', 'Sanctified Lasherweave Raiment'), +(11, 10, 5, 0, 264, 50707, 'Phase 4', 'Druid', 'Feral Bear', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(11, 10, 6, 0, 264, 51297, 'Phase 4', 'Druid', 'Feral Bear', 'Legs', 'Both', 'Sanctified Lasherweave Legguards'), +(11, 10, 7, 0, 264, 50607, 'Phase 4', 'Druid', 'Feral Bear', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(11, 10, 8, 0, 264, 50670, 'Phase 4', 'Druid', 'Feral Bear', 'Wrists', 'Both', 'Toskk''s Maximized Wristguards'), +(11, 10, 9, 0, 264, 50675, 'Phase 4', 'Druid', 'Feral Bear', 'Hands', 'Both', 'Aldriana''s Gloves of Secrecy'), +(11, 10, 10, 0, 264, 50402, 'Phase 4', 'Druid', 'Feral Bear', 'Finger1', 'Both', 'Ashen Band of Endless Vengeance'), +(11, 10, 12, 1, 264, 47088, 'Phase 4', 'Druid', 'Feral Bear', 'Trinket1', 'Alliance', 'Satrina''s Impeding Scarab'), +(11, 10, 12, 2, 264, 47451, 'Phase 4', 'Druid', 'Feral Bear', 'Trinket1', 'Horde', 'Juggernaut''s Vitality'), +(11, 10, 14, 0, 264, 50466, 'Phase 4', 'Druid', 'Feral Bear', 'Back', 'Both', 'Sentinel''s Winter Cloak'), +(11, 10, 15, 0, 264, 50735, 'Phase 4', 'Druid', 'Feral Bear', 'MainHand', 'Both', 'Oathbinder, Charge of the Ranger-General'), +(11, 10, 17, 0, 264, 50456, 'Phase 4', 'Druid', 'Feral Bear', 'Ranged', 'Both', 'Idol of the Crying Moon'); + +-- ilvl 290 (Phase 5) +INSERT INTO `playerbots_bis_gear` VALUES +(11, 10, 0, 0, 290, 51296, 'Phase 5', 'Druid', 'FeralBear', 'Head', 'Both', 'Sanctified Lasherweave Headguard'), +(11, 10, 1, 0, 290, 50682, 'Phase 5', 'Druid', 'FeralBear', 'Neck', 'Both', 'Bile-Encrusted Medallion'), +(11, 10, 2, 0, 290, 51299, 'Phase 5', 'Druid', 'FeralBear', 'Shoulders', 'Both', 'Sanctified Lasherweave Shoulderpads'), +(11, 10, 4, 0, 290, 50656, 'Phase 5', 'Druid', 'FeralBear', 'Chest', 'Both', 'Ikfirus''s Sack of Wonder'), +(11, 10, 5, 0, 290, 50707, 'Phase 5', 'Druid', 'FeralBear', 'Waist', 'Both', 'Astrylian''s Sutured Cinch'), +(11, 10, 6, 0, 290, 51297, 'Phase 5', 'Druid', 'FeralBear', 'Legs', 'Both', 'Sanctified Lasherweave Legguards'), +(11, 10, 7, 0, 290, 50607, 'Phase 5', 'Druid', 'FeralBear', 'Feet', 'Both', 'Frostbitten Fur Boots'), +(11, 10, 8, 0, 290, 54580, 'Phase 5', 'Druid', 'FeralBear', 'Wrists', 'Both', 'Umbrage Armbands'), +(11, 10, 9, 0, 290, 51295, 'Phase 5', 'Druid', 'FeralBear', 'Hands', 'Both', 'Sanctified Lasherweave Handgrips'), +(11, 10, 10, 0, 290, 50622, 'Phase 5', 'Druid', 'FeralBear', 'Finger1', 'Both', 'Devium''s Eternally Cold Ring'), +(11, 10, 11, 0, 290, 50404, 'Phase 5', 'Druid', 'FeralBear', 'Finger2', 'Both', 'Ashen Band of Endless Courage'), +(11, 10, 12, 0, 290, 50364, 'Phase 5', 'Druid', 'FeralBear', 'Trinket1', 'Both', 'Sindragosa''s Flawless Fang'), +(11, 10, 13, 0, 290, 50356, 'Phase 5', 'Druid', 'FeralBear', 'Trinket2', 'Both', 'Corroded Skeleton Key'), +(11, 10, 14, 0, 290, 50466, 'Phase 5', 'Druid', 'FeralBear', 'Back', 'Both', 'Sentinel''s Winter Cloak'), +(11, 10, 15, 0, 290, 50735, 'Phase 5', 'Druid', 'FeralBear', 'MainHand', 'Both', 'Oathbinder, Charge of the Ranger-General'), +(11, 10, 17, 0, 290, 50456, 'Phase 5', 'Druid', 'FeralBear', 'Ranged', 'Both', 'Idol of the Crying Moon'); + diff --git a/data/sql/playerbots/updates/2026_04_28_01_ai_playerbot_bis_texts.sql b/data/sql/playerbots/updates/2026_04_28_01_ai_playerbot_bis_texts.sql new file mode 100644 index 00000000000..42fece3d68a --- /dev/null +++ b/data/sql/playerbots/updates/2026_04_28_01_ai_playerbot_bis_texts.sql @@ -0,0 +1,183 @@ +-- ######################################################### +-- Playerbots - Add /p autogear bis command texts +-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN, +-- zhTW, esES, esMX, ruRU) +-- ######################################################### + +DELETE FROM ai_playerbot_texts WHERE name IN ( + 'bis_autogear_unavailable_error', + 'bis_no_rows_fallback', + 'bis_command_unavailable_error', + 'bis_altbot_refused_error', + 'bis_quality_floor_error', + 'bis_pvp_refused_error', + 'bis_invalid_arg_error', + 'bis_arg_above_limit_error', + 'bis_no_rows_autogear_msg', + 'bis_closest_match_msg', + 'bis_applying_msg', + 'bis_applied_msg' +); +DELETE FROM ai_playerbot_texts_chance WHERE name IN ( + 'bis_autogear_unavailable_error', + 'bis_no_rows_fallback', + 'bis_command_unavailable_error', + 'bis_altbot_refused_error', + 'bis_quality_floor_error', + 'bis_pvp_refused_error', + 'bis_invalid_arg_error', + 'bis_arg_above_limit_error', + 'bis_no_rows_autogear_msg', + 'bis_closest_match_msg', + 'bis_applying_msg', + 'bis_applied_msg' +); + +INSERT INTO `ai_playerbot_texts` + (`id`, `name`, `text`, `say_type`, `reply_type`, + `text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`, + `text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`) +VALUES + (1767, 'bis_autogear_unavailable_error', + 'autogear command is not allowed, please check the configuration.', 0, 0, + '자동 장비 명령이 허용되지 않습니다. 설정을 확인하세요.', + 'La commande autogear n''est pas autorisée, veuillez vérifier la configuration.', + 'Der autogear-Befehl ist nicht erlaubt, bitte überprüfe die Konfiguration.', + '自动装备命令未启用,请检查配置。', + '自動裝備指令未啟用,請檢查設定。', + 'El comando autogear no está permitido, por favor revisa la configuración.', + 'El comando autogear no está permitido, por favor revisa la configuración.', + 'Команда autogear не разрешена, проверьте конфигурацию.'), + + (1768, 'bis_no_rows_fallback', + 'No BiS for your tier/spec/level, check cfg, running autogear instead', 0, 0, + '해당 등급/전문화/레벨에 BiS 목록이 없습니다. 설정을 확인하세요. 대신 자동 장비를 실행합니다.', + 'Pas de BiS pour votre tier/spé/niveau, vérifiez la config, exécution d''autogear à la place.', + 'Kein BiS für deinen Tier/Spec/Level, prüfe die Config, führe stattdessen autogear aus.', + '您的等级/天赋/级别没有BiS数据,请检查配置,改为运行autogear。', + '你的等級/天賦/級別沒有BiS資料,請檢查設定,改為執行autogear。', + 'No hay BiS para tu tier/spec/nivel, revisa la configuración, ejecutando autogear en su lugar.', + 'No hay BiS para tu tier/spec/nivel, revisa la configuración, ejecutando autogear en su lugar.', + 'Нет BiS для вашего тира/спека/уровня, проверьте конфиг, запускаю autogear.'), + + (1769, 'bis_command_unavailable_error', + 'bis command is not allowed, please check the configuration.', 0, 0, + 'bis 명령이 허용되지 않습니다. 설정을 확인하세요.', + 'La commande bis n''est pas autorisée, veuillez vérifier la configuration.', + 'Der bis-Befehl ist nicht erlaubt, bitte überprüfe die Konfiguration.', + 'bis命令未启用,请检查配置。', + 'bis指令未啟用,請檢查設定。', + 'El comando bis no está permitido, por favor revisa la configuración.', + 'El comando bis no está permitido, por favor revisa la configuración.', + 'Команда bis не разрешена, проверьте конфигурацию.'), + + (1770, 'bis_altbot_refused_error', + 'You cannot use bis on alt bots.', 0, 0, + '부캐 봇에는 bis를 사용할 수 없습니다.', + 'Vous ne pouvez pas utiliser bis sur des bots alternatifs.', + 'Du kannst bis nicht auf Zweitbots verwenden.', + '你不能在副号机器人上使用bis。', + '你不能在副號機器人上使用bis。', + 'No puedes usar bis en bots alternativos.', + 'No puedes usar bis en bots alternativos.', + 'Вы не можете использовать bis на дополнительных ботах.'), + + (1771, 'bis_quality_floor_error', + 'AutoGearQualityLimit must be 4 for BiS.', 0, 0, + 'BiS를 사용하려면 AutoGearQualityLimit이 4여야 합니다.', + 'AutoGearQualityLimit doit être à 4 pour utiliser BiS.', + 'AutoGearQualityLimit muss für BiS auf 4 stehen.', + 'BiS要求AutoGearQualityLimit设置为4。', + 'BiS要求AutoGearQualityLimit設定為4。', + 'AutoGearQualityLimit debe ser 4 para BiS.', + 'AutoGearQualityLimit debe ser 4 para BiS.', + 'AutoGearQualityLimit должен быть 4 для BiS.'), + + (1772, 'bis_pvp_refused_error', + 'bis is PvE only, bot is configured as PvP.', 0, 0, + 'bis는 PvE 전용이며, 이 봇은 PvP로 설정되어 있습니다.', + 'bis est uniquement pour le JcE, ce bot est configuré en JcJ.', + 'bis ist nur für PvE, dieser Bot ist als PvP konfiguriert.', + 'bis仅适用于PvE,此机器人配置为PvP。', + 'bis僅適用於PvE,此機器人設定為PvP。', + 'bis es solo para PvE, el bot está configurado como PvP.', + 'bis es solo para PvE, el bot está configurado como PvP.', + 'bis только для PvE, бот настроен на PvP.'), + + (1773, 'bis_invalid_arg_error', + 'Invalid BiS ilvl argument ''%param''. Use a positive integer.', 0, 0, + '잘못된 BiS 아이템 레벨 인수 ''%param''. 양의 정수를 사용하세요.', + 'Argument iLvl BiS invalide ''%param''. Utilisez un entier positif.', + 'Ungültiges BiS-iLvl-Argument ''%param''. Verwende eine positive ganze Zahl.', + '无效的BiS物品等级参数“%param”。请使用正整数。', + '無效的BiS物品等級參數「%param」。請使用正整數。', + 'Argumento iLvl BiS inválido ''%param''. Usa un entero positivo.', + 'Argumento iLvl BiS inválido ''%param''. Usa un entero positivo.', + 'Неверный аргумент iLvl BiS ''%param''. Используйте положительное целое число.'), + + (1774, 'bis_arg_above_limit_error', + 'BiS ilvl %requested exceeds AutoGearScoreLimit %limit, refusing', 0, 0, + 'BiS 아이템 레벨 %requested이(가) AutoGearScoreLimit %limit을(를) 초과합니다. 거부합니다.', + 'iLvl BiS %requested dépasse AutoGearScoreLimit %limit, refusé.', + 'BiS-iLvl %requested überschreitet AutoGearScoreLimit %limit, abgelehnt.', + 'BiS物品等级%requested超过AutoGearScoreLimit %limit,已拒绝。', + 'BiS物品等級%requested超過AutoGearScoreLimit %limit,已拒絕。', + 'iLvl BiS %requested supera AutoGearScoreLimit %limit, rechazado.', + 'iLvl BiS %requested supera AutoGearScoreLimit %limit, rechazado.', + 'BiS iLvl %requested превышает AutoGearScoreLimit %limit, отказано.'), + + (1775, 'bis_no_rows_autogear_msg', + 'No BiS at ilvl %ilvl, using Autogear %ilvl instead', 0, 0, + '아이템 레벨 %ilvl의 BiS가 없어 대신 Autogear %ilvl을(를) 사용합니다.', + 'Pas de BiS à l''iLvl %ilvl, utilisation d''Autogear %ilvl à la place.', + 'Kein BiS auf iLvl %ilvl, verwende stattdessen Autogear %ilvl.', + '物品等级%ilvl没有BiS,改用Autogear %ilvl。', + '物品等級%ilvl沒有BiS,改用Autogear %ilvl。', + 'No hay BiS en iLvl %ilvl, usando Autogear %ilvl en su lugar.', + 'No hay BiS en iLvl %ilvl, usando Autogear %ilvl en su lugar.', + 'Нет BiS на iLvl %ilvl, использую Autogear %ilvl.'), + + (1776, 'bis_closest_match_msg', + 'No BiS at ilvl %requested, using closest match at ilvl %resolved', 0, 0, + '아이템 레벨 %requested의 BiS가 없어 가장 가까운 아이템 레벨 %resolved을(를) 사용합니다.', + 'Pas de BiS à l''iLvl %requested, utilisation de la correspondance la plus proche à l''iLvl %resolved.', + 'Kein BiS auf iLvl %requested, verwende nächstliegende Übereinstimmung auf iLvl %resolved.', + '物品等级%requested没有BiS,使用最接近的物品等级%resolved。', + '物品等級%requested沒有BiS,使用最接近的物品等級%resolved。', + 'No hay BiS en iLvl %requested, usando coincidencia más cercana en iLvl %resolved.', + 'No hay BiS en iLvl %requested, usando coincidencia más cercana en iLvl %resolved.', + 'Нет BiS на iLvl %requested, использую ближайшее совпадение на iLvl %resolved.'), + + (1777, 'bis_applying_msg', 'Applying BiS gear', 0, 0, + 'BiS 장비를 적용합니다.', + 'Application de l''équipement BiS.', + 'Wende BiS-Ausrüstung an.', + '正在装备BiS装备。', + '正在裝備BiS裝備。', + 'Aplicando equipo BiS.', + 'Aplicando equipo BiS.', + 'Применяю BiS-снаряжение.'), + + (1778, 'bis_applied_msg', 'BiS applied', 0, 0, + 'BiS 장비가 적용되었습니다.', + 'Équipement BiS appliqué.', + 'BiS-Ausrüstung angewendet.', + 'BiS装备已应用。', + 'BiS裝備已套用。', + 'Equipo BiS aplicado.', + 'Equipo BiS aplicado.', + 'BiS-снаряжение применено.'); + +INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES + ('bis_autogear_unavailable_error', 100), + ('bis_no_rows_fallback', 100), + ('bis_command_unavailable_error', 100), + ('bis_altbot_refused_error', 100), + ('bis_quality_floor_error', 100), + ('bis_pvp_refused_error', 100), + ('bis_invalid_arg_error', 100), + ('bis_arg_above_limit_error', 100), + ('bis_no_rows_autogear_msg', 100), + ('bis_closest_match_msg', 100), + ('bis_applying_msg', 100), + ('bis_applied_msg', 100); diff --git a/src/Ai/Base/Actions/TrainerAction.cpp b/src/Ai/Base/Actions/TrainerAction.cpp index 4f46bba186a..b0530cc578a 100644 --- a/src/Ai/Base/Actions/TrainerAction.cpp +++ b/src/Ai/Base/Actions/TrainerAction.cpp @@ -5,10 +5,14 @@ #include "TrainerAction.h" +#include "AiFactory.h" +#include "BisListMgr.h" #include "BudgetValues.h" #include "Event.h" #include "PlayerbotFactory.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" +#include "ReputationMgr.h" #include "Trainer.h" bool TrainerAction::Execute(Event event) @@ -269,6 +273,282 @@ bool MaintenanceAction::Execute(Event /*event*/) return true; } +bool BisGearAction::RunAutogearFallback(uint16 effectiveIlvl) +{ + if (!sPlayerbotAIConfig.autoGearCommand) + { + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_autogear_unavailable_error", + "autogear command is not allowed, please check the configuration.", {})); + return false; + } + + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_no_rows_fallback", + "No BiS for your tier/spec/level, check cfg, running autogear instead", {})); + + // Wipe all equipped slots so autogear gears from scratch at the requested ilvl + // (avoids old high-tier items surviving the incremental 1.2x threshold). + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) + continue; + if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); + } + + uint32 gs = effectiveIlvl == 0 + ? 0 + : PlayerbotFactory::CalcMixedGearScore(effectiveIlvl, sPlayerbotAIConfig.autoGearQualityLimit); + PlayerbotFactory factory(bot, bot->GetLevel(), sPlayerbotAIConfig.autoGearQualityLimit, gs); + factory.InitEquipment(false, sPlayerbotAIConfig.twoRoundsGearInit); + factory.InitAmmo(); + if (bot->GetLevel() >= sPlayerbotAIConfig.minEnchantingBotLevel) + factory.ApplyEnchantAndGemsNew(); + bot->DurabilityRepairAll(false, 1.0f, false); + return true; +} + +bool BisGearAction::Execute(Event event) +{ + if (!sPlayerbotAIConfig.autoGearBisCommand) + { + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_command_unavailable_error", + "bis command is not allowed, please check the configuration.", {})); + return false; + } + + if (!sPlayerbotAIConfig.autoGearCommandAltBots && + !sPlayerbotAIConfig.IsInRandomAccountList(bot->GetSession()->GetAccountId())) + { + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_altbot_refused_error", "You cannot use bis on alt bots.", {})); + return false; + } + + if (sPlayerbotAIConfig.autoGearQualityLimit < 4) + { + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_quality_floor_error", "AutoGearQualityLimit must be 4 for BiS.", {})); + return false; + } + + if (sRandomPlayerbotMgr.IsSpecPvp(bot->GetGUID().GetCounter(), bot->getClass())) + { + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_pvp_refused_error", "bis is PvE only, bot is configured as PvP.", {})); + return false; + } + + uint16 ilvl = static_cast(sPlayerbotAIConfig.autoGearScoreLimit); + + // Optional explicit ilvl override: `/p autogear bis 55`. + // Garbage or out-of-range args are hard-rejected: no autogear fallback, no gear change. + std::string const param = event.getParam(); + if (!param.empty()) + { + unsigned long parsed = 0; + size_t pos = 0; + bool valid = false; + try + { + parsed = std::stoul(param, &pos); + valid = (parsed > 0 && pos == param.size() && parsed <= 0xFFFFu); + } + catch (...) + { + valid = false; + } + + if (!valid) + { + std::map phs; + phs["%param"] = param; + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_invalid_arg_error", + "Invalid BiS ilvl argument '%param'. Use a positive integer.", phs)); + return false; + } + if (parsed > static_cast(sPlayerbotAIConfig.autoGearScoreLimit)) + { + std::map phs; + phs["%requested"] = std::to_string(parsed); + phs["%limit"] = std::to_string(sPlayerbotAIConfig.autoGearScoreLimit); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_arg_above_limit_error", + "BiS ilvl %requested exceeds AutoGearScoreLimit %limit, refusing", phs)); + return false; + } + ilvl = static_cast(parsed); + } + uint8 cls = bot->getClass(); + uint8 tab = AiFactory::GetPlayerSpecTab(bot); + uint8 faction = bot->GetTeamId() == TEAM_ALLIANCE ? 1 : 2; + + // Druid Bear (Feral Tank) shares tab 1 with Cat. Use sentinel tab 10 when tank strategy active. + constexpr uint8 BIS_TAB_DRUID_BEAR = 10; + constexpr uint16 BIS_ILVL_FALLBACK_WINDOW = 20; + uint16 resolvedIlvl = 0; + std::map bisMap; + if (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && PlayerbotAI::IsTank(bot)) + bisMap = sBisListMgr->GetBisForNearest(ilvl, BIS_ILVL_FALLBACK_WINDOW, cls, BIS_TAB_DRUID_BEAR, faction, + &resolvedIlvl); + if (bisMap.empty()) + bisMap = sBisListMgr->GetBisForNearest(ilvl, BIS_ILVL_FALLBACK_WINDOW, cls, tab, faction, &resolvedIlvl); + + // No rows within fallback window -> full autogear fallback at the effective ilvl. + if (bisMap.empty()) + { + std::map phs; + phs["%ilvl"] = std::to_string(ilvl); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_no_rows_autogear_msg", + "No BiS at ilvl %ilvl, using Autogear %ilvl instead", phs)); + return RunAutogearFallback(ilvl); + } + + if (resolvedIlvl != ilvl) + { + std::map phs; + phs["%requested"] = std::to_string(ilvl); + phs["%resolved"] = std::to_string(resolvedIlvl); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_closest_match_msg", + "No BiS at ilvl %requested, using closest match at ilvl %resolved", phs)); + } + + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_applying_msg", "Applying BiS gear", {})); + + // 1. Wipe everything currently equipped so autogear starts from a clean slate. + // Old items linger in inventory otherwise and autogear leaves slots empty on bag conflicts. + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) + continue; + if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); + } + + // Wipe equippable items from bags too. Autogear can shove old equipped items into bags + // (HandleAutoStoreBagItemOpcode), and a unique-equipped duplicate stuck in a bag blocks + // CanEquipNewItem on subsequent BiS runs. Spare consumables/reagents. + auto destroyIfEquippable = [&](uint8 bag, uint8 slot) + { + Item* item = bot->GetItemByPos(bag, slot); + if (!item) + return; + ItemTemplate const* tmpl = item->GetTemplate(); + if (!tmpl) + return; + if (tmpl->Class == ITEM_CLASS_WEAPON || tmpl->Class == ITEM_CLASS_ARMOR) + bot->DestroyItem(bag, slot, true); + }; + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot) + destroyIfEquippable(INVENTORY_SLOT_BAG_0, slot); + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + if (Bag* container = bot->GetBagByPos(bag)) + for (uint32 slot = 0; slot < container->GetBagSize(); ++slot) + destroyIfEquippable(bag, slot); + } + + // 2. Run full autogear on the empty bot so every slot gets a best-available pick. + // Uncovered slots will keep the autogear pick; BiS overwrites the rest below. + if (sPlayerbotAIConfig.autoGearCommand) + { + uint32 fillGs = ilvl == 0 + ? 0 + : PlayerbotFactory::CalcMixedGearScore(ilvl, sPlayerbotAIConfig.autoGearQualityLimit); + PlayerbotFactory fillFactory(bot, bot->GetLevel(), sPlayerbotAIConfig.autoGearQualityLimit, fillGs); + fillFactory.InitEquipment(false, sPlayerbotAIConfig.twoRoundsGearInit); + } + + // 2b. Pre-destroy autogear picks that would conflict with any BiS item by entry. + // Autogear may have placed the exact item BiS wants into trinket2/finger2 (or vice versa); + // unique-equipped enforcement would then make BiS's equip silently drop one copy. + std::set bisEntries; + for (auto const& kv : bisMap) + bisEntries.insert(kv.second); + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + if (bisEntries.count(item->GetEntry())) + bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); + } + + // 3. Apply BiS: only touch slots where the bot can actually equip the BiS item. + // If item requires reputation, grant the required rank first. If CanUseItem still + // fails (class/race/skill/level), keep autogear's pick for that slot. + for (auto const& kv : bisMap) + { + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(kv.second); + if (!proto) + continue; + + // Grant required reputation rank if the item gates on it. + if (proto->RequiredReputationFaction && proto->RequiredReputationRank > 0) + { + if (FactionEntry const* fac = sFactionStore.LookupEntry(proto->RequiredReputationFaction)) + { + ReputationRank requiredRank = static_cast(proto->RequiredReputationRank); + if (bot->GetReputationRank(proto->RequiredReputationFaction) < requiredRank) + { + int32 standing = ReputationMgr::ReputationRankToStanding( + static_cast(requiredRank - 1)) + 1; + bot->GetReputationMgr().SetReputation(fac, standing); + } + } + } + + if (bot->CanUseItem(proto) != EQUIP_ERR_OK) + continue; + + uint8 slot = kv.first; + if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); + + uint16 dest = 0; + InventoryResult eqResult = bot->CanEquipNewItem(slot, dest, kv.second, false); + + // Paired slots (finger 10<->11, trinket 12<->13): destroy paired slot and retry once + // when unique-equipped or autogear residue blocks the first attempt. + if (eqResult != EQUIP_ERR_OK) + { + uint8 pairedSlot = 0xFF; + if (slot == EQUIPMENT_SLOT_FINGER1) pairedSlot = EQUIPMENT_SLOT_FINGER2; + else if (slot == EQUIPMENT_SLOT_FINGER2) pairedSlot = EQUIPMENT_SLOT_FINGER1; + else if (slot == EQUIPMENT_SLOT_TRINKET1) pairedSlot = EQUIPMENT_SLOT_TRINKET2; + else if (slot == EQUIPMENT_SLOT_TRINKET2) pairedSlot = EQUIPMENT_SLOT_TRINKET1; + + if (pairedSlot != 0xFF) + { + if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, pairedSlot)) + bot->DestroyItem(INVENTORY_SLOT_BAG_0, pairedSlot, true); + eqResult = bot->CanEquipNewItem(slot, dest, kv.second, false); + } + } + + if (eqResult == EQUIP_ERR_OK) + { + bot->EquipNewItem(dest, kv.second, true); + bot->AutoUnequipOffhandIfNeed(); + } + } + + PlayerbotFactory factory(bot, bot->GetLevel(), ITEM_QUALITY_EPIC, 0); + factory.InitAmmo(); + if (bot->GetLevel() >= sPlayerbotAIConfig.minEnchantingBotLevel) + factory.ApplyEnchantAndGemsNew(); + + bot->DurabilityRepairAll(false, 1.0f, false); + + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bis_applied_msg", "BiS applied", {})); + return true; +} + bool RemoveGlyphAction::Execute(Event /*event*/) { for (uint32 slotIndex = 0; slotIndex < MAX_GLYPH_SLOT_INDEX; ++slotIndex) diff --git a/src/Ai/Base/Actions/TrainerAction.h b/src/Ai/Base/Actions/TrainerAction.h index bfb6538de1b..733bed529e0 100644 --- a/src/Ai/Base/Actions/TrainerAction.h +++ b/src/Ai/Base/Actions/TrainerAction.h @@ -53,4 +53,14 @@ class AutoGearAction : public Action bool Execute(Event event) override; }; +class BisGearAction : public Action +{ +public: + BisGearAction(PlayerbotAI* botAI) : Action(botAI, "autogear bis") {} + bool Execute(Event event) override; + +private: + bool RunAutogearFallback(uint16 effectiveIlvl); +}; + #endif diff --git a/src/Ai/Base/ChatActionContext.h b/src/Ai/Base/ChatActionContext.h index 497ae2e9c13..9bcfda61750 100644 --- a/src/Ai/Base/ChatActionContext.h +++ b/src/Ai/Base/ChatActionContext.h @@ -139,6 +139,7 @@ class ChatActionContext : public NamedObjectContext creators["maintenance"] = &ChatActionContext::maintenance; creators["remove glyph"] = &ChatActionContext::remove_glyph; creators["autogear"] = &ChatActionContext::autogear; + creators["autogear bis"] = &ChatActionContext::autogear_bis; creators["equip upgrade"] = &ChatActionContext::equip_upgrade; creators["attack my target"] = &ChatActionContext::attack_my_target; creators["pull my target"] = &ChatActionContext::pull_my_target; @@ -261,6 +262,7 @@ class ChatActionContext : public NamedObjectContext static Action* maintenance(PlayerbotAI* botAI) { return new MaintenanceAction(botAI); } static Action* remove_glyph(PlayerbotAI* botAI) { return new RemoveGlyphAction(botAI); } static Action* autogear(PlayerbotAI* botAI) { return new AutoGearAction(botAI); } + static Action* autogear_bis(PlayerbotAI* botAI) { return new BisGearAction(botAI); } static Action* equip_upgrade(PlayerbotAI* botAI) { return new EquipUpgradeAction(botAI); } static Action* co(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI); } static Action* nc(PlayerbotAI* botAI) { return new ChangeNonCombatStrategyAction(botAI); } diff --git a/src/Ai/Base/ChatTriggerContext.h b/src/Ai/Base/ChatTriggerContext.h index ef8827c2927..700fa95b460 100644 --- a/src/Ai/Base/ChatTriggerContext.h +++ b/src/Ai/Base/ChatTriggerContext.h @@ -65,6 +65,7 @@ class ChatTriggerContext : public NamedObjectContext creators["maintenance"] = &ChatTriggerContext::maintenance; creators["remove glyph"] = &ChatTriggerContext::remove_glyph; creators["autogear"] = &ChatTriggerContext::autogear; + creators["autogear bis"] = &ChatTriggerContext::autogear_bis; creators["equip upgrade"] = &ChatTriggerContext::equip_upgrade; creators["attack"] = &ChatTriggerContext::attack; creators["pull"] = &ChatTriggerContext::pull; @@ -220,6 +221,7 @@ class ChatTriggerContext : public NamedObjectContext static Trigger* maintenance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "maintenance"); } static Trigger* remove_glyph(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "remove glyph"); } static Trigger* autogear(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "autogear"); } + static Trigger* autogear_bis(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "autogear bis"); } static Trigger* equip_upgrade(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "equip upgrade"); } static Trigger* co(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "co"); } static Trigger* nc(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "nc"); } diff --git a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp index 9bb30a1ca7f..4afc86dcb3d 100644 --- a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp +++ b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp @@ -111,6 +111,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("maintenance"); supported.push_back("remove glyph"); supported.push_back("autogear"); + supported.push_back("autogear bis"); supported.push_back("equip upgrade"); supported.push_back("chat"); supported.push_back("home"); diff --git a/src/Ai/Raid/ICC/Action/ICCActions.h b/src/Ai/Raid/ICC/Action/ICCActions.h new file mode 100644 index 00000000000..23a8d2c6933 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions.h @@ -0,0 +1,878 @@ +#ifndef _PLAYERBOT_ICCA_H +#define _PLAYERBOT_ICCA_H + +#include + +#include "Action.h" +#include "MovementActions.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "AttackAction.h" +#include "LastMovementValue.h" +#include "ObjectGuid.h" +#include "PlayerbotAIConfig.h" +#include "ICCStrategy.h" +#include "ScriptedCreature.h" +#include "SharedDefines.h" +#include "Trigger.h" +#include "CellImpl.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "Vehicle.h" +#include "ICCTriggers.h" + +inline const Position ICC_LM_TANK_POSITION = Position(-391.0f, 2259.0f, 42.0f); +inline const Position ICC_LM_BONE_STORM_AT_POSITION = Position(-390.02332f, 2179.3481f, 41.96729f); +inline const Position ICC_LM_MID_POSITION = Position(-393.61722f, 2216.335f, 41.99396f); +inline const Position ICC_DARK_RECKONING_SAFE_POSITION = Position(-523.33386f, 2211.2031f, 62.823116f); +inline const Position ICC_LDW_TANK_POSTION = Position(-593.7436f, 2211.298f, 49.476673f); //old closer to stairs -570.1f, 2211.2456f, 49.476616f +inline const Position ICC_LDW_RANGED_POSITION = Position(-653.11096f, 2211.9568f,51.551437f); //old in frint of the boss (-607.0631f, 2212.2488f, 49.47009f) +inline const Position ICC_ROTTING_FROST_GIANT_TANK_POSITION = Position(-328.5085f, 2225.5142f, 199.97298f); +inline const Position ICC_GUNSHIP_ROCKET_JUMP_ALLY = Position (-370.04645f, 1993.3536f, 466.65656f); +inline const Position ICC_GUNSHIP_ROCKET_JUMP_ALLY2 = Position (-392.66208f, 2064.893f, 466.5672f); +inline const Position ICC_GUNSHIP_ROCKET_JUMP_ALLY_MIDDLE_POINT = Position(-383.01166f, 2022.8046f, 467.38193f); +inline const Position ICC_GUNSHIP_ROCKET_JUMP_ALLY_FRIENDLY_POINT = Position(-389.26788f, 2045.9136f, 467.56168f); +inline const Position ICC_GUNSHIP_ROCKET_JUMP_HORDE = Position (-449.5343f, 2477.2024f, 471.31906f); +inline const Position ICC_GUNSHIP_ROCKET_JUMP_HORDE2 = Position (-429.81586f, 2400.6804f, 471.56537f); +inline const Position ICC_GUNSHIP_ROCKET_JUMP_HORDE_MIDDLE_POINT = Position(-449.17645f, 2449.2705f, 470.9257f); +inline const Position ICC_GUNSHIP_ROCKET_JUMP_HORDE_FRIENDLY_POINT = Position(-444.74533f, 2420.1208f, 470.78748f); +inline const Position ICC_DBS_TANK_POSITION = Position(-494.26517f, 2211.549f, 541.11414f); +inline const Position ICC_FESTERGUT_TANK_POSITION = Position(4269.1772f, 3144.7673f, 360.38577f); +inline const Position ICC_FESTERGUT_RANGED_SPORE = Position(4264.3623f, 3120.889f, 360.38565f); //old closer to gates 4261.143f, 3109.4146f, 360.38605f +inline const Position ICC_FESTERGUT_RANGED_SPORE_2 = Position(4288.7974f, 3115.6274f, 360.38577f); //new position +inline const Position ICC_FESTERGUT_MELEE_SPORE = Position(4269.1772f, 3144.7673f, 360.38577f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_1 = Position(4453.2085f, 3108.7488f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_2 = Position(4459.4390f, 3118.0210f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_3 = Position(4473.4062f, 3126.2861f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_4 = Position(4469.9370f, 3137.0173f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_5 = Position(4476.7583f, 3145.7160f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_6 = Position(4466.2100f, 3150.0630f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_7 = Position(4464.4297f, 3161.2317f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_8 = Position(4447.2900f, 3163.0322f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_9 = Position(4437.1250f, 3168.4062f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_10 = Position(4417.2217f, 3148.8909f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_11 = Position(4420.9727f, 3137.7700f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_12 = Position(4414.8716f, 3127.9534f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_13 = Position(4425.1553f, 3123.4717f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_14 = Position(4426.3760f, 3113.4153f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_15 = Position(4443.8027f, 3113.9207f, 360.38626f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_16 = Position(4432.477f, 3156.7651f, 360.38568f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_17 = Position(4458.083f, 3132.5842f, 360.38565f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_18 = Position(4457.4565f, 3144.8442f, 360.38565f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_19 = Position(4422.5460f, 3158.0435f, 360.38565f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_20 = Position(4432.1646f, 3142.3418f, 360.38565f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_21 = Position(4436.649f, 3126.1245f, 360.38565f); +inline const Position ICC_ROTFACE_RANGED_POSITION_HC_22 = Position(4450.2363f, 3122.4033f, 360.38565f); +inline const Position ICC_ROTFACE_EXPLOSION_POSITION_1 = Position(4481.433f, 3137.0117f, 360.38522f); +inline const Position ICC_ROTFACE_EXPLOSION_POSITION_2 = Position(4474.4565f, 3156.9617f, 360.38522f); +inline const Position ICC_ROTFACE_EXPLOSION_POSITION_3 = Position(4452.0586f, 3170.2231f, 360.38522f); +inline const Position ICC_ROTFACE_EXPLOSION_POSITION_4 = Position(4421.2437f, 3161.2275f, 360.38522f); +inline const Position ICC_ROTFACE_EXPLOSION_POSITION_5 = Position(4410.904f, 3136.9976f, 360.38522f); +inline const Position ICC_ROTFACE_EXPLOSION_POSITION_6 = Position(4417.603f, 3119.4712f, 360.38522f); +inline const Position ICC_ROTFACE_EXPLOSION_POSITION_7 = Position(4442.514f, 3105.2234f, 360.38522f); +inline const Position ICC_ROTFACE_EXPLOSION_POSITION_8 = Position(4465.7173f, 3113.6284f, 360.38522f); +inline const Position ICC_ROTFACE_TANK_POSITION = Position(4447.061f, 3150.9758f, 360.38568f); +inline const Position ICC_ROTFACE_BIG_OOZE_POSITION = Position(4432.687f, 3142.3035f, 360.38568f); +inline const Position ICC_ROTFACE_SAFE_POSITION = Position(4446.557f, 3065.6594f, 360.38568f); +inline const Position ICC_ROTFACE_CENTER_POSITION = Position(4446.0547f, 3144.8677f, 360.38568f); //actual center 4.74089 4445.6616f, 3137.1526f, 360.38608 +inline const Position ICC_ROTFACE_CENTER_POSITION_BOSS = Position(4445.656f, 3137.1663f, 360.38565f); +inline const Position ICC_PUTRICIDE_TANK_POSITION = Position(4373.227f, 3222.058f, 389.4029f); +inline const Position ICC_PUTRICIDE_GREEN_POSITION = Position(4423.4126f, 3194.2715f, 389.37683f); +inline const Position ICC_PUTRICIDE_GATE_POSITION = Position(4356.3345f, 3167.9407f, 389.39825f); +inline const Position ICC_BPC_OT_POSITION = Position(4649.2236f, 2796.0972f, 361.1815f); +inline const Position ICC_BPC_MT_POSITION = Position(4648.5674f, 2744.847f, 361.18222f); +inline const Position ICC_BPC_CENTER_POSITION = Position(4638.7056f, 2769.3713f, 361.17108f); +inline const Position ICC_BQL_CENTER_POSITION = Position(4595.0f, 2769.0f, 400.0f); +inline const Position ICC_BQL_LWALL1_POSITION = Position(4624.685f, 2789.4895f, 400.13834f); +inline const Position ICC_BQL_LWALL2_POSITION = Position(4600.749f, 2805.7568f, 400.1374f); +inline const Position ICC_BQL_LWALL3_POSITION = Position(4572.857f, 2797.3872f, 400.1374f); +inline const Position ICC_BQL_RWALL1_POSITION = Position(4625.724f, 2748.9917f, 400.13693f); +inline const Position ICC_BQL_RWALL2_POSITION = Position(4608.3774f, 2735.7466f, 400.13693f); +inline const Position ICC_BQL_RWALL3_POSITION = Position(4576.813f, 2739.6067f, 400.13693f); +inline const Position ICC_BQL_LRWALL4_POSITION = Position(4539.345f, 2769.3853f, 403.7267f); +inline const Position ICC_BQL_TANK_POSITION = Position(4633.7964f, 2769.2515f, 401.74777f); //old 4629.746f, 2769.6396f, 401.7479f +inline const Position ICC_VDW_HEAL_POSITION = Position(4203.752f, 2483.4343f, 364.87274f); +inline const Position ICC_VDW_GROUP1_POSITION = Position(4203.585f, 2464.422f, 364.87323f); +inline const Position ICC_VDW_GROUP2_POSITION = Position(4203.5806f, 2505.2383f, 364.87323f); +inline const Position ICC_VDW_PORTALSTART_POSITION = Position(4202.637f, 2488.171f, 375.00256f); +inline const Position ICC_SINDRAGOSA_TANK_POSITION = Position(4408.016f, 2508.0647f, 203.37955f); //X: 4183.136 Y: 2492.9358 Z: 364.87595 Orientation: 5.3975997 +inline const Position ICC_SINDRAGOSA_FLYING_POSITION = Position(4525.6f, 2485.15f, 245.082f); +inline const Position ICC_SINDRAGOSA_RANGED_POSITION = Position(4441.572f, 2484.482f, 203.37836f); +inline const Position ICC_SINDRAGOSA_MELEE_POSITION = Position(4423.1646f, 2486.6792f, 203.3749f); //old 4423.4546f, 2491.7175f, 203.37686f +inline const Position ICC_SINDRAGOSA_BLISTERING_COLD_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f); +inline const Position ICC_SINDRAGOSA_THOMB1_POSITION = Position(4433.6484f, 2469.4133f, 203.3806f); +inline const Position ICC_SINDRAGOSA_THOMB2_POSITION = Position(4434.143f, 2486.201f, 203.37473f); +inline const Position ICC_SINDRAGOSA_THOMB3_POSITION = Position(4436.1147f, 2501.464f, 203.38266f); +inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC1_POSITION = Position(4444.9707f, 2455.7322f, 203.38701f); +inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC2_POSITION = Position(4461.3945f, 2463.5513f, 203.38727f); +inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC3_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f); +inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC4_POSITION = Position(4459.9336f, 2507.409f, 203.38606f); +inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC5_POSITION = Position(4442.3096f, 2512.4688f, 203.38647f); +inline const Position ICC_SINDRAGOSA_CENTER_POSITION = Position(4408.0464f, 2484.478f, 203.37529f); +inline const Position ICC_SINDRAGOSA_THOMBMB2_POSITION = Position(4436.895f, 2498.1401f, 203.38133f); +inline const Position ICC_SINDRAGOSA_FBOMB_POSITION = Position(4449.3647f, 2486.4524f, 203.379f); +inline const Position ICC_SINDRAGOSA_FBOMB10_POSITION = Position(4449.3647f, 2486.4524f, 203.379f); +inline const Position ICC_SINDRAGOSA_LOS2_POSITION = Position(4441.8286f, 2501.946f, 203.38435f); +inline const Position ICC_LICH_KING_ADDS_POSITION = Position(476.7332f, -2095.3894f, 840.857f); // old 486.63647f, -2095.7915f, 840.857f +inline const Position ICC_LICH_KING_MELEE_POSITION = Position(503.5546f, -2106.8213f, 840.857f); +inline const Position ICC_LICH_KING_RANGED_POSITION = Position(501.3563f, -2085.1816f, 840.857f); +inline const Position ICC_LICH_KING_ASSISTHC_POSITION = Position(517.2145f, -2125.0674f, 840.857f); +inline const Position ICC_LICH_KING_CENTER_POSITION = Position(503.62036f, -2124.7336f, 840.857f); +inline const Position ICC_LK_FROST1_POSITION = Position(503.96548f, -2183.216f, 840.857f); +inline const Position ICC_LK_FROST2_POSITION = Position(563.07166f, -2125.7578f, 840.857f); +inline const Position ICC_LK_FROST3_POSITION = Position(503.40182f, -2067.3435f, 840.857f); +inline const Position ICC_LK_FROSTR1_POSITION = Position(481.168f, -2177.8723f, 840.857f); +inline const Position ICC_LK_FROSTR2_POSITION = Position(562.20807f, -2100.2393f, 840.857f); +inline const Position ICC_LK_FROSTR3_POSITION = Position(526.35297f, -2071.0317f, 840.857f); +inline const Position ICC_LK_VILE_SPIRIT1_POSITION = Position(505.24002f, -2086.7778f, 840.857f); +inline const Position ICC_LK_VILE_SPIRIT2_POSITION = Position(532.668f, -2122.603f, 840.857f); +inline const Position ICC_LK_VILE_SPIRIT3_POSITION = Position(502.8796f, -2159.7466f, 840.857f); + +//Lord Marrogwar +class IccLmTankPositionAction : public AttackAction +{ +public: + IccLmTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc lm tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + bool MoveTowardPosition(Position const& position, float incrementSize); +}; + +class IccSpikeAction : public AttackAction +{ +public: + IccSpikeAction(PlayerbotAI* botAI) : AttackAction(botAI, "icc spike") {} + bool Execute(Event event) override; + + std::vector FindAliveSpikes(); + bool HandleSpikeMarking(std::vector const& spikes, Unit* boss); + bool HandleNoSpikesMarking(Unit* boss); + bool HandleSpikeAssignment(std::vector const& spikes, Unit* boss); + bool MoveTowardPosition(Position const& position, float incrementSize); + static std::vector CalculateBalancedGroupSizes(size_t totalMembers, size_t numSpikes); + static size_t GetAssignedSpikeIndex(size_t memberIndex, std::vector const& groupSizes); + static std::string GetRTIValueForSpike(size_t spikeIndex); + bool IsSpikeInColdFlame(Unit* spike); + static Player* GetSpikeVictim(Unit* spike); +}; + +//Lady Deathwhisper +class IccDarkReckoningAction : public MovementAction +{ +public: + IccDarkReckoningAction(PlayerbotAI* botAI, std::string const name = "icc dark reckoning") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccRangedPositionLadyDeathwhisperAction : public AttackAction +{ +public: + IccRangedPositionLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc ranged position lady deathwhisper") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + bool MaintainRangedSpacing(); +}; + +class IccAddsLadyDeathwhisperAction : public AttackAction +{ +public: + IccAddsLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc adds lady deathwhisper") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + bool IsTargetedByShade(uint32 shadeEntry); + bool MoveTowardPosition(Position const& position, float incrementSize); + bool HandleAddTargeting(Unit* boss); + bool UpdateRaidTargetIcon(Unit* target); + bool HandleNonTankAddEvasion(); + bool IsAdd(Unit* unit); + bool IsAssistTankAlive(); + bool ApplyNearbyAddCC(); + bool ApplyCCToAdd(Unit* add); + bool IsAddsAlive(); + bool EngageBoss(); + Unit* FindAndCollectAdd(Unit* boss); + Unit* FindAddNearBoss(Unit* boss, float maxDist); + +}; + +class IccShadeLadyDeathwhisperAction : public MovementAction +{ +public: + IccShadeLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc shade lady deathwhisper") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +//Gunship Battle +class IccRottingFrostGiantTankPositionAction : public AttackAction +{ +public: + IccRottingFrostGiantTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotting frost giant tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +// Gunship Battle +class IccCannonFireAction : public Action +{ +public: + IccCannonFireAction(PlayerbotAI* botAI, std::string const name = "icc cannon fire") : Action(botAI, name) {} + bool Execute(Event event) override; + +private: + Unit* FindValidCannonTarget(); + bool TryCastCannonSpell(uint32 spellId, Unit* target, Unit* vehicleBase); +}; + +class IccGunshipEnterCannonAction : public MovementAction +{ +public: + IccGunshipEnterCannonAction(PlayerbotAI* botAI, std::string const name = "icc gunship enter cannon") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; + +private: + Unit* FindBestAvailableCannon(); + bool IsValidCannon(Unit* vehicle); + bool EnterVehicle(Unit* vehicleBase, bool moveIfFar); +}; + +class IccGunshipRocketJumpAction : public AttackAction +{ +public: + IccGunshipRocketJumpAction(PlayerbotAI* botAI, std::string const name = "icc gunship rocket jump") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + +private: + enum class GunshipSide + { + NONE, + ALLY, + HORDE + }; + + GunshipSide DetectShip() const; + Item* FindRocketPack() const; + bool UseRocketPack(Position const& destination, bool walkIfOutOfRange); + bool RocketPackJumpToward(Position const& target); + bool ExitCannonIfSeated(); + bool CleanupSkullIcon(uint8 skullIconIndex); + bool UpdateBossSkullIcon(Unit* boss, uint8 skullIconIndex); + bool IsMainTankOnEnemyShip(GunshipSide side) const; + bool AnyNonTankAwayFromFriendly(GunshipSide side) const; + Unit* FindNearestFriendlyCannon(GunshipSide side) const; +}; + +class IccGunshipRocketPackSetupAction : public MovementAction +{ +public: + IccGunshipRocketPackSetupAction(PlayerbotAI* botAI, std::string const name = "icc gunship rocket pack setup") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; + +private: + Item* FindRocketPack() const; + bool AcquireRocketPack(); + bool EquipRocketPack(); +}; + +//DBS +class IccDbsTankPositionAction : public AttackAction +{ +public: + IccDbsTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc dbs tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + bool CrowdControlBloodBeasts(); + bool EvadeBloodBeasts(); + bool PositionInRangedFormation(); +}; + +class IccAddsDbsAction : public AttackAction +{ +public: + IccAddsDbsAction(PlayerbotAI* botAI, std::string const name = "icc adds dbs") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + Unit* FindPriorityTarget(Unit* boss); + bool UpdateSkullMarker(Unit* priorityTarget); +}; + +class IccDogsTankPositionAction : public AttackAction +{ +public: + IccDogsTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc dogs tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +//FESTERGUT +class IccFestergutGroupPositionAction : public AttackAction +{ +public: + IccFestergutGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc festergut group position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + bool HasSporesInGroup(); + bool PositionNonTankMembers(); + int CalculatePositionIndex(Group* group); +}; + +class IccFestergutSporeAction : public AttackAction +{ +public: + IccFestergutSporeAction(PlayerbotAI* botAI, std::string const name = "icc festergut spore") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + Position CalculateSpreadPosition(); + struct SporeInfo + { + std::vector sporedPlayers; + ObjectGuid lowestGuid; + bool hasLowestGuid = false; + }; + SporeInfo FindSporedPlayers(); + Position DetermineTargetPosition(bool hasSpore, const SporeInfo& sporeInfo, const Position& spreadRangedPos); + bool CheckMainTankSpore(); + bool GooNear(Position const& pos); +}; + +class IccFestergutAvoidMalleableGooAction : public MovementAction +{ +public: + IccFestergutAvoidMalleableGooAction(PlayerbotAI* botAI, + std::string const name = "icc festergut avoid malleable goo") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +//Rotface +class IccRotfaceTankPositionAction : public AttackAction +{ +public: + IccRotfaceTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + bool MarkBossWithSkull(Unit* boss); + bool PositionMainTankAndMelee(Unit* boss, Unit* smallOoze = nullptr); + bool HandleAssistTankPositioning(Unit* boss); + Unit* FindAssignedBigOoze(Unit* boss, std::vector& bigOozes); + bool HandleBigOozeKiting(Unit* bigOoze); +}; + +class IccRotfaceGroupPositionAction : public AttackAction +{ +public: + IccRotfaceGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface group position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + //bool MoveAwayFromBigOoze(Unit* bigOoze); + bool HandlePuddleAvoidance(Unit* boss); + bool MoveAwayFromPuddle(Unit* boss, Unit* puddle, float puddleDistance); + bool HandleOozeTargeting(); + bool HandleOozeMemberPositioning(Unit* mySmallOoze); + bool PositionRangedAndHealers(Unit* boss,Unit* smallOoze); + bool PositionHeroicGrid(Unit* boss); +}; + +class IccRotfaceMoveAwayFromExplosionAction : public MovementAction +{ +public: + IccRotfaceMoveAwayFromExplosionAction(PlayerbotAI* botAI, std::string const name = "icc rotface move away from explosion") + : MovementAction(botAI, name), + _escapePosition(0.0f, 0.0f, 0.0f), + _hasEscape(false), + _holdUntil(0) {} + bool Execute(Event event) override; + +private: + Position _escapePosition; + bool _hasEscape; + uint32 _holdUntil; +}; + +class IccRotfaceAvoidVileGasAction : public MovementAction +{ +public: + IccRotfaceAvoidVileGasAction(PlayerbotAI* botAI, std::string const name = "icc rotface avoid vile gas") + : MovementAction(botAI, name), + _safeSpot(0.0f, 0.0f, 0.0f), + _hasSafeSpot(false) {} + bool Execute(Event event) override; + +private: + Position _safeSpot; + bool _hasSafeSpot; +}; + +//PP +class IccPutricideMutatedPlagueAction : public AttackAction +{ +public: + IccPutricideMutatedPlagueAction(PlayerbotAI* botAI, std::string const name = "icc putricide mutated plague") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccPutricideGrowingOozePuddleAction : public AttackAction +{ +public: + IccPutricideGrowingOozePuddleAction(PlayerbotAI* botAI, std::string const name = "icc putricide growing ooze puddle") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + Unit* FindClosestThreateningPuddle(); + Position CalculateSafeMovePosition(Unit* closestPuddle); + bool IsPositionTooCloseToOtherPuddles(float x, float y, Unit* ignorePuddle); + bool PathCrossesAnyPuddle(float fromX, float fromY, float toX, float toY, Unit* ignorePuddle); +}; + +class IccPutricideVolatileOozeAction : public AttackAction +{ +public: + IccPutricideVolatileOozeAction(PlayerbotAI* botAI, std::string const name = "icc putricide volatile ooze") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + bool MarkOozeWithSkull(Unit* ooze); + Unit* FindAuraTarget(); +}; + +class IccPutricideGasCloudAction : public AttackAction +{ +public: + IccPutricideGasCloudAction(PlayerbotAI* botAI, std::string const name = "icc putricide gas cloud") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + + bool HandleGaseousBloatMovement(Unit* gasCloud); + bool HandleGroupAuraSituation(Unit* gasCloud); + bool GroupHasGaseousBloat(Group* group); +}; + +class IccPutricideAvoidMalleableGooAction : public MovementAction +{ +public: + IccPutricideAvoidMalleableGooAction(PlayerbotAI* botAI, std::string const name = "icc putricide avoid malleable goo") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; + + bool HandleTankPositioning(Unit* boss); + bool HandleUnboundPlague(Unit* boss); + bool HandleBossPositioning(Unit* boss); + bool HasObstacleBetween(Position const& from, Position const& to); + bool IsOnPath(Position const& from, Position const& to, Position const& point, float threshold); +}; + +class IccPutricideAbominationAction : public AttackAction +{ +public: + IccPutricideAbominationAction(PlayerbotAI* botAI, std::string const name = "icc putricide abomination") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + +private: + bool BecomeAbomination(); + bool IsSomeoneAlreadyPiloting(); + Unit* FindClosestPuddle(float maxRange); + Unit* PickSlashTarget(Unit* boss); + bool TryRegurgitate(Unit* abo, Unit* target); + bool TryEatOoze(Unit* abo, Unit* puddle); +}; + +//BPC +class IccBpcKelesethTankAction : public AttackAction +{ +public: + IccBpcKelesethTankAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bpc keleseth tank") {} + bool Execute(Event event) override; +}; + +class IccBpcMainTankAction : public AttackAction +{ +public: + IccBpcMainTankAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bpc main tank") {} + bool Execute(Event event) override; + + bool MarkEmpoweredPrince(); +}; + +class IccBpcEmpoweredVortexAction : public MovementAction +{ +public: + IccBpcEmpoweredVortexAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc bpc empowered vortex") {} + bool Execute(Event event) override; + + bool MaintainRangedSpacing(); + bool HandleEmpoweredVortexSpread(); +}; + +class IccBpcKineticBombAction : public AttackAction +{ +public: + IccBpcKineticBombAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bpc kinetic bomb") {} + bool Execute(Event event) override; + + Unit* FindNearestBomb(); +}; + +class IccBpcBallOfFlameAction : public MovementAction +{ +public: + IccBpcBallOfFlameAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc bpc ball of flame") {} + bool Execute(Event event) override; +}; + +//Blood Queen Lana'thel +class IccBqlGroupPositionAction : public AttackAction +{ +public: + IccBqlGroupPositionAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bql group position") {} + bool Execute(Event event) override; + + bool HandleTankPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura); + bool HandleShadowsMovement(); + Position AdjustControlPoint(const Position& wall, const Position& center, float factor); + Position CalculateBezierPoint(float t, const Position path[4]); + bool HandleGroupPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura); + +private: + // Evaluate curves + struct CurveInfo + { + Position moveTarget; + int curveIdx = 0; + bool foundSafe = false; + float minDist = 0.0f; + float score = 0.0f; + Position closestPoint; + float t_closest = 0.0f; + }; +}; + +class IccBqlPactOfDarkfallenAction : public MovementAction +{ +public: + IccBqlPactOfDarkfallenAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc bql pact of darkfallen") {} + bool Execute(Event event) override; + + bool CalculateCenterPosition(Position& targetPos, const std::vector& playersWithAura); + bool MoveToTargetPosition(const Position& targetPos, int auraCount); +}; + +class IccBqlVampiricBiteAction : public AttackAction +{ +public: + IccBqlVampiricBiteAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bql vampiric bite") {} + bool Execute(Event event) override; + + Player* FindBestBiteTarget(Group* group); + bool IsInvalidTarget(Player* player); + bool MoveTowardsTarget(Player* target); + bool CastVampiricBite(Player* target); +}; + +// Sister Svalna +class IccValkyreSpearAction : public AttackAction +{ +public: + IccValkyreSpearAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc valkyre spear") {} + bool Execute(Event event) override; +}; + +class IccSisterSvalnaAction : public AttackAction +{ +public: + IccSisterSvalnaAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sister svalna") {} + bool Execute(Event event) override; +}; + +// Valithria Dreamwalker + +class IccValithriaGroupAction : public AttackAction +{ +public: + IccValithriaGroupAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc valithria group") {} + bool Execute(Event event) override; + + bool Handle25ManGroupLogic(); + bool HandleMarkingLogic(bool inGroup1, bool inGroup2, bool singleMarkMode); + bool Handle10ManGroupLogic(); + +private: + bool ApplyCrowdControl(Unit* zombie); +}; + +class IccValithriaZombieKiteAction : public MovementAction +{ +public: + IccValithriaZombieKiteAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc valithria zombie kite") {} + bool Execute(Event event) override; +}; + +class IccValithriaPortalAction : public MovementAction +{ +public: + IccValithriaPortalAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc valithria portal") {} + bool Execute(Event event) override; +}; + +class IccValithriaHealAction : public AttackAction +{ +public: + IccValithriaHealAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc valithria heal") {} + bool Execute(Event event) override; +}; + +class IccValithriaDreamCloudAction : public MovementAction +{ +public: + IccValithriaDreamCloudAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc valithria dream cloud") {} + bool Execute(Event event) override; + +private: + std::vector CollectClouds(uint32 entry, Unit* reference); +}; + +//Sindragosa +class IccSindragosaGroupPositionAction : public AttackAction +{ +public: + IccSindragosaGroupPositionAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa group position") {} + bool Execute(Event event) override; + + bool HandleTankPositioning(Unit* boss); + bool HandleNonTankPositioning(); + bool MoveIncrementallyToPosition(const Position& targetPos, float maxStep); +}; + +class IccSindragosaFrostBeaconAction : public MovementAction +{ +public: + IccSindragosaFrostBeaconAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc sindragosa frost beacon") {} + bool Execute(Event event) override; + + bool HandleSupportActions(); + bool HandleBeaconedPlayer(const Unit* boss); + bool HandleNonBeaconedPlayer(const Unit* boss); + bool MoveToPositionIfNeeded(const Position& position, float tolerance); + bool MoveToPosition(const Position& position); + bool IsBossFlying(const Unit* boss); + bool TryDropTombFlares(Unit const* boss); + + private: + static constexpr float POSITION_TOLERANCE = 1.0f; + static constexpr float TOMB_POSITION_TOLERANCE = 0.5f; + static constexpr float MIN_SAFE_DISTANCE = 13.0f; + static constexpr float MOVE_TOLERANCE = 2.0f; + // Keyed per-instance to avoid cross-instance pollution when multiple ICCs run simultaneously + static std::map> s_flaredRedThisPhase; + static std::map s_flaredBluePhase3; + static std::map s_lastPhase3; + static uint32 s_nextFlareMs; + static constexpr uint32 FLARE_ITEM_COOLDOWN_MS = 1000; +}; + +class IccSindragosaHotAction : public Action +{ +public: + IccSindragosaHotAction(PlayerbotAI* botAI) : Action(botAI, "icc sindragosa hot") {} + bool Execute(Event event) override; +}; + +class IccSindragosaBlisteringColdAction : public MovementAction +{ +public: + IccSindragosaBlisteringColdAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc sindragosa blistering cold") {} + bool Execute(Event event) override; +}; + +class IccSindragosaUnchainedMagicAction : public AttackAction +{ +public: + IccSindragosaUnchainedMagicAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa unchained magic") {} + bool Execute(Event event) override; +}; + +class IccSindragosaChilledToTheBoneAction : public AttackAction +{ +public: + IccSindragosaChilledToTheBoneAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa chilled to the bone") {} + bool Execute(Event event) override; +}; + +class IccSindragosaMysticBuffetAction : public MovementAction +{ +public: + IccSindragosaMysticBuffetAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc sindragosa mystic buffet") {} + bool Execute(Event event) override; +}; + +class IccSindragosaFrostBombAction : public MovementAction +{ +public: + IccSindragosaFrostBombAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc sindragosa frost bomb") {} + bool Execute(Event event) override; + +private: + struct FrostBombContext + { + Unit* marker = nullptr; + std::vector tombs; + }; + bool CollectContext(FrostBombContext& ctx) const; + int ResolveGroupIndex(Group* group) const; + void PinGroupToCurrentZone(); + std::vector SelectTombs(std::vector const& tombs, int groupIndex, int groupCount) const; + Unit* ResolveStickyTomb(std::vector const& myTombs); + bool HandleRtiMarking(Group* group, int groupIndex, std::vector const& myTombs, Unit* losTomb); + // Keyed per-instance to avoid cross-instance pollution when multiple ICCs run simultaneously + static std::map, int> s_groupAssignments; + static std::map, ObjectGuid> s_tombAssignments; + static std::set> s_freedFallback; + + // Per-bot last LOS move stamp. When the LOS tomb dies/loses mark mid-walk + // the bot would otherwise freeze in the open. Replaying the last move for + // up to 2 seconds keeps it on its path until a new LOS target is chosen. + struct LastLosMove + { + uint32 timestampMs = 0; + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + }; + // Keyed per-instance to avoid cross-instance pollution + static std::map, LastLosMove> s_lastLosMove; +}; + +class IccSindragosaTankSwapPositionAction : public AttackAction +{ + public: + IccSindragosaTankSwapPositionAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa tank swap position") {} + bool Execute(Event event) override; +}; + +//LK +class IccLichKingShadowTrapAction : public MovementAction +{ +public: + IccLichKingShadowTrapAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc lich king shadow trap") {} + bool Execute(Event event) override; +}; + +class IccLichKingNecroticPlagueAction : public MovementAction +{ +public: + IccLichKingNecroticPlagueAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc lich king necrotic plague") {} + bool Execute(Event event) override; +}; + +class IccLichKingWinterAction : public AttackAction +{ +public: + IccLichKingWinterAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc lich king winter") {} + bool Execute(Event event) override; + + bool IsPositionSafeFromDefile(float x, float y, float z, float minSafeDistance) const; + bool IsPositionSafeFromShadowTraps(float x, float y) const; + bool IsValidCollectibleAdd(Unit* unit) const; + bool HandleTankPositioning(); + bool HandleMeleePositioning(); + bool HandleRangedPositioning(); + bool HandleMainTankAddManagement(Unit* boss, Position const* tankPos); + bool HandleAssistTankAddManagement(Unit* boss, Position const* tankPos); + bool HandlePetManagement(); + +private: + static constexpr float PLATFORM_Z = 840.857f; + static constexpr float BEHIND_DISTANCE = 4.0f; + bool FixPlatformPosition(); + bool ClearInvalidTarget(); + Position const* GetMainTankPosition(); + Position const* GetMainTankRangedPosition(); + bool TryMoveToPosition(float x, float y, float z, bool forced = true); +}; + +class IccLichKingAddsAction : public AttackAction +{ +public: + IccLichKingAddsAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc lich king adds") {} + bool Execute(Event event) override; + + bool HandleTeleportationFixes(Difficulty diff, Unit* terenas); + bool HandleHeroicNonTankPositioning(Difficulty diff, Unit* terenas); + bool HandleSpiritMarkingAndTargeting(Difficulty diff, Unit* terenas); + bool HandleQuakeMechanics(Unit* boss); + bool HandleShamblingHorrors(Unit* boss, bool hasPlague); + bool HandleRagingSpiritFlanking(Unit* boss, bool hasPlague, Difficulty diff); + bool HandleAssistTankAddManagement(Unit* boss, Difficulty diff); + bool HandleMeleePositioning(Unit* boss, bool hasPlague, Difficulty diff); + bool HandleMainTankTargeting(Unit* boss, Difficulty diff); + bool HandleNonTankHeroicPositioning(Unit* boss, Difficulty diff, bool hasPlague); + bool HandleRangedPositioning(Unit* boss, bool hasPlague, Difficulty diff); + bool HandleDefileMechanics(Unit* boss, Difficulty diff); + bool HandleCenterStacking(Unit* boss, Difficulty diff); + bool HandleValkyrMechanics(Difficulty diff); + bool HandleValkyrMarking(std::vector const& valkyrs, Difficulty diff); + bool HandleValkyrAssignment(std::vector const& valkyrs); + bool HandleVileSpiritMechanics(); + bool HandleIceSphereMechanics(); + bool ApplyCCToValkyr(Unit* valkyr); + bool IsValkyr(Unit* unit); + std::vector CalculateBalancedGroupSizes(size_t totalAssist, size_t numValkyrs); + size_t GetAssignedValkyrIndex(size_t assistIndex, std::vector const& groupSizes); + std::string GetRTIValueForValkyr(size_t valkyrIndex); + std::pair DefileAwareStep(float tx, float ty, + std::vector const& defiles, + Difficulty diff); +}; + +class IccLichKingSpiritBombAction : public MovementAction +{ +public: + IccLichKingSpiritBombAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc lich king spirit bomb") {} + bool Execute(Event event) override; + + static bool IsBombThreatActive(PlayerbotAI* botAI, Player* bot); +}; + +#endif diff --git a/src/Ai/Raid/ICC/Action/ICCActions_BPC.cpp b/src/Ai/Raid/ICC/Action/ICCActions_BPC.cpp new file mode 100644 index 00000000000..2b4405b0080 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_BPC.cpp @@ -0,0 +1,821 @@ +#include "ICCActions.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "Vehicle.h" +#include "RtiValue.h" +#include "GenericSpellActions.h" +#include "GenericActions.h" +#include "ICCTriggers.h" +#include "Multiplier.h" + +static float const BPC_FLOOR_Z = 361.18222f; + +static bool CastClassTaunt(Player* bot, PlayerbotAI* botAI, Unit* target) +{ + if (!target || !target->IsAlive()) + return false; + + switch (bot->getClass()) + { + case CLASS_PALADIN: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + } + case CLASS_DEATH_KNIGHT: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + } + case CLASS_DRUID: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + } + case CLASS_WARRIOR: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + } + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; +} + +bool IccBpcKelesethTankAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "prince keleseth"); + if (!boss) + return false; + + static float const Z_TELEPORT_THRESHOLD = 6.0f; + + // Teleport Z-axis back to center-level if the bot is significantly off in Z + float centerZ = ICC_BPC_CENTER_POSITION.GetPositionZ(); + if (std::abs(bot->GetPositionZ() - centerZ) > Z_TELEPORT_THRESHOLD) + { + bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), centerZ, bot->GetOrientation()); + return false; + } + + if (!botAI->IsAssistTank(bot)) + { + // Non-assist-tank: pull toward center if too far + static float const CENTER_MOVE_THRESHOLD = 30.0f; + static float const MOVE_INCREMENT = 15.0f; + float distToCenter = bot->GetExactDist2d(ICC_BPC_CENTER_POSITION); + if (distToCenter > CENTER_MOVE_THRESHOLD) + { + float dirX = ICC_BPC_CENTER_POSITION.GetPositionX() - bot->GetPositionX(); + float dirY = ICC_BPC_CENTER_POSITION.GetPositionY() - bot->GetPositionY(); + float length = std::sqrt(dirX * dirX + dirY * dirY); + if (length > 0.001f) + { + dirX /= length; + dirY /= length; + float needed = distToCenter - CENTER_MOVE_THRESHOLD; + float step = std::min(MOVE_INCREMENT, needed); + float moveX = bot->GetPositionX() + dirX * step; + float moveY = bot->GetPositionY() + dirY * step; + MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + return false; + } + } + } + + if (!botAI->IsAssistTank(bot)) + return false; + + bool const isBossVictim = boss->GetVictim() == bot; + + if (!isBossVictim) + { + CastClassTaunt(bot, botAI, boss); + bot->SetTarget(boss->GetGUID()); + bot->SetFacingToObject(boss); + } + else + { + // Collect stray Dark Nuclei via taunt, move toward if out of range + static float const TAUNT_RANGE = 30.0f; + std::list nuclei; + bot->GetCreatureListWithEntryInGrid(nuclei, NPC_DARK_NUCLEUS, 100.0f); + + Unit* strayNucleus = nullptr; + for (Creature* nucleus : nuclei) + { + if (nucleus && nucleus->IsAlive() && nucleus->GetVictim() != bot) + { + strayNucleus = nucleus; + break; + } + } + + if (strayNucleus) + { + float dist = bot->GetExactDist2d(strayNucleus); + if (dist <= TAUNT_RANGE) + CastClassTaunt(bot, botAI, strayNucleus); + else + { + float dirX = strayNucleus->GetPositionX() - bot->GetPositionX(); + float dirY = strayNucleus->GetPositionY() - bot->GetPositionY(); + float length = std::sqrt(dirX * dirX + dirY * dirY); + if (length > 0.001f) + { + dirX /= length; + dirY /= length; + static float const NUCLEUS_MOVE_STEP = 10.0f; + static float const NUCLEUS_APPROACH_BUFFER = 5.0f; + float step = std::min(NUCLEUS_MOVE_STEP, dist - TAUNT_RANGE + NUCLEUS_APPROACH_BUFFER); + float moveX = bot->GetPositionX() + dirX * step; + float moveY = bot->GetPositionY() + dirY * step; + MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + return false; + } + } + } + + bot->SetTarget(boss->GetGUID()); + bot->SetFacingToObject(boss); + Attack(boss); + + return false; +} + +bool IccBpcMainTankAction::Execute(Event /*event*/) +{ + if (!botAI->IsMainTank(bot)) + { + if (botAI->IsTank(bot)) + MarkEmpoweredPrince(); + return false; + } + + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram"); + + bool const isVictimOfValanar = valanar && valanar->GetVictim() == bot; + bool const isVictimOfTaldaram = taldaram && taldaram->GetVictim() == bot; + + // Move to MT position if targeted by both princes and not already close + static float const MT_POSITION_THRESHOLD = 15.0f; + if (isVictimOfValanar && isVictimOfTaldaram && bot->GetExactDist2d(ICC_BPC_MT_POSITION) > MT_POSITION_THRESHOLD) + { + float dirX = ICC_BPC_MT_POSITION.GetPositionX() - bot->GetPositionX(); + float dirY = ICC_BPC_MT_POSITION.GetPositionY() - bot->GetPositionY(); + float length = std::sqrt(dirX * dirX + dirY * dirY); + if (length > 0.001f) + { + dirX /= length; + dirY /= length; + float moveDist = std::min(3.0f, length); + float moveX = bot->GetPositionX() + dirX * moveDist; + float moveY = bot->GetPositionY() + dirY * moveDist; + MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + } + + // Taunt princes not targeting us + if (valanar && !isVictimOfValanar) + { + CastClassTaunt(bot, botAI, valanar); + bot->SetTarget(valanar->GetGUID()); + bot->SetFacingToObject(valanar); + Attack(valanar); + } + + if (taldaram && !isVictimOfTaldaram) + { + CastClassTaunt(bot, botAI, taldaram); + bot->SetTarget(taldaram->GetGUID()); + bot->SetFacingToObject(taldaram); + Attack(taldaram); + } + + // Taunt nearby hostile adds not targeting a tank + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + if (unit->GetEntry() == NPC_PRINCE_KELESETH || unit->GetEntry() == NPC_PRINCE_VALANAR || + unit->GetEntry() == NPC_PRINCE_TALDARAM || unit->GetEntry() == NPC_DARK_NUCLEUS) + continue; + + static float const ADD_TAUNT_RANGE = 20.0f; + if (bot->GetDistance2d(unit) > ADD_TAUNT_RANGE) + continue; + + Unit* victim = unit->GetVictim(); + Player* victimPlayer = victim ? victim->ToPlayer() : nullptr; + if (!victimPlayer || !botAI->IsTank(victimPlayer)) + { + CastClassTaunt(bot, botAI, unit); + break; + } + } + + // Target marking for all tanks, called after main tank priority actions + if (botAI->IsTank(bot)) + MarkEmpoweredPrince(); + + return false; +} + +bool IccBpcMainTankAction::MarkEmpoweredPrince() +{ + static constexpr uint8 SKULL_RAID_ICON = 7; + + // Find empowered prince (Invocation of Blood) + Unit* empoweredPrince = nullptr; + GuidVector const& targets = AI_VALUE(GuidVector, "possible targets"); + + for (auto const& targetGuid : targets) + { + Unit* unit = botAI->GetUnit(targetGuid); + if (!unit || !unit->IsAlive()) + continue; + + if (botAI->HasAura("Invocation of Blood", unit)) + { + uint32 const entry = unit->GetEntry(); + if (entry == NPC_PRINCE_KELESETH || entry == NPC_PRINCE_VALANAR || entry == NPC_PRINCE_TALDARAM) + { + empoweredPrince = unit; + break; + } + } + } + + // Handle marking if we found an empowered prince + if (empoweredPrince && empoweredPrince->IsAlive()) + { + Group* group = bot->GetGroup(); + if (group) + { + ObjectGuid const currentSkullGuid = group->GetTargetIcon(SKULL_RAID_ICON); + Unit* markedUnit = botAI->GetUnit(currentSkullGuid); + + // Clear dead marks or marks that are not on empowered prince + if (markedUnit && (!markedUnit->IsAlive() || markedUnit != empoweredPrince)) + group->SetTargetIcon(SKULL_RAID_ICON, bot->GetGUID(), ObjectGuid::Empty); + + // Mark alive empowered prince if needed + if (!currentSkullGuid || !markedUnit) + group->SetTargetIcon(SKULL_RAID_ICON, bot->GetGUID(), empoweredPrince->GetGUID()); + } + } + + return false; +} + +bool IccBpcEmpoweredVortexAction::Execute(Event /*event*/) +{ + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + if (!valanar) + return false; + + // Check if boss is casting empowered vortex + bool const isCastingVortex = valanar->HasUnitState(UNIT_STATE_CASTING) && + (valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX1) || + valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX2) || + valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX3) || + valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX4)); + + // Use complex positioning system for empowered vortex + if (isCastingVortex) + return HandleEmpoweredVortexSpread(); + + // Use simple ranged spreading for non-vortex situations + return MaintainRangedSpacing(); +} + +bool IccBpcEmpoweredVortexAction::MaintainRangedSpacing() +{ + static float const IDEAL_RADIUS = 25.0f; + static float const RADIUS_TOLERANCE = 3.0f; + static float const MOVE_INCREMENT = 3.0f; + static float const MIN_SPACING = 13.0f; + + bool const isRanged = botAI->IsRanged(bot) || botAI->IsHeal(bot); + if (!isRanged) + return false; + + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + if (!valanar) + return false; + + // Collect ranged/healer bots sorted by GUID for consistent slot assignment + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector rangedBots; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + if (botAI->IsTank(member)) + continue; + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (!memberAI) + continue; + if (memberAI->IsRanged(member) || memberAI->IsHeal(member)) + rangedBots.push_back(member); + } + + if (rangedBots.empty()) + return false; + + std::sort(rangedBots.begin(), rangedBots.end(), + [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); + + // Find this bot's slot index + int slotIndex = -1; + for (size_t i = 0; i < rangedBots.size(); ++i) + { + if (rangedBots[i] == bot) + { + slotIndex = static_cast(i); + break; + } + } + + if (slotIndex < 0) + return false; + + // Calculate angular position around Valanar + float angleStep = 2.0f * static_cast(M_PI) / static_cast(rangedBots.size()); + float targetAngle = angleStep * static_cast(slotIndex); + + // Collect shock vortex positions to avoid + static float const VORTEX_AVOID_DIST = 13.0f; + std::list vortexList; + bot->GetCreatureListWithEntryInGrid(vortexList, NPC_SHOCK_VORTEX, 100.0f); + + // Find safe angle — try assigned slot first, then nudge away from vortices + static float const ANGLE_NUDGE = static_cast(M_PI) / 18.0f; + static int const MAX_NUDGE_STEPS = 18; + float bestAngle = targetAngle; + + auto isAngleSafe = [&](float angle) -> bool + { + float testX = valanar->GetPositionX() + IDEAL_RADIUS * std::cos(angle); + float testY = valanar->GetPositionY() + IDEAL_RADIUS * std::sin(angle); + for (Creature* vortex : vortexList) + { + if (!vortex || !vortex->IsAlive()) + continue; + float vdx = testX - vortex->GetPositionX(); + float vdy = testY - vortex->GetPositionY(); + if (std::sqrt(vdx * vdx + vdy * vdy) < VORTEX_AVOID_DIST) + return false; + } + return true; + }; + + if (!isAngleSafe(bestAngle)) + { + for (int i = 1; i <= MAX_NUDGE_STEPS; ++i) + { + float plusAngle = targetAngle + ANGLE_NUDGE * static_cast(i); + if (isAngleSafe(plusAngle)) + { + bestAngle = plusAngle; + break; + } + float minusAngle = targetAngle - ANGLE_NUDGE * static_cast(i); + if (isAngleSafe(minusAngle)) + { + bestAngle = minusAngle; + break; + } + } + } + + float targetX = valanar->GetPositionX() + IDEAL_RADIUS * std::cos(bestAngle); + float targetY = valanar->GetPositionY() + IDEAL_RADIUS * std::sin(bestAngle); + float targetZ = bot->GetPositionZ(); + + float distToSlot = bot->GetExactDist2d(targetX, targetY); + if (distToSlot <= RADIUS_TOLERANCE) + return false; + + // Move toward assigned slot + float dx = targetX - bot->GetPositionX(); + float dy = targetY - bot->GetPositionY(); + float len = std::sqrt(dx * dx + dy * dy); + if (len < 0.001f) + return false; + + dx /= len; + dy /= len; + float step = std::min(MOVE_INCREMENT, distToSlot); + float moveX = bot->GetPositionX() + dx * step; + float moveY = bot->GetPositionY() + dy * step; + + if (bot->IsWithinLOS(moveX, moveY, targetZ)) + { + MoveTo(bot->GetMapId(), moveX, moveY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + } + + return false; +} + +bool IccBpcEmpoweredVortexAction::HandleEmpoweredVortexSpread() +{ + static std::map, uint32> spreadLockTimers; + static uint32 const SPREAD_LOCK_DURATION_MS = 250; + static float const MOVE_INCREMENT = 4.0f; + static float const SLOT_TOLERANCE = 2.0f; + static float const MIN_SPACING = 13.0f; + + if (botAI->IsTank(bot)) + return false; + + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + if (!valanar) + return false; + + // If locked in position, don't move — allow combat/healing + uint32 now = getMSTime(); + + // Prune expired entries to prevent unbounded growth + for (auto it = spreadLockTimers.begin(); it != spreadLockTimers.end(); ) + { + if (getMSTimeDiff(it->second, now) >= SPREAD_LOCK_DURATION_MS) + it = spreadLockTimers.erase(it); + else + ++it; + } + + uint32 const instanceId = bot->GetInstanceId(); + auto it = spreadLockTimers.find({instanceId, bot->GetGUID()}); + if (it != spreadLockTimers.end()) + return false; + + // Collect ALL non-tank bots for spreading (melee must scatter too) + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector spreadBots; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + if (botAI->IsTank(member)) + continue; + spreadBots.push_back(member); + } + + if (spreadBots.empty()) + return false; + + std::sort(spreadBots.begin(), spreadBots.end(), + [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); + + int slotIndex = -1; + for (size_t i = 0; i < spreadBots.size(); ++i) + { + if (spreadBots[i] == bot) + { + slotIndex = static_cast(i); + break; + } + } + + if (slotIndex < 0) + return false; + + // Calculate radius so angular spacing >= MIN_SPACING + // Arc length between slots = 2*pi*R / N, need >= MIN_SPACING + float const botCount = static_cast(spreadBots.size()); + float idealRadius = (botCount * MIN_SPACING) / (2.0f * static_cast(M_PI)); + // Clamp radius to room bounds (room ~40y radius from center) + if (idealRadius < 15.0f) + idealRadius = 15.0f; + if (idealRadius > 38.0f) + idealRadius = 38.0f; + + float angleStep = 2.0f * static_cast(M_PI) / botCount; + float targetAngle = angleStep * static_cast(slotIndex); + + // Collect shock vortex positions to avoid + static float const VORTEX_AVOID_DIST = 13.0f; + std::list vortexList; + bot->GetCreatureListWithEntryInGrid(vortexList, NPC_SHOCK_VORTEX, 100.0f); + + // Find safe angle — try assigned slot first, then nudge away from vortices + static float const ANGLE_NUDGE = static_cast(M_PI) / 18.0f; + static int const MAX_NUDGE_STEPS = 18; + float bestAngle = targetAngle; + + auto isAngleSafe = [&](float angle) -> bool + { + float testX = valanar->GetPositionX() + idealRadius * std::cos(angle); + float testY = valanar->GetPositionY() + idealRadius * std::sin(angle); + for (Creature* vortex : vortexList) + { + if (!vortex || !vortex->IsAlive()) + continue; + float vdx = testX - vortex->GetPositionX(); + float vdy = testY - vortex->GetPositionY(); + if (std::sqrt(vdx * vdx + vdy * vdy) < VORTEX_AVOID_DIST) + return false; + } + return true; + }; + + if (!isAngleSafe(bestAngle)) + { + for (int i = 1; i <= MAX_NUDGE_STEPS; ++i) + { + float plusAngle = targetAngle + ANGLE_NUDGE * static_cast(i); + if (isAngleSafe(plusAngle)) + { + bestAngle = plusAngle; + break; + } + float minusAngle = targetAngle - ANGLE_NUDGE * static_cast(i); + if (isAngleSafe(minusAngle)) + { + bestAngle = minusAngle; + break; + } + } + } + + float targetX = valanar->GetPositionX() + idealRadius * std::cos(bestAngle); + float targetY = valanar->GetPositionY() + idealRadius * std::sin(bestAngle); + float targetZ = bot->GetPositionZ(); + + float distToSlot = bot->GetExactDist2d(targetX, targetY); + + // Close enough to slot — lock position + if (distToSlot <= SLOT_TOLERANCE) + { + spreadLockTimers[{instanceId, bot->GetGUID()}] = now; + return false; + } + + // Move toward assigned slot + float dx = targetX - bot->GetPositionX(); + float dy = targetY - bot->GetPositionY(); + float len = std::sqrt(dx * dx + dy * dy); + if (len < 0.001f) + return false; + + dx /= len; + dy /= len; + float step = std::min(MOVE_INCREMENT, distToSlot); + float moveX = bot->GetPositionX() + dx * step; + float moveY = bot->GetPositionY() + dy * step; + + if (bot->IsWithinLOS(moveX, moveY, targetZ)) + { + MoveTo(bot->GetMapId(), moveX, moveY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + + return false; +} + +bool IccBpcKineticBombAction::Execute(Event /*event*/) +{ + if (!botAI->IsRangedDps(bot)) + return false; + + static float const MAX_HEIGHT_DIFF = 30.0f; + static float const SAFE_HEIGHT_Z = 371.16473f; + static float const TELEPORT_DOWN_Z = 367.16473f; + + // Fix bot stuck above arena + if (bot->GetPositionZ() > SAFE_HEIGHT_Z) + { + bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), TELEPORT_DOWN_Z, + bot->GetOrientation()); + } + + // Assign bombs 1-to-1 to nearest available ranged DPS + Unit* assignedBomb = FindNearestBomb(); + if (!assignedBomb) + return false; + + // Already attacking a valid bomb that's still low enough, keep going + if (Unit* currentTarget = AI_VALUE(Unit*, "current target")) + { + if (currentTarget == assignedBomb && currentTarget->IsAlive()) + { + float const heightDiff = currentTarget->GetPositionZ() - BPC_FLOOR_Z; + if (heightDiff < MAX_HEIGHT_DIFF) + return false; + } + } + + bot->SetTarget(assignedBomb->GetGUID()); + bot->SetFacingToObject(assignedBomb); + Attack(assignedBomb); + return false; +} + +Unit* IccBpcKineticBombAction::FindNearestBomb() +{ + static constexpr std::array BOMB_ENTRIES = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2, NPC_KINETIC_BOMB3, + NPC_KINETIC_BOMB4}; + static float const MAX_HEIGHT_DIFF = 35.0f; + + GuidVector const targets = AI_VALUE(GuidVector, "possible targets no los"); + if (targets.empty()) + return nullptr; + + // Gather all valid bombs, sorted by Z ascending (lowest = most urgent) + std::vector kineticBombs; + for (auto const& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (std::find(BOMB_ENTRIES.begin(), BOMB_ENTRIES.end(), unit->GetEntry()) == BOMB_ENTRIES.end()) + continue; + + if (unit->GetPositionZ() - BPC_FLOOR_Z > MAX_HEIGHT_DIFF) + continue; + + kineticBombs.push_back(unit); + } + + if (kineticBombs.empty()) + return nullptr; + + std::sort(kineticBombs.begin(), kineticBombs.end(), + [](Unit* unitA, Unit* unitB) { return unitA->GetPositionZ() < unitB->GetPositionZ(); }); + + // Gather alive ranged DPS bots only (no real players) + std::vector rangedDps; + Group* group = bot->GetGroup(); + if (group) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && botAI->IsRangedDps(member)) + rangedDps.push_back(member); + } + } + else + { + rangedDps.push_back(bot); + } + + // Greedy 1-to-1 assignment: hunters first, druids second, then any ranged DPS + static float const MAX_ASSIGN_RANGE = 80.0f; + std::set assigned; + for (Unit* bomb : kineticBombs) + { + Player* nearest = nullptr; + float nearestDist = std::numeric_limits::max(); + + // Priority classes: hunter > druid > any + static constexpr std::array classPriority = {CLASS_HUNTER, CLASS_DRUID, 0}; + for (uint8 priorityClass : classPriority) + { + nearest = nullptr; + nearestDist = std::numeric_limits::max(); + + for (Player* dps : rangedDps) + { + if (assigned.count(dps)) + continue; + + if (priorityClass != 0 && dps->getClass() != priorityClass) + continue; + + float dist = dps->GetDistance(bomb); + if (dist < nearestDist && dist < MAX_ASSIGN_RANGE) + { + nearestDist = dist; + nearest = dps; + } + } + + if (nearest) + break; + } + + if (nearest) + { + assigned.insert(nearest); + if (nearest == bot) + return bomb; + } + } + + return nullptr; +} + +bool IccBpcBallOfFlameAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "prince taldaram"); + if (!boss) + return false; + + Unit* ballOfFlameUnit = bot->FindNearestCreature(NPC_BALL_OF_FLAME, 100.0f); + Unit* infernoFlameUnit = bot->FindNearestCreature(NPC_BALL_OF_INFERNO_FLAME, 100.0f); + + bool const hasBallOfFlame = ballOfFlameUnit && (ballOfFlameUnit->GetVictim() == bot); + bool const hasInfernoFlame = infernoFlameUnit && (infernoFlameUnit->GetVictim() == bot); + + float infernoDist = infernoFlameUnit ? infernoFlameUnit->GetDistance2d(boss) : 0.0f; + // Hunters excluded — they can DPS from range without needing to soak + if (infernoFlameUnit && infernoDist > 2.0f && infernoDist <= 10.0f && !hasInfernoFlame && + bot->getClass() != CLASS_HUNTER) + { + if (!botAI->IsTank(bot) && infernoFlameUnit->GetVictim() != bot) + { + float flameX = infernoFlameUnit->GetPositionX(); + float flameY = infernoFlameUnit->GetPositionY(); + float botX = bot->GetPositionX(); + float botY = bot->GetPositionY(); + + // Calculate direction vector + float dx = flameX - botX; + float dy = flameY - botY; + float distance = std::sqrt(dx * dx + dy * dy); + + // Normalize and scale to 5 units (or remaining distance if less than 5) + float step = std::min(5.0f, distance); + if (distance > 0.001f) + { + dx = dx / distance * step; + dy = dy / distance * step; + } + + // Calculate intermediate position + float newX = botX + dx; + float newY = botY + dy; + + MoveTo(infernoFlameUnit->GetMapId(), newX, newY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + } + + // If victim of ball of flame, keep at least 15f from other party members + if (hasBallOfFlame || hasInfernoFlame) + { + static float const SAFE_DIST = 15.0f; + GuidVector members = AI_VALUE(GuidVector, "group members"); + for (auto const& memberGuid : members) + { + Unit* member = botAI->GetUnit(memberGuid); + if (!member || !member->IsAlive() || member == bot) + continue; + float dist = bot->GetExactDist2d(member); + if (dist < SAFE_DIST) + { + // Move away from this member + float dx = bot->GetPositionX() - member->GetPositionX(); + float dy = bot->GetPositionY() - member->GetPositionY(); + float len = std::sqrt(dx * dx + dy * dy); + if (len < 0.001f) + continue; + dx /= len; + dy /= len; + float moveX = bot->GetPositionX() + dx * (SAFE_DIST - dist + 1.0f); + float moveY = bot->GetPositionY() + dy * (SAFE_DIST - dist + 1.0f); + float moveZ = bot->GetPositionZ(); + if (bot->IsWithinLOS(moveX, moveY, moveZ)) + { + MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED); + } + } + } + } + + return false; +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_BQL.cpp b/src/Ai/Raid/ICC/Action/ICCActions_BQL.cpp new file mode 100644 index 00000000000..4e3602e714e --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_BQL.cpp @@ -0,0 +1,1318 @@ +#include "ICCActions.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "Vehicle.h" +#include "RtiValue.h" +#include "GenericSpellActions.h" +#include "GenericActions.h" +#include "ICCTriggers.h" +#include "Multiplier.h" + +bool IccBqlGroupPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); + if (!boss) + return false; + + Aura* frenzyAura = botAI->GetAura("Frenzied Bloodthirst", bot); + Aura* shadowAura = botAI->GetAura("Swarming Shadows", bot); + bool isTank = botAI->IsTank(bot); + // Handle tank positioning + if (isTank && HandleTankPosition(boss, frenzyAura, shadowAura)) + return true; + + // Handle swarming shadows movement + if (shadowAura && HandleShadowsMovement()) + return true; + + // Handle group positioning + if (!frenzyAura && !shadowAura && HandleGroupPosition(boss, frenzyAura, shadowAura)) + return true; + + return false; +} + +bool IccBqlGroupPositionAction::HandleTankPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura) +{ + if (frenzyAura || shadowAura) + return false; + + // Main tank positioning + if (botAI->IsMainTank(bot) && botAI->HasAggro(boss)) + { + if (bot->GetExactDist2d(ICC_BQL_TANK_POSITION) > 3.0f) + { + MoveTo(bot->GetMapId(), ICC_BQL_TANK_POSITION.GetPositionX(), ICC_BQL_TANK_POSITION.GetPositionY(), + ICC_BQL_TANK_POSITION.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + } + + // Assist tank positioning + if (botAI->IsAssistTank(bot) && !botAI->GetAura("Blood Mirror", bot)) + { + if (Unit* mainTank = AI_VALUE(Unit*, "main tank")) + { + MoveTo(bot->GetMapId(), mainTank->GetPositionX(), mainTank->GetPositionY(), mainTank->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + } + + if (botAI->IsAssistTank(bot) && botAI->GetAura("Blood Mirror", bot) && boss && boss->HealthAbovePct(90)) + return true; // don't do anything to avoid taking bite + + return false; +} + +bool IccBqlGroupPositionAction::HandleShadowsMovement() +{ + float const SAFE_SHADOW_DIST = 4.0f; + float const ARC_STEP = 0.05f; + float const CURVE_SPACING = 15.0f; + int const MAX_CURVES = 3; + float const maxClosestDist = botAI->IsMelee(bot) ? 25.0f : 20.0f; + Position const& center = ICC_BQL_CENTER_POSITION; + float const OUTER_CURVE_PREFERENCE = 200.0f; // Strong preference for outer curves + float const CURVE_SWITCH_PENALTY = 50.0f; // Penalty for switching curves + float const DISTANCE_PENALTY_FACTOR = 100.0f; // Penalty per yard moved from current position + float const MAX_CURVE_JUMP_DIST = 5.0f; // Maximum distance for jumping between curves + + // Track current curve to avoid unnecessary switching (keyed per-instance to avoid + // cross-instance pollution when multiple ICCs run simultaneously) + static std::map, int> botCurrentCurve; + auto curveKey = std::make_pair(bot->GetInstanceId(), bot->GetGUID()); + int currentCurve = botCurrentCurve.count(curveKey) ? botCurrentCurve[curveKey] : 0; + + // Find closest wall path + Position lwall[4] = {ICC_BQL_LWALL1_POSITION, AdjustControlPoint(ICC_BQL_LWALL2_POSITION, center, 1.30f), + AdjustControlPoint(ICC_BQL_LWALL3_POSITION, center, 1.30f), ICC_BQL_LRWALL4_POSITION}; + Position rwall[4] = {ICC_BQL_RWALL1_POSITION, AdjustControlPoint(ICC_BQL_RWALL2_POSITION, center, 1.30f), + AdjustControlPoint(ICC_BQL_RWALL3_POSITION, center, 1.30f), ICC_BQL_LRWALL4_POSITION}; + Position* basePath = (bot->GetExactDist2d(lwall[0]) < bot->GetExactDist2d(rwall[0])) ? lwall : rwall; + + // Find all swarming shadows + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + constexpr int MAX_SHADOW_NPCS = 100; + Unit* shadows[MAX_SHADOW_NPCS]{}; // Reasonable max estimate + int shadowCount = 0; + for (int i = 0; i < npcs.size() && shadowCount < MAX_SHADOW_NPCS; i++) + { + Unit* unit = botAI->GetUnit(npcs[i]); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SWARMING_SHADOWS) + shadows[shadowCount++] = unit; + } + + // Helper lambda to check if a position is inside a shadow + auto IsPositionInShadow = [&](Position const& pos) -> bool + { + for (int i = 0; i < shadowCount; ++i) + { + if (pos.GetExactDist2d(shadows[i]) < SAFE_SHADOW_DIST) + return true; + } + return false; + }; + + // If bot is at the 4th position (end of the wall), move towards 3rd position or center to avoid getting stuck + float distToL4 = bot->GetExactDist2d(lwall[3]); + float distToR4 = bot->GetExactDist2d(rwall[3]); + float const STUCK_DIST = 2.0f; // within 2 yards is considered stuck at the end + + if (distToL4 < STUCK_DIST || distToR4 < STUCK_DIST) + { + // Move towards 3rd position of the same wall, or towards center if blocked + Position target; + if (distToL4 < distToR4) + { + target = lwall[2]; + } + else + { + target = rwall[2]; + } + + float tx = target.GetPositionX(); + float ty = target.GetPositionY(); + float tz = target.GetPositionZ(); + bot->UpdateAllowedPositionZ(tx, ty, tz); + if (!bot->IsWithinLOS(tx, ty, tz) || IsPositionInShadow(Position(tx, ty, tz))) + { + tx = center.GetPositionX(); + ty = center.GetPositionY(); + tz = center.GetPositionZ(); + } + + if (bot->GetExactDist2d(tx, ty) > 1.0f) + { + MoveTo(bot->GetMapId(), tx, ty, tz, false, false, false, true, MovementPriority::MOVEMENT_FORCED, + true, false); + } + return false; + } + + CurveInfo bestCurve; + bestCurve.foundSafe = false; + bestCurve.score = FLT_MAX; + bool foundCurve = false; + + // Keep track of information about all curves for possible fallback + CurveInfo curveInfos[MAX_CURVES]; + for (int i = 0; i < MAX_CURVES; i++) + { + curveInfos[i].foundSafe = false; + curveInfos[i].score = FLT_MAX; + } + + // Evaluate all curves starting from outermost (lowest index) + for (int curveIdx = 0; curveIdx < MAX_CURVES; curveIdx++) + { + float curveShrink = float(curveIdx) * CURVE_SPACING; + float shrinkFactor = 1.30f - (curveShrink / 30.0f); + if (shrinkFactor < 1.0f) + shrinkFactor = 1.0f; + + Position path[4] = {basePath[0], AdjustControlPoint(basePath[1], center, shrinkFactor / 1.30f), + AdjustControlPoint(basePath[2], center, shrinkFactor / 1.30f), basePath[3]}; + + // Find closest point on curve + float minDist = 9999.0f; + float t_closest = 0.0f; + Position closestPoint = path[0]; + + for (float t = 0.0f; t <= 1.0f; t += ARC_STEP) + { + Position pt = CalculateBezierPoint(t, path); + float dist = bot->GetExactDist2d(pt); + if (dist < minDist) + { + minDist = dist; + t_closest = t; + closestPoint = pt; + } + } + + // Check if the closest point is safe + bool closestIsSafe = !IsPositionInShadow(closestPoint); + + // Find closest safe point by searching in both directions from closest point + Position safeMoveTarget = closestPoint; + bool foundSafe = closestIsSafe; + + // Only search for safe spots if the closest point isn't already safe + if (!closestIsSafe) + { + // Find the nearest safe point along the curve, not by direct distance + // but by distance along the curve from the closest point + + // Search forward on curve from closest point + float forwardT = -1.0f; + Position forwardPt; + for (float t = t_closest + ARC_STEP; t <= 1.0f; t += ARC_STEP) + { + Position pt = CalculateBezierPoint(t, path); + if (!IsPositionInShadow(pt)) + { + forwardT = t; + forwardPt = pt; + break; + } + } + + // Search backward on curve from closest point + float backwardT = -1.0f; + Position backwardPt; + for (float t = t_closest - ARC_STEP; t >= 0.0f; t -= ARC_STEP) + { + Position pt = CalculateBezierPoint(t, path); + if (!IsPositionInShadow(pt)) + { + backwardT = t; + backwardPt = pt; + break; + } + } + + // Choose the closest safe point based on curve distance, not direct distance + if (forwardT >= 0 && backwardT >= 0) + { + // Both directions have safe points, choose the closer one by curve distance + if (std::abs(forwardT - t_closest) < std::abs(backwardT - t_closest)) + { + safeMoveTarget = forwardPt; + foundSafe = true; + } + else + { + safeMoveTarget = backwardPt; + foundSafe = true; + } + } + else if (forwardT >= 0) + { + safeMoveTarget = forwardPt; + foundSafe = true; + } + else if (backwardT >= 0) + { + safeMoveTarget = backwardPt; + foundSafe = true; + } + } + + // Score this curve + float distancePenalty = 0.0f; + float score = 0.0f; + + if (foundSafe) + { + // If we found a safe point, penalize based on travel distance along the curve to reach it + float safeDist = bot->GetExactDist2d(safeMoveTarget); + + // Add distance penalty based on how far we need to move along the curve + distancePenalty = safeDist * (1.0f / DISTANCE_PENALTY_FACTOR); + score = safeDist + distancePenalty; + } + else + { + // No safe point found, assign a high score + distancePenalty = minDist * (1.0f / DISTANCE_PENALTY_FACTOR); + score = minDist + distancePenalty + 1000.0f; // Penalty for unsafe position + } + + // Apply strong penalty for curves that are too far + if (minDist > maxClosestDist) + score += 500.0f; + + // Apply penalty for unsafe curves + if (!foundSafe) + score += 1000.0f; + + // Apply curve index preference (strongly prefer outer curves) + score += curveIdx * OUTER_CURVE_PREFERENCE; + + // Apply curve switching penalty + if (curveIdx != currentCurve && currentCurve != 0) + score += CURVE_SWITCH_PENALTY; + + // MORE IMPORTANT: Apply additional curve switching penalty if the bot is far away + // from the target curve (prevent jumping between curves when far away) + if (curveIdx != currentCurve && minDist > MAX_CURVE_JUMP_DIST) + score += 2000.0f; // Strong penalty to prevent jumping between curves + + // Store this curve's info + curveInfos[curveIdx].moveTarget = foundSafe ? safeMoveTarget : closestPoint; + curveInfos[curveIdx].foundSafe = foundSafe; + curveInfos[curveIdx].minDist = minDist; + curveInfos[curveIdx].curveIdx = curveIdx; + curveInfos[curveIdx].score = score; + curveInfos[curveIdx].closestPoint = closestPoint; + curveInfos[curveIdx].t_closest = t_closest; + + // Only update if this curve is better than our current best + if (!foundCurve || score < bestCurve.score) + { + bestCurve = curveInfos[curveIdx]; + foundCurve = true; + } + } + + // Fallback: If we're trying to switch to a far curve and we're not near any curve, + // find and use the closest curve instead of making a direct beeline + if (foundCurve && bestCurve.minDist > MAX_CURVE_JUMP_DIST && bestCurve.curveIdx != currentCurve) + { + // Look for the closest curve first + float closestDist = FLT_MAX; + int closestCurveIdx = -1; + + for (int i = 0; i < MAX_CURVES; i++) + { + if (curveInfos[i].minDist < closestDist) + { + closestDist = curveInfos[i].minDist; + closestCurveIdx = i; + } + } + + // If we found a closer curve, use that instead + if (closestCurveIdx >= 0 && closestCurveIdx != bestCurve.curveIdx) + { + bestCurve = curveInfos[closestCurveIdx]; + } + } + + // Remember the selected curve for next time + if (foundCurve) + { + botCurrentCurve[curveKey] = bestCurve.curveIdx; + } + + // Create a move plan to guide the bot along the curve if necessary + if (foundCurve && bot->GetExactDist2d(bestCurve.moveTarget) > 1.0f) + { + // Final check: ensure we're not moving into a shadow + if (!IsPositionInShadow(bestCurve.moveTarget)) + { + // Get the curve + float curveShrink = float(bestCurve.curveIdx) * CURVE_SPACING; + float shrinkFactor = 1.30f - (curveShrink / 30.0f); + if (shrinkFactor < 1.0f) + shrinkFactor = 1.0f; + + Position path[4] = {basePath[0], AdjustControlPoint(basePath[1], center, shrinkFactor / 1.30f), + AdjustControlPoint(basePath[2], center, shrinkFactor / 1.30f), basePath[3]}; + + // CRITICAL CHANGE: First check if we need to move to the curve + float distToClosestPoint = bot->GetExactDist2d(bestCurve.closestPoint); + + // If we're not on the curve yet, first move to the closest point on the curve + if (distToClosestPoint > 2.0f) + { + botAI->Reset(); + return MoveTo(bot->GetMapId(), bestCurve.closestPoint.GetPositionX(), + bestCurve.closestPoint.GetPositionY(), bestCurve.closestPoint.GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + } + + // Now we know we're on or very close to the curve, so we'll follow it properly + + // Find target point on curve (t_target parameter) + float t_target = 0.0f; + float targetMinDist = 9999.0f; + + for (float t = 0.0f; t <= 1.0f; t += ARC_STEP) + { + Position pt = CalculateBezierPoint(t, path); + float dist = bestCurve.moveTarget.GetExactDist2d(pt); + if (dist < targetMinDist) + { + targetMinDist = dist; + t_target = t; + } + } + + // Find an intermediate point along the curve between closest and target + float t_step = (t_target > bestCurve.t_closest) ? ARC_STEP : -ARC_STEP; + float t_intermediate = bestCurve.t_closest + t_step; + Position intermediateTarget; + bool foundValidIntermediate = false; + + // Limit the distance we move along the curve in one step + float const MAX_CURVE_MOVEMENT = 7.0f; // Max yards to move along curve + float curveDistanceMoved = 0.0f; + Position lastPos = bestCurve.closestPoint; + + while ((t_step > 0 && t_intermediate <= t_target) || (t_step < 0 && t_intermediate >= t_target)) + { + Position pt = CalculateBezierPoint(t_intermediate, path); + + // Check if this point is safe + if (!IsPositionInShadow(pt)) + { + // Calculate distance moved along curve so far + curveDistanceMoved += lastPos.GetExactDist2d(pt); + lastPos = pt; + + // If we've moved the maximum allowed distance, use this position + if (curveDistanceMoved >= MAX_CURVE_MOVEMENT) + { + intermediateTarget = pt; + foundValidIntermediate = true; + break; + } + + // Otherwise, continue moving along the curve + intermediateTarget = pt; + foundValidIntermediate = true; + } + else + { + // We've hit a shadow, stop here + break; + } + + t_intermediate += t_step; + } + + // If we found a valid intermediate point, use it + if (foundValidIntermediate) + { + botAI->Reset(); + MoveTo(bot->GetMapId(), intermediateTarget.GetPositionX(), intermediateTarget.GetPositionY(), + intermediateTarget.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + botAI->Reset(); + // Fallback to direct movement to the target point on the curve + MoveTo(bot->GetMapId(), bestCurve.moveTarget.GetPositionX(), bestCurve.moveTarget.GetPositionY(), + bestCurve.moveTarget.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + + return false; +} + +Position IccBqlGroupPositionAction::AdjustControlPoint(Position const& wall, Position const& center, float factor) +{ + float dx = wall.GetPositionX() - center.GetPositionX(); + float dy = wall.GetPositionY() - center.GetPositionY(); + float dz = wall.GetPositionZ() - center.GetPositionZ(); + return Position(center.GetPositionX() + dx * factor, center.GetPositionY() + dy * factor, + center.GetPositionZ() + dz * factor); +} + +Position IccBqlGroupPositionAction::CalculateBezierPoint(float t, Position const path[4]) +{ + float omt = 1 - t; + float omt2 = omt * omt; + float omt3 = omt2 * omt; + float t2 = t * t; + float t3 = t2 * t; + + float x = omt3 * path[0].GetPositionX() + 3 * omt2 * t * path[1].GetPositionX() + + 3 * omt * t2 * path[2].GetPositionX() + t3 * path[3].GetPositionX(); + + float y = omt3 * path[0].GetPositionY() + 3 * omt2 * t * path[1].GetPositionY() + + 3 * omt * t2 * path[2].GetPositionY() + t3 * path[3].GetPositionY(); + + float z = omt3 * path[0].GetPositionZ() + 3 * omt2 * t * path[1].GetPositionZ() + + 3 * omt * t2 * path[2].GetPositionZ() + t3 * path[3].GetPositionZ(); + + return Position(x, y, z); +} + +bool IccBqlGroupPositionAction::HandleGroupPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura) +{ + if (frenzyAura || shadowAura) + return false; + + GuidVector members = AI_VALUE(GuidVector, "group members"); + bool isRanged = botAI->IsRanged(bot); + bool isTank = botAI->IsTank(bot); + bool isMeleeDps = botAI->IsMelee(bot) && !isTank; + + // Air-phase latch: only arm once boss has been anchored at tank pos (ground phase + // established). Prevents false-trigger at pull when boss comes near center. + // Disarm when boss returns to tank pos (ground phase resumed after landing). + // Keyed per-instance so concurrent ICC raids don't share the latch. + static std::map groundPhaseEstablishedByInstance; + // Tracks airborne state on the previous tick, so we can detect the air->ground edge. + static std::map bossWasAirborneByInstance; + // Armed when boss lands from air, disarmed when boss returns to tank pos. + // While armed, bots skip the pre-air center stack so they don't bunch up at center + // during the post-air walk-back and die to lingering AoE. + static std::map postAirLandingByInstance; + uint32 instanceId = bot->GetInstanceId(); + bool& groundPhaseEstablished = groundPhaseEstablishedByInstance[instanceId]; + bool& wasAirborne = bossWasAirborneByInstance[instanceId]; + bool& postAirLanding = postAirLandingByInstance[instanceId]; + float bossFromTank = boss->GetExactDist2d(ICC_BQL_TANK_POSITION); + float bossFromCenter = boss->GetExactDist2d(ICC_BQL_CENTER_POSITION); + bool bossAirborne = (boss->GetPositionZ() - ICC_BQL_CENTER_POSITION.GetPositionZ()) > 5.0f; + + // Landing edge: arm post-air latch + if (wasAirborne && !bossAirborne) + postAirLanding = true; + wasAirborne = bossAirborne; + + if (!bossAirborne && bossFromTank < 10.0f) + { + groundPhaseEstablished = true; + postAirLanding = false; + } + + bool bossMovingToCenter = groundPhaseEstablished && !bossAirborne && !postAirLanding && + bossFromCenter < 20.0f && bossFromTank > 10.0f; + bool isAirPhase = bossAirborne || bossMovingToCenter; + + // Pre-airborne: nitro boost + move all bots to center (not ring slots yet) + if (bossMovingToCenter) + { + float cx = ICC_BQL_CENTER_POSITION.GetPositionX(); + float cy = ICC_BQL_CENTER_POSITION.GetPositionY(); + float cz = ICC_BQL_CENTER_POSITION.GetPositionZ(); + if (bot->GetExactDist2d(cx, cy) > 2.0f && bot->IsWithinLOS(cx, cy, cz)) + { + MoveTo(bot->GetMapId(), cx, cy, cz, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + return true; // block until at center + } + return false; // at center, let combat rotation run + } + + // Air phase: every bot spreads around room center on concentric rings, hunters outer + if (bossAirborne) + { + // Bloodbolt Whirl is heavy raid-wide damage — pop personal defensive CD to survive + if (boss->FindCurrentSpellBySpellId(SPELL_BLOODBOLT_WHIRL)) + { + static char const* defensives[] = { + "shield wall", "last stand", "icebound fortitude", "survival instincts", + "barkskin", "dispersion", "ice block", "divine shield", "divine protection", + "evasion", "cloak of shadows", "deterrence", "shamanistic rage" + }; + for (char const* spell : defensives) + { + if (botAI->CanCastSpell(spell, bot)) + { + botAI->CastSpell(spell, bot); + break; + } + } + } + + float const SHADOW_AVOID_DIST = 7.0f; + + std::vector hunters; + std::vector nonHunters; + std::vector tanks; + for (auto const& guid : members) + { + Unit* member = botAI->GetUnit(guid); + if (!member || !member->IsAlive()) + continue; + Player* player = member->ToPlayer(); + if (!player) + continue; + if (!sPlayerbotsMgr.GetPlayerbotAI(player)) + continue; + // Frenzied biters roam freely to reach their bite target — exclude from slot pool + // (and tank stack) so we don't reserve a slot for them and so other bots can use + // any slot they left. + if (botAI->GetAura("Frenzied Bloodthirst", player)) + continue; + if (botAI->IsTank(player)) + { + tanks.push_back(player); + continue; + } + if (player->getClass() == CLASS_HUNTER) + hunters.push_back(player); + else + nonHunters.push_back(player); + } + + auto guidSort = [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }; + std::sort(hunters.begin(), hunters.end(), guidSort); + std::sort(nonHunters.begin(), nonHunters.end(), guidSort); + std::sort(tanks.begin(), tanks.end(), guidSort); + + // Tanks stack together at lowest-GUID tank. Anchor tank sits at fixed center position. + if (isTank) + { + Player* anchorTank = tanks.empty() ? nullptr : tanks.front(); + float tx, ty, tz; + if (anchorTank == bot || !anchorTank) + { + tx = ICC_BQL_CENTER_POSITION.GetPositionX(); + ty = ICC_BQL_CENTER_POSITION.GetPositionY(); + tz = ICC_BQL_CENTER_POSITION.GetPositionZ(); + } + else + { + tx = anchorTank->GetPositionX(); + ty = anchorTank->GetPositionY(); + tz = anchorTank->GetPositionZ(); + } + if (bot->GetExactDist2d(tx, ty) > 2.0f && bot->IsWithinLOS(tx, ty, tz)) + { + MoveTo(bot->GetMapId(), tx, ty, tz, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + return true; + } + return false; + } + + // Roster order: hunters first (they get outer slots), then others (inner slots) + std::vector roster; + roster.insert(roster.end(), hunters.begin(), hunters.end()); + roster.insert(roster.end(), nonHunters.begin(), nonHunters.end()); + + int myIndex = -1; + for (int i = 0; i < (int)roster.size(); i++) + { + if (roster[i] == bot) + { + myIndex = i; + break; + } + } + if (myIndex < 0) + return false; + + // 4 rings around boss center, 73 slots total, all pairs >=7f apart, ring gap 8f + struct AirSlot { float radius; float angle; }; + float const D2R = float(M_PI) / 180.0f; + static AirSlot const allSlots[] = { + // Inner ring r=10f, 8 slots at 45° — chord 7.65f + {10.0f, 0.0f * D2R}, {10.0f, 45.0f * D2R}, {10.0f, 90.0f * D2R}, {10.0f, 135.0f * D2R}, + {10.0f, 180.0f * D2R}, {10.0f, 225.0f * D2R}, {10.0f, 270.0f * D2R}, {10.0f, 315.0f * D2R}, + // Ring 2 r=18f, 15 slots at 24° — chord 7.5f + {18.0f, 0.0f * D2R}, {18.0f, 24.0f * D2R}, {18.0f, 48.0f * D2R}, {18.0f, 72.0f * D2R}, + {18.0f, 96.0f * D2R}, {18.0f, 120.0f * D2R}, {18.0f, 144.0f * D2R}, {18.0f, 168.0f * D2R}, + {18.0f, 192.0f * D2R}, {18.0f, 216.0f * D2R}, {18.0f, 240.0f * D2R}, {18.0f, 264.0f * D2R}, + {18.0f, 288.0f * D2R}, {18.0f, 312.0f * D2R}, {18.0f, 336.0f * D2R}, + // Ring 3 r=26f, 20 slots at 18° — chord 8.1f + {26.0f, 0.0f * D2R}, {26.0f, 18.0f * D2R}, {26.0f, 36.0f * D2R}, {26.0f, 54.0f * D2R}, + {26.0f, 72.0f * D2R}, {26.0f, 90.0f * D2R}, {26.0f, 108.0f * D2R}, {26.0f, 126.0f * D2R}, + {26.0f, 144.0f * D2R}, {26.0f, 162.0f * D2R}, {26.0f, 180.0f * D2R}, {26.0f, 198.0f * D2R}, + {26.0f, 216.0f * D2R}, {26.0f, 234.0f * D2R}, {26.0f, 252.0f * D2R}, {26.0f, 270.0f * D2R}, + {26.0f, 288.0f * D2R}, {26.0f, 306.0f * D2R}, {26.0f, 324.0f * D2R}, {26.0f, 342.0f * D2R}, + // Outer ring r=34f, 30 slots at 12° — chord 7.1f + {34.0f, 0.0f * D2R}, {34.0f, 12.0f * D2R}, {34.0f, 24.0f * D2R}, {34.0f, 36.0f * D2R}, + {34.0f, 48.0f * D2R}, {34.0f, 60.0f * D2R}, {34.0f, 72.0f * D2R}, {34.0f, 84.0f * D2R}, + {34.0f, 96.0f * D2R}, {34.0f, 108.0f * D2R}, {34.0f, 120.0f * D2R}, {34.0f, 132.0f * D2R}, + {34.0f, 144.0f * D2R}, {34.0f, 156.0f * D2R}, {34.0f, 168.0f * D2R}, {34.0f, 180.0f * D2R}, + {34.0f, 192.0f * D2R}, {34.0f, 204.0f * D2R}, {34.0f, 216.0f * D2R}, {34.0f, 228.0f * D2R}, + {34.0f, 240.0f * D2R}, {34.0f, 252.0f * D2R}, {34.0f, 264.0f * D2R}, {34.0f, 276.0f * D2R}, + {34.0f, 288.0f * D2R}, {34.0f, 300.0f * D2R}, {34.0f, 312.0f * D2R}, {34.0f, 324.0f * D2R}, + {34.0f, 336.0f * D2R}, {34.0f, 348.0f * D2R}, + }; + int const totalSlots = sizeof(allSlots) / sizeof(allSlots[0]); + int const OUTER_RING_START = 8 + 15 + 20; // first index of outer ring + int const MID_RING_START = 8 + 15; + + float const cx = ICC_BQL_CENTER_POSITION.GetPositionX(); + float const cy = ICC_BQL_CENTER_POSITION.GetPositionY(); + float const cz = ICC_BQL_CENTER_POSITION.GetPositionZ(); + + // Shadow safety (rare during air phase but possible at transition) + std::list shadowList; + bot->GetCreatureListWithEntryInGrid(shadowList, NPC_SWARMING_SHADOWS, 100.0f); + auto IsInShadow = [&](float x, float y) -> bool + { + for (Creature* shadow : shadowList) + { + if (!shadow->IsAlive()) + continue; + float sdx = x - shadow->GetPositionX(); + float sdy = y - shadow->GetPositionY(); + if ((sdx * sdx + sdy * sdy) < SHADOW_AVOID_DIST * SHADOW_AVOID_DIST) + return true; + } + return false; + }; + auto AirSlotPos = [&](int idx, float& x, float& y) + { + float r = allSlots[idx].radius; + float a = allSlots[idx].angle; + x = cx + r * std::cos(a); + y = cy + r * std::sin(a); + }; + auto IsAirSlotSafe = [&](int idx) -> bool + { + float sx, sy; + AirSlotPos(idx, sx, sy); + return !IsInShadow(sx, sy); + }; + + // Persistent memory separate from ground phase (different slot sets). + // Keyed per-instance to avoid cross-instance pollution. + static std::map, int> airSlotMemory; + uint32 const airInstanceId = bot->GetInstanceId(); + + std::vector reservedSlots; + for (Player* rp : roster) + { + if (rp == bot) + continue; + auto it = airSlotMemory.find(std::make_pair(airInstanceId, rp->GetGUID())); + if (it != airSlotMemory.end() && it->second >= 0 && it->second < totalSlots) + reservedSlots.push_back(it->second); + } + auto IsReserved = [&](int s) -> bool + { + return std::find(reservedSlots.begin(), reservedSlots.end(), s) != reservedSlots.end(); + }; + + bool botInShadow = IsInShadow(bot->GetPositionX(), bot->GetPositionY()); + + int myAssignedSlot = -1; + auto myAirKey = std::make_pair(airInstanceId, bot->GetGUID()); + + auto myMemIt = airSlotMemory.find(myAirKey); + if (myMemIt != airSlotMemory.end()) + { + int prev = myMemIt->second; + if (prev >= 0 && prev < totalSlots && !IsReserved(prev) && IsAirSlotSafe(prev)) + myAssignedSlot = prev; + } + + // Pick a new slot — hunters start from outer ring going in, others from inner going out + if (myAssignedSlot < 0) + { + bool isHunter = (bot->getClass() == CLASS_HUNTER); + for (int attempt = 0; attempt < totalSlots; attempt++) + { + int s; + if (isHunter) + s = (totalSlots - 1) - ((myIndex + attempt) % totalSlots); + else + s = (myIndex + attempt) % totalSlots; + if (IsReserved(s)) + continue; + if (IsAirSlotSafe(s)) + { + myAssignedSlot = s; + break; + } + } + } + + if (myAssignedSlot < 0) + { + airSlotMemory.erase(myAirKey); + + // No safe slot available — if standing in shadow, flee away from nearest shadow + if (botInShadow) + { + Creature* nearest = nullptr; + float bestDist = 1e9f; + for (Creature* shadow : shadowList) + { + if (!shadow->IsAlive()) + continue; + float d = bot->GetExactDist2d(shadow); + if (d < bestDist) + { + bestDist = d; + nearest = shadow; + } + } + if (nearest) + { + float dx = bot->GetPositionX() - nearest->GetPositionX(); + float dy = bot->GetPositionY() - nearest->GetPositionY(); + float mag = std::sqrt(dx * dx + dy * dy); + if (mag > 0.001f) + { + float fleeX = bot->GetPositionX() + (dx / mag) * 10.0f; + float fleeY = bot->GetPositionY() + (dy / mag) * 10.0f; + float fleeZ = bot->GetPositionZ(); + bot->UpdateAllowedPositionZ(fleeX, fleeY, fleeZ); + MoveTo(bot->GetMapId(), fleeX, fleeY, fleeZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + } + } + else + { + airSlotMemory[myAirKey] = myAssignedSlot; + + float candidateX, candidateY; + AirSlotPos(myAssignedSlot, candidateX, candidateY); + float candidateZ = cz; + bot->UpdateAllowedPositionZ(candidateX, candidateY, candidateZ); + + MovementPriority prio = botInShadow ? MovementPriority::MOVEMENT_FORCED : MovementPriority::MOVEMENT_COMBAT; + float moveGate = botInShadow ? 0.0f : 1.0f; + + if (bot->IsWithinLOS(candidateX, candidateY, candidateZ) && + bot->GetExactDist2d(candidateX, candidateY) > moveGate) + { + MoveTo(bot->GetMapId(), candidateX, candidateY, candidateZ, false, false, false, true, + prio, true, false); + // Still moving to slot — block combat so we actually get there + return true; + } + } + // At slot (or fleeing shadow) — let combat rotation attack/heal + return false; + } + + if (isMeleeDps && !isAirPhase) + { + if (bot->GetDistance2d(boss) > 2.0f) + { + MoveTo(bot->GetMapId(), boss->GetPositionX(), boss->GetPositionY(), boss->GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + } + return false; + } + + // Ground phase ranged positioning — persistent slot assignment, only affected bots reassign + if (isRanged && !isAirPhase) + { + float const SHADOW_AVOID_DIST = 7.0f; + + // Gather ranged bots + melee bots (skip real players) + std::vector hunters; + std::vector otherRanged; + std::vector meleeBots; + for (auto const& guid : members) + { + Unit* member = botAI->GetUnit(guid); + if (!member || !member->IsAlive()) + continue; + Player* player = member->ToPlayer(); + if (!player) + continue; + if (!sPlayerbotsMgr.GetPlayerbotAI(player)) + continue; + if (botAI->IsRanged(player)) + { + if (player->getClass() == CLASS_HUNTER) + hunters.push_back(player); + else + otherRanged.push_back(player); + } + else if (botAI->IsMelee(player)) + { + meleeBots.push_back(player); + } + } + + auto guidSort = [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }; + std::sort(hunters.begin(), hunters.end(), guidSort); + std::sort(otherRanged.begin(), otherRanged.end(), guidSort); + std::sort(meleeBots.begin(), meleeBots.end(), guidSort); + + std::vector roster; + roster.insert(roster.end(), hunters.begin(), hunters.end()); + roster.insert(roster.end(), otherRanged.begin(), otherRanged.end()); + + int myIndex = -1; + for (int i = 0; i < (int)roster.size(); i++) + { + if (roster[i] == bot) + { + myIndex = i; + break; + } + } + if (myIndex < 0) + return false; + + // Fixed world-space anchor and direction + float const anchorX = ICC_BQL_TANK_POSITION.GetPositionX(); + float const anchorY = ICC_BQL_TANK_POSITION.GetPositionY(); + float const anchorZ = ICC_BQL_TANK_POSITION.GetPositionZ(); + float baseDx = ICC_BQL_CENTER_POSITION.GetPositionX() - anchorX; + float baseDy = ICC_BQL_CENTER_POSITION.GetPositionY() - anchorY; + float const anchorToCenter = std::atan2(baseDy, baseDx); + + // 19 fixed slots: 6 inner (20f) + 7 middle (27f) + 6 outer (35f) + struct Slot { float radius; float angleOffset; }; + float const D2R = float(M_PI) / 180.0f; + static Slot const allSlots[] = { + {20.0f, -60.0f * D2R}, {20.0f, -36.0f * D2R}, {20.0f, -12.0f * D2R}, + {20.0f, 12.0f * D2R}, {20.0f, 36.0f * D2R}, {20.0f, 60.0f * D2R}, + {27.0f, -45.0f * D2R}, {27.0f, -30.0f * D2R}, {27.0f, -15.0f * D2R}, + {27.0f, 0.0f}, {27.0f, 15.0f * D2R}, {27.0f, 30.0f * D2R}, + {27.0f, 45.0f * D2R}, + {35.0f, -60.0f * D2R}, {35.0f, -36.0f * D2R}, {35.0f, -12.0f * D2R}, + {35.0f, 12.0f * D2R}, {35.0f, 36.0f * D2R}, {35.0f, 60.0f * D2R}, + }; + int const totalSlots = sizeof(allSlots) / sizeof(allSlots[0]); + + // Shadow list + safety check + std::list shadowList; + bot->GetCreatureListWithEntryInGrid(shadowList, NPC_SWARMING_SHADOWS, 100.0f); + auto IsInShadow = [&](float x, float y) -> bool + { + for (Creature* shadow : shadowList) + { + if (!shadow->IsAlive()) + continue; + float sdx = x - shadow->GetPositionX(); + float sdy = y - shadow->GetPositionY(); + if ((sdx * sdx + sdy * sdy) < SHADOW_AVOID_DIST * SHADOW_AVOID_DIST) + return true; + } + return false; + }; + auto SlotPos = [&](int idx, float& x, float& y) + { + float angle = anchorToCenter + allSlots[idx].angleOffset; + float r = allSlots[idx].radius; + x = anchorX + r * std::cos(angle); + y = anchorY + r * std::sin(angle); + }; + auto IsSlotSafe = [&](int idx) -> bool + { + float sx, sy; + SlotPos(idx, sx, sy); + return !IsInShadow(sx, sy); + }; + + // Persistent per-bot slot memory shared across all bots. + // Keyed per-instance to avoid cross-instance pollution. + static std::map, int> botSlotMemory; + uint32 const groundInstanceId = bot->GetInstanceId(); + + // Collect every OTHER bot's remembered slot as "reserved" — each bot owns its own + // memory and we must respect their claim, even if we can't see the same shadows. + // This prevents cascading reassignments when one bot moves. + std::vector reservedSlots; + for (Player* rp : roster) + { + if (rp == bot) + continue; + auto it = botSlotMemory.find(std::make_pair(groundInstanceId, rp->GetGUID())); + if (it != botSlotMemory.end() && it->second >= 0 && it->second < totalSlots) + reservedSlots.push_back(it->second); + } + + auto IsReserved = [&](int s) -> bool + { + return std::find(reservedSlots.begin(), reservedSlots.end(), s) != reservedSlots.end(); + }; + + int myAssignedSlot = -1; + bool myFellBack = false; + auto myGroundKey = std::make_pair(groundInstanceId, bot->GetGUID()); + + // Step 1: keep my remembered slot if still safe and not reserved by someone else + auto myMemIt = botSlotMemory.find(myGroundKey); + if (myMemIt != botSlotMemory.end()) + { + int prev = myMemIt->second; + if (prev >= 0 && prev < totalSlots && !IsReserved(prev) && IsSlotSafe(prev)) + myAssignedSlot = prev; + } + + // Step 2: need a new slot — pick first safe slot not reserved by others + if (myAssignedSlot < 0) + { + // Prefer slots by roster seniority (hunters/lower-GUID first get inner slots) + // Start from index myIndex to fall into "my natural zone", wrap around + for (int attempt = 0; attempt < totalSlots; attempt++) + { + int s = (myIndex + attempt) % totalSlots; + if (IsReserved(s)) + continue; + if (IsSlotSafe(s)) + { + myAssignedSlot = s; + break; + } + } + } + + if (myAssignedSlot < 0) + { + // No safe unreserved slot — fall back to melee. Forget my slot so others can use it. + botSlotMemory.erase(myGroundKey); + myFellBack = true; + } + else + { + botSlotMemory[myGroundKey] = myAssignedSlot; + } + + if (myAssignedSlot >= 0) + { + float candidateX, candidateY; + SlotPos(myAssignedSlot, candidateX, candidateY); + float candidateZ = anchorZ; + bot->UpdateAllowedPositionZ(candidateX, candidateY, candidateZ); + + if (bot->IsWithinLOS(candidateX, candidateY, candidateZ) && + bot->GetExactDist2d(candidateX, candidateY) > 1.0f) + { + MoveTo(bot->GetMapId(), candidateX, candidateY, candidateZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + else if (myFellBack && !meleeBots.empty()) + { + // No safe slot — flee to lowest-GUID melee bot until a safe slot frees up + Player* anchor = meleeBots.front(); + if (anchor != bot) + { + float ax = anchor->GetPositionX(); + float ay = anchor->GetPositionY(); + float az = anchor->GetPositionZ(); + if (bot->GetExactDist2d(ax, ay) > 3.0f && bot->IsWithinLOS(ax, ay, az)) + { + MoveTo(bot->GetMapId(), ax, ay, az, false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + } + } + + return false; +} + +bool IccBqlPactOfDarkfallenAction::Execute(Event /*event*/) +{ + // Check if bot has Pact of the Darkfallen + if (!botAI->GetAura("Pact of the Darkfallen", bot)) + return false; + Group* group = bot->GetGroup(); + if (!group) + return false; + + // Find other players with Pact of the Darkfallen + Player* tankWithAura = nullptr; + std::vector playersWithAura; + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || member == bot) + continue; + if (botAI->GetAura("Pact of the Darkfallen", member)) + { + playersWithAura.push_back(member); + if (botAI->IsTank(member)) + tankWithAura = member; + } + } + + if (playersWithAura.empty()) + return false; + + // Determine target position + Position targetPos; + if (tankWithAura) + { + // If there's a tank with aura, everyone moves to the tank (including the tank itself for center positioning) + if (botAI->IsTank(bot)) + { + // If current bot is the tank, stay put or move slightly for better positioning + targetPos.Relocate(bot); + } + else + { + // Non-tank bots move to the tank + targetPos.Relocate(tankWithAura); + } + } + else if (playersWithAura.size() >= 2) + { + // Calculate center position of all players with aura (including bot) + CalculateCenterPosition(targetPos, playersWithAura); + } + else if (playersWithAura.size() == 1) + { + // Move to the single other player with aura + targetPos.Relocate(playersWithAura[0]); + } + else + { + // No valid movement case found + return true; + } + + // Move to target position if needed + return MoveToTargetPosition(targetPos, playersWithAura.size() + 1); // +1 to include the bot itself +} + +bool IccBqlPactOfDarkfallenAction::CalculateCenterPosition(Position& targetPos, const std::vector& playersWithAura) +{ + float sumX = bot->GetPositionX(); + float sumY = bot->GetPositionY(); + float sumZ = bot->GetPositionZ(); + + // Add positions of all other players with aura + for (Player* player : playersWithAura) + { + sumX += player->GetPositionX(); + sumY += player->GetPositionY(); + sumZ += player->GetPositionZ(); + } + + // Calculate average position (center) + int totalPlayers = playersWithAura.size() + 1; // +1 for the bot itself + targetPos.Relocate(sumX / totalPlayers, sumY / totalPlayers, sumZ / totalPlayers); + + return false; +} + +bool IccBqlPactOfDarkfallenAction::MoveToTargetPosition(Position const& targetPos, int auraCount) +{ + float const POSITION_TOLERANCE = 0.1f; + float distance = bot->GetDistance(targetPos); + if (distance <= POSITION_TOLERANCE) + return true; + + // Calculate movement increment + float dx = targetPos.GetPositionX() - bot->GetPositionX(); + float dy = targetPos.GetPositionY() - bot->GetPositionY(); + float dz = targetPos.GetPositionZ() - bot->GetPositionZ(); + float len = sqrt(dx * dx + dy * dy); + + float moveX, moveY, moveZ; + if (len > 5.0f && auraCount <= 2) + { + dx /= len; + dy /= len; + moveX = bot->GetPositionX() + dx * 5.0f; + moveY = bot->GetPositionY() + dy * 5.0f; + moveZ = bot->GetPositionZ() + (dz / distance) * 5.0f; + } + else + { + moveX = targetPos.GetPositionX(); + moveY = targetPos.GetPositionY(); + moveZ = targetPos.GetPositionZ(); + } + + botAI->Reset(); + MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); + return false; +} + +bool IccBqlVampiricBiteAction::Execute(Event /*event*/) +{ + // Only act when bot has Frenzied Bloodthirst + if (!botAI->GetAura("Frenzied Bloodthirst", bot)) + return false; + + // Bloodbolt Whirl defensive CDs (divine shield, ice block, cloak) make bot untargetable + // and block the bite cast. Strip them now so the bite can land. + botAI->RemoveAura("divine shield"); + botAI->RemoveAura("ice block"); + botAI->RemoveAura("cloak of shadows"); + botAI->RemoveAura("deterrence"); + + float const BITE_RANGE = 4.0f; + Group* group = bot->GetGroup(); + if (!group) + return false; + + // Find best target + Player* target = FindBestBiteTarget(group); + if (!target) + return false; + + // Handle movement or casting + if (bot->GetExactDist2d(target) > BITE_RANGE) + return MoveTowardsTarget(target); + + return CastVampiricBite(target); +} + +Player* IccBqlVampiricBiteAction::FindBestBiteTarget(Group* group) +{ + // Collect frenzied biters (sorted by GUID) and all candidate targets. + // Lower-GUID biters claim their nearest valid target first; higher-GUID + // biters skip claimed targets. Deterministic across all bots so two biters + // never converge on the same victim. + std::vector biters; + std::vector dpsHealCandidates; + std::vector tankCandidates; + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (botAI->GetAura("Frenzied Bloodthirst", member)) + { + biters.push_back(member); + continue; + } + + // Skip already-bitten / shadowed / mind-controlled + if (botAI->GetAura("Essence of the Blood Queen", member) || + botAI->GetAura("Uncontrollable Frenzy", member) || + botAI->GetAura("Swarming Shadows", member)) + continue; + + if (botAI->IsTank(member)) + tankCandidates.push_back(member); + else if (botAI->IsDps(member) || botAI->IsHeal(member)) + dpsHealCandidates.push_back(member); + } + + std::sort(biters.begin(), biters.end(), + [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); + + auto pickFromPool = [&](std::vector& pool) -> Player* + { + std::set claimed; + Player* myPick = nullptr; + for (Player* biter : biters) + { + float bestDist = FLT_MAX; + Player* bestTarget = nullptr; + for (Player* cand : pool) + { + if (claimed.count(cand->GetGUID())) + continue; + float d = biter->GetDistance(cand); + if (d < bestDist) + { + bestDist = d; + bestTarget = cand; + } + } + if (!bestTarget) + continue; + claimed.insert(bestTarget->GetGUID()); + if (biter == bot) + { + myPick = bestTarget; + break; + } + } + return myPick; + }; + + if (Player* pick = pickFromPool(dpsHealCandidates)) + return pick; + + // Fallback: tanks only when no DPS/heal target available + return pickFromPool(tankCandidates); +} + +bool IccBqlVampiricBiteAction::IsInvalidTarget(Player* player) +{ + return botAI->GetAura("Frenzied Bloodthirst", player) || botAI->GetAura("Essence of the Blood Queen", player) || + botAI->GetAura("Uncontrollable Frenzy", player) || botAI->GetAura("Swarming Shadows", player); +} + +bool IccBqlVampiricBiteAction::MoveTowardsTarget(Player* target) +{ + if (IsInvalidTarget(target) || !target->IsAlive()) + return false; + + float x = target->GetPositionX(); + float y = target->GetPositionY(); + float z = target->GetPositionZ(); + + // Don't gate movement on LOS — if blocked, take a step toward target so LOS + // can clear next tick. Multiplier zeroes all non-bite actions for biters, + // so silently aborting here would idle the bot until the aura expires. + + float dx = x - bot->GetPositionX(); + float dy = y - bot->GetPositionY(); + float dz = z - bot->GetPositionZ(); + float len = sqrt(dx * dx + dy * dy); + + float moveX, moveY, moveZ; + if (len > 5.0f) + { + dx /= len; + dy /= len; + moveX = bot->GetPositionX() + dx * 5.0f; + moveY = bot->GetPositionY() + dy * 5.0f; + moveZ = bot->GetPositionZ() + (dz / len) * 5.0f; + } + else + { + moveX = x; + moveY = y; + moveZ = z; + } + + MoveTo(target->GetMapId(), moveX, moveY, moveZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED); + + return false; +} + +bool IccBqlVampiricBiteAction::CastVampiricBite(Player* target) +{ + if (IsInvalidTarget(target) || !target->IsAlive()) + return false; + + return botAI->CanCastSpell("Vampiric Bite", target) && botAI->CastSpell("Vampiric Bite", target); +} \ No newline at end of file diff --git a/src/Ai/Raid/ICC/Action/ICCActions_DBS.cpp b/src/Ai/Raid/ICC/Action/ICCActions_DBS.cpp new file mode 100644 index 00000000000..648c7ff8a4a --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_DBS.cpp @@ -0,0 +1,504 @@ + +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "Multiplier.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "ICCActions.h" +#include "ICCTriggers.h" +#include "RtiValue.h" +#include "Vehicle.h" + +bool IccDbsTankPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); + if (!boss) + return false; + + // Class-specific taunt with forced cooldown reset + auto CastClassTaunt = [&](Unit* target) -> bool + { + if (!target || !target->IsAlive()) + return false; + + switch (bot->getClass()) + { + case CLASS_PALADIN: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + } + case CLASS_DEATH_KNIGHT: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + } + case CLASS_DRUID: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + } + case CLASS_WARRIOR: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + } + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; + }; + + if (botAI->IsTank(bot)) + { + bool const hasRuneOfBlood = botAI->GetAura("Rune of Blood", bot) != nullptr; + + if (hasRuneOfBlood) + { + // Stop attacking boss but still taunt loose blood beasts + if (bot->GetVictim() == boss) + bot->AttackStop(); + + std::array const beastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3, + NPC_BLOOD_BEAST4}; + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + bool const isBloodBeast = + std::find(beastEntries.begin(), beastEntries.end(), unit->GetEntry()) != beastEntries.end(); + if (!isBloodBeast) + continue; + + Unit* victim = unit->GetVictim(); + Player* victimPlayer = victim ? victim->ToPlayer() : nullptr; + if (!victimPlayer || !botAI->IsTank(victimPlayer)) + { + CastClassTaunt(unit); + break; + } + } + + if (bot->GetExactDist2d(ICC_DBS_TANK_POSITION) > 5.0f) + return MoveTo(bot->GetMapId(), ICC_DBS_TANK_POSITION.GetPositionX(), + ICC_DBS_TANK_POSITION.GetPositionY(), ICC_DBS_TANK_POSITION.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_NORMAL); + return true; + } + + // Tank without Rune of Blood: taunt boss if current tank has the debuff + Unit* currentTarget = boss->GetVictim(); + if (currentTarget && currentTarget != bot && botAI->GetAura("Rune of Blood", currentTarget)) + CastClassTaunt(boss); + + // Taunt any blood beasts not targeting a tank + std::array const bloodBeastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3, + NPC_BLOOD_BEAST4}; + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + bool const isBloodBeast = std::find(bloodBeastEntries.begin(), bloodBeastEntries.end(), unit->GetEntry()) != + bloodBeastEntries.end(); + if (!isBloodBeast) + continue; + + Unit* victim = unit->GetVictim(); + Player* victimPlayer = victim ? victim->ToPlayer() : nullptr; + if (!victimPlayer || !botAI->IsTank(victimPlayer)) + { + CastClassTaunt(unit); + break; + } + } + + if (bot->GetExactDist2d(ICC_DBS_TANK_POSITION) > 5.0f) + return MoveTo(bot->GetMapId(), ICC_DBS_TANK_POSITION.GetPositionX(), ICC_DBS_TANK_POSITION.GetPositionY(), + ICC_DBS_TANK_POSITION.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + + return false; + } + + if (!botAI->IsTank(bot)) + { + if (CrowdControlBloodBeasts()) + return true; + } + + // Handle ranged and healer positioning + if (botAI->IsRanged(bot) || botAI->IsHeal(bot)) + { + // Handle evasion from blood beasts + if (EvadeBloodBeasts()) + return true; + + // Position in formation + return PositionInRangedFormation(); + } + + return false; +} + +bool IccDbsTankPositionAction::CrowdControlBloodBeasts() +{ + std::array const bloodBeastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3, + NPC_BLOOD_BEAST4}; + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + // Check if this is a blood beast + bool const isBloodBeast = + std::find(bloodBeastEntries.begin(), bloodBeastEntries.end(), unit->GetEntry()) != bloodBeastEntries.end(); + + if (!isBloodBeast) + continue; + + switch (bot->getClass()) + { + case CLASS_MAGE: + { + if (!botAI->HasAura("Frost Nova", unit)) + return botAI->CastSpell("Frost Nova", unit); + break; + } + case CLASS_DRUID: + { + if (!botAI->HasAura("Entangling Roots", unit)) + return botAI->CastSpell("Entangling Roots", unit); + break; + } + case CLASS_PALADIN: + { + if (!botAI->HasAura("Hammer of Justice", unit)) + return botAI->CastSpell("Hammer of Justice", unit); + break; + } + case CLASS_WARRIOR: + { + if (!botAI->HasAura("Hamstring", unit)) + return botAI->CastSpell("Hamstring", unit); + break; + } + case CLASS_HUNTER: + { + if (!botAI->HasAura("Concussive Shot", unit)) + return botAI->CastSpell("Concussive Shot", unit); + break; + } + case CLASS_ROGUE: + { + if (!botAI->HasAura("Kidney Shot", unit)) + return botAI->CastSpell("Kidney Shot", unit); + break; + } + case CLASS_SHAMAN: + { + if (!botAI->HasAura("Frost Shock", unit)) + return botAI->CastSpell("Frost Shock", unit); + break; + } + case CLASS_DEATH_KNIGHT: + { + if (!botAI->HasAura("Chains of Ice", unit)) + return botAI->CastSpell("Chains of Ice", unit); + break; + } + case CLASS_PRIEST: + { + if (!botAI->HasAura("Psychic Scream", unit)) + return botAI->CastSpell("Psychic Scream", unit); + break; + } + case CLASS_WARLOCK: + { + if (!botAI->HasAura("Fear", unit)) + return botAI->CastSpell("Fear", unit); + break; + } + default: + break; + } + } + + return false; +} + +bool IccDbsTankPositionAction::EvadeBloodBeasts() +{ + float const evasionDistance = 12.0f; + std::array const bloodBeastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3, + NPC_BLOOD_BEAST4}; + + // Get the nearest hostile NPCs + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit) + continue; + + // Check if this is a blood beast + bool const isBloodBeast = + std::find(bloodBeastEntries.begin(), bloodBeastEntries.end(), unit->GetEntry()) != bloodBeastEntries.end(); + + // Only evade if it's a blood beast targeting us + if (isBloodBeast && unit->GetVictim() == bot) + { + float const currentDistance = bot->GetDistance2d(unit); + + // Move away if too close + if (currentDistance < evasionDistance) + return MoveAway(unit, evasionDistance - currentDistance); + } + } + + return false; +} + +bool IccDbsTankPositionAction::PositionInRangedFormation() +{ + // Get group + Group* group = bot->GetGroup(); + if (!group) + return false; + + int32 const totalSlots = 15; // 3 rows x 5 cols + uint32 const dbsInstanceId = bot->GetInstanceId(); + + // Persistent per-bot slot memory shared across all bots. + // Keyed per-instance to avoid cross-instance pollution. + static std::map, int> botSlotMemory; + auto myKey = std::make_pair(dbsInstanceId, bot->GetGUID()); + + // Single pass: collect natural index (alive ranged/healer non-tank order) + // and other bots' reserved slots. + int32 myIndex = -1; + int32 currentIndex = 0; + std::vector reservedSlots; + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member) + continue; + + if (member != bot) + { + auto it = botSlotMemory.find(std::make_pair(dbsInstanceId, member->GetGUID())); + if (it != botSlotMemory.end() && it->second >= 0 && it->second < totalSlots) + reservedSlots.push_back(it->second); + } + + if (!member->IsAlive()) + continue; + + if ((botAI->IsRanged(member) || botAI->IsHeal(member)) && !botAI->IsTank(member)) + { + if (member == bot) + { + myIndex = currentIndex; + } + currentIndex++; + } + } + + if (myIndex == -1) + return false; + + auto IsReserved = [&](int s) -> bool + { + return std::find(reservedSlots.begin(), reservedSlots.end(), s) != reservedSlots.end(); + }; + + int myAssignedSlot = -1; + + // Step 1: keep my remembered slot if still in range and not reserved by someone else. + auto myMemIt = botSlotMemory.find(myKey); + if (myMemIt != botSlotMemory.end()) + { + int prev = myMemIt->second; + if (prev >= 0 && prev < totalSlots && !IsReserved(prev)) + myAssignedSlot = prev; + } + + // Step 2: pick first unreserved slot, seeded from my natural index. + if (myAssignedSlot < 0) + { + for (int attempt = 0; attempt < totalSlots; attempt++) + { + int s = (myIndex + attempt) % totalSlots; + if (!IsReserved(s)) + { + myAssignedSlot = s; + break; + } + } + } + + // Step 3: overflow (16th+ ranged) - stack with closest non-tank melee bot, + // lowest GUID on tie. Forget my slot so others can take it. + if (myAssignedSlot < 0) + { + botSlotMemory.erase(myKey); + + Player* anchor = nullptr; + float anchorDist = 0.0f; + uint64 anchorGuidRaw = 0; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || member == bot || !member->IsAlive()) + continue; + if (!botAI->IsMelee(member) || botAI->IsTank(member)) + continue; + + float d = bot->GetExactDist2d(member); + uint64 g = member->GetGUID().GetRawValue(); + if (!anchor || d < anchorDist || (d == anchorDist && g < anchorGuidRaw)) + { + anchor = member; + anchorDist = d; + anchorGuidRaw = g; + } + } + + if (!anchor) + return false; + + float ax = anchor->GetPositionX(); + float ay = anchor->GetPositionY(); + float az = anchor->GetPositionZ(); + if (bot->GetExactDist2d(ax, ay) > 3.0f) + return MoveTo(bot->GetMapId(), ax, ay, az, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + return false; + } + + botSlotMemory[myKey] = myAssignedSlot; + + // Fixed positions calculation + float const tankToBossAngle = 3.14f; + float const minBossDistance = 11.0f; + float const spreadDistance = 10.0f; + int32 const columnsPerRow = 5; + + // Calculate position in a fixed grid (3 rows x 5 columns) + int32 const row = myAssignedSlot / columnsPerRow; + int32 const col = myAssignedSlot % columnsPerRow; + + // Calculate base position + float xOffset = (col - 2) * spreadDistance; // Center around tank position + float yOffset = minBossDistance + (row * spreadDistance); // Each row further back + + // Add zigzag offset for odd rows + if (row % 2 == 1) + xOffset += spreadDistance / 2.0f; + + // Rotate position based on tank-to-boss angle + float finalX = + ICC_DBS_TANK_POSITION.GetPositionX() + (cos(tankToBossAngle) * yOffset - sin(tankToBossAngle) * xOffset); + float finalY = + ICC_DBS_TANK_POSITION.GetPositionY() + (sin(tankToBossAngle) * yOffset + cos(tankToBossAngle) * xOffset); + float finalZ = ICC_DBS_TANK_POSITION.GetPositionZ(); + + // Update Z coordinate + bot->UpdateAllowedPositionZ(finalX, finalY, finalZ); + + // Move if not in position + if (bot->GetExactDist2d(finalX, finalY) > 3.0f) + return MoveTo(bot->GetMapId(), finalX, finalY, finalZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + + return false; +} + +bool IccAddsDbsAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); + if (!boss) + return false; + + // This action is only for melee + if (!botAI->IsMelee(bot)) + return false; + + Unit* priorityTarget = FindPriorityTarget(boss); + + // Update raid target icons if needed + UpdateSkullMarker(priorityTarget); + + return false; +} + +Unit* IccAddsDbsAction::FindPriorityTarget(Unit* boss) +{ + GuidVector const targets = AI_VALUE(GuidVector, "possible targets no los"); + + // Blood beast entry IDs + std::array const addEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3, NPC_BLOOD_BEAST4}; + + // First check for alive adds + for (uint32 const entry : addEntries) + { + for (ObjectGuid const& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == entry) + return unit; + } + } + + // Only fallback to boss if it's alive + return boss->IsAlive() ? const_cast(boss) : nullptr; +} + +bool IccAddsDbsAction::UpdateSkullMarker(Unit* priorityTarget) +{ + if (!priorityTarget) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + uint8 const skullIconId = 7; + + // Get current skull target + ObjectGuid const currentSkull = group->GetTargetIcon(skullIconId); + Unit* currentSkullUnit = botAI->GetUnit(currentSkull); + + // Determine if skull marker needs updating + bool const needsUpdate = !currentSkullUnit || !currentSkullUnit->IsAlive() || currentSkullUnit != priorityTarget; + + // Update if needed + if (needsUpdate) + group->SetTargetIcon(skullIconId, bot->GetGUID(), priorityTarget->GetGUID()); + + return false; +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_Dogs.cpp b/src/Ai/Raid/ICC/Action/ICCActions_Dogs.cpp new file mode 100644 index 00000000000..d7b9f5dc669 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_Dogs.cpp @@ -0,0 +1,110 @@ +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "Multiplier.h" +#include "Playerbots.h" +#include "ICCActions.h" +#include "ICCTriggers.h" + +bool IccDogsTankPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "stinky"); + if (!boss) + boss = AI_VALUE2(Unit*, "find target", "precious"); + if (!boss) + return false; + + auto CastClassTaunt = [&](Unit* target) -> bool + { + if (!target || !target->IsAlive()) + return false; + + switch (bot->getClass()) + { + case CLASS_PALADIN: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + } + case CLASS_DEATH_KNIGHT: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + } + case CLASS_DRUID: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + } + case CLASS_WARRIOR: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + } + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; + }; + + if (botAI->IsTank(bot)) + { + Aura* aura = botAI->GetAura("mortal wound", bot, false, true); + bool const hasMortalWound = aura && aura->GetStackAmount() >= 8; + + if (hasMortalWound) + { + if (bot->GetVictim() == boss) + bot->AttackStop(); + + return true; + } + + // Tank without high mortal wound stacks: taunt boss if current tank has the debuff + Unit* currentTarget = boss->GetVictim(); + if (currentTarget && currentTarget != bot) + { + Aura* victimAura = botAI->GetAura("mortal wound", currentTarget, false, true); + if (victimAura && victimAura->GetStackAmount() >= 8) + CastClassTaunt(boss); + } + + // Taunt nearby hostile adds not targeting a tank + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + if (unit == boss) + continue; + + if (bot->GetDistance2d(unit) > 20.0f) + continue; + + Unit* victim = unit->GetVictim(); + Player* victimPlayer = victim ? victim->ToPlayer() : nullptr; + if (!victimPlayer || !botAI->IsTank(victimPlayer)) + { + CastClassTaunt(unit); + break; + } + } + + return false; + } + + return false; +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_FG.cpp b/src/Ai/Raid/ICC/Action/ICCActions_FG.cpp new file mode 100644 index 00000000000..f0994e6f5bd --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_FG.cpp @@ -0,0 +1,614 @@ +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "Multiplier.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "ICCActions.h" +#include "ICCTriggers.h" +#include "ICCScripts.h" +#include "RtiValue.h" +#include "Vehicle.h" +#include +#include + +// Festergut +bool IccFestergutGroupPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); + if (!boss) + return false; + + bot->SetTarget(boss->GetGUID()); + + auto CastClassTaunt = [&](Unit* target) -> bool + { + if (!target || !target->IsAlive()) + return false; + + switch (bot->getClass()) + { + case CLASS_PALADIN: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + } + case CLASS_DEATH_KNIGHT: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + } + case CLASS_DRUID: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + } + case CLASS_WARRIOR: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + } + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; + }; + + if (botAI->IsTank(bot)) + { + Aura* aura = botAI->GetAura("gastric bloat", bot, false, true); + bool const hasGastricBloat = aura && aura->GetStackAmount() >= 6; + + if (hasGastricBloat) + { + if (bot->GetVictim() == boss) + bot->AttackStop(); + + if (bot->GetExactDist2d(ICC_FESTERGUT_TANK_POSITION) > 5.0f) + return MoveTo(bot->GetMapId(), ICC_FESTERGUT_TANK_POSITION.GetPositionX(), + ICC_FESTERGUT_TANK_POSITION.GetPositionY(), ICC_FESTERGUT_TANK_POSITION.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + return true; + } + + Unit* currentTarget = boss->GetVictim(); + if (currentTarget && currentTarget != bot) + { + Aura* victimAura = botAI->GetAura("gastric bloat", currentTarget, false, true); + if (victimAura && victimAura->GetStackAmount() >= 6) + CastClassTaunt(boss); + } + + if (bot->GetExactDist2d(ICC_FESTERGUT_TANK_POSITION) > 5.0f) + return MoveTo(bot->GetMapId(), ICC_FESTERGUT_TANK_POSITION.GetPositionX(), + ICC_FESTERGUT_TANK_POSITION.GetPositionY(), ICC_FESTERGUT_TANK_POSITION.GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_NORMAL); + + return false; + } + + // Check for spores in the group + if (HasSporesInGroup()) + return false; + + // No spore, no goo dodge - melee stack on main tank. + if (botAI->IsMelee(bot)) + { + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + if (mainTank && bot->GetExactDist2d(mainTank) > 3.0f) + return MoveTo(bot->GetMapId(), mainTank->GetPositionX(), mainTank->GetPositionY(), + mainTank->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + return false; + } + + // Position non-tank ranged and healers + return PositionNonTankMembers(); +} + +bool IccFestergutGroupPositionAction::HasSporesInGroup() +{ + GuidVector const members = AI_VALUE(GuidVector, "group members"); + + for (auto const& memberGuid : members) + { + Unit* unit = botAI->GetUnit(memberGuid); + if (unit && unit->HasAura(SPELL_GAS_SPORE)) + return true; + } + + return false; +} + +bool IccFestergutGroupPositionAction::PositionNonTankMembers() +{ + // Only position ranged and healers without spores + if (!(botAI->IsRanged(bot) || botAI->IsHeal(bot))) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + int32 positionIndex = CalculatePositionIndex(group); + if (positionIndex == -1) + return false; + + // Position calculation parameters + constexpr float tankToBossAngle = 4.58f; + constexpr float minBossDistance = 15.0f; + constexpr float spreadDistance = 10.0f; + constexpr int32 columnsPerRow = 6; + + // Calculate grid position + int32 row = positionIndex / columnsPerRow; + int32 col = positionIndex % columnsPerRow; + + // Calculate base position + float xOffset = (col - 2) * spreadDistance; // Center around tank position + float yOffset = minBossDistance + (row * spreadDistance); // Each row further back + + // Add zigzag offset for odd rows + if (row % 2 == 1) + xOffset += spreadDistance / 2.0f; + + // Rotate position based on tank-to-boss angle + float finalX = + ICC_FESTERGUT_TANK_POSITION.GetPositionX() + (cos(tankToBossAngle) * yOffset - sin(tankToBossAngle) * xOffset); + float finalY = + ICC_FESTERGUT_TANK_POSITION.GetPositionY() + (sin(tankToBossAngle) * yOffset + cos(tankToBossAngle) * xOffset); + float finalZ = ICC_FESTERGUT_TANK_POSITION.GetPositionZ(); + + // Update Z coordinate + bot->UpdateAllowedPositionZ(finalX, finalY, finalZ); + + // Move if not in position + if (bot->GetExactDist2d(finalX, finalY) > 3.0f) + return MoveTo(bot->GetMapId(), finalX, finalY, finalZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + + return false; +} + +int32 IccFestergutGroupPositionAction::CalculatePositionIndex(Group* group) +{ + std::vector healerGuids; + std::vector rangedDpsGuids; + std::vector hunterGuids; + + // Collect all eligible members with their GUIDs + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || botAI->IsTank(member)) + continue; + + ObjectGuid memberGuid = member->GetGUID(); + + if (botAI->IsHeal(member)) + healerGuids.push_back(memberGuid); + else if (botAI->IsRanged(member)) + { + if (member->getClass() == CLASS_HUNTER) + hunterGuids.push_back(memberGuid); + else + rangedDpsGuids.push_back(memberGuid); + } + } + + // Sort GUIDs for consistent ordering across all bots + std::sort(healerGuids.begin(), healerGuids.end()); + std::sort(rangedDpsGuids.begin(), rangedDpsGuids.end()); + std::sort(hunterGuids.begin(), hunterGuids.end()); + + ObjectGuid botGuid = bot->GetGUID(); + + // Find which group this bot belongs to + auto healerIt = std::find(healerGuids.begin(), healerGuids.end(), botGuid); + auto rangedIt = std::find(rangedDpsGuids.begin(), rangedDpsGuids.end(), botGuid); + auto hunterIt = std::find(hunterGuids.begin(), hunterGuids.end(), botGuid); + + // Calculate global position index considering group constraints + int32 const healerRows = 2; + int32 const columnsPerRow = 6; + + // Healers: rows 0-1 (first two rows) + if (healerIt != healerGuids.end()) + { + int32 healerIndex = static_cast(std::distance(healerGuids.begin(), healerIt)); + + // Ensure healers only occupy first two rows + if (healerIndex < healerRows * columnsPerRow) + return healerIndex; + + // If too many healers, overflow to later rows but keep them early + return healerIndex; // Will be in row = index / 6, col = index % 6 + } + + // Non-hunter ranged DPS: can be any row (no strict restriction) + if (rangedIt != rangedDpsGuids.end()) + { + int32 rangedIndex = static_cast(std::distance(rangedDpsGuids.begin(), rangedIt)); + + // Start after all healers, then fill remaining spots + return static_cast(healerGuids.size()) + rangedIndex; + } + + // Hunters: never in 1st row (row 0) + if (hunterIt != hunterGuids.end()) + { + int32 hunterIndex = static_cast(std::distance(hunterGuids.begin(), hunterIt)); + + // Calculate how many non-healer positions are before this hunter position + int32 baseOffset = static_cast(healerGuids.size()) + static_cast(rangedDpsGuids.size()); + + // Each row of hunters starts at positions that are multiples of columnsPerRow + // To avoid row 0, skip first column slots reserved for healers/non-hunters + return baseOffset + hunterIndex; + } + + return -1; +} + +bool IccFestergutSporeAction::Execute(Event /*event*/) +{ + constexpr float positionTolerance = 4.0f; + + bool hasSpore = bot->HasAura(SPELL_GAS_SPORE); + + Position spreadRangedPos = CalculateSpreadPosition(); + SporeInfo sporeInfo = FindSporedPlayers(); + Position targetPos = DetermineTargetPosition(hasSpore, sporeInfo, spreadRangedPos); + + if (bot->GetExactDist2d(targetPos) > positionTolerance) + { + botAI->Reset(); + return MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(), + true, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + + // In position — let combat rotation run (multiplier blocks movement so bot stays put) + return false; +} + +Position IccFestergutSporeAction::CalculateSpreadPosition() +{ + constexpr float spreadRadius = 2.0f; + constexpr float gooNearSporeRadius = 12.0f; + constexpr uint32 impactLifetimeMs = 8000; + constexpr uint32 cycleIdleResetMs = 2000; + + // Group-wide sticky slot decision. Spore + malleable goo can overlap: if + // an active goo lands near the current spread spot we flip to the other + // spot, and stay there for the rest of this spore cycle so bots aren't + // pulled back into the danger zone when the goo expires. The action only + // runs while the spore trigger is active, so a gap >2s between calls + // means the cycle ended - reset to the primary slot for the next cycle. + // State is keyed by instance ID so concurrent ICC raids don't share slots. + struct SpreadSlotState { uint32 lastCallMs = 0; int currentSlot = 1; }; + static std::unordered_map s_slotState; + SpreadSlotState& state = s_slotState[bot->GetMap()->GetInstanceId()]; + + uint32 now = getMSTime(); + if (state.lastCallMs == 0 || now - state.lastCallMs > cycleIdleResetMs) + state.currentSlot = 1; + state.lastCallMs = now; + + Position currentSpot = (state.currentSlot == 2) ? ICC_FESTERGUT_RANGED_SPORE_2 + : ICC_FESTERGUT_RANGED_SPORE; + + auto it = IcecrownHelpers::malleableGooImpacts.find(bot->GetMap()->GetInstanceId()); + if (it != IcecrownHelpers::malleableGooImpacts.end()) + { + for (auto const& impact : it->second) + { + if (getMSTimeDiff(impact.castTime, now) > impactLifetimeMs) + continue; + float dx = impact.position.GetPositionX() - currentSpot.GetPositionX(); + float dy = impact.position.GetPositionY() - currentSpot.GetPositionY(); + if (dx * dx + dy * dy < gooNearSporeRadius * gooNearSporeRadius) + { + state.currentSlot = (state.currentSlot == 1) ? 2 : 1; + currentSpot = (state.currentSlot == 2) ? ICC_FESTERGUT_RANGED_SPORE_2 + : ICC_FESTERGUT_RANGED_SPORE; + break; + } + } + } + + // Unique angle based on bot's GUID + float angle = (bot->GetGUID().GetCounter() % 16) * (M_PI / 8); + + Position spreadRangedPos = currentSpot; + spreadRangedPos.Relocate(spreadRangedPos.GetPositionX() + cos(angle) * spreadRadius, + spreadRangedPos.GetPositionY() + sin(angle) * spreadRadius, + spreadRangedPos.GetPositionZ(), spreadRangedPos.GetOrientation()); + + return spreadRangedPos; +} + +IccFestergutSporeAction::SporeInfo IccFestergutSporeAction::FindSporedPlayers() +{ + SporeInfo info; + GuidVector const members = AI_VALUE(GuidVector, "group members"); + + for (auto const& memberGuid : members) + { + Unit* unit = botAI->GetUnit(memberGuid); + if (!unit) + continue; + + if (unit->HasAura(SPELL_GAS_SPORE)) + { + info.sporedPlayers.push_back(unit); + + if (!info.hasLowestGuid || unit->GetGUID() < info.lowestGuid) + { + info.lowestGuid = unit->GetGUID(); + info.hasLowestGuid = true; + } + } + } + + return info; +} + +bool IccFestergutSporeAction::GooNear(Position const& pos) +{ + constexpr uint32 impactLifetimeMs = 8000; + constexpr float gooDangerRadius = 12.0f; + + uint32 now = getMSTime(); + auto it = IcecrownHelpers::malleableGooImpacts.find(bot->GetMap()->GetInstanceId()); + if (it != IcecrownHelpers::malleableGooImpacts.end()) + { + for (auto const& impact : it->second) + { + if (getMSTimeDiff(impact.castTime, now) > impactLifetimeMs) + continue; + float dx = pos.GetPositionX() - impact.position.GetPositionX(); + float dy = pos.GetPositionY() - impact.position.GetPositionY(); + if (dx * dx + dy * dy < gooDangerRadius * gooDangerRadius) + return true; + } + } + return false; +} + +Position IccFestergutSporeAction::DetermineTargetPosition(bool hasSpore, SporeInfo const& sporeInfo, + Position const& spreadRangedPos) +{ + // No spores at all + if (sporeInfo.sporedPlayers.empty()) + return botAI->IsMelee(bot) ? ICC_FESTERGUT_MELEE_SPORE : spreadRangedPos; + + bool mainTankHasSpore = CheckMainTankSpore(); + + // Goo overlap override — checked before hasSpore so non-spored bots also redirect. + bool gooAtMelee = GooNear(ICC_FESTERGUT_MELEE_SPORE); + bool gooAtRanged = GooNear(ICC_FESTERGUT_RANGED_SPORE) || GooNear(ICC_FESTERGUT_RANGED_SPORE_2); + + if (gooAtMelee && !gooAtRanged) + { + // Goo at melee: tank + melee-spore bot hold, other melee flee to ranged slot 1. + bool isMeleeSporeBot = (hasSpore && bot->GetGUID() == sporeInfo.lowestGuid && !botAI->IsTank(bot) && !mainTankHasSpore); + if (botAI->IsMainTank(bot) || isMeleeSporeBot) + return ICC_FESTERGUT_MELEE_SPORE; + if (botAI->IsMelee(bot)) + return ICC_FESTERGUT_RANGED_SPORE; + return spreadRangedPos; + } + + if (gooAtRanged && !gooAtMelee) + { + // Goo at ranged: all ranged collapse to melee spot. + if (!botAI->IsMelee(bot)) + return ICC_FESTERGUT_MELEE_SPORE; + return ICC_FESTERGUT_MELEE_SPORE; + } + + // Normal spore logic (no overlap or both spots hit). + if (!hasSpore) + return botAI->IsMelee(bot) ? ICC_FESTERGUT_MELEE_SPORE : spreadRangedPos; + + if (botAI->IsMainTank(bot)) + return ICC_FESTERGUT_MELEE_SPORE; + + if (bot->GetGUID() == sporeInfo.lowestGuid && !botAI->IsTank(bot) && !mainTankHasSpore) + return ICC_FESTERGUT_MELEE_SPORE; + + return spreadRangedPos; +} + +bool IccFestergutSporeAction::CheckMainTankSpore() +{ + GuidVector const members = AI_VALUE(GuidVector, "group members"); + + for (auto const& memberGuid : members) + { + Unit* unit = botAI->GetUnit(memberGuid); + if (!unit) + continue; + + if (botAI->IsMainTank(unit->ToPlayer()) && unit->HasAura(SPELL_GAS_SPORE)) + return true; + } + + return false; +} + +bool IccFestergutAvoidMalleableGooAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); + if (!boss) + return false; + + // Tanks hold aggro at the fixed tank spot - never dodge. + if (botAI->IsTank(bot)) + return false; + + // Festergut heroic - Putricide throws Malleable Goo from the balcony at + // random non-tank players. The impact is a triggered cast with no cast bar + // and no DynamicObject, so the IccPutricideListenerScript stamps the target's + // position into IcecrownHelpers::malleableGooImpacts at OnSpellCast time. + // Any bot within 12yd of an active impact must flee; danger persists 8s. + // Once a bot dodges, we return true (blocking group-position) until the + // impact expires so the bot doesn't immediately re-enter the danger zone. + constexpr uint32 impactLifetimeMs = 8000; + constexpr float gooDangerRadius = 12.0f; + + uint32 now = getMSTime(); + float botX = bot->GetPositionX(); + float botY = bot->GetPositionY(); + float botZ = bot->GetPositionZ(); + ObjectGuid botGuid = bot->GetGUID(); + + std::vector goos; + goos.reserve(4); + bool botInDanger = false; + auto impactIt = IcecrownHelpers::malleableGooImpacts.find(bot->GetMap()->GetInstanceId()); + if (impactIt != IcecrownHelpers::malleableGooImpacts.end()) + { + for (auto const& impact : impactIt->second) + { + if (getMSTimeDiff(impact.castTime, now) > impactLifetimeMs) + continue; + goos.push_back(impact.position); + + float dx = botX - impact.position.GetPositionX(); + float dy = botY - impact.position.GetPositionY(); + if (dx * dx + dy * dy < gooDangerRadius * gooDangerRadius) + botInDanger = true; + } + } + + if (!botInDanger) + { + // Already safe. Return false so DPS/heal rotations can still fire - + // the multiplier blocks repositioning actions during the wait window + // via festergutGooWaitUntil so the bot stays put without idling. + return false; + } + + // Keep 10yd between fleeing bots. During spore phase melee stack at the + // tank spot, so we ignore melee allies (the tank/melee pile would reject + // every nearby candidate). When no spore is active, melee also spread. + constexpr float botSpacing = 10.0f; + bool sporeActive = false; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->HasAura(SPELL_GAS_SPORE)) + { + sporeActive = true; + break; + } + } + } + + std::vector alliesToSpace; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || member->GetGUID() == botGuid) + continue; + if (sporeActive && botAI->IsMelee(member)) + continue; + alliesToSpace.push_back(member->GetPosition()); + } + } + + constexpr int angleSteps = 24; + float const radii[] = {13.0f, 16.0f, 20.0f}; + float bestScore = -1.0f; + float bestX = botX; + float bestY = botY; + bool found = false; + + // Per-bot preferred flee angle - stable across ticks, distinct per GUID - + // so stacked bots fan out into different sectors instead of converging. + float preferredAngle = (botGuid.GetCounter() % angleSteps) * (2.0f * float(M_PI) / angleSteps); + constexpr float angleBias = 3.0f; + + for (float r : radii) + { + for (int i = 0; i < angleSteps; ++i) + { + float a = (2.0f * float(M_PI) * i) / angleSteps; + float cx = botX + std::cos(a) * r; + float cy = botY + std::sin(a) * r; + + float minGooDistSq = std::numeric_limits::max(); + bool safe = true; + for (Position const& g : goos) + { + float gdx = cx - g.GetPositionX(); + float gdy = cy - g.GetPositionY(); + float d2 = gdx * gdx + gdy * gdy; + if (d2 < gooDangerRadius * gooDangerRadius) + { + safe = false; + break; + } + if (d2 < minGooDistSq) + minGooDistSq = d2; + } + if (!safe) + continue; + + bool tooCloseToAlly = false; + for (Position const& a2 : alliesToSpace) + { + float adx = cx - a2.GetPositionX(); + float ady = cy - a2.GetPositionY(); + if (adx * adx + ady * ady < botSpacing * botSpacing) + { + tooCloseToAlly = true; + break; + } + } + if (tooCloseToAlly) + continue; + + if (!bot->IsWithinLOS(cx, cy, botZ)) + continue; + + float travel = std::sqrt((cx - botX) * (cx - botX) + (cy - botY) * (cy - botY)); + float score = std::sqrt(minGooDistSq) - travel * 0.1f + std::cos(a - preferredAngle) * angleBias; + + if (score > bestScore) + { + bestScore = score; + bestX = cx; + bestY = cy; + found = true; + } + } + if (found) + break; + } + + if (found) + { + return MoveTo(bot->GetMapId(), bestX, bestY, botZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + + return false; +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_GSB.cpp b/src/Ai/Raid/ICC/Action/ICCActions_GSB.cpp new file mode 100644 index 00000000000..7d181dc7c42 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_GSB.cpp @@ -0,0 +1,1074 @@ +#include "EquipAction.h" +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "Multiplier.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "ICCActions.h" +#include "ICCTriggers.h" +#include "RtiValue.h" +#include "Vehicle.h" + +static bool CastClassTaunt(Player* bot, PlayerbotAI* botAI, Unit* target) +{ + if (!target || !target->IsAlive()) + return false; + + switch (bot->getClass()) + { + case CLASS_PALADIN: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + } + case CLASS_DEATH_KNIGHT: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + } + case CLASS_DRUID: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + } + case CLASS_WARRIOR: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + } + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; +} + +bool IccCannonFireAction::Execute(Event /*event*/) +{ + Unit* vehicleBase = bot->GetVehicleBase(); + Vehicle* vehicle = bot->GetVehicle(); + + if (!vehicleBase || !vehicle) + return false; + + Unit* target = FindValidCannonTarget(); + if (!target) + return false; + + // Use Incinerating Blast when we have enough energy, otherwise Cannon Blast + static constexpr float ENERGY_THRESHOLD = 90.0f; + if (vehicleBase->GetPower(POWER_ENERGY) >= ENERGY_THRESHOLD) + { + uint32 const blastSpellId = AI_VALUE2(uint32, "vehicle spell id", "incinerating blast"); + if (TryCastCannonSpell(blastSpellId, target, vehicleBase)) + return true; + } + + uint32 const cannonSpellId = AI_VALUE2(uint32, "vehicle spell id", "cannon blast"); + return TryCastCannonSpell(cannonSpellId, target, vehicleBase); +} + +Unit* IccCannonFireAction::FindValidCannonTarget() +{ + // availableTargetsGS is ordered per-faction (earliest = highest priority). + // For each entry, enumerate all creatures and pick the nearest hostile + // alive one. FindNearestCreature returns the single closest match which + // may be a friendly of the same entry on our own ship, causing the entry + // to be skipped even when a hostile of that entry exists on the enemy + // ship. + static constexpr float CANNON_TARGET_RANGE = 150.0f; + + for (size_t i = 0; i < availableTargetsGS.size(); ++i) + { + std::list candidates; + bot->GetCreatureListWithEntryInGrid(candidates, availableTargetsGS[i], CANNON_TARGET_RANGE); + + Unit* best = nullptr; + float bestDist = FLT_MAX; + for (Creature* c : candidates) + { + if (!c || !c->IsAlive() || !c->IsHostileTo(bot)) + continue; + float const d = bot->GetExactDist(c); + if (d < bestDist) + { + bestDist = d; + best = c; + } + } + + if (best) + return best; + } + + return nullptr; +} + +bool IccCannonFireAction::TryCastCannonSpell(uint32 spellId, Unit* target, Unit* vehicleBase) +{ + static constexpr uint32 COOLDOWN_MS = 1000; + + if (botAI->CanCastVehicleSpell(spellId, target) && botAI->CastVehicleSpell(spellId, target)) + { + vehicleBase->AddSpellCooldown(spellId, 0, COOLDOWN_MS); + return true; + } + + return false; +} + +bool IccGunshipEnterCannonAction::Execute(Event /*event*/) +{ + // Never switch vehicles while already in one + if (bot->GetVehicle()) + return false; + + // Require rocket pack acquired and equipped before boarding a cannon + Item* rocketPack = bot->GetItemByEntry(ITEM_GOBLIN_ROCKET_PACK); + if (!rocketPack || !rocketPack->IsEquipped()) + return false; + + Unit* bestVehicle = FindBestAvailableCannon(); + if (!bestVehicle) + return false; + + return EnterVehicle(bestVehicle, true); +} + +Unit* IccGunshipEnterCannonAction::FindBestAvailableCannon() +{ + Unit* bestVehicle = nullptr; + + GuidVector const npcs = AI_VALUE(GuidVector, "nearest vehicles"); + for (ObjectGuid const& npcGuid : npcs) + { + Unit* vehicleBase = botAI->GetUnit(npcGuid); + if (!IsValidCannon(vehicleBase)) + continue; + + // Prefer the closest valid cannon + if (!bestVehicle || bot->GetExactDist(vehicleBase) < bot->GetExactDist(bestVehicle)) + bestVehicle = vehicleBase; + } + + return bestVehicle; +} + +bool IccGunshipEnterCannonAction::IsValidCannon(Unit* vehicle) +{ + if (!vehicle) + return false; + + if (vehicle->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + return false; + + if (!vehicle->IsFriendlyTo(bot)) + return false; + + if (!vehicle->GetVehicleKit() || !vehicle->GetVehicleKit()->GetAvailableSeatCount()) + return false; + + uint32 const entry = vehicle->GetEntry(); + if (entry != NPC_CANNONA && entry != NPC_CANNONH) + return false; + + // Frozen or disabled cannon - skip + if (vehicle->HasAura(SPELL_FROZEN_CANNON) || vehicle->HasAura(SPELL_BELOW_ZERO)) + return false; + + return true; +} + +bool IccGunshipEnterCannonAction::EnterVehicle(Unit* vehicleBase, bool moveIfFar) +{ + float const dist = bot->GetDistance(vehicleBase); + + if (dist > INTERACTION_DISTANCE && !moveIfFar) + return false; + + if (dist > INTERACTION_DISTANCE) + return MoveTo(vehicleBase); + + botAI->RemoveShapeshift(); + bot->GetMotionMaster()->Clear(); + bot->StopMoving(); + + vehicleBase->HandleSpellClick(bot); + + if (!bot->IsOnVehicle(vehicleBase)) + return false; + + // Dismount - bots can enter a vehicle while still mounted + WorldPacket emptyPacket; + bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); + + return true; +} + +IccGunshipRocketJumpAction::GunshipSide IccGunshipRocketJumpAction::DetectShip() const +{ + Unit* cannonA = bot->FindNearestCreature(NPC_CANNONA, 100.0f); + if (cannonA && cannonA->IsFriendlyTo(bot)) + return GunshipSide::ALLY; + + Unit* cannonH = bot->FindNearestCreature(NPC_CANNONH, 100.0f); + if (cannonH && cannonH->IsFriendlyTo(bot)) + return GunshipSide::HORDE; + + return GunshipSide::NONE; +} + +bool IccGunshipRocketJumpAction::Execute(Event /*event*/) +{ + GunshipSide const side = DetectShip(); + if (side == GunshipSide::NONE) + return false; + + // Z-axis correction: teleport bot back if fallen below deck + if (!bot->GetVehicle()) + { + Position const& zRef = (side == GunshipSide::ALLY) ? ICC_GUNSHIP_ROCKET_JUMP_ALLY2 : ICC_GUNSHIP_ROCKET_JUMP_HORDE2; + Position const& zDest = (side == GunshipSide::ALLY) ? ICC_GUNSHIP_ROCKET_JUMP_ALLY2 : ICC_GUNSHIP_ROCKET_JUMP_HORDE2; + float const Z_THRESHOLD = zRef.GetPositionZ() - 5.0f; + if (bot->GetPositionZ() < Z_THRESHOLD) + { + bot->TeleportTo(bot->GetMapId(), zDest.GetPositionX(), zDest.GetPositionY(), + zDest.GetPositionZ(), bot->GetOrientation()); + botAI->Reset(); + return false; + } + } + + float const maxWaitingDistance = (side == GunshipSide::ALLY) ? 30.0f : 25.0f; + static constexpr float MAX_ATTACK_DISTANCE = 20.0f; + static constexpr float HOLD_RADIUS = 20.0f; + static constexpr uint8 SKULL_ICON_INDEX = 7; + + uint32 const mageEntry = (side == GunshipSide::ALLY) ? NPC_KOR_KRON_BATTLE_MAGE : NPC_SKYBREAKER_SORCERER; + Position const& waitPos = (side == GunshipSide::ALLY) ? ICC_GUNSHIP_ROCKET_JUMP_ALLY2 : ICC_GUNSHIP_ROCKET_JUMP_HORDE_FRIENDLY_POINT; + Position const& attackPos = (side == GunshipSide::ALLY) ? ICC_GUNSHIP_ROCKET_JUMP_ALLY : ICC_GUNSHIP_ROCKET_JUMP_HORDE; + + Unit* boss = nullptr; + { + std::list mages; + bot->GetCreatureListWithEntryInGrid(mages, mageEntry, 200.0f); + for (Creature* m : mages) + { + if (m && m->IsAlive() && m->HasUnitState(UNIT_STATE_CASTING)) + { + boss = m; + break; + } + } + if (!boss) + boss = bot->FindNearestCreature(mageEntry, 200.0f); + } + + CleanupSkullIcon(SKULL_ICON_INDEX); + + uint32 const cannonEntry = (side == GunshipSide::ALLY) ? NPC_CANNONA : NPC_CANNONH; + bool cannonsHaveBelowZero = false; + { + std::list cannons; + bot->GetCreatureListWithEntryInGrid(cannons, cannonEntry, 100.0f); + for (Creature* c : cannons) + { + if (c && c->IsFriendlyTo(bot) && c->HasAura(SPELL_BELOW_ZERO)) + { + cannonsHaveBelowZero = true; + break; + } + } + } + + bool const botOnEnemyShip = bot->GetExactDist2d(attackPos) <= MAX_ATTACK_DISTANCE; + bool const mageAlive = boss && boss->IsAlive() && boss->HasUnitState(UNIT_STATE_CASTING); + + // Assist tank stays on friendly ship to collect and tank adds + if (botAI->IsAssistTank(bot)) + { + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + // Find nearest uncollected add near hold position + Unit* targetAdd = nullptr; + float closestDist = FLT_MAX; + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (unit->GetExactDist2d(waitPos) > HOLD_RADIUS) + continue; + + Unit* victim = unit->GetVictim(); + bool alreadyTanked = victim && victim->IsPlayer() && botAI->IsTank(victim->ToPlayer()); + if (alreadyTanked) + continue; + + float dist = bot->GetExactDist2d(unit); + if (dist < closestDist) + { + closestDist = dist; + targetAdd = unit; + } + } + + if (targetAdd) + { + CastClassTaunt(bot, botAI, targetAdd); + bot->SetTarget(targetAdd->GetGUID()); + bot->SetFacingToObject(targetAdd); + Attack(targetAdd); + return false; + } + + // No uncollected adds - keep attacking adds targeting us + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (unit->GetExactDist2d(waitPos) > HOLD_RADIUS) + continue; + + if (unit->GetVictim() == bot) + { + bot->SetTarget(unit->GetGUID()); + bot->SetFacingToObject(unit); + Attack(unit); + return false; + } + } + + // No adds — return to hold position if drifted + if (bot->GetExactDist2d(waitPos) > HOLD_RADIUS) + { + return MoveTo(bot->GetMapId(), waitPos.GetPositionX(), waitPos.GetPositionY(), waitPos.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_NORMAL); + } + + return false; + } + + bool const isMainTank = botAI->IsMainTank(bot); + bool const isHorde = (side == GunshipSide::HORDE); + Position const& middlePoint = ICC_GUNSHIP_ROCKET_JUMP_HORDE_MIDDLE_POINT; + static constexpr float JUMP_GATE = 30.0f; + + // Main tank: tank captain during below-zero OR while an ally is on enemy ship. + // Otherwise return to friendly ship — captain one-shots if tanked too long. + if (isMainTank) + { + uint32 const captainEntry = (side == GunshipSide::ALLY) ? NPC_HIGH_OVERLORD_SAURFANG : NPC_MURADIN_BRONZEBEARD; + Unit* captain = bot->FindNearestCreature(captainEntry, 1000.0f); + + // Battle Fury buff bugged on captain - strip all difficulty variants every tick. todo: remove when boss agro fixed + if (captain) + { + static constexpr uint32 BATTLE_FURY_IDS[] = {SPELL_BATTLE_FURY1, SPELL_BATTLE_FURY2, + SPELL_BATTLE_FURY3, SPELL_BATTLE_FURY4, + SPELL_BATTLE_FURY5}; + for (uint32 const id : BATTLE_FURY_IDS) + if (captain->HasAura(id)) + captain->RemoveAurasDueToSpell(id); + } + + bool allyAtMage = false; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + if (botAI->IsMainTank(member) || botAI->IsAssistTank(member)) + continue; + if (member->GetExactDist2d(attackPos) <= 20.0f) + { + allyAtMage = true; + break; + } + } + } + bool const shouldTankCaptain = cannonsHaveBelowZero || allyAtMage; + + // Captain gone or outside tanking window: return to friendly ship if stranded, else hold. + // Drop target/threat so combat rotation doesn't chase adds on friendly ship. + if (!captain || !captain->IsAlive() || !shouldTankCaptain) + { + if (bot->GetVictim()) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->SetTarget(ObjectGuid::Empty); + } + if (botOnEnemyShip) + { + // Main tank jumps back last - wait for all non-tanks to leave enemy ship + if (AnyNonTankAwayFromFriendly(side)) + return false; + + if (ExitCannonIfSeated()) + return true; + + if (isHorde) + { + float const distFriendly = bot->GetExactDist2d(waitPos); + float const distMiddle = bot->GetExactDist2d(middlePoint); + if (distMiddle <= 10.0f && distFriendly > 5.0f) + { + if (UseRocketPack(waitPos, /*walkIfOutOfRange=*/true)) + return true; + return MoveTo(bot->GetMapId(), waitPos.GetPositionX(), waitPos.GetPositionY(), + waitPos.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + if (distMiddle <= JUMP_GATE) + { + if (UseRocketPack(middlePoint, /*walkIfOutOfRange=*/false)) + return true; + return MoveTo(bot->GetMapId(), middlePoint.GetPositionX(), middlePoint.GetPositionY(), + middlePoint.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + if (UseRocketPack(middlePoint, /*walkIfOutOfRange=*/true)) + return true; + return MoveTo(bot->GetMapId(), middlePoint.GetPositionX(), middlePoint.GetPositionY(), + middlePoint.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + + // Alliance MT return: ALLY_MIDDLE_POINT -> ALLY_FRIENDLY_POINT + Position const& allyFriendly = ICC_GUNSHIP_ROCKET_JUMP_ALLY_FRIENDLY_POINT; + Position const& allyMiddle = ICC_GUNSHIP_ROCKET_JUMP_ALLY_MIDDLE_POINT; + float const distAllyMid = bot->GetExactDist2d(allyMiddle); + float const distAllyFriendly = bot->GetExactDist2d(allyFriendly); + + if (distAllyMid <= 10.0f && distAllyFriendly > 5.0f) + { + if (UseRocketPack(allyFriendly, /*walkIfOutOfRange=*/true)) + return true; + return MoveTo(bot->GetMapId(), allyFriendly.GetPositionX(), allyFriendly.GetPositionY(), + allyFriendly.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + if (distAllyMid <= JUMP_GATE) + { + if (UseRocketPack(allyMiddle, /*walkIfOutOfRange=*/false)) + return true; + return MoveTo(bot->GetMapId(), allyMiddle.GetPositionX(), allyMiddle.GetPositionY(), + allyMiddle.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + if (UseRocketPack(allyMiddle, /*walkIfOutOfRange=*/true)) + return true; + return MoveTo(bot->GetMapId(), allyMiddle.GetPositionX(), allyMiddle.GetPositionY(), + allyMiddle.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + if (bot->GetExactDist2d(waitPos) > maxWaitingDistance) + { + if (ExitCannonIfSeated()) + return true; + return UseRocketPack(waitPos, /*walkIfOutOfRange=*/true); + } + return false; + } + + // Not on enemy ship: rocket directly to captain + if (!botOnEnemyShip) + { + if (ExitCannonIfSeated()) + return true; + + Position const captainPos(captain->GetPositionX(), captain->GetPositionY(), captain->GetPositionZ()); + + if (isHorde) + { + Position const& friendlyPoint = ICC_GUNSHIP_ROCKET_JUMP_HORDE_FRIENDLY_POINT; + float const distFriendly = bot->GetExactDist2d(friendlyPoint); + float const distMiddle = bot->GetExactDist2d(middlePoint); + float const distCaptain = bot->GetExactDist2d(&captainPos); + + if (distCaptain <= JUMP_GATE || distMiddle <= 5.0f) + return UseRocketPack(captainPos, /*walkIfOutOfRange=*/false); + if (distFriendly <= 5.0f) + return RocketPackJumpToward(middlePoint); + if (distMiddle > JUMP_GATE && distCaptain > JUMP_GATE) + return UseRocketPack(friendlyPoint, /*walkIfOutOfRange=*/true); + + Position const* nearest = &friendlyPoint; + float nearestDist = distFriendly; + if (distMiddle < nearestDist) { nearest = &middlePoint; nearestDist = distMiddle; } + if (distCaptain < nearestDist) { nearest = &captainPos; nearestDist = distCaptain; } + return UseRocketPack(*nearest, /*walkIfOutOfRange=*/true); + } + + // Alliance: ALLY_FRIENDLY_POINT -> ALLY_MIDDLE_POINT -> captain + Position const& allyFriendly = ICC_GUNSHIP_ROCKET_JUMP_ALLY_FRIENDLY_POINT; + Position const& allyMiddle = ICC_GUNSHIP_ROCKET_JUMP_ALLY_MIDDLE_POINT; + float const distAllyFriendly = bot->GetExactDist2d(allyFriendly); + float const distAllyMid = bot->GetExactDist2d(allyMiddle); + float const distCaptainA = bot->GetExactDist2d(&captainPos); + + if (distCaptainA <= JUMP_GATE || distAllyMid <= 5.0f) + return UseRocketPack(captainPos, /*walkIfOutOfRange=*/false); + if (distAllyFriendly <= 5.0f) + return RocketPackJumpToward(allyMiddle); + if (distAllyMid > JUMP_GATE && distCaptainA > JUMP_GATE) + return UseRocketPack(allyFriendly, /*walkIfOutOfRange=*/true); + + Position const* nearestA = &allyFriendly; + float nearestDistA = distAllyFriendly; + if (distAllyMid < nearestDistA) { nearestA = &allyMiddle; nearestDistA = distAllyMid; } + if (distCaptainA < nearestDistA) { nearestA = &captainPos; nearestDistA = distCaptainA; } + return UseRocketPack(*nearestA, /*walkIfOutOfRange=*/true); + } + + // On enemy ship. If not captain's victim, force aggro. + if (captain->GetVictim() != bot) + { + CastClassTaunt(bot, botAI, captain); + bot->SetTarget(captain->GetGUID()); + bot->SetFacingToObject(captain); + Attack(captain); + return false; + } + + // Captain's victim: drag boss to 40f-from-friendly-cannon spot on enemy ship + static constexpr float TANK_OFFSET_FROM_CANNON = 40.0f; + static constexpr float TANK_POS_TOLERANCE = 3.0f; + Unit* friendlyCannon = FindNearestFriendlyCannon(side); + if (friendlyCannon) + { + float const cx = friendlyCannon->GetPositionX(); + float const cy = friendlyCannon->GetPositionY(); + float const dirX = attackPos.GetPositionX() - cx; + float const dirY = attackPos.GetPositionY() - cy; + float const dirLen = std::sqrt(dirX * dirX + dirY * dirY); + if (dirLen > 0.1f) + { + float const tx = cx + (dirX / dirLen) * TANK_OFFSET_FROM_CANNON; + float const ty = cy + (dirY / dirLen) * TANK_OFFSET_FROM_CANNON; + float const tz = bot->GetPositionZ(); + float const dx = bot->GetPositionX() - tx; + float const dy = bot->GetPositionY() - ty; + if (std::sqrt(dx * dx + dy * dy) > TANK_POS_TOLERANCE) + { + bot->SetTarget(captain->GetGUID()); + return MoveTo(bot->GetMapId(), tx, ty, tz, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + } + } + + bot->SetTarget(captain->GetGUID()); + bot->SetFacingToObject(captain); + Attack(captain); + return false; + } + + // Non-tank rocket jump logic + + if (cannonsHaveBelowZero && mageAlive) + { + UpdateBossSkullIcon(boss, SKULL_ICON_INDEX); + if (!botOnEnemyShip) + { + if (ExitCannonIfSeated()) + return true; + + if (isHorde) + { + Position const& friendlyPoint = ICC_GUNSHIP_ROCKET_JUMP_HORDE_FRIENDLY_POINT; + float const distFriendly = bot->GetExactDist2d(friendlyPoint); + float const distMiddle = bot->GetExactDist2d(middlePoint); + float const distMage = bot->GetExactDist2d(attackPos); + + // Mage in range or already at middle: jump mage + if (distMage <= JUMP_GATE || distMiddle <= 5.0f) + return UseRocketPack(attackPos, /*walkIfOutOfRange=*/false); + // At friendly: jump middle + if (distFriendly <= 5.0f) + return RocketPackJumpToward(middlePoint); + // Both middle and mage out of range: detour through friendly + if (distMiddle > JUMP_GATE && distMage > JUMP_GATE) + return UseRocketPack(friendlyPoint, /*walkIfOutOfRange=*/true); + + // Fallback: jump nearest of the three + Position const* nearest = &friendlyPoint; + float nearestDist = distFriendly; + if (distMiddle < nearestDist) { nearest = &middlePoint; nearestDist = distMiddle; } + if (distMage < nearestDist) { nearest = &attackPos; nearestDist = distMage; } + return UseRocketPack(*nearest, /*walkIfOutOfRange=*/true); + } + + // Alliance non-tank: ALLY_FRIENDLY_POINT -> ALLY_MIDDLE_POINT -> mage (attackPos) + Position const& allyFriendly = ICC_GUNSHIP_ROCKET_JUMP_ALLY_FRIENDLY_POINT; + Position const& allyMiddle = ICC_GUNSHIP_ROCKET_JUMP_ALLY_MIDDLE_POINT; + float const distAllyFriendly = bot->GetExactDist2d(allyFriendly); + float const distAllyMid = bot->GetExactDist2d(allyMiddle); + float const distMageA = bot->GetExactDist2d(attackPos); + + if (distMageA <= JUMP_GATE || distAllyMid <= 5.0f) + return UseRocketPack(attackPos, /*walkIfOutOfRange=*/false); + if (distAllyFriendly <= 5.0f) + return RocketPackJumpToward(allyMiddle); + if (distAllyMid > JUMP_GATE && distMageA > JUMP_GATE) + return UseRocketPack(allyFriendly, /*walkIfOutOfRange=*/true); + + Position const* nearestA = &allyFriendly; + float nearestDistA = distAllyFriendly; + if (distAllyMid < nearestDistA) { nearestA = &allyMiddle; nearestDistA = distAllyMid; } + if (distMageA < nearestDistA) { nearestA = &attackPos; nearestDistA = distMageA; } + return UseRocketPack(*nearestA, /*walkIfOutOfRange=*/true); + } + + // On enemy ship: attack mage + context->GetValue("rti")->Set("skull"); + if (!bot->GetVehicle()) + Attack(boss); + return false; + } + else + { + // Stranded at middle point: force jump back to friendly point + if (isHorde) + { + float const distMiddle = bot->GetExactDist2d(middlePoint); + float const distFriendly = bot->GetExactDist2d(waitPos); + if (distMiddle <= 10.0f && distFriendly > 5.0f) + { + if (ExitCannonIfSeated()) + return true; + if (UseRocketPack(waitPos, /*walkIfOutOfRange=*/true)) + return true; + return MoveTo(bot->GetMapId(), waitPos.GetPositionX(), waitPos.GetPositionY(), + waitPos.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + } + + // Return to friendly point if on enemy ship or drifted too far + if (botOnEnemyShip || bot->GetExactDist2d(waitPos) > maxWaitingDistance) + { + if (ExitCannonIfSeated()) + return true; + + if (isHorde) + { + float const distMiddle = bot->GetExactDist2d(middlePoint); + // Middle in range: jump middle (next tick stranded override jumps friendly) + if (distMiddle <= JUMP_GATE) + { + if (UseRocketPack(middlePoint, /*walkIfOutOfRange=*/false)) + return true; + return MoveTo(bot->GetMapId(), middlePoint.GetPositionX(), middlePoint.GetPositionY(), + middlePoint.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + // Middle out of range: walk 10f toward middle + if (UseRocketPack(middlePoint, /*walkIfOutOfRange=*/true)) + return true; + return MoveTo(bot->GetMapId(), middlePoint.GetPositionX(), middlePoint.GetPositionY(), + middlePoint.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + + // Alliance return: ALLY_MIDDLE_POINT -> ALLY_FRIENDLY_POINT + Position const& allyFriendly = ICC_GUNSHIP_ROCKET_JUMP_ALLY_FRIENDLY_POINT; + Position const& allyMiddle = ICC_GUNSHIP_ROCKET_JUMP_ALLY_MIDDLE_POINT; + float const distAllyMid = bot->GetExactDist2d(allyMiddle); + float const distAllyFriendly = bot->GetExactDist2d(allyFriendly); + + if (distAllyMid <= 10.0f && distAllyFriendly > 5.0f) + { + if (UseRocketPack(allyFriendly, /*walkIfOutOfRange=*/true)) + return true; + return MoveTo(bot->GetMapId(), allyFriendly.GetPositionX(), allyFriendly.GetPositionY(), + allyFriendly.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + if (distAllyMid <= JUMP_GATE) + { + if (UseRocketPack(allyMiddle, /*walkIfOutOfRange=*/false)) + return true; + return MoveTo(bot->GetMapId(), allyMiddle.GetPositionX(), allyMiddle.GetPositionY(), + allyMiddle.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + if (UseRocketPack(allyMiddle, /*walkIfOutOfRange=*/true)) + return true; + return MoveTo(bot->GetMapId(), allyMiddle.GetPositionX(), allyMiddle.GetPositionY(), + allyMiddle.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + + // Melee DPS on friendly ship: assist the assist tank on adds it's tanking + if (botAI->IsMelee(bot) && !botAI->IsTank(bot)) + { + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + if (!botAI->IsAssistTank(member)) + continue; + Unit* assistTarget = member->GetVictim(); + if (!assistTarget || !assistTarget->IsAlive()) + break; + if (assistTarget->GetExactDist2d(waitPos) > HOLD_RADIUS) + break; + bot->SetTarget(assistTarget->GetGUID()); + bot->SetFacingToObject(assistTarget); + Attack(assistTarget); + return false; + } + } + } + + // Ranged DPS star-mark axethrower/rifleman for weapon focus fire. + // Handoff uses the previous star's spot as anchor so the marker + // doesn't jump to whichever add is closest to the picking bot. + if (botAI->IsRangedDps(bot)) + { + static constexpr uint8 STAR_ICON_INDEX = 0; + static constexpr float ADD_SEARCH_RANGE = 200.0f; + uint32 const addEntry = (side == GunshipSide::ALLY) + ? NPC_KOR_KRON_AXETHROWER : NPC_SKYBREAKER_RIFLEMAN; + + static std::map s_lastStarPos; + uint32 const instId = bot->GetInstanceId(); + + if (Group* group = bot->GetGroup()) + { + Unit* starTarget = botAI->GetUnit(group->GetTargetIcon(STAR_ICON_INDEX)); + bool const validStar = starTarget && starTarget->IsAlive() + && starTarget->GetEntry() == addEntry; + + if (validStar) + { + s_lastStarPos[instId] = Position(starTarget->GetPositionX(), + starTarget->GetPositionY(), + starTarget->GetPositionZ()); + } + else + { + Position refPos; + auto const it = s_lastStarPos.find(instId); + if (it != s_lastStarPos.end()) + refPos = it->second; + else + refPos = Position(bot->GetPositionX(), bot->GetPositionY(), + bot->GetPositionZ()); + + std::list adds; + bot->GetCreatureListWithEntryInGrid(adds, addEntry, ADD_SEARCH_RANGE); + + Unit* nextAdd = nullptr; + float bestDist = FLT_MAX; + for (Creature* c : adds) + { + if (!c || !c->IsAlive() || !c->IsHostileTo(bot)) + continue; + float const d = c->GetExactDist(&refPos); + if (d < bestDist) + { + bestDist = d; + nextAdd = c; + } + } + + if (nextAdd) + { + group->SetTargetIcon(STAR_ICON_INDEX, bot->GetGUID(), nextAdd->GetGUID()); + s_lastStarPos[instId] = Position(nextAdd->GetPositionX(), + nextAdd->GetPositionY(), + nextAdd->GetPositionZ()); + starTarget = nextAdd; + } + else + { + starTarget = nullptr; + } + } + + if (starTarget) + { + context->GetValue("rti")->Set("star"); + if (!bot->GetVehicle()) + Attack(starTarget); + } + } + } + } + + return false; +} + +bool IccGunshipRocketJumpAction::IsMainTankOnEnemyShip(GunshipSide side) const +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + uint32 const captainEntry = (side == GunshipSide::ALLY) ? NPC_HIGH_OVERLORD_SAURFANG : NPC_MURADIN_BRONZEBEARD; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + if (!botAI->IsMainTank(member)) + continue; + Unit* captain = member->FindNearestCreature(captainEntry, 15.0f); + return captain != nullptr; + } + return false; +} + +bool IccGunshipRocketJumpAction::AnyNonTankAwayFromFriendly(GunshipSide side) const +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + Position const& waitPos = (side == GunshipSide::ALLY) ? ICC_GUNSHIP_ROCKET_JUMP_ALLY2 : ICC_GUNSHIP_ROCKET_JUMP_HORDE2; + float const threshold = (side == GunshipSide::ALLY) ? 40.0f : 45.0f; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + if (botAI->IsMainTank(member) || botAI->IsAssistTank(member)) + continue; + if (member->GetExactDist2d(waitPos) > threshold) + return true; + } + return false; +} + +bool IccGunshipRocketJumpAction::ExitCannonIfSeated() +{ + Vehicle* vehicle = bot->GetVehicle(); + if (!vehicle) + return false; + + VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot); + if (!seat || !seat->CanEnterOrExit()) + return false; + + WorldPacket packet; + bot->GetSession()->HandleRequestVehicleExit(packet); + return true; +} + +Item* IccGunshipRocketJumpAction::FindRocketPack() const +{ + return bot->GetItemByEntry(ITEM_GOBLIN_ROCKET_PACK); +} + +bool IccGunshipRocketJumpAction::UseRocketPack(Position const& destination, bool walkIfOutOfRange) +{ + Item* rocketPack = FindRocketPack(); + if (!rocketPack) + return false; + + if (!bot->HasAura(SPELL_ROCKET_PACK_USEABLE)) + return false; + + if (bot->IsNonMeleeSpellCast(true)) + return false; + + if (bot->CanUseItem(rocketPack) != EQUIP_ERR_OK) + return false; + + // Throttle rocket pack use to prevent mid-air re-jump spam + static constexpr uint32 ROCKET_PACK_COOLDOWN_MS = 2500; + static std::map lastRocketPackUse; + uint32 const now = getMSTime(); + auto const it = lastRocketPackUse.find(bot->GetGUID()); + bool const onCooldown = it != lastRocketPackUse.end() && + getMSTimeDiff(it->second, now) < ROCKET_PACK_COOLDOWN_MS; + + static constexpr float MAX_JUMP_RANGE = 30.0f; + float const dist = bot->GetExactDist(&destination); + + // On cooldown: consume tick to block MoveTo fallback when target is reachable + if (onCooldown) + { + if (dist <= MAX_JUMP_RANGE) + return true; + return false; + } + if (dist > MAX_JUMP_RANGE) + { + if (!walkIfOutOfRange) + return false; + + // Walk 10f toward destination, retry jump next tick + static constexpr float STEP_DISTANCE = 10.0f; + float const ratio = STEP_DISTANCE / dist; + float const stepX = bot->GetPositionX() + (destination.GetPositionX() - bot->GetPositionX()) * ratio; + float const stepY = bot->GetPositionY() + (destination.GetPositionY() - bot->GetPositionY()) * ratio; + float const stepZ = bot->GetPositionZ() + (destination.GetPositionZ() - bot->GetPositionZ()) * ratio; + return MoveTo(bot->GetMapId(), stepX, stepY, stepZ, false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + + // Settle bot before firing rocket pack - moving bots tend to misfire mid-stride + if (bot->isMoving()) + { + bot->GetMotionMaster()->Clear(); + bot->StopMoving(); + return true; + } + + uint8 bagIndex = rocketPack->GetBagSlot(); + uint8 slot = rocketPack->GetSlot(); + uint8 castCount = 1; + uint32 spellId = SPELL_ROCKET_PACK_USE; + ObjectGuid itemGuid = rocketPack->GetGUID(); + uint32 glyphIndex = 0; + uint8 castFlags = 0; + + WorldPacket packet(CMSG_USE_ITEM); + packet << bagIndex << slot << castCount << spellId << itemGuid << glyphIndex << castFlags; + packet << uint32(TARGET_FLAG_DEST_LOCATION); + packet.appendPackGUID(0); + packet << destination.GetPositionX() << destination.GetPositionY() << destination.GetPositionZ(); + + bot->GetSession()->HandleUseItemOpcode(packet); + lastRocketPackUse[bot->GetGUID()] = now; + return true; +} + +bool IccGunshipRocketJumpAction::RocketPackJumpToward(Position const& target) +{ + static constexpr float JUMP_DISTANCE = 30.0f; + float const dist = bot->GetExactDist(&target); + if (dist <= JUMP_DISTANCE) + return UseRocketPack(target, /*walkIfOutOfRange=*/false); + + float const ratio = JUMP_DISTANCE / dist; + Position jumpTo( + bot->GetPositionX() + (target.GetPositionX() - bot->GetPositionX()) * ratio, + bot->GetPositionY() + (target.GetPositionY() - bot->GetPositionY()) * ratio, + bot->GetPositionZ() + (target.GetPositionZ() - bot->GetPositionZ()) * ratio); + return UseRocketPack(jumpTo, /*walkIfOutOfRange=*/false); +} + +Unit* IccGunshipRocketJumpAction::FindNearestFriendlyCannon(GunshipSide side) const +{ + if (side == GunshipSide::NONE) + return nullptr; + + uint32 const cannonEntry = (side == GunshipSide::ALLY) ? NPC_CANNONA : NPC_CANNONH; + Unit* cannon = bot->FindNearestCreature(cannonEntry, 300.0f); + if (cannon && cannon->IsFriendlyTo(bot)) + return cannon; + return nullptr; +} + +bool IccGunshipRocketJumpAction::CleanupSkullIcon(uint8 skullIconIndex) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + ObjectGuid const currentSkullTarget = group->GetTargetIcon(skullIconIndex); + if (currentSkullTarget.IsEmpty()) + return false; + + Unit* skullTarget = ObjectAccessor::GetUnit(*bot, currentSkullTarget); + if (!skullTarget || !skullTarget->IsAlive()) + group->SetTargetIcon(skullIconIndex, bot->GetGUID(), ObjectGuid::Empty); + + return false; +} + +bool IccGunshipRocketJumpAction::UpdateBossSkullIcon(Unit* boss, uint8 skullIconIndex) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + if (group->GetTargetIcon(skullIconIndex) != boss->GetGUID()) + group->SetTargetIcon(skullIconIndex, bot->GetGUID(), boss->GetGUID()); + + return false; +} + +bool IccGunshipRocketPackSetupAction::Execute(Event /*event*/) +{ + Item* rocketPack = FindRocketPack(); + if (!rocketPack) + return AcquireRocketPack(); + if (!rocketPack->IsEquipped()) + return EquipRocketPack(); + return false; +} + +Item* IccGunshipRocketPackSetupAction::FindRocketPack() const +{ + return bot->GetItemByEntry(ITEM_GOBLIN_ROCKET_PACK); +} + +bool IccGunshipRocketPackSetupAction::AcquireRocketPack() +{ + static constexpr float ZAFOD_SEARCH_RADIUS = 150.0f; + static constexpr uint32 ZAFOD_GOSSIP_MENU_ID = 10885; + static constexpr uint32 ZAFOD_GOSSIP_OPTION = 0; + + Creature* zafod = bot->FindNearestCreature(NPC_ZAFOD_BOOMBOX, ZAFOD_SEARCH_RADIUS); + if (!zafod) + return false; + + if (bot->GetDistance(zafod) > INTERACTION_DISTANCE) + return MoveTo(bot->GetMapId(), zafod->GetPositionX(), zafod->GetPositionY(), zafod->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_NORMAL); + + bot->GetMotionMaster()->Clear(); + bot->StopMoving(); + bot->SetFacingToObject(zafod); + + WorldPacket gossipHello(CMSG_GOSSIP_HELLO); + gossipHello << zafod->GetGUID(); + bot->GetSession()->HandleGossipHelloOpcode(gossipHello); + + WorldPacket gossipSelect(CMSG_GOSSIP_SELECT_OPTION); + gossipSelect << zafod->GetGUID(); + gossipSelect << uint32(ZAFOD_GOSSIP_MENU_ID) << uint32(ZAFOD_GOSSIP_OPTION); + bot->GetSession()->HandleGossipSelectOptionOpcode(gossipSelect); + return true; +} + +bool IccGunshipRocketPackSetupAction::EquipRocketPack() +{ + EquipAction* equipAction = dynamic_cast(botAI->GetAiObjectContext()->GetAction("equip")); + if (!equipAction) + return false; + + ItemIds ids; + ids.insert(ITEM_GOBLIN_ROCKET_PACK); + equipAction->EquipItems(ids); + return true; +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_LDW.cpp b/src/Ai/Raid/ICC/Action/ICCActions_LDW.cpp new file mode 100644 index 00000000000..c6573abc331 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_LDW.cpp @@ -0,0 +1,1044 @@ +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "Multiplier.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "ICCActions.h" +#include "ICCTriggers.h" +#include "RtiValue.h" +#include "Vehicle.h" + +// Lady Deathwhisper +bool IccDarkReckoningAction::Execute(Event /*event*/) +{ + constexpr float SAFE_DISTANCE_THRESHOLD = 2.0f; + + if (bot->HasAura(SPELL_DARK_RECKONING) && + bot->GetExactDist2d(ICC_DARK_RECKONING_SAFE_POSITION) > SAFE_DISTANCE_THRESHOLD) + { + return MoveTo(bot->GetMapId(), ICC_DARK_RECKONING_SAFE_POSITION.GetPositionX(), + ICC_DARK_RECKONING_SAFE_POSITION.GetPositionY(), ICC_DARK_RECKONING_SAFE_POSITION.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + } + + return false; +} + +bool IccRangedPositionLadyDeathwhisperAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); + if (!boss) + return false; + + float const currentDistance = bot->GetDistance2d(boss); + float const minDistance = 7.0f; + float const maxDistance = 30.0f; + + if (currentDistance < minDistance || currentDistance > maxDistance) + return false; + + if (!botAI->IsRanged(bot) && !botAI->IsHeal(bot)) + return false; + + return MaintainRangedSpacing(); +} + +bool IccRangedPositionLadyDeathwhisperAction::MaintainRangedSpacing() +{ + float const safeSpacingRadius = 3.0f; + float const moveIncrement = 2.0f; + float const maxMoveDistance = 5.0f; + bool const isRanged = botAI->IsRanged(bot) || botAI->IsHeal(bot); + + if (!isRanged) + return false; + + GuidVector const members = AI_VALUE(GuidVector, "group members"); + + float totalX = 0.0f; + float totalY = 0.0f; + int nearbyCount = 0; + + for (auto const& memberGuid : members) + { + Unit* member = botAI->GetUnit(memberGuid); + if (!member || !member->IsAlive() || member == bot) + continue; + + float const distance = bot->GetExactDist2d(member); + if (distance < safeSpacingRadius) + { + float dx = bot->GetPositionX() - member->GetPositionX(); + float dy = bot->GetPositionY() - member->GetPositionY(); + + float weight = (safeSpacingRadius - distance) / safeSpacingRadius; + totalX += dx * weight; + totalY += dy * weight; + ++nearbyCount; + } + } + + if (nearbyCount > 0) + { + float magnitude = std::sqrt(totalX * totalX + totalY * totalY); + if (magnitude > 0.001f) + { + totalX /= magnitude; + totalY /= magnitude; + + float const moveDistance = std::min(moveIncrement, maxMoveDistance); + + float targetX = bot->GetPositionX() + totalX * moveDistance; + float targetY = bot->GetPositionY() + totalY * moveDistance; + float targetZ = bot->GetPositionZ(); + + if (bot->IsWithinLOS(targetX, targetY, targetZ)) + { + Position targetPos(targetX, targetY, targetZ); + MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + } + else + { + targetX = bot->GetPositionX() + totalX * (moveDistance * 0.5f); + targetY = bot->GetPositionY() + totalY * (moveDistance * 0.5f); + Position targetPos(targetX, targetY, targetZ); + MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + } + } + } + + return false; +} + +bool IccAddsLadyDeathwhisperAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); + if (!boss) + return false; + + Difficulty diff = bot->GetRaidDifficulty(); + + if (sPlayerbotAIConfig.EnableICCBuffs && diff && + (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) + { + if (!bot->HasAura(SPELL_NO_THREAT) && botAI->HasAggro(boss) && !botAI->IsTank(bot)) + bot->AddAura(SPELL_NO_THREAT, bot); + + if (botAI->IsMainTank(bot) && !bot->HasAura(SPELL_SPITEFULL_FURY) && boss->GetVictim() != bot) + bot->AddAura(SPELL_SPITEFULL_FURY, bot); + } + + if (botAI->HasAura("Dominate Mind", bot, false, false) && !bot->HasAura(SPELL_CYCLONE)) + bot->AddAura(SPELL_CYCLONE, bot); + else if (bot->HasAura(SPELL_CYCLONE) && !botAI->HasAura("Dominate Mind", bot, false, false)) + bot->RemoveAura(SPELL_CYCLONE); + + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + + PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member); + if (memberBotAI && !memberBotAI->IsRealPlayer()) + continue; + + if (botAI->HasAura("Dominate Mind", member, false, false) && !member->HasAura(SPELL_CYCLONE)) + member->AddAura(SPELL_CYCLONE, member); + else if (member->HasAura(SPELL_CYCLONE) && !botAI->HasAura("Dominate Mind", member, false, false)) + member->RemoveAura(SPELL_CYCLONE); + } + } + + constexpr uint32 shadeEntryId = NPC_SHADE; + + if (botAI->IsTank(bot)) + { + if (bot->HasAura(SPELL_TOUCH_OF_INSIGNIFICANCE)) + bot->RemoveAura(SPELL_TOUCH_OF_INSIGNIFICANCE); + + if (IsTargetedByShade(shadeEntryId)) + return false; + + Unit* currentTarget = bot->GetVictim(); + bool const isAttackingAdd = currentTarget && IsAdd(currentTarget); + if (isAttackingAdd && currentTarget->IsAlive()) + { + if (botAI->IsAssistTank(bot) || !IsAssistTankAlive()) + { + constexpr float MARK_RANGE = 25.0f; + Unit* nearAdd = FindAddNearBoss(boss, MARK_RANGE); + UpdateRaidTargetIcon(nearAdd ? nearAdd : boss); + } + + if (currentTarget->GetVictim() == bot) + { + constexpr float KITE_THRESHOLD = 25.0f; + if (!boss->HealthAbovePct(95)) + { + Position const& tankPos = ICC_LDW_TANK_POSTION; + if (bot->GetExactDist2d(tankPos.GetPositionX(), tankPos.GetPositionY()) > KITE_THRESHOLD) + MoveTo(bot->GetMapId(), tankPos.GetPositionX(), tankPos.GetPositionY(), tankPos.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + else + { + constexpr float CONSOLIDATE_THRESHOLD = 5.0f; + if (currentTarget->GetExactDist2d(boss) > CONSOLIDATE_THRESHOLD) + MoveTo(bot->GetMapId(), boss->GetPositionX(), boss->GetPositionY(), boss->GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + } + + return false; + } + + Unit* targetAdd = FindAndCollectAdd(boss); + if (targetAdd) + { + if (botAI->IsAssistTank(bot) || !IsAssistTankAlive()) + { + constexpr float MARK_RANGE = 25.0f; + Unit* nearAdd = FindAddNearBoss(boss, MARK_RANGE); + UpdateRaidTargetIcon(nearAdd ? nearAdd : boss); + } + } + + if (boss->IsAlive()) + { + { + constexpr float MARK_RANGE = 25.0f; + Unit* nearAdd = FindAddNearBoss(boss, MARK_RANGE); + UpdateRaidTargetIcon(nearAdd ? nearAdd : boss); + } + + if (!boss->HealthAbovePct(95)) + { + bool anyTankHasAggro = false; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsTank(member) && boss->GetVictim() == member) + { + anyTankHasAggro = true; + break; + } + } + } + + if (anyTankHasAggro) + { + Position const& tankPos = ICC_LDW_TANK_POSTION; + if (bot->GetExactDist2d(tankPos.GetPositionX(), tankPos.GetPositionY()) > 5.0f) + MoveTo(bot->GetMapId(), tankPos.GetPositionX(), tankPos.GetPositionY(), tankPos.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + } + } + + return false; + } + + // Phase 1 mana drain: every hunter keeps its own Viper Sting up on boss. + // Per-hunter aura tracking via caster-GUID so multiple hunters stack cleanly. + if (bot->getClass() == CLASS_HUNTER && boss && boss->HealthAbovePct(95) && boss->IsAlive() && !boss->HasAura(SPELL_VIPER_STING, bot->GetGUID()) && botAI->CanCastSpell(SPELL_VIPER_STING, boss)) + botAI->CastSpell("viper sting", boss); + + if (HandleNonTankAddEvasion()) + return false; + + ApplyNearbyAddCC(); + + if (!boss->HealthAbovePct(95) && (botAI->IsRanged(bot) || botAI->IsHeal(bot))) + { + bool threatened = false; + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (unit && unit->IsAlive() && unit->GetVictim() == bot && (IsAdd(unit) || unit->GetEntry() == NPC_SHADE)) + { + threatened = true; + break; + } + } + + if (!threatened) + { + constexpr float REGROUP_DISTANCE = 18.0f; + Position const& tankPos = ICC_LDW_TANK_POSTION; + if (bot->GetExactDist2d(tankPos.GetPositionX(), tankPos.GetPositionY()) > REGROUP_DISTANCE) + MoveTo(bot->GetMapId(), tankPos.GetPositionX(), tankPos.GetPositionY(), tankPos.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + } + } + + if (IsAddsAlive()) + return false; + + if (boss->HealthAbovePct(95)) + return EngageBoss(); + + return false; +} + +Unit* IccAddsLadyDeathwhisperAction::FindAndCollectAdd(Unit* boss) +{ + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + Unit* bestAdd = nullptr; + float bestScore = FLT_MAX; + + for (auto const& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (!unit || !unit->IsAlive()) + continue; + + if (!IsAdd(unit)) + continue; + + Unit* victim = unit->GetVictim(); + if (victim && victim->IsPlayer() && botAI->IsTank(victim->ToPlayer())) + continue; + + float distance = bot->GetExactDist(unit); + float score = distance; + if (victim && victim != bot) + score += 5.0f; + + if (score < bestScore) + { + bestScore = score; + bestAdd = unit; + } + } + + if (!bestAdd) + return nullptr; + + float const tauntRange = 30.0f; + float const currentDist = bot->GetExactDist(bestAdd); + + if (currentDist > tauntRange) + { + float moveX = bot->GetPositionX(); + float moveY = bot->GetPositionY(); + float moveZ = bot->GetPositionZ(); + float step = 5.0f; + float dx = bestAdd->GetPositionX() - bot->GetPositionX(); + float dy = bestAdd->GetPositionY() - bot->GetPositionY(); + float dist = std::sqrt(dx * dx + dy * dy); + if (dist > 0.1f) + { + dx /= dist; + dy /= dist; + float moveDist = std::min(step, dist - (tauntRange - 5.0f)); + moveX = bot->GetPositionX() + dx * moveDist; + moveY = bot->GetPositionY() + dy * moveDist; + } + MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + return nullptr; + } + + bool taunted = false; + if (botAI->CastSpell("taunt", bestAdd)) + taunted = true; + else + { + switch (bot->getClass()) + { + case CLASS_PALADIN: + taunted = botAI->CastSpell("hand of reckoning", bestAdd); + break; + case CLASS_DEATH_KNIGHT: + taunted = botAI->CastSpell("dark command", bestAdd); + break; + case CLASS_DRUID: + taunted = botAI->CastSpell("growl", bestAdd); + break; + default: + break; + } + } + + if (!taunted) + { + if (currentDist > 5.0f) + { + float dx = bestAdd->GetPositionX() - bot->GetPositionX(); + float dy = bestAdd->GetPositionY() - bot->GetPositionY(); + float dist = std::sqrt(dx * dx + dy * dy); + dx /= dist; + dy /= dist; + float step = std::max(0.5f, std::min(3.0f, dist - 3.0f)); + float moveX = bot->GetPositionX() + dx * step; + float moveY = bot->GetPositionY() + dy * step; + MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + return nullptr; + } + } + + if (boss && boss->HealthAbovePct(95)) + { + constexpr float CONSOLIDATE_THRESHOLD = 5.0f; + if (bestAdd->GetExactDist2d(boss) > CONSOLIDATE_THRESHOLD) + MoveTo(bot->GetMapId(), boss->GetPositionX(), boss->GetPositionY(), boss->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + + return bestAdd; +} + +bool IccAddsLadyDeathwhisperAction::HandleNonTankAddEvasion() +{ + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + bool targetedByAdd = false; + for (auto const& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (!unit || !unit->IsAlive() || unit->GetVictim() != bot) + continue; + + if (IsAdd(unit)) + { + targetedByAdd = true; + ApplyCCToAdd(unit); + } + } + + if (!targetedByAdd) + return false; + + Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); + if (!boss) + return false; + + if (boss->HealthAbovePct(95)) + { + Unit* closestAdd = nullptr; + float closestDist = FLT_MAX; + GuidVector const npcs2 = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npcGuid : npcs2) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (!unit || !unit->IsAlive() || unit->GetVictim() != bot || !IsAdd(unit)) + continue; + + float d = bot->GetExactDist2d(unit); + if (d < closestDist) + { + closestDist = d; + closestAdd = unit; + } + } + + if (!closestAdd) + return false; + + constexpr float KEEP_DISTANCE = 10.0f; + if (closestDist >= KEEP_DISTANCE) + return false; + + float dx = bot->GetPositionX() - closestAdd->GetPositionX(); + float dy = bot->GetPositionY() - closestAdd->GetPositionY(); + float len = std::sqrt(dx * dx + dy * dy); + if (len < 0.001f) + return false; + + dx /= len; + dy /= len; + float moveX = bot->GetPositionX() + dx * (KEEP_DISTANCE - closestDist + 2.0f); + float moveY = bot->GetPositionY() + dy * (KEEP_DISTANCE - closestDist + 2.0f); + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + else + { + constexpr float ARRIVAL_THRESHOLD = 5.0f; + if (bot->GetExactDist2d(boss) <= ARRIVAL_THRESHOLD) + return false; + + return MoveTo(bot->GetMapId(), boss->GetPositionX(), boss->GetPositionY(), boss->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT); + } +} + +bool IccAddsLadyDeathwhisperAction::ApplyNearbyAddCC() +{ + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + constexpr float CC_RANGE = 10.0f; + + for (auto const& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (!unit || !unit->IsAlive()) + continue; + + if (!IsAdd(unit)) + continue; + + if (bot->GetDistance(unit) <= CC_RANGE) + ApplyCCToAdd(unit); + } + + return false; +} + +bool IccAddsLadyDeathwhisperAction::ApplyCCToAdd(Unit* add) +{ + if (!add || !add->IsAlive()) + return false; + + switch (bot->getClass()) + { + case CLASS_MAGE: + if (!botAI->HasAura("Frost Nova", add)) + botAI->CastSpell("Frost Nova", add); + break; + case CLASS_DRUID: + if (!botAI->HasAura("Entangling Roots", add)) + botAI->CastSpell("Entangling Roots", add); + break; + case CLASS_PALADIN: + if (!botAI->HasAura("Hammer of Justice", add)) + botAI->CastSpell("Hammer of Justice", add); + break; + case CLASS_WARRIOR: + if (!botAI->HasAura("Hamstring", add)) + botAI->CastSpell("Hamstring", add); + break; + case CLASS_HUNTER: + if (!botAI->HasAura("Concussive Shot", add)) + botAI->CastSpell("Concussive Shot", add); + break; + case CLASS_ROGUE: + if (!botAI->HasAura("Kidney Shot", add)) + botAI->CastSpell("Kidney Shot", add); + break; + case CLASS_SHAMAN: + if (!botAI->HasAura("Frost Shock", add)) + botAI->CastSpell("Frost Shock", add); + break; + case CLASS_DEATH_KNIGHT: + if (!botAI->HasAura("Chains of Ice", add)) + botAI->CastSpell("Chains of Ice", add); + break; + case CLASS_PRIEST: + if (!botAI->HasAura("Psychic Scream", add)) + botAI->CastSpell("Psychic Scream", add); + break; + case CLASS_WARLOCK: + if (!botAI->HasAura("Fear", add)) + botAI->CastSpell("Fear", add); + break; + default: + break; + } + + return false; +} + +bool IccAddsLadyDeathwhisperAction::IsAddsAlive() +{ + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (unit && unit->IsAlive() && IsAdd(unit)) + return true; + } + + return false; +} + +bool IccAddsLadyDeathwhisperAction::EngageBoss() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); + if (!boss || !boss->IsAlive()) + return false; + + bool const isMelee = botAI->IsMelee(bot); + bool const isRanged = botAI->IsRanged(bot) || botAI->IsHeal(bot); + + if (isMelee) + { + float dist = bot->GetDistance(boss); + if (dist > 25.0f) + { + float dx = boss->GetPositionX() - bot->GetPositionX(); + float dy = boss->GetPositionY() - bot->GetPositionY(); + float len = std::sqrt(dx * dx + dy * dy); + if (len > 0.1f) + { + dx /= len; + dy /= len; + float step = std::min(3.0f, len); + float moveX = bot->GetPositionX() + dx * step; + float moveY = bot->GetPositionY() + dy * step; + MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + return false; + } + } + } + + if (isRanged) + { + Position const rangedPos = ICC_LDW_RANGED_POSITION; + float const distToPos = bot->GetDistance2d(rangedPos.GetPositionX(), rangedPos.GetPositionY()); + if (distToPos > 30.0f) + { + MoveTo(bot->GetMapId(), rangedPos.GetPositionX(), rangedPos.GetPositionY(), rangedPos.GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_COMBAT); + return false; + } + } + + return false; +} + +Unit* IccAddsLadyDeathwhisperAction::FindAddNearBoss(Unit* boss, float maxDist) +{ + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + Unit* best = nullptr; + float bestDist = maxDist; + + for (auto const& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (!unit || !unit->IsAlive() || !IsAdd(unit)) + continue; + + float dist = unit->GetExactDist2d(boss); + if (dist < bestDist) + { + bestDist = dist; + best = unit; + } + } + + return best; +} + +bool IccAddsLadyDeathwhisperAction::IsAdd(Unit* unit) +{ + if (!unit) + return false; + + uint32 entry = unit->GetEntry(); + return std::find(addEntriesLady.begin(), addEntriesLady.end(), entry) != addEntriesLady.end(); +} + +bool IccAddsLadyDeathwhisperAction::IsAssistTankAlive() +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + + if (botAI->IsTank(member) && !botAI->IsMainTank(member)) + return true; + } + + return false; +} + +bool IccAddsLadyDeathwhisperAction::IsTargetedByShade(uint32 shadeEntry) +{ + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (unit && unit->GetEntry() == shadeEntry && unit->GetVictim() == bot) + return true; + } + + return false; +} + +bool IccAddsLadyDeathwhisperAction::MoveTowardPosition(Position const& position, float incrementSize) +{ + float const dirX = position.GetPositionX() - bot->GetPositionX(); + float const dirY = position.GetPositionY() - bot->GetPositionY(); + float const length = std::sqrt(dirX * dirX + dirY * dirY); + + if (length < 0.001f) + return false; + + float const normalizedDirX = dirX / length; + float const normalizedDirY = dirY / length; + + float const moveX = bot->GetPositionX() + normalizedDirX * incrementSize; + float const moveY = bot->GetPositionY() + normalizedDirY * incrementSize; + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); +} + +bool IccAddsLadyDeathwhisperAction::HandleAddTargeting(Unit* boss) +{ + GuidVector const targets = AI_VALUE(GuidVector, "possible targets no los"); + + Unit* priorityTarget = nullptr; + bool hasValidAdds = false; + + for (auto const& entry : addEntriesLady) + { + for (auto const& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == entry) + { + priorityTarget = unit; + hasValidAdds = true; + break; + } + } + + if (priorityTarget) + break; + } + + if (!hasValidAdds && boss->IsAlive()) + priorityTarget = boss; + + if (priorityTarget) + UpdateRaidTargetIcon(priorityTarget); + + return false; +} + +bool IccAddsLadyDeathwhisperAction::UpdateRaidTargetIcon(Unit* target) +{ + static constexpr uint8 SKULL_ICON_INDEX = 7; + + if (!target || !target->IsAlive()) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + ObjectGuid currentSkull = group->GetTargetIcon(SKULL_ICON_INDEX); + Unit* currentSkullUnit = botAI->GetUnit(currentSkull); + + bool const currentIsAdd = currentSkullUnit && currentSkullUnit->IsAlive() && IsAdd(currentSkullUnit); + bool const targetIsAdd = IsAdd(target); + + bool const shouldUpdate = !currentSkullUnit || !currentSkullUnit->IsAlive() || (targetIsAdd && !currentIsAdd); + + if (shouldUpdate) + group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), target->GetGUID()); + + return false; +} + +bool IccShadeLadyDeathwhisperAction::Execute(Event /*event*/) +{ + static constexpr uint32 VENGEFUL_SHADE_ID = NPC_SHADE; + static constexpr float SAFE_DISTANCE = 12.0f; + + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + float fleeX = 0.0f; + float fleeY = 0.0f; + float closestDist = SAFE_DISTANCE; + int shadeCount = 0; + + for (auto const& npcGuid : npcs) + { + Unit* shade = botAI->GetUnit(npcGuid); + + if (!shade || shade->GetEntry() != VENGEFUL_SHADE_ID) + continue; + + if (!shade->GetVictim() || shade->GetVictim()->GetGUID() != bot->GetGUID()) + continue; + + float const currentDistance = bot->GetDistance2d(shade); + + if (currentDistance >= SAFE_DISTANCE) + continue; + + float dx = bot->GetPositionX() - shade->GetPositionX(); + float dy = bot->GetPositionY() - shade->GetPositionY(); + float dist = std::sqrt(dx * dx + dy * dy); + + if (dist < 0.001f) + continue; + + float weight = (SAFE_DISTANCE - currentDistance) / dist; + fleeX += dx * weight; + fleeY += dy * weight; + + if (currentDistance < closestDist) + closestDist = currentDistance; + + shadeCount++; + } + + if (shadeCount == 0) + return false; + + float fleeDist = std::sqrt(fleeX * fleeX + fleeY * fleeY); + if (fleeDist < 0.001f) + return false; + + fleeX /= fleeDist; + fleeY /= fleeDist; + + float moveDistance = SAFE_DISTANCE - closestDist + 2.0f; + float targetX = bot->GetPositionX() + fleeX * moveDistance; + float targetY = bot->GetPositionY() + fleeY * moveDistance; + float targetZ = bot->GetPositionZ(); + + if (!bot->IsWithinLOS(targetX, targetY, targetZ)) + { + moveDistance *= 0.5f; + targetX = bot->GetPositionX() + fleeX * moveDistance; + targetY = bot->GetPositionY() + fleeY * moveDistance; + } + + botAI->Reset(); + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); +} + +// Rotting Giant WiP (waiting for core fix) +bool IccRottingFrostGiantTankPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "rotting frost giant"); + if (!boss) + return false; + + Aura* aura = botAI->GetAura("death plague", bot, false, false); + if (aura) + bot->RemoveAura(aura->GetId()); + + /* TODO: code works for handling plague, but atm script is bugged and one bot can have 2 plagues at the same time or + when cured, which should not happen, and it is immpossible to handle plague atm the legit way. const bool hasCure + = botAI->GetAura("recently infected", bot) != nullptr; + + // Tank behavior - unchanged + if (botAI->IsTank(bot) && botAI->HasAggro(boss) && !isInfected) + if (bot->GetExactDist2d(ICC_ROTTING_FROST_GIANT_TANK_POSITION) > 5.0f) + return MoveTo(bot->GetMapId(), ICC_ROTTING_FROST_GIANT_TANK_POSITION.GetPositionX(), + ICC_ROTTING_FROST_GIANT_TANK_POSITION.GetPositionY(), + ICC_ROTTING_FROST_GIANT_TANK_POSITION.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + + if (botAI->IsTank(bot)) + return false; + + // Handle infected bot behavior - move near a non-infected, non-cured bot + if (isInfected) + { + const GuidVector members = AI_VALUE(GuidVector, "group members"); + + // Count how many bots are targeting each potential target + std::map targetCounts; + + // First, identify all infected bots and their current targets (approximate) + for (auto const& memberGuid : members) + { + Unit* member = botAI->GetUnit(memberGuid); + if (!member || !member->IsAlive() || member == bot) + continue; + + const bool memberIsInfected = botAI->GetAura("death plague", member) != nullptr; + + if (memberIsInfected) + { + // Find the nearest non-infected bot to this infected bot (as a guess of its target) + float minDist = 5.0f; // Only count if they're close enough to likely be targeting + Unit* likelyTarget = nullptr; + + for (auto const& targetGuid : members) + { + Unit* potentialTarget = botAI->GetUnit(targetGuid); + if (!potentialTarget || !potentialTarget->IsAlive() || potentialTarget == member) + continue; + + const bool targetIsInfected = botAI->GetAura("death plague", potentialTarget) != nullptr; + const bool targetHasCure = botAI->GetAura("recently infected", potentialTarget) != nullptr; + + if (!targetIsInfected && !targetHasCure) + { + float dist = member->GetExactDist2d(potentialTarget); + if (dist < minDist) + { + minDist = dist; + likelyTarget = potentialTarget; + } + } + } + + if (likelyTarget) + { + targetCounts[likelyTarget->GetGUID()]++; + } + } + } + + // Find viable targets and score them based on various factors + std::vector> viableTargets; + + // First try to find ranged, non-infected, non-cured bots + for (auto const& memberGuid : members) + { + Unit* member = botAI->GetUnit(memberGuid); + if (!member || !member->IsAlive() || member == bot) + continue; + + const bool memberHasCure = botAI->GetAura("recently infected", member) != nullptr; + const bool memberIsInfected = botAI->GetAura("death plague", member) != nullptr; + + if (!memberIsInfected && !memberHasCure) + { + // Base score is distance (lower is better) + float score = bot->GetExactDist2d(member); + + // Prefer ranged targets + if (botAI->IsRanged(bot)) + { + score *= 0.7f; // Bonus for ranged targets + } + + // Apply penalty based on how many other infected bots are targeting this one + int targetingCount = targetCounts[member->GetGUID()]; + score *= (1.0f + targetingCount * 0.5f); // Increase score (worse) for heavily targeted bots + + viableTargets.push_back(std::make_pair(member, score)); + } + } + + // Sort targets by score (lowest/best first) + std::sort(viableTargets.begin(), viableTargets.end(), + [](const std::pair& a, const std::pair& b) + { return a.second < b.second; }); + + // Choose the best target + Unit* targetBot = nullptr; + if (!viableTargets.empty()) + { + targetBot = viableTargets[0].first; + } + + // Move to target bot if found + if (targetBot) + { + // If we're already close enough (1 yard), no need to move + if (bot->GetExactDist2d(targetBot) > 1.0f) + { + // Calculate a unique angle based on bot's GUID to ensure different approach angles + // This helps spread infected bots around the target + uint32 guidLow = bot->GetGUID().GetCounter(); + float angleOffset = float(guidLow % 628) / 100.0f; // Random angle between 0 and 2π + + // Calculate position 1 yard away from target at our unique angle + float angle = targetBot->GetOrientation() + angleOffset; + float targetX = targetBot->GetPositionX() + cos(angle) * 1.0f; + float targetY = targetBot->GetPositionY() + sin(angle) * 1.0f; + float targetZ = targetBot->GetPositionZ(); + + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + return true; // Already in position + } + // No suitable target found, continue with normal behavior + } + + // For ranged bots, only spread from non-infected bots + if (botAI->IsRanged(bot)) + { + const float safeSpacingRadius = 11.0f; + const float moveIncrement = 2.0f; + const float maxMoveDistance = 15.0f; + + const GuidVector members = AI_VALUE(GuidVector, "group members"); + + // Calculate a combined vector representing all nearby NON-INFECTED members' positions + float totalX = 0.0f; + float totalY = 0.0f; + int nearbyCount = 0; + + for (auto const& memberGuid : members) + { + Unit* member = botAI->GetUnit(memberGuid); + if (!member || !member->IsAlive() || member == bot) + continue; + + // Only spread from non-infected bots (can stay near infected or cured bots) + const bool memberIsInfected = botAI->GetAura("death plague", member) != nullptr; + if (memberIsInfected) + continue; + + const float distance = bot->GetExactDist2d(member); + if (distance < safeSpacingRadius) + { + // Calculate vector from member to bot + float dx = bot->GetPositionX() - member->GetPositionX(); + float dy = bot->GetPositionY() - member->GetPositionY(); + + // Weight by inverse distance (closer members have more influence) + float weight = (safeSpacingRadius - distance) / safeSpacingRadius; + totalX += dx * weight; + totalY += dy * weight; + nearbyCount++; + } + } + + // If we have nearby non-infected members, move away in the combined direction + if (nearbyCount > 0) + { + // Normalize the combined vector + float magnitude = std::sqrt(totalX * totalX + totalY * totalY); + if (magnitude > 0.001f) // Avoid division by zero + { + totalX /= magnitude; + totalY /= magnitude; + + // Calculate move distance based on nearest member + float moveDistance = std::min(moveIncrement, maxMoveDistance); + + // Create target position in the combined direction + float targetX = bot->GetPositionX() + totalX * moveDistance; + float targetY = bot->GetPositionY() + totalY * moveDistance; + float targetZ = bot->GetPositionZ(); // Maintain current Z + + // Check if the target position is valid and move there + if (bot->IsWithinLOS(targetX, targetY, targetZ)) + { + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + } + else + { + // If los check fails, try shorter distance + targetX = bot->GetPositionX() + totalX * (moveDistance * 0.5f); + targetY = bot->GetPositionY() + totalY * (moveDistance * 0.5f); + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + } + } + } + } + */ + return false; +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_LK.cpp b/src/Ai/Raid/ICC/Action/ICCActions_LK.cpp new file mode 100644 index 00000000000..1230e9d6ca5 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_LK.cpp @@ -0,0 +1,4568 @@ +#include "ICCActions.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "Vehicle.h" +#include "RtiValue.h" +#include "GenericSpellActions.h" +#include "GenericActions.h" +#include "ICCTriggers.h" +#include "ICCScripts.h" +#include "Multiplier.h" + +static bool IsLkShambling(uint32 entry) +{ + return entry == NPC_SHAMBLING_HORROR1 || entry == NPC_SHAMBLING_HORROR2 || + entry == NPC_SHAMBLING_HORROR3 || entry == NPC_SHAMBLING_HORROR4; +} + +static bool IsLkRagingSpirit(uint32 entry) +{ + return entry == NPC_RAGING_SPIRIT1 || entry == NPC_RAGING_SPIRIT2 || + entry == NPC_RAGING_SPIRIT3 || entry == NPC_RAGING_SPIRIT4; +} + +static bool IsLkValkyr(Unit* unit) +{ + if (!unit) + return false; + + uint32 const entry = unit->GetEntry(); + return entry == NPC_VALKYR_SHADOWGUARD1 || entry == NPC_VALKYR_SHADOWGUARD2 || + entry == NPC_VALKYR_SHADOWGUARD3 || entry == NPC_VALKYR_SHADOWGUARD4; +} + +static bool IsLkVileSpirit(Unit* unit) +{ + if (!unit) + return false; + + uint32 const entry = unit->GetEntry(); + return entry == NPC_VILE_SPIRIT1 || entry == NPC_VILE_SPIRIT2 || + entry == NPC_VILE_SPIRIT3 || entry == NPC_VILE_SPIRIT4; +} + +static bool IsLkWickedSpirit(uint32 entry) +{ + return entry == NPC_WICKED_SPIRIT1 || entry == NPC_WICKED_SPIRIT2 || + entry == NPC_WICKED_SPIRIT3 || entry == NPC_WICKED_SPIRIT4; +} + +static bool IsIceSphere(uint32 entry) +{ + return entry == NPC_ICE_SPHERE1 || entry == NPC_ICE_SPHERE2 || + entry == NPC_ICE_SPHERE3 || entry == NPC_ICE_SPHERE4; +} + +static bool IsLkCollectibleAdd(Unit* unit) +{ + if (!unit || !unit->IsAlive()) + return false; + + uint32 const entry = unit->GetEntry(); + return IsLkShambling(entry) || IsLkRagingSpirit(entry) || + entry == NPC_DRUDGE_GHOUL1 || entry == NPC_DRUDGE_GHOUL2 || + entry == NPC_DRUDGE_GHOUL3 || entry == NPC_DRUDGE_GHOUL4; +} + +static bool HasFrontalAbility(uint32 entry) +{ + return IsLkShambling(entry) || IsLkRagingSpirit(entry); +} + +static bool IsHeroicLk(Difficulty diff) +{ + return diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC; +} + +static bool HasAnyRemorselessWinter(Unit* boss) +{ + if (!boss) + return false; + + if (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || + boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4) || + boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || + boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8)) + return true; + + if (!boss->HasUnitState(UNIT_STATE_CASTING)) + return false; + + return boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8); +} + +// Apply heroic cheat buffs to bots & players +static void ApplyHeroicBuffToMember(PlayerbotAI* botAI, Player* member, bool applyNoThreat) +{ + if (!member->HasAura(SPELL_EXPERIENCED)) + member->AddAura(SPELL_EXPERIENCED, member); + + if (!member->HasAura(SPELL_AGEIS_OF_DALARAN)) + member->AddAura(SPELL_AGEIS_OF_DALARAN, member); + + if (!member->HasAura(SPELL_PAIN_SUPPRESION)) + member->AddAura(SPELL_PAIN_SUPPRESION, member); + + if (applyNoThreat && !botAI->IsTank(member) && !member->HasAura(SPELL_NO_THREAT)) + member->AddAura(SPELL_NO_THREAT, member); +} + +static float GetDefileEffectiveRadius(Unit const* defile, Difficulty diff) +{ + static constexpr float BASE_RADIUS = 6.0f; + + for (uint32 const auraId : DEFILE_AURAS) + { + Aura const* grow = defile->GetAura(auraId); + if (!grow) + continue; + + float const multiplier = + (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_10MAN_NORMAL) ? 1.4f : 0.95f; + + return BASE_RADIUS + grow->GetStackAmount() * multiplier; + } + + return BASE_RADIUS; +} + +// Compute the centroid of alive group members for a stable reference point. +// Unlike a single reference player, the centroid barely shifts when one +// player dies, preventing the raid from oscillating between positions. +static Position ComputeGroupCentroid(Player* bot) +{ + Group* group = bot->GetGroup(); + if (!group) + return Position(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); + + float sumX = 0.0f; + float sumY = 0.0f; + int count = 0; + + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || !member->IsInWorld()) + continue; + + sumX += member->GetPositionX(); + sumY += member->GetPositionY(); + ++count; + } + + if (count == 0) + return Position(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); + + return Position(sumX / count, sumY / count, bot->GetPositionZ()); +} + +static Position const& SelectClosestOf3(Position const& ref, Position const& p1, Position const& p2, Position const& p3) +{ + float const dx1 = ref.GetPositionX() - p1.GetPositionX(); + float const dy1 = ref.GetPositionY() - p1.GetPositionY(); + float const d1 = dx1 * dx1 + dy1 * dy1; + + float const dx2 = ref.GetPositionX() - p2.GetPositionX(); + float const dy2 = ref.GetPositionY() - p2.GetPositionY(); + float const d2 = dx2 * dx2 + dy2 * dy2; + + float const dx3 = ref.GetPositionX() - p3.GetPositionX(); + float const dy3 = ref.GetPositionY() - p3.GetPositionY(); + float const d3 = dx3 * dx3 + dy3 * dy3; + + if (d2 < d1 && d2 < d3) + return p2; + if (d3 < d1 && d3 < d2) + return p3; + return p1; +} + +// Single-target taunt with class-specific fallbacks +static bool CastSingleTargetTaunt(PlayerbotAI* botAI, Player* bot, Unit* target) +{ + if (!target || !target->IsAlive()) + return false; + + if (botAI->CastSpell("taunt", target)) + return true; + + switch (bot->getClass()) + { + case CLASS_PALADIN: + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + case CLASS_DEATH_KNIGHT: + if (botAI->CastSpell("dark command", target)) + return true; + break; + case CLASS_DRUID: + if (botAI->CastSpell("growl", target)) + return true; + break; + default: + break; + } + + // Ranged poke generates threat without moving + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; +} + +// AoE taunt — returns true if a spell was cast +static bool CastAoeTaunt(PlayerbotAI* botAI, Player* bot) +{ + switch (bot->getClass()) + { + case CLASS_WARRIOR: + if (botAI->CastSpell("challenging shout", bot)) + return true; + break; + case CLASS_DRUID: + if (botAI->CastSpell("challenging roar", bot)) + return true; + break; + default: + break; + } + return false; +} + +bool IccLichKingShadowTrapAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!boss || !botAI->IsTank(bot)) + return false; + + Difficulty const diff = bot->GetRaidDifficulty(); + + if (sPlayerbotAIConfig.EnableICCBuffs && IsHeroicLk(diff)) + ApplyHeroicBuffToMember(botAI, bot, false); + + static constexpr float CIRCLE_RADIUS = 20.0f; + static constexpr float SAFE_DISTANCE = 12.0f; + static constexpr float SEARCH_DISTANCE = SAFE_DISTANCE + 5.0f; + static constexpr int TEST_POSITIONS = 16; + static constexpr float ANGLE_STEP = 2.0f * float(M_PI) / TEST_POSITIONS; + + float const centerX = ICC_LICH_KING_ASSISTHC_POSITION.GetPositionX(); + float const centerY = ICC_LICH_KING_ASSISTHC_POSITION.GetPositionY(); + float const centerZ = ICC_LICH_KING_ASSISTHC_POSITION.GetPositionZ(); + + // Collect shadow traps within search range + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector nearbyTraps; + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || unit->GetEntry() != NPC_SHADOW_TRAP) + continue; + + if (bot->GetDistance(unit) < SEARCH_DISTANCE) + nearbyTraps.push_back(unit); + } + + if (nearbyTraps.empty()) + return false; + + // Check whether the current position is already safe + auto isSafePosition = [&](float x, float y) -> bool + { + for (Unit const* trap : nearbyTraps) + { + if (trap->GetDistance2d(x, y) < SAFE_DISTANCE) + return false; + } + return true; + }; + + if (isSafePosition(bot->GetPositionX(), bot->GetPositionY())) + return false; + + // Walk clockwise around the centre circle to find the nearest safe spot + float const currentAngle = std::atan2(bot->GetPositionY() - centerY, bot->GetPositionX() - centerX); + + for (int i = 1; i <= TEST_POSITIONS + 1; ++i) + { + // Last iteration tests the diametrically opposite point as a fallback + float const testAngle = (i <= TEST_POSITIONS) + ? currentAngle - ANGLE_STEP * i + : currentAngle + float(M_PI); + + float testX = centerX + std::cos(testAngle) * CIRCLE_RADIUS; + float testY = centerY + std::sin(testAngle) * CIRCLE_RADIUS; + float testZ = centerZ; + + bot->UpdateAllowedPositionZ(testX, testY, testZ); + + if (!bot->IsWithinLOS(testX, testY, testZ)) + continue; + + if (!isSafePosition(testX, testY)) + continue; + + return MoveTo(bot->GetMapId(), testX, testY, testZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool IccLichKingNecroticPlagueAction::Execute(Event /*event*/) +{ + if (!botAI->HasAura("Necrotic Plague", bot)) + return false; + + static constexpr float DELIVER_RANGE = 3.0f; + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + // Prefer shamblings tanked by the assist tank so plague is delivered + // to the correct add pile. Fall back to nearest if none qualify. + Unit* closestHorror = nullptr; + float minDist = 150.0f; + Unit* fallbackHorror = nullptr; + float fallbackDist = 150.0f; + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (!IsLkShambling(unit->GetEntry())) + continue; + + float const dist = bot->GetDistance(unit); + + Unit* victim = unit->GetVictim(); + if (victim && botAI->IsAssistTank(victim->ToPlayer())) + { + if (dist < minDist) + { + minDist = dist; + closestHorror = unit; + } + } + else if (dist < fallbackDist) + { + fallbackDist = dist; + fallbackHorror = unit; + } + } + + if (!closestHorror) + { + closestHorror = fallbackHorror; + minDist = fallbackDist; + } + + // No alive Horror — stop moving so healers can dispel immediately. + // Keeping the plague active with no valid target risks a raid wipe. + if (!closestHorror) + { + bot->StopMoving(); + bot->AttackStop(); + bot->SetTarget(ObjectGuid::Empty); + return true; + } + + // Already in delivery range — hold position for dispel + if (minDist <= DELIVER_RANGE) + { + bot->StopMoving(); + return true; + } + + // Move toward the Horror, suppressing combat so we don't get sidetracked + bot->AttackStop(); + bot->SetTarget(ObjectGuid::Empty); + + // During winter (boss <= 71% HP) approach from behind the shambling to + // avoid its frontal Shockwave. + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + bool const duringWinter = boss && !boss->HealthAbovePct(71); + + if (duringWinter) + { + Unit* victim = closestHorror->GetVictim(); + if (victim) + { + static constexpr float BEHIND_OFFSET = 2.0f; + float const vecX = closestHorror->GetPositionX() - victim->GetPositionX(); + float const vecY = closestHorror->GetPositionY() - victim->GetPositionY(); + float const vecLen = std::hypot(vecX, vecY); + + if (vecLen > 0.1f) + { + float const dirX = vecX / vecLen; + float const dirY = vecY / vecLen; + + // Try directly behind, then slight left/right arcs + static constexpr std::array Arcs = {0.0f, 0.35f, -0.35f, 0.70f, -0.70f}; + + for (float const arc : Arcs) + { + float const cosA = std::cos(arc); + float const sinA = std::sin(arc); + float const rotX = dirX * cosA - dirY * sinA; + float const rotY = dirX * sinA + dirY * cosA; + + float const destX = closestHorror->GetPositionX() + rotX * BEHIND_OFFSET; + float const destY = closestHorror->GetPositionY() + rotY * BEHIND_OFFSET; + + if (bot->IsWithinLOS(destX, destY, closestHorror->GetPositionZ())) + { + return MoveTo(bot->GetMapId(), destX, destY, closestHorror->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); + } + } + } + } + } + + return MoveTo(bot->GetMapId(), closestHorror->GetPositionX(), closestHorror->GetPositionY(), + closestHorror->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); +} + +bool IccLichKingWinterAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!boss) + return false; + + Difficulty const diff = bot->GetRaidDifficulty(); + + if (sPlayerbotAIConfig.EnableICCBuffs && IsHeroicLk(diff)) + ApplyHeroicBuffToMember(botAI, bot, true); + + // Speed boost to help escape the inward push + if (bot->GetDistance2d(boss) < 35.0f && !bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + + // Heroic: if more than 2 Raging Spirits are alive, kill the one with the + // highest HP. Only the main tank does this to avoid simultaneous actions. + if (botAI->IsMainTank(bot) && IsHeroicLk(diff)) + { + GuidVector const& heroicNpcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector spirits; + + for (ObjectGuid const& guid : heroicNpcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (IsLkRagingSpirit(unit->GetEntry())) + spirits.push_back(unit); + } + + if (spirits.size() > 2) + { + Unit* killTarget = *std::max_element(spirits.begin(), spirits.end(), + [](Unit const* a, Unit const* b) { return a->GetHealth() < b->GetHealth(); }); + bot->Kill(bot, killTarget); + } + } + + // Skull marking during Remorseless Winter. + // Priority: Shambling Horror > Raging Spirit (both on MT within 10 yd). + // Only re-mark when the current skull target is dead/gone. + if (Group* group = bot->GetGroup()) + { + static constexpr float MARK_RANGE = 10.0f; + + Unit* currentSkull = botAI->GetUnit(group->GetTargetIcon(7)); + bool const currentValid = currentSkull && currentSkull->IsAlive() && (IsLkShambling(currentSkull->GetEntry()) || IsLkRagingSpirit(currentSkull->GetEntry())); + + if (!currentValid) + { + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + GuidVector const& winterNpcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + Unit* bestShambling = nullptr; + Unit* bestSpirit = nullptr; + + for (ObjectGuid const& guid : winterNpcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (!mainTank || unit->GetVictim() != mainTank) + continue; + + if (mainTank->GetDistance(unit) > MARK_RANGE) + continue; + + uint32 const entry = unit->GetEntry(); + if (!bestShambling && IsLkShambling(entry)) + bestShambling = unit; + else if (!bestSpirit && IsLkRagingSpirit(entry)) + bestSpirit = unit; + + if (bestShambling) + break; + } + + Unit* markTarget = bestShambling ? bestShambling : bestSpirit; + + if (markTarget) + group->SetTargetIcon(7, bot->GetGUID(), markTarget->GetGUID()); + else if (!group->GetTargetIcon(7).IsEmpty()) + group->SetTargetIcon(7, bot->GetGUID(), ObjectGuid::Empty); + } + } + + // Teleport back onto the platform if the engine pushed us through + FixPlatformPosition(); + + // Staging during Remorseless Winter cast: non-tanks converge on ONE shared + // safe vile spirit anchor and hold for 4s before falling through to the + // final-spot logic. The chosen anchor is locked-in on first detection of + // the cast so all bots agree even if defile state shifts mid-cast. + // Among safe slots VILE_SPIRIT1 and VILE_SPIRIT3, picks + // the one closest to the group centroid; VILE_SPIRIT2 is fallback only. + struct WinterStageState + { + uint32 startMs; + Position const* pos; + }; + static std::map, WinterStageState> s_winterStage; + auto const winterKey = std::make_pair(boss->GetInstanceId(), boss->GetGUID()); + + bool const bossCastingWinter = boss->HasUnitState(UNIT_STATE_CASTING) && + (boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8)); + + static constexpr uint32 STAGE_DURATION_MS = 4000; + + // Enter staging block if boss is casting Winter OR a staging entry exists + // that hasn't yet expired. The cast ends before the aura applies, so we + // can't rely on bossCastingWinter staying true for the full window. + uint32 const now = getMSTime(); + auto winterIt = s_winterStage.find(winterKey); + bool const stageActive = winterIt != s_winterStage.end() && + getMSTimeDiff(winterIt->second.startMs, now) < STAGE_DURATION_MS; + + if (!botAI->IsTank(bot) && (bossCastingWinter || stageActive)) + { + if (winterIt == s_winterStage.end()) + { + Position const* candidates[2] = {&ICC_LK_VILE_SPIRIT1_POSITION, + &ICC_LK_VILE_SPIRIT3_POSITION}; + Position const centroid = ComputeGroupCentroid(bot); + + Position const* chosen = nullptr; + float bestDist = std::numeric_limits::max(); + for (Position const* slot : candidates) + { + if (!IsPositionSafeFromDefile(slot->GetPositionX(), slot->GetPositionY(), + PLATFORM_Z, 3.0f)) + continue; + + if (!IsPositionSafeFromShadowTraps(slot->GetPositionX(), slot->GetPositionY())) + continue; + + float const d = std::hypot(centroid.GetPositionX() - slot->GetPositionX(), + centroid.GetPositionY() - slot->GetPositionY()); + if (d < bestDist) + { + bestDist = d; + chosen = slot; + } + } + + if (!chosen && + IsPositionSafeFromDefile(ICC_LK_VILE_SPIRIT2_POSITION.GetPositionX(), + ICC_LK_VILE_SPIRIT2_POSITION.GetPositionY(), + PLATFORM_Z, 3.0f) && + IsPositionSafeFromShadowTraps(ICC_LK_VILE_SPIRIT2_POSITION.GetPositionX(), + ICC_LK_VILE_SPIRIT2_POSITION.GetPositionY())) + chosen = &ICC_LK_VILE_SPIRIT2_POSITION; + + winterIt = s_winterStage.emplace(winterKey, WinterStageState{now, chosen}).first; + } + + uint32 const elapsed = getMSTimeDiff(winterIt->second.startMs, now); + Position const* stagePos = winterIt->second.pos; + + if (stagePos && elapsed < STAGE_DURATION_MS) + { + static constexpr float STAGE_TOLERANCE = 3.0f; + static std::map, bool> s_stageInbound; + auto const stageKey = std::make_pair(bot->GetInstanceId(), bot->GetGUID()); + float const dist = bot->GetDistance2d(stagePos->GetPositionX(), stagePos->GetPositionY()); + if (dist > STAGE_TOLERANCE) + { + if (!s_stageInbound[stageKey]) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->SetTarget(ObjectGuid::Empty); + context->GetValue("current target")->Set(nullptr); + botAI->Reset(); + s_stageInbound[stageKey] = true; + } + TryMoveToPosition(stagePos->GetPositionX(), stagePos->GetPositionY(), PLATFORM_Z, true); + } + else + { + s_stageInbound[stageKey] = false; + bot->StopMoving(); + } + return true; + } + } + else if (!bossCastingWinter && !stageActive) + { + // Only clear when no staging window is active (timer expired AND not + // re-casting). This keeps the staging entry alive across the brief + // gap between cast end and aura application, and prevents tanks from + // wiping the entry mid-staging (tanks always hit this branch). + s_winterStage.erase(winterKey); + } + + // Defile evacuation has absolute priority over everything else + if (!IsPositionSafeFromDefile(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 3.0f)) + { + Position const& safePos = botAI->IsTank(bot) + ? *GetMainTankPosition() + : *GetMainTankRangedPosition(); + + TryMoveToPosition(safePos.GetPositionX(), safePos.GetPositionY(), PLATFORM_Z, true); + return true; + } + + // If any ice sphere is targeting this bot, move to the midpoint between the + // melee and ranged frost positions + if (!botAI->IsTank(bot)) + { + GuidVector const& sphereNpcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (ObjectGuid const& guid : sphereNpcs) + { + Unit* sphere = botAI->GetUnit(guid); + if (!sphere || !sphere->IsAlive() || !IsIceSphere(sphere->GetEntry())) + continue; + + if (sphere->GetVictim() != bot) + continue; + + Position const& meleePos = *GetMainTankPosition(); + Position const& rangedPos = *GetMainTankRangedPosition(); + float const midX = (meleePos.GetPositionX() + rangedPos.GetPositionX()) * 0.5f; + float const midY = (meleePos.GetPositionY() + rangedPos.GetPositionY()) * 0.5f; + TryMoveToPosition(midX, midY, PLATFORM_Z, true); + return false; + } + } + + ClearInvalidTarget(); + HandlePetManagement(); + + if (botAI->IsTank(bot)) + HandleTankPositioning(); + else if (!botAI->IsRanged(bot)) + { + // Non-tank melee: + // - Add (shambling/spirit) within 5y of MT: flank it. + // - No add near MT: wait at midpoint between melee and ranged + // anchors — staying close enough to engage when an add lands on + // MT, far enough not to eat shambling frontals on stragglers. + // Never pulled to MT/melee anchor. + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + bool addNearTank = false; + + if (mainTank && mainTank->IsAlive()) + { + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (ObjectGuid const& guid : npcs) + { + Unit* add = botAI->GetUnit(guid); + if (!add || !add->IsAlive()) + continue; + if (!IsLkShambling(add->GetEntry()) && !IsLkRagingSpirit(add->GetEntry())) + continue; + if (add->GetDistance2d(mainTank) <= 5.0f) + { + addNearTank = true; + break; + } + } + } + + if (addNearTank) + HandleMeleePositioning(); + else + { + Position const& meleePos = *GetMainTankPosition(); + Position const& rangedPos = *GetMainTankRangedPosition(); + float const midX = (meleePos.GetPositionX() + rangedPos.GetPositionX()) * 0.5f; + float const midY = (meleePos.GetPositionY() + rangedPos.GetPositionY()) * 0.5f; + + static constexpr float MID_TOLERANCE = 2.0f; + if (bot->GetDistance2d(midX, midY) > MID_TOLERANCE) + TryMoveToPosition(midX, midY, PLATFORM_Z, true); + else + bot->StopMoving(); + return false; + } + } + else + HandleRangedPositioning(); + + return false; +} + +bool IccLichKingWinterAction::FixPlatformPosition() +{ + if (std::abs(bot->GetPositionZ() - PLATFORM_Z) > 1.0f) + { + bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), + PLATFORM_Z, bot->GetOrientation()); + return true; + } + + return false; +} + +bool IccLichKingWinterAction::ClearInvalidTarget() +{ + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + if (!currentTarget) + return false; + + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + bool doClear = false; + + // Bots must not fight the boss during Remorseless Winter + if (boss && currentTarget == boss) + doClear = true; + + // Tanks must not chase ice spheres + if (botAI->IsTank(bot) && IsIceSphere(currentTarget->GetEntry())) + doClear = true; + + if (doClear) + { + context->GetValue("current target")->Set(nullptr); + bot->AttackStop(); + bot->SetTarget(ObjectGuid::Empty); + return true; + } + + return false; +} + +Position const* IccLichKingWinterAction::GetMainTankPosition() +{ + // Use group centroid so the chosen frost slot follows the majority of the + // raid, not the tanks. Non-tanks can't survive Remorseless Winter ticks + // while traversing the platform, so dragging them to a far slot just + // because tanks parked there gets them killed. Tanks tolerate a few + // ticks and will reroute to the majority slot too. + Position ref = ComputeGroupCentroid(bot); + + return &SelectClosestOf3(ref, ICC_LK_FROST1_POSITION, ICC_LK_FROST2_POSITION, ICC_LK_FROST3_POSITION); +} + +Position const* IccLichKingWinterAction::GetMainTankRangedPosition() +{ + Position const* melee = GetMainTankPosition(); + + if (melee == &ICC_LK_FROST1_POSITION) + return &ICC_LK_FROSTR1_POSITION; + if (melee == &ICC_LK_FROST2_POSITION) + return &ICC_LK_FROSTR2_POSITION; + + return &ICC_LK_FROSTR3_POSITION; +} + +bool IccLichKingWinterAction::IsPositionSafeFromDefile(float x, float y, float /*z*/, float minSafeDistance) const +{ + static constexpr float BASE_RADIUS = 6.0f; + static constexpr float SAFETY_MARGIN = 3.0f; + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || unit->GetEntry() != DEFILE_NPC_ID) + continue; + + float currentRadius = BASE_RADIUS; + for (uint32 const auraId : DEFILE_AURAS) + { + Aura* grow = unit->GetAura(auraId); + if (!grow) + continue; + + float const growthMultiplier = + (bot->GetRaidDifficulty() == RAID_DIFFICULTY_10MAN_HEROIC || + bot->GetRaidDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL) + ? 1.4f + : 0.95f; + + currentRadius = BASE_RADIUS + grow->GetStackAmount() * growthMultiplier; + break; + } + + float const dx = x - unit->GetPositionX(); + float const dy = y - unit->GetPositionY(); + + if (std::sqrt(dx * dx + dy * dy) < currentRadius + SAFETY_MARGIN + minSafeDistance) + return false; + } + + return true; +} + +bool IccLichKingWinterAction::IsPositionSafeFromShadowTraps(float x, float y) const +{ + // Shadow trap has a fixed 12 yd lethal radius (matches the SAFE_DISTANCE + // used by IccLichKingShadowTrapAction). + static constexpr float TRAP_SAFE_RADIUS = 12.0f; + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || unit->GetEntry() != NPC_SHADOW_TRAP) + continue; + + float const dx = x - unit->GetPositionX(); + float const dy = y - unit->GetPositionY(); + + if (std::sqrt(dx * dx + dy * dy) < TRAP_SAFE_RADIUS) + return false; + } + + return true; +} + +bool IccLichKingWinterAction::TryMoveToPosition(float targetX, float targetY, float targetZ, bool forced) +{ + float const dx = targetX - bot->GetPositionX(); + float const dy = targetY - bot->GetPositionY(); + float const dist = std::hypot(dx, dy); + + if (dist < 0.1f) + return true; + + MovementPriority const priority = forced + ? MovementPriority::MOVEMENT_FORCED + : MovementPriority::MOVEMENT_COMBAT; + + if (IsPositionSafeFromDefile(targetX, targetY, targetZ, 3.0f) && + IsPositionSafeFromShadowTraps(targetX, targetY) && + bot->IsWithinLOS(targetX, targetY, targetZ)) + { + // Sample the straight-line path for defile and shadow trap crossings + bool pathSafe = true; + static constexpr float PATH_CHECK_INTERVAL = 3.0f; + int const numChecks = std::max(1, static_cast(dist / PATH_CHECK_INTERVAL)); + + for (int i = 1; i < numChecks; ++i) + { + float const t = static_cast(i) / static_cast(numChecks); + float const checkX = bot->GetPositionX() + dx * t; + float const checkY = bot->GetPositionY() + dy * t; + + if (!IsPositionSafeFromDefile(checkX, checkY, targetZ, 2.0f) || + !IsPositionSafeFromShadowTraps(checkX, checkY)) + { + pathSafe = false; + break; + } + } + + if (pathSafe) + { + MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, priority, true, false); + return true; + } + // Path crosses defile or shadow trap — fall through to arc-stepping logic + } + + // Arc-stepping: rotate around the movement direction to find a way around defile/traps + static constexpr int STEPS = 8; + static constexpr float STEP_DIST = 8.0f; + + float const dirX = dx / dist; + float const dirY = dy / dist; + + for (int i = 1; i <= STEPS; ++i) + { + for (int sign : {1, -1}) + { + float const arc = float(M_PI) / STEPS * i * sign; + float const cosA = std::cos(arc); + float const sinA = std::sin(arc); + + float const testX = bot->GetPositionX() + (dirX * cosA - dirY * sinA) * STEP_DIST; + float const testY = bot->GetPositionY() + (dirX * sinA + dirY * cosA) * STEP_DIST; + + if (!IsPositionSafeFromDefile(testX, testY, targetZ, 3.0f)) + continue; + if (!IsPositionSafeFromShadowTraps(testX, testY)) + continue; + if (!bot->IsWithinLOS(testX, testY, targetZ)) + continue; + + MoveTo(bot->GetMapId(), testX, testY, targetZ, false, false, false, true, priority, true, false); + return false; + } + } + + // Probe along the direct path, shortening the distance until safe + static constexpr float PROBE_STEP = 5.0f; + + for (float probeLen = dist - PROBE_STEP; probeLen >= PROBE_STEP; probeLen -= PROBE_STEP) + { + float probeX = bot->GetPositionX() + dirX * probeLen; + float probeY = bot->GetPositionY() + dirY * probeLen; + float probeZ = targetZ; + bot->UpdateAllowedPositionZ(probeX, probeY, probeZ); + + if (!IsPositionSafeFromDefile(probeX, probeY, probeZ, 3.0f)) + continue; + if (!IsPositionSafeFromShadowTraps(probeX, probeY)) + continue; + if (!bot->IsWithinLOS(probeX, probeY, probeZ)) + continue; + + MoveTo(bot->GetMapId(), probeX, probeY, probeZ, + false, false, false, true, priority, true, false); + return false; + } + + // Small nudge forward + { + float nudgeX = bot->GetPositionX() + dirX * 2.0f; + float nudgeY = bot->GetPositionY() + dirY * 2.0f; + float nudgeZ = targetZ; + bot->UpdateAllowedPositionZ(nudgeX, nudgeY, nudgeZ); + + if (bot->IsWithinLOS(nudgeX, nudgeY, nudgeZ)) + { + MoveTo(bot->GetMapId(), nudgeX, nudgeY, nudgeZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + return false; + } + } + + // Last resort: move away from the nearest defile to get to safety first + { + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + Unit* nearestDefile = nullptr; + float nearestDefileDist = FLT_MAX; + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || unit->GetEntry() != DEFILE_NPC_ID) + continue; + + float const d = bot->GetDistance2d(unit); + if (d < nearestDefileDist) + { + nearestDefileDist = d; + nearestDefile = unit; + } + } + + if (nearestDefile) + { + float const awayX = bot->GetPositionX() - nearestDefile->GetPositionX(); + float const awayY = bot->GetPositionY() - nearestDefile->GetPositionY(); + float const awayLen = std::hypot(awayX, awayY); + + if (awayLen > 0.1f) + { + float const escapeX = bot->GetPositionX() + (awayX / awayLen) * 5.0f; + float const escapeY = bot->GetPositionY() + (awayY / awayLen) * 5.0f; + + MoveTo(bot->GetMapId(), escapeX, escapeY, targetZ, + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + return false; + } + } + } + + // No defile nearby — safe to move directly to the target. + + if (bot->IsWithinLOS(targetX, targetY, targetZ)) + { + botAI->Reset(); + MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool IccLichKingWinterAction::IsValidCollectibleAdd(Unit* unit) const +{ + return IsLkCollectibleAdd(unit); +} + +bool IccLichKingWinterAction::HandleTankPositioning() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!boss) + return false; + + // Both tanks converge on the same frost position + Position const& frostPos = *GetMainTankPosition(); + static constexpr float FROST_AT_POS_TOLERANCE = 3.0f; + + if (botAI->IsMainTank(bot)) + { + float const dist = bot->GetDistance2d(frostPos.GetPositionX(), frostPos.GetPositionY()); + static std::map, bool> s_mtInbound; + auto const mtKey = std::make_pair(bot->GetInstanceId(), bot->GetGUID()); + + if (dist > FROST_AT_POS_TOLERANCE) + { + if (!s_mtInbound[mtKey]) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->SetTarget(ObjectGuid::Empty); + context->GetValue("current target")->Set(nullptr); + botAI->Reset(); + s_mtInbound[mtKey] = true; + } + TryMoveToPosition(frostPos.GetPositionX(), frostPos.GetPositionY(), PLATFORM_Z, true); + return false; + } + + s_mtInbound[mtKey] = false; + return HandleMainTankAddManagement(boss, &frostPos); + } + + if (botAI->IsAssistTank(bot)) + return HandleAssistTankAddManagement(boss, &frostPos); + + return false; +} + +bool IccLichKingWinterAction::HandleMeleePositioning() +{ + // Frost engagement zone: bots within MELEE_FLANK_RADIUS of the frost anchor + // are considered "engaged" and trusted to flank. Outside, they're inbound + // and may need to wait at the ranged anchor for MT to gain control. + static constexpr float MELEE_FLANK_RADIUS = 10.0f; + Position const& tankPos = *GetMainTankPosition(); + float const distToPos = bot->GetDistance2d(tankPos.GetPositionX(), tankPos.GetPositionY()); + + // Transition guard: only retreat to ranged frost position if the bot is + // still inbound (outside the engagement zone) AND a nearby Shambling is + // not yet under MT control. Once engaged, trust flank logic — no retreats. + if (distToPos > MELEE_FLANK_RADIUS) + { + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + if (mainTank && mainTank->IsAlive()) + { + static constexpr float NEARBY_SHAMBLING_RANGE = 10.0f; + GuidVector const& transNpcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + bool nearbyShamblingLoose = false; + + for (ObjectGuid const& guid : transNpcs) + { + Unit* add = botAI->GetUnit(guid); + if (!add || !add->IsAlive()) + continue; + + if (!IsLkShambling(add->GetEntry())) + continue; + + if (bot->GetExactDist2d(add) > NEARBY_SHAMBLING_RANGE) + continue; + + Unit* victim = add->GetVictim(); + if (!victim || victim->GetGUID() != mainTank->GetGUID()) + { + nearbyShamblingLoose = true; + break; + } + } + + if (nearbyShamblingLoose) + { + Position const& rangedPos = *GetMainTankRangedPosition(); + TryMoveToPosition(rangedPos.GetPositionX(), rangedPos.GetPositionY(), PLATFORM_Z, true); + return false; + } + } + } + + // No re-anchor to tank position: flanking owns positioning. Pulling melee + // toward MT during winter puts bots in front of shamblings (frontal cone). + + // Only adds within MT_ADD_RANGE of the main tank are valid melee targets. + // Adds further out are not under tank control — engaging them puts bots + // in front of unrooted shamblings. + static constexpr float MT_ADD_RANGE = 5.0f; + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + + auto addNearMT = [&](Unit* add) -> bool + { + return mainTank && mainTank->IsAlive() && add && + add->GetDistance2d(mainTank) <= MT_ADD_RANGE; + }; + + // Acquire a valid add target + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + if (!currentTarget || !currentTarget->IsAlive() || + !IsLkCollectibleAdd(currentTarget) || IsIceSphere(currentTarget->GetEntry()) || + !addNearMT(currentTarget)) + { + Unit* newTarget = nullptr; + + // Priority: skull-marked target (only if near MT) + if (Group* group = bot->GetGroup()) + { + Unit* skull = botAI->GetUnit(group->GetTargetIcon(7)); + if (skull && skull->IsAlive() && IsLkCollectibleAdd(skull) && addNearMT(skull)) + newTarget = skull; + } + + // Fallback: nearest valid add within MT range + if (!newTarget) + { + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + float closestDist = FLT_MAX; + + for (ObjectGuid const& guid : npcs) + { + Unit* add = botAI->GetUnit(guid); + if (!IsLkCollectibleAdd(add)) + continue; + if (!addNearMT(add)) + continue; + + float const d = bot->GetDistance(add); + if (d < closestDist) + { + closestDist = d; + newTarget = add; + } + } + } + + if (!newTarget) + return false; + + context->GetValue("current target")->Set(newTarget); + bot->SetTarget(newTarget->GetGUID()); + currentTarget = newTarget; + } + + // Settle band: if a candidate flank slot is safe AND the bot is already + // within FLANK_SETTLE_DIST of it, stay put and attack — no per-tick step + // (prevents back-and-forth jitter near the slot). + static constexpr float FLANK_SETTLE_DIST = 2.0f; + + // Position behind the current melee target using mainTank→add vector + if (mainTank && mainTank->IsAlive()) + { + float const vecX = currentTarget->GetPositionX() - mainTank->GetPositionX(); + float const vecY = currentTarget->GetPositionY() - mainTank->GetPositionY(); + float const vecLen = std::hypot(vecX, vecY); + + if (vecLen > 0.1f) + { + float const dirX = vecX / vecLen; + float const dirY = vecY / vecLen; + + static constexpr std::array Arcs = {0.0f, 0.35f, -0.35f, 0.70f, -0.70f}; + + for (float const arc : Arcs) + { + float const cosA = std::cos(arc); + float const sinA = std::sin(arc); + float const rotX = dirX * cosA - dirY * sinA; + float const rotY = dirX * sinA + dirY * cosA; + + float const destX = currentTarget->GetPositionX() + rotX * BEHIND_DISTANCE; + float const destY = currentTarget->GetPositionY() + rotY * BEHIND_DISTANCE; + + float const bDx = destX - bot->GetPositionX(); + float const bDy = destY - bot->GetPositionY(); + float const bDist = std::hypot(bDx, bDy); + + if (!IsPositionSafeFromDefile(destX, destY, bot->GetPositionZ(), 2.0f)) + continue; + if (!bot->IsWithinLOS(destX, destY, bot->GetPositionZ())) + continue; + + if (bDist < FLANK_SETTLE_DIST) + { + bot->SetFacingToObject(currentTarget); + Attack(currentTarget); + return false; + } + + float const step = std::min(1.25f, bDist); + TryMoveToPosition(bot->GetPositionX() + (bDx / bDist) * step, bot->GetPositionY() + (bDy / bDist) * step, bot->GetPositionZ(), true); + return false; + } + } + } + + // Fallback: stored orientation-based angles + static constexpr std::array BehindAngles = + { + float(M_PI), // 180° directly behind + float(M_PI) * 0.75f, // 135° left-rear flank + float(M_PI) * 1.25f, // 225° right-rear flank + float(M_PI) * 0.5f, // 90° left flank + float(M_PI) * 1.5f, // 270° right flank + float(M_PI) * 0.875f, // 157° closer left-rear + }; + + for (float const angleOffset : BehindAngles) + { + float const angle = currentTarget->GetOrientation() + angleOffset; + float const destX = currentTarget->GetPositionX() + std::cos(angle) * BEHIND_DISTANCE; + float const destY = currentTarget->GetPositionY() + std::sin(angle) * BEHIND_DISTANCE; + + float const bDx = destX - bot->GetPositionX(); + float const bDy = destY - bot->GetPositionY(); + float const bDist = std::hypot(bDx, bDy); + + if (!IsPositionSafeFromDefile(destX, destY, bot->GetPositionZ(), 2.0f)) + continue; + if (!bot->IsWithinLOS(destX, destY, bot->GetPositionZ())) + continue; + + if (bDist < FLANK_SETTLE_DIST) + { + bot->SetFacingToObject(currentTarget); + Attack(currentTarget); + return false; + } + + float const step = std::min(1.25f, bDist); + TryMoveToPosition(bot->GetPositionX() + (bDx / bDist) * step, + bot->GetPositionY() + (bDy / bDist) * step, + bot->GetPositionZ(), true); + return false; + } + + bot->SetFacingToObject(currentTarget); + Attack(currentTarget); + return false; +} + +bool IccLichKingWinterAction::HandleRangedPositioning() +{ + Position const& targetPos = *GetMainTankRangedPosition(); + + // Evacuate defile first + if (!IsPositionSafeFromDefile(bot->GetPositionX(), bot->GetPositionY(), + bot->GetPositionZ(), 3.0f)) + { + TryMoveToPosition(targetPos.GetPositionX(), targetPos.GetPositionY(), PLATFORM_Z, true); + return false; + } + + // Move to ranged frost position. Clear target + reset only on the FIRST + // tick of the inbound phase — otherwise we cancel the bot's own movement + // every tick and it ends up walking 1y per cycle. + static std::map, bool> s_rangedInbound; + auto const rangedKey = std::make_pair(bot->GetInstanceId(), bot->GetGUID()); + bool const farFromAnchor = + bot->GetDistance2d(targetPos.GetPositionX(), targetPos.GetPositionY()) > 2.0f; + + if (farFromAnchor) + { + bool const wasInbound = s_rangedInbound[rangedKey]; + if (!wasInbound) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->SetTarget(ObjectGuid::Empty); + context->GetValue("current target")->Set(nullptr); + botAI->Reset(); + s_rangedInbound[rangedKey] = true; + } + TryMoveToPosition(targetPos.GetPositionX(), targetPos.GetPositionY(), PLATFORM_Z, true); + // Skip target acquisition while inbound — Attack() would cancel movement. + return false; + } + + s_rangedInbound[rangedKey] = false; + + if (!botAI->IsRangedDps(bot)) + return false; + + static constexpr float SPHERE_RANGE = 30.0f; + + // Keep attacking current sphere if still alive and in range + Unit* cur = bot->GetVictim(); + if (cur && cur->IsAlive() && IsIceSphere(cur->GetEntry()) && + bot->GetDistance(cur) <= SPHERE_RANGE) + { + bot->SetFacingToObject(cur); + Attack(cur); + return false; + } + + // Drop stale sphere target + if (cur && IsIceSphere(cur->GetEntry())) + { + bot->AttackStop(); + bot->SetTarget(ObjectGuid::Empty); + } + + // Find the nearest sphere within range + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + Unit* nearestSphere = nullptr; + float nearestDist = SPHERE_RANGE; + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || !IsIceSphere(unit->GetEntry())) + continue; + + float const d = bot->GetDistance(unit); + if (d < nearestDist) + { + nearestDist = d; + nearestSphere = unit; + } + } + + if (nearestSphere) + { + bot->SetTarget(nearestSphere->GetGUID()); + bot->SetFacingToObject(nearestSphere); + Attack(nearestSphere); + return true; + } + + // No spheres — engage skull-marked add or nearest valid add + Unit* addTarget = nullptr; + if (Group* group = bot->GetGroup()) + { + Unit* skull = botAI->GetUnit(group->GetTargetIcon(7)); + if (skull && skull->IsAlive() && IsLkCollectibleAdd(skull)) + addTarget = skull; + } + + if (!addTarget) + { + float closestDist = SPHERE_RANGE; + for (ObjectGuid const& guid : npcs) + { + Unit* add = botAI->GetUnit(guid); + if (!IsLkCollectibleAdd(add)) + continue; + + float const d = bot->GetDistance(add); + if (d < closestDist) + { + closestDist = d; + addTarget = add; + } + } + } + + if (addTarget) + { + context->GetValue("current target")->Set(addTarget); + bot->SetTarget(addTarget->GetGUID()); + bot->SetFacingToObject(addTarget); + Attack(addTarget); + return true; + } + + return false; +} + +bool IccLichKingWinterAction::HandleMainTankAddManagement(Unit* boss, Position const* frostPos) +{ + static constexpr float ENGAGE_RADIUS = 12.0f; + static constexpr float TAUNT_RADIUS = 30.0f; + static constexpr int AOE_TAUNT_MIN = 2; + + bool hasAliveAssistTank = false; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->IsAlive() && member != bot && botAI->IsAssistTank(member)) + { + hasAliveAssistTank = true; + break; + } + } + } + + GuidVector const& targets = AI_VALUE(GuidVector, "possible targets"); + + Unit* priorityAdd = nullptr; // attacking non-tank + Unit* secondaryAdd = nullptr; // not yet on MT + Unit* fallbackAdd = nullptr; // already on MT + int nearbyCount = 0; + + for (ObjectGuid const& guid : targets) + { + Unit* add = botAI->GetUnit(guid); + if (!IsLkCollectibleAdd(add)) + continue; + + float const addDist = bot->GetDistance(add); + float const maxEngage = hasAliveAssistTank ? ENGAGE_RADIUS : 25.0f; + + if (addDist <= TAUNT_RADIUS) + ++nearbyCount; + + Unit* victim = add->GetVictim(); + + // Taunt pass: all adds in range that are NOT already on MT + bool const onMT = victim && victim->IsPlayer() && botAI->IsMainTank(victim->ToPlayer()); + if (!onMT && addDist <= TAUNT_RADIUS) + CastSingleTargetTaunt(botAI, bot, add); + + // Priority taunt: adds on the assist tank within 10 yd + bool const onAT = victim && victim->IsPlayer() && botAI->IsAssistTank(victim->ToPlayer()); + if (onAT && addDist <= 10.0f) + CastSingleTargetTaunt(botAI, bot, add); + + if (nearbyCount >= AOE_TAUNT_MIN) + CastAoeTaunt(botAI, bot); + + if (addDist > maxEngage) + continue; + + if (victim && victim->IsPlayer() && !botAI->IsTank(victim->ToPlayer())) + { + if (!priorityAdd || addDist < bot->GetDistance(priorityAdd)) + priorityAdd = add; + } + else if (victim != bot) + { + if (!secondaryAdd || addDist < bot->GetDistance(secondaryAdd)) + secondaryAdd = add; + } + else + { + if (!fallbackAdd || addDist < bot->GetDistance(fallbackAdd)) + fallbackAdd = add; + } + } + + Unit* targetAdd = priorityAdd ? priorityAdd : secondaryAdd ? secondaryAdd : fallbackAdd; + + if (!targetAdd) + { + Unit* cur = bot->GetVictim(); + if (cur && !IsLkCollectibleAdd(cur)) + bot->SetTarget(ObjectGuid::Empty); + return false; + } + + // Stack-consolidation and orientation nudge. + // Move away from the ranged position when either: + // (a) any two Shamblings/Spirits on the MT are more than 1 yd apart, or + // (b) any add is facing toward the ranged position. + { + static constexpr float STACK_THRESHOLD = 1.0f; + static constexpr float NUDGE_DIST = 2.0f; + + Position const& rangedPos = *GetMainTankRangedPosition(); + float const awayDx = frostPos->GetPositionX() - rangedPos.GetPositionX(); + float const awayDy = frostPos->GetPositionY() - rangedPos.GetPositionY(); + float const awayLen = std::hypot(awayDx, awayDy); + + if (awayLen > 0.01f) + { + float const awayNx = awayDx / awayLen; + float const awayNy = awayDy / awayLen; + + std::vector stackAdds; + for (ObjectGuid const& guid : targets) + { + Unit* add = botAI->GetUnit(guid); + if (!add || !add->IsAlive()) + continue; + + uint32 const entry = add->GetEntry(); + if (!IsLkShambling(entry) && !IsLkRagingSpirit(entry)) + continue; + + Unit* v = add->GetVictim(); + if (!v || !v->IsPlayer() || !botAI->IsMainTank(v->ToPlayer())) + continue; + + stackAdds.push_back(add); + } + + bool needNudge = false; + + // (a) spread check + for (size_t i = 0; !needNudge && i < stackAdds.size(); ++i) + { + for (size_t j = i + 1; !needNudge && j < stackAdds.size(); ++j) + { + if (stackAdds[i]->GetDistance2d(stackAdds[j]) > STACK_THRESHOLD) + needNudge = true; + } + } + + // (b) orientation check — add facing toward rangedPos + for (size_t i = 0; !needNudge && i < stackAdds.size(); ++i) + { + Unit* add = stackAdds[i]; + float const toRangedX = rangedPos.GetPositionX() - add->GetPositionX(); + float const toRangedY = rangedPos.GetPositionY() - add->GetPositionY(); + float const dot = std::cos(add->GetOrientation()) * toRangedX + + std::sin(add->GetOrientation()) * toRangedY; + if (dot > 0.0f) + needNudge = true; + } + + if (needNudge) + { + float const nudgeX = bot->GetPositionX() + awayNx * NUDGE_DIST; + float const nudgeY = bot->GetPositionY() + awayNy * NUDGE_DIST; + TryMoveToPosition(nudgeX, nudgeY, PLATFORM_Z, false); + } + } + } + + float const addDist = bot->GetDistance(targetAdd); + + if (addDist <= ENGAGE_RADIUS || !hasAliveAssistTank) + { + // Pull toward frostPos if solo-tanking and add is far + if (addDist > ENGAGE_RADIUS && !hasAliveAssistTank) + { + float const pullDx = targetAdd->GetPositionX() - frostPos->GetPositionX(); + float const pullDy = targetAdd->GetPositionY() - frostPos->GetPositionY(); + float const pullLen = std::hypot(pullDx, pullDy); + float const pullRatio = std::min(1.0f, 15.0f / (pullLen > 0.1f ? pullLen : 0.1f)); + TryMoveToPosition(frostPos->GetPositionX() + pullDx * pullRatio, + frostPos->GetPositionY() + pullDy * pullRatio, + PLATFORM_Z, false); + } + + bot->SetTarget(targetAdd->GetGUID()); + bot->SetFacingToObject(targetAdd); + Attack(targetAdd); + } + else + { + // Add is still being herded by assist tank + bot->SetTarget(targetAdd->GetGUID()); + bot->SetFacingToObject(targetAdd); + Attack(targetAdd); + } + + return false; +} + +bool IccLichKingWinterAction::HandleAssistTankAddManagement(Unit* boss, Position const* frostPos) +{ + static constexpr float FROST_TOL = 3.0f; + static constexpr float MELE_RANGE = 5.0f; + static constexpr float TAUNT_RADIUS = 30.0f; + + GuidVector const& targets = AI_VALUE(GuidVector, "possible targets"); + + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + + std::vector addsOnUs; + std::vector addsLoose; + + for (ObjectGuid const& guid : targets) + { + Unit* add = botAI->GetUnit(guid); + if (!IsLkCollectibleAdd(add)) + continue; + + Unit* victim = add->GetVictim(); + + // Skip adds already securely held by the main tank + if (mainTank && victim && victim->GetGUID() == mainTank->GetGUID()) + continue; + + if (victim == bot) + addsOnUs.push_back(add); + else + addsLoose.push_back(add); + } + + // adds are on us — walk back to frost position + if (!addsOnUs.empty()) + { + for (Unit* add : addsOnUs) + CastSingleTargetTaunt(botAI, bot, add); + + if (addsOnUs.size() >= 2) + CastAoeTaunt(botAI, bot); + + float const distToFrost = bot->GetExactDist2d(frostPos->GetPositionX(), + frostPos->GetPositionY()); + if (distToFrost > FROST_TOL) + TryMoveToPosition(frostPos->GetPositionX(), frostPos->GetPositionY(), PLATFORM_Z, true); + + // Maintain combat target on the way back + Unit* currentTarget = bot->GetVictim(); + bool keepCurrent = false; + + if (currentTarget && currentTarget->IsAlive()) + { + for (Unit* add : addsOnUs) + { + if (add->GetGUID() == currentTarget->GetGUID()) + { + keepCurrent = true; + break; + } + } + } + + if (!keepCurrent) + { + currentTarget = nullptr; + + // Prefer shambling as combat target + for (Unit* add : addsOnUs) + { + if (IsLkShambling(add->GetEntry())) + { + currentTarget = add; + break; + } + } + + if (!currentTarget) + currentTarget = addsOnUs.front(); + } + + if (currentTarget) + { + bot->SetTarget(currentTarget->GetGUID()); + bot->SetFacingToObject(currentTarget); + Attack(currentTarget); + } + + return false; + } + + // return to frost position if we drifted + float const distToFrost = bot->GetExactDist2d(frostPos->GetPositionX(), + frostPos->GetPositionY()); + if (distToFrost > FROST_TOL) + { + botAI->Reset(); + TryMoveToPosition(frostPos->GetPositionX(), frostPos->GetPositionY(), PLATFORM_Z, true); + + Unit* cur = bot->GetVictim(); + if (cur && !IsLkCollectibleAdd(cur)) + bot->SetTarget(ObjectGuid::Empty); + return false; + } + + // at frost position, collect the nearest loose add + Unit* targetAdd = nullptr; + float closestDist = FLT_MAX; + + // Priority 1: rescue add attacking a non-tank + for (Unit* add : addsLoose) + { + Unit* victim = add->GetVictim(); + if (victim && victim->IsPlayer() && !botAI->IsTank(victim->ToPlayer())) + { + float const dist = bot->GetDistance(add); + if (dist < closestDist) + { + closestDist = dist; + targetAdd = add; + } + } + } + + // Priority 2: any loose add + if (!targetAdd) + { + for (Unit* add : addsLoose) + { + float const dist = bot->GetDistance(add); + if (dist < closestDist) + { + closestDist = dist; + targetAdd = add; + } + } + } + + if (!targetAdd) + { + Unit* cur = bot->GetVictim(); + if (cur && !IsLkCollectibleAdd(cur)) + bot->SetTarget(ObjectGuid::Empty); + + // No loose adds — idle at frost pos but allow lower-priority actions to run + return false; + } + + CastSingleTargetTaunt(botAI, bot, targetAdd); + + // Also taunt other loose adds in range + for (Unit* add : addsLoose) + { + if (add == targetAdd) + continue; + if (bot->GetExactDist2d(add) <= TAUNT_RADIUS) + CastSingleTargetTaunt(botAI, bot, add); + } + + // AoE taunt if 2+ loose adds are close enough + { + int nearbyLoose = 0; + for (Unit* add : addsLoose) + { + if (bot->GetExactDist2d(add) <= TAUNT_RADIUS) + ++nearbyLoose; + } + if (nearbyLoose >= 2) + CastAoeTaunt(botAI, bot); + } + + bot->SetTarget(targetAdd->GetGUID()); + + if (closestDist > MELE_RANGE) + { + botAI->Reset(); + TryMoveToPosition(targetAdd->GetPositionX(), targetAdd->GetPositionY(), PLATFORM_Z, false); + } + else + { + bot->SetFacingToObject(targetAdd); + Attack(targetAdd); + } + + return false; +} + +bool IccLichKingWinterAction::HandlePetManagement() +{ + Pet* pet = bot->GetPet(); + if (!pet || !pet->IsAlive()) + return false; + + CharmInfo* ci = pet->GetCharmInfo(); + if (!ci) + return false; + + if (botAI->IsHeal(bot)) + { + if (ci->GetCommandState() != COMMAND_FOLLOW) + { + pet->AttackStop(); + pet->InterruptNonMeleeSpells(false); + pet->GetMotionMaster()->MoveFollow(bot, PET_FOLLOW_DIST, pet->GetFollowAngle()); + ci->SetCommandState(COMMAND_FOLLOW); + ci->SetIsCommandAttack(false); + ci->SetIsAtStay(false); + ci->SetIsReturning(true); + ci->SetIsCommandFollow(true); + ci->SetIsFollowing(false); + ci->RemoveStayPosition(); + } + return false; + } + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + Unit* bestTarget = nullptr; + int bestPriority = -1; + float bestHpPct = 101.0f; + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + uint32 const entry = unit->GetEntry(); + int priority = -1; + + if (IsLkShambling(entry)) + priority = 3; + else if (IsLkRagingSpirit(entry)) + priority = 2; + else if (IsIceSphere(entry)) + priority = 1; + + if (priority < 0) + continue; + + float const hp = unit->GetHealthPct(); + if (priority > bestPriority || (priority == bestPriority && hp < bestHpPct)) + { + bestPriority = priority; + bestHpPct = hp; + bestTarget = unit; + } + } + + if (!bestTarget) + return false; + + // Skip if the pet is already attacking the right unit + if (pet->GetVictim() == bestTarget) + return false; + + ci->SetIsCommandAttack(true); + ci->SetIsAtStay(false); + ci->SetIsReturning(false); + ci->SetIsCommandFollow(false); + ci->SetIsFollowing(false); + ci->SetCommandState(COMMAND_ATTACK); + + if (pet->IsAIEnabled) + { + pet->AI()->AttackStart(bestTarget); + pet->GetMotionMaster()->MoveChase(bestTarget); + } + + return true; +} + +bool IccLichKingAddsAction::Execute(Event /*event*/) +{ + // Being carried by a Val'kyr — no actions possible + if (bot->HasAura(SPELL_HARVEST_SOUL_VALKYR)) + return false; + + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + Difficulty const diff = bot->GetRaidDifficulty(); + bool const hasPlague = botAI->HasAura("Necrotic Plague", bot); + Unit* const terenas = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f); + + // Heroic cheat buffs — apply to all group members + if (sPlayerbotAIConfig.EnableICCBuffs && IsHeroicLk(diff)) + { + Group* buffGroup = bot->GetGroup(); + if (buffGroup) + { + for (GroupReference* itr = buffGroup->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || !member->IsInWorld()) + continue; + + ApplyHeroicBuffToMember(botAI, member, true); + + if (boss && boss->HealthBelowPct(60) && boss->HealthAbovePct(40) && + !member->HasAura(SPELL_EMPOWERED_BLOOD)) + member->AddAura(SPELL_EMPOWERED_BLOOD, member); + } + } + } + + // Skull marking: phase 1 marks the boss; phase 2+ prioritises Raging Spirits. + // Val'kyr marking owns skull while any Val'kyr is actively grabbing — skip here. + if (Group* group = bot->GetGroup()) + { + bool anyValkyrGrabbing = false; + { + GuidVector const& nearbyNpcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (ObjectGuid const& guid : nearbyNpcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && IsLkValkyr(unit) && + unit->HasAura(SPELL_HARVEST_SOUL_VALKYR) && + (!IsHeroicLk(diff) || unit->HealthAbovePct(49))) + { + anyValkyrGrabbing = true; + break; + } + } + } + + if (!anyValkyrGrabbing) + { + // Phase 3 (boss < 40%, non-winter): skull priority is Raging Spirit > Vile Spirit > boss. + // During Vile Spirit windows, melee can't reach the spirits — mark boss on cross + // and route melee DPS there. + bool phase3 = false; + if (boss && boss->HealthBelowPct(40) && !HasAnyRemorselessWinter(boss)) + { + phase3 = true; + + Unit* currentSkull = botAI->GetUnit(group->GetTargetIcon(7)); + bool const ragingMarked = currentSkull && currentSkull->IsAlive() && + IsLkRagingSpirit(currentSkull->GetEntry()); + + GuidVector const& p3Npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + Unit* nearestRaging = nullptr; + float nearestRagingDist = std::numeric_limits::max(); + + for (ObjectGuid const& guid : p3Npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (!IsLkRagingSpirit(unit->GetEntry())) + continue; + + float const dist = bot->GetDistance(unit); + if (dist < nearestRagingDist) + { + nearestRagingDist = dist; + nearestRaging = unit; + } + } + + static constexpr uint8 CROSS_ICON = 6; + + // Priority: Raging Spirit > boss. Vile Spirits are not marked + // (handled positionally by HandleVileSpiritMechanics + AT chase + // + hunter frost trap). + if (nearestRaging) + { + if (!ragingMarked && group->GetTargetIcon(7) != nearestRaging->GetGUID()) + group->SetTargetIcon(7, bot->GetGUID(), nearestRaging->GetGUID()); + } + else if (group->GetTargetIcon(7) != boss->GetGUID()) + { + group->SetTargetIcon(7, bot->GetGUID(), boss->GetGUID()); + } + + // Cross marker is no longer used for vile windows — clear if set + if (!group->GetTargetIcon(CROSS_ICON).IsEmpty()) + group->SetTargetIcon(CROSS_ICON, bot->GetGUID(), ObjectGuid::Empty); + + // Per-bot RTI: melee non-tanks always target skull (boss or raging) + if (botAI->IsMelee(bot) && !botAI->IsTank(bot)) + context->GetValue("rti")->Set("skull"); + } + + if (phase3) + { + // skip default skull logic + } + else if (boss && boss->HealthAbovePct(71)) + { + if (group->GetTargetIcon(7) != boss->GetGUID()) + group->SetTargetIcon(7, bot->GetGUID(), boss->GetGUID()); + } + else if (boss) + { + Unit* currentSkull = botAI->GetUnit(group->GetTargetIcon(7)); + bool const spiritMarked = currentSkull && currentSkull->IsAlive() && + IsLkRagingSpirit(currentSkull->GetEntry()); + + if (!spiritMarked) + { + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + Unit* nearestSpirit = nullptr; + float nearestDist = std::numeric_limits::max(); + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (!IsLkRagingSpirit(unit->GetEntry())) + continue; + + float const dist = bot->GetDistance(unit); + if (dist < nearestDist) + { + nearestDist = dist; + nearestSpirit = unit; + } + } + + if (nearestSpirit) + { + if (group->GetTargetIcon(7) != nearestSpirit->GetGUID()) + group->SetTargetIcon(7, bot->GetGUID(), nearestSpirit->GetGUID()); + } + else if (group->GetTargetIcon(7) != boss->GetGUID()) + { + group->SetTargetIcon(7, bot->GetGUID(), boss->GetGUID()); + } + } + } + } + } + + // Val'kyr edge-dive (ongoing) + if (bot->HasAura(SPELL_VALKYR_CARRY)) + { + if (bot->GetPositionZ() > 779.0f) + return JumpTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), 740.01f); + + bot->Kill(bot, bot); + return true; + } + + // Detect bots fallen off edge and initiate dive + if (boss && boss->GetHealthPct() < 70.0f && boss->GetHealthPct() > 40.0f && + !HasAnyRemorselessWinter(boss)) + { + static constexpr float PLATFORM_CENTER_X = 503.0f; + static constexpr float PLATFORM_CENTER_Y = -2124.0f; + static constexpr float PLATFORM_EDGE_MIN = 52.0f; + static constexpr float PLATFORM_EDGE_MAX = 70.0f; + static constexpr float PLATFORM_MIN_Z = 844.0f; + + float const dx = bot->GetPositionX() - PLATFORM_CENTER_X; + float const dy = bot->GetPositionY() - PLATFORM_CENTER_Y; + float const platDist = std::hypot(dx, dy); + + if (platDist > PLATFORM_EDGE_MIN && platDist < PLATFORM_EDGE_MAX && + bot->GetPositionZ() > PLATFORM_MIN_Z) + { + bot->AddAura(SPELL_VALKYR_CARRY, bot); + return JumpTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), 740.01f); + } + } + + if (HandleTeleportationFixes(diff, terenas)) + return true; + + HandleHeroicNonTankPositioning(diff, terenas); + HandleSpiritMarkingAndTargeting(diff, terenas); + + if (terenas) + return false; + + // Normal encounter flow + if (HandleQuakeMechanics(boss)) + return true; + + HandleShamblingHorrors(boss, hasPlague); + + if (HandleAssistTankAddManagement(boss, diff)) + return true; + + if (HandleRagingSpiritFlanking(boss, hasPlague, diff)) + return true; + + HandleMeleePositioning(boss, hasPlague, diff); + HandleMainTankTargeting(boss, diff); + HandleNonTankHeroicPositioning(boss, diff, hasPlague); + HandleRangedPositioning(boss, hasPlague, diff); + if (HandleDefileMechanics(boss, diff)) + return true; + if (HandleCenterStacking(boss, diff)) + return true; + HandleValkyrMechanics(diff); + HandleVileSpiritMechanics(); + HandleIceSphereMechanics(); + + return false; +} + +bool IccLichKingAddsAction::HandleTeleportationFixes(Difficulty diff, Unit* terenas) +{ + static constexpr float PLATFORM_Z = 840.857f; + static constexpr float SPIRIT_Z = 1049.865f; + static constexpr float MAX_Y_DRIFT = 200.0f; + static constexpr float MAX_Z_DIFF = 1.0f; + static constexpr float SPIRIT_Z_TOLERANCE = 5.0f; + + // Normal mode: snap back if teleported far outside the encounter area + // (Harvest Soul victim exits Frostmourne room). Land on the main tank, + // falling back to assist tank, then the fixed adds anchor. Iterate group + // directly so distance/LOS gating in PartyMemberValue::Check doesn't hide + // a far-away main tank. + if (!IsHeroicLk(diff) && std::abs(bot->GetPositionY() - (-2095.7915f)) > MAX_Y_DRIFT) + { + Player* mainTank = nullptr; + Player* assistTank = nullptr; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + if (!mainTank && botAI->IsMainTank(member)) + mainTank = member; + else if (!assistTank && botAI->IsAssistTank(member)) + assistTank = member; + } + } + Unit* anchor = mainTank ? static_cast(mainTank) : static_cast(assistTank); + + if (anchor) + bot->TeleportTo(bot->GetMapId(), anchor->GetPositionX(), anchor->GetPositionY(), + anchor->GetPositionZ(), bot->GetOrientation()); + else + bot->TeleportTo(bot->GetMapId(), + ICC_LICH_KING_ADDS_POSITION.GetPositionX(), + ICC_LICH_KING_ADDS_POSITION.GetPositionY(), + ICC_LICH_KING_ADDS_POSITION.GetPositionZ(), + bot->GetOrientation()); + return true; + } + + // Fix bots going underground (buggy ice-platform collisions) + if (!botAI->GetAura("Harvest Soul", bot, false, false) && + !botAI->GetAura("Harvest Souls", bot, false, false) && + std::abs(bot->GetPositionZ() - PLATFORM_Z) > MAX_Z_DIFF) + { + bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), + PLATFORM_Z, bot->GetOrientation()); + return true; + } + + // Heroic: keep bot near main tank during Harvest Soul (fall back to Terenas). + // Resolve main tank via group iteration so distance/LOS gating in + // PartyMemberValue::Check doesn't hide a far-away main tank. + if (terenas && botAI->GetAura("Harvest Soul", bot, false, false) && + std::abs(bot->GetPositionZ() - SPIRIT_Z) > SPIRIT_Z_TOLERANCE) + { + Player* mainTank = nullptr; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member)) + { + mainTank = member; + break; + } + } + } + Unit* anchor = mainTank ? static_cast(mainTank) : terenas; + bot->TeleportTo(bot->GetMapId(), anchor->GetPositionX(), anchor->GetPositionY(), + SPIRIT_Z, bot->GetOrientation()); + return true; + } + + return false; +} + +bool IccLichKingSpiritBombAction::IsBombThreatActive(PlayerbotAI* botAI, Player* bot) +{ + if (!botAI || !bot || !botAI->IsMainTank(bot)) + return false; + + Difficulty const diff = bot->GetMap() ? bot->GetMap()->GetDifficulty() : RAID_DIFFICULTY_10MAN_NORMAL; + if (!IsHeroicLk(diff)) + return false; + + if (!bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f)) + return false; + + GuidVector const& npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SPIRIT_BOMB) + return true; + } + + return false; +} + +bool IccLichKingSpiritBombAction::Execute(Event event) +{ + Difficulty const diff = bot->GetMap() ? bot->GetMap()->GetDifficulty() : RAID_DIFFICULTY_10MAN_NORMAL; + Unit* terenas = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f); + + if (!botAI->IsMainTank(bot) || !terenas || !IsHeroicLk(diff)) + return false; + + // Snap back to spirit-room Z if glitched through the floor + static constexpr float SPIRIT_Z = 1049.865f; + static constexpr float SPIRIT_Z_TOLERANCE = 5.0f; + if (std::abs(bot->GetPositionZ() - SPIRIT_Z) > SPIRIT_Z_TOLERANCE) + { + bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), + SPIRIT_Z, bot->GetOrientation()); + return true; + } + + static constexpr float SAFE_DIST = 14.0f; + static constexpr float SAFE_HEIGHT = 12.0f; + static constexpr float DENSITY_RADIUS = 10.0f; + static constexpr float DENSITY_PENALTY = 10.0f; + static constexpr float MAX_HEIGHT_DIFF = 8.0f; + static constexpr uint32 UNSAFE_MEM_MS = 15000; + + static std::map s_lastUnsafeX; + static std::map s_lastUnsafeY; + static std::map s_lastUnsafeTime; + uint32 const instId = bot->GetInstanceId(); + float& lastUnsafeX = s_lastUnsafeX[instId]; + float& lastUnsafeY = s_lastUnsafeY[instId]; + uint32& lastUnsafeTime = s_lastUnsafeTime[instId]; + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + std::vector bombs; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SPIRIT_BOMB) + bombs.push_back(unit); + } + + if (bombs.empty()) + return false; + + // Check current position safety + bool currentlySafe = true; + for (Unit const* bomb : bombs) + { + float const hDist = std::hypot(bot->GetPositionX() - bomb->GetPositionX(), + bot->GetPositionY() - bomb->GetPositionY()); + float const vDist = std::abs(bot->GetPositionZ() - bomb->GetPositionZ()); + + if (hDist < SAFE_DIST && vDist <= SAFE_HEIGHT) + { + currentlySafe = false; + break; + } + } + + if (currentlySafe) + return true; + + lastUnsafeX = bot->GetPositionX(); + lastUnsafeY = bot->GetPositionY(); + lastUnsafeTime = getMSTime(); + + // Search for the best nearby safe position + static constexpr std::array SearchDistances = {6.0f, 10.0f, 15.0f, 20.0f, 25.0f}; + static constexpr int ANGLE_COUNT = 36; + + float bestScore = -std::numeric_limits::max(); + float bestX = 0.0f; + float bestY = 0.0f; + float bestZ = 0.0f; + bool found = false; + + for (float const radius : SearchDistances) + { + for (int i = 0; i < ANGLE_COUNT; ++i) + { + float const angle = i * 2.0f * float(M_PI) / ANGLE_COUNT; + float const testX = bot->GetPositionX() + radius * std::cos(angle); + float const testY = bot->GetPositionY() + radius * std::sin(angle); + float testZ = bot->GetPositionZ(); + + bot->UpdateAllowedPositionZ(testX, testY, testZ); + + if (std::abs(testZ - bot->GetPositionZ()) >= MAX_HEIGHT_DIFF) + continue; + if (!bot->IsWithinLOS(testX, testY, testZ)) + continue; + + bool posSafe = true; + float minHDist = std::numeric_limits::max(); + int nearbyCount = 0; + + for (Unit const* bomb : bombs) + { + float const hDist = std::hypot(testX - bomb->GetPositionX(), + testY - bomb->GetPositionY()); + float const vDist = std::abs(testZ - bomb->GetPositionZ()); + + minHDist = std::min(minHDist, hDist); + + if (hDist < DENSITY_RADIUS) + ++nearbyCount; + + if (hDist < SAFE_DIST && vDist <= SAFE_HEIGHT) + { + posSafe = false; + break; + } + } + + if (!posSafe) + continue; + + if (lastUnsafeTime != 0 && (getMSTime() - lastUnsafeTime) < UNSAFE_MEM_MS && + std::hypot(testX - lastUnsafeX, testY - lastUnsafeY) < SAFE_DIST) + continue; + + float const distBonus = std::max(0.0f, 30.0f - radius); + float const score = minHDist - nearbyCount * DENSITY_PENALTY + distBonus; + + if (score > bestScore) + { + bestScore = score; + bestX = testX; + bestY = testY; + bestZ = testZ; + found = true; + } + } + + if (found && radius <= 15.0f) + break; + } + + // Fallback: surrounded with no clean path. Pick direction where the + // blocking bomb is highest on Z axis and move thru there + if (!found) + { + float bestEscapeZ = std::numeric_limits::max(); + for (float const radius : SearchDistances) + { + for (int i = 0; i < ANGLE_COUNT; ++i) + { + float const angle = i * 2.0f * float(M_PI) / ANGLE_COUNT; + float const testX = bot->GetPositionX() + radius * std::cos(angle); + float const testY = bot->GetPositionY() + radius * std::sin(angle); + float testZ = bot->GetPositionZ(); + + bot->UpdateAllowedPositionZ(testX, testY, testZ); + + if (std::abs(testZ - bot->GetPositionZ()) >= MAX_HEIGHT_DIFF) + continue; + if (!bot->IsWithinLOS(testX, testY, testZ)) + continue; + + // Highest blocking bomb on the line from bot to test pos + float worstBombZ = -std::numeric_limits::max(); + bool blocked = false; + for (Unit const* bomb : bombs) + { + float const hDist = std::hypot(testX - bomb->GetPositionX(), + testY - bomb->GetPositionY()); + if (hDist < SAFE_DIST) + { + blocked = true; + worstBombZ = std::max(worstBombZ, bomb->GetPositionZ()); + } + } + + if (!blocked) + continue; + + if (worstBombZ < bestEscapeZ) + { + bestEscapeZ = worstBombZ; + bestX = testX; + bestY = testY; + bestZ = testZ; + found = true; + } + } + + if (found) + break; + } + } + + if (found && bot->IsWithinLOS(bestX, bestY, bestZ) && + std::abs(bestZ - bot->GetPositionZ()) <= MAX_HEIGHT_DIFF) + { + MoveTo(bot->GetMapId(), bestX, bestY, bestZ, + false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + + return true; +} + +bool IccLichKingAddsAction::HandleHeroicNonTankPositioning(Difficulty diff, Unit* terenas) +{ + if (!terenas || botAI->IsMainTank(bot) || !IsHeroicLk(diff)) + return false; + + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + if (!mainTank) + return false; + + // Stack on main tank — 3-yard threshold avoids micro-jitter + if (bot->GetExactDist2d(mainTank) > 3.0f) + { + MoveTo(bot->GetMapId(), mainTank->GetPositionX(), mainTank->GetPositionY(), + mainTank->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED); + } + + return false; +} + +bool IccLichKingAddsAction::HandleSpiritMarkingAndTargeting(Difficulty diff, Unit* terenas) +{ + if (!terenas || botAI->IsMainTank(bot) || !IsHeroicLk(diff)) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + static constexpr uint8 STAR_ICON = 0; + static constexpr float MAX_Z_DIFF = 20.0f; + + auto const spiritTargetsGroup = [&](Unit* spirit) -> bool + { + Unit* victim = spirit->GetVictim(); + if (!victim || !victim->IsPlayer()) + return false; + + Group* g = victim->ToPlayer()->GetGroup(); + return g && g->GetGUID() == group->GetGUID(); + }; + + Unit* currentMark = botAI->GetUnit(group->GetTargetIcon(STAR_ICON)); + bool const needNewMark = !currentMark || !currentMark->IsAlive(); + bool const markOnTarget = currentMark && currentMark->IsAlive() && spiritTargetsGroup(currentMark); + + if (needNewMark || !markOnTarget) + { + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + Unit* prioritySpirit = nullptr; + Unit* nearestSpirit = nullptr; + float priorityDist = 100.0f; + float nearestDist = 100.0f; + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || !unit->isTargetableForAttack()) + continue; + + if (!IsLkWickedSpirit(unit->GetEntry())) + continue; + + if (std::abs(unit->GetPositionZ() - bot->GetPositionZ()) > MAX_Z_DIFF) + continue; + + float const dist = bot->GetDistance(unit); + if (spiritTargetsGroup(unit) && dist < priorityDist) + { + prioritySpirit = unit; + priorityDist = dist; + } + if (dist < nearestDist) + { + nearestSpirit = unit; + nearestDist = dist; + } + } + + Unit* toMark = prioritySpirit ? prioritySpirit : nearestSpirit; + if (toMark && (needNewMark || (prioritySpirit && !markOnTarget))) + group->SetTargetIcon(STAR_ICON, bot->GetGUID(), toMark->GetGUID()); + } + + // Ranged DPS focus the star target + if (botAI->IsRangedDps(bot)) + { + context->GetValue("rti")->Set("star"); + + Unit* starTarget = botAI->GetUnit(group->GetTargetIcon(STAR_ICON)); + if (starTarget && starTarget->IsAlive()) + { + bot->SetTarget(starTarget->GetGUID()); + bot->SetFacingToObject(starTarget); + Attack(starTarget); + bot->Kill(bot, starTarget); + return true; + } + } + + return false; +} + +bool IccLichKingAddsAction::HandleQuakeMechanics(Unit* boss) +{ + if (!boss || !boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_QUAKE)) + return false; + + static constexpr float QUAKE_MIN = 35.0f; + static constexpr float QUAKE_MAX = 45.0f; + static constexpr float QUAKE_TARGET = 40.0f; + + float const dist = bot->GetExactDist2d(boss); + if (dist >= QUAKE_MIN && dist <= QUAKE_MAX) + return false; + + // Interrupt active spell cast before repositioning + if (bot->HasUnitState(UNIT_STATE_CASTING)) + bot->InterruptNonMeleeSpells(false); + + float const dx = bot->GetPositionX() - boss->GetPositionX(); + float const dy = bot->GetPositionY() - boss->GetPositionY(); + float const len = std::hypot(dx, dy); + if (len < 0.01f) + return false; + + float const targetX = boss->GetPositionX() + (dx / len) * QUAKE_TARGET; + float const targetY = boss->GetPositionY() + (dy / len) * QUAKE_TARGET; + + return MoveTo(bot->GetMapId(), targetX, targetY, bot->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT); +} + +bool IccLichKingAddsAction::HandleRagingSpiritFlanking(Unit* boss, bool hasPlague, Difficulty diff) +{ + if (!boss || botAI->IsTank(bot) || hasPlague) + return false; + if (HasAnyRemorselessWinter(boss)) + return false; + if (bot->GetVehicle()) + return false; + if (bot->HasAura(SPELL_HARVEST_SOUL_VALKYR) || bot->HasAura(SPELL_VALKYR_CARRY)) + return false; + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + // Designated trap hunter (lowest-GUID alive bot hunter) skips flanking when + // vile spirits are active so HandleVileSpiritMechanics can place the hunter + // at center for Frost Trap duty. On heroic, raging + vile coexist during P3 + // and would otherwise keep the hunter stuck in flanking. + if (bot->getClass() == CLASS_HUNTER) + { + bool vileAlive = false; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && IsLkVileSpirit(unit)) + { + vileAlive = true; + break; + } + } + + if (vileAlive) + { + if (Group* hunterGroup = bot->GetGroup()) + { + ObjectGuid bestGuid; + for (GroupReference* ref = hunterGroup->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + if (member->getClass() != CLASS_HUNTER) + continue; + if (!GET_PLAYERBOT_AI(member)) + continue; + + if (bestGuid.IsEmpty() || member->GetGUID() < bestGuid) + bestGuid = member->GetGUID(); + } + if (!bestGuid.IsEmpty() && bot->GetGUID() == bestGuid) + return false; + } + } + } + + std::vector spirits; + Unit* nearestSpirit = nullptr; + float nearestDist = std::numeric_limits::max(); + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + if (!IsLkRagingSpirit(unit->GetEntry())) + continue; + + float const dist = bot->GetExactDist2d(unit); + if (dist > 40.0f) + continue; + + spirits.push_back(unit); + if (dist < nearestDist) + { + nearestDist = dist; + nearestSpirit = unit; + } + } + + if (spirits.empty()) + return false; + + bool const isRanged = botAI->IsRanged(bot); + static constexpr float RANGED_SAFE_DIST = 15.0f; + + auto IsSafeFromAllSpirits = [&](float x, float y) -> bool + { + for (Unit* spirit : spirits) + { + float const dx = x - spirit->GetPositionX(); + float const dy = y - spirit->GetPositionY(); + float const d = std::hypot(dx, dy); + + if (isRanged && d < RANGED_SAFE_DIST) + return false; + + if (d < 0.1f) + return false; + + // Cone test: dot of spirit's facing vs vector spirit->point. + // dot > 0 means point is in front half of spirit. + float const dot = (std::cos(spirit->GetOrientation()) * dx + + std::sin(spirit->GetOrientation()) * dy) / d; + if (dot > 0.0f) + return false; + } + return true; + }; + + // Already safe from every spirit -- nothing to do + if (IsSafeFromAllSpirits(bot->GetPositionX(), bot->GetPositionY())) + return false; + + std::vector defiles; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == DEFILE_NPC_ID) + defiles.push_back(unit); + } + + auto IsDefileSafe = [&](float x, float y) -> bool + { + static constexpr float SAFETY_MARGIN = 2.0f; + for (Unit const* defile : defiles) + { + float const radius = GetDefileEffectiveRadius(defile, diff); + float const d = std::hypot(x - defile->GetPositionX(), y - defile->GetPositionY()); + if (d < radius + SAFETY_MARGIN) + return false; + } + return true; + }; + + static constexpr float MELEE_STEP = 7.0f; + static constexpr float RANGED_STEP = 5.0f; + float const stepSize = isRanged ? RANGED_STEP : MELEE_STEP; + + auto TryDest = [&](float destX, float destY) -> bool + { + float destZ = nearestSpirit->GetPositionZ(); + bot->UpdateAllowedPositionZ(destX, destY, destZ); + + if (!IsSafeFromAllSpirits(destX, destY)) + return false; + if (!IsDefileSafe(destX, destY)) + return false; + if (!bot->IsWithinLOS(destX, destY, destZ)) + return false; + + float const bDx = destX - bot->GetPositionX(); + float const bDy = destY - bot->GetPositionY(); + float const bDist = std::hypot(bDx, bDy); + if (bDist < 0.5f) + return false; + + // Ranged: don't chase spirits across the room. Reject destinations that + // require more than one short hop. + if (isRanged && bDist > RANGED_STEP * 1.5f) + return false; + + float const step = std::min(stepSize, bDist); + float const stepX = bot->GetPositionX() + (bDx / bDist) * step; + float const stepY = bot->GetPositionY() + (bDy / bDist) * step; + float stepZ = bot->GetPositionZ(); + bot->UpdateAllowedPositionZ(stepX, stepY, stepZ); + + MoveTo(bot->GetMapId(), stepX, stepY, stepZ, + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + return true; + }; + + static constexpr int ANGLE_STEPS = 24; + + if (isRanged) + { + // Step away from current bot position. Prefer the direction directly away + // from the nearest spirit, then sweep outward in alternating angles. + float const awayX = bot->GetPositionX() - nearestSpirit->GetPositionX(); + float const awayY = bot->GetPositionY() - nearestSpirit->GetPositionY(); + float const awayLen = std::hypot(awayX, awayY); + float const baseAngle = (awayLen > 0.01f) ? std::atan2(awayY, awayX) : 0.0f; + + for (int i = 0; i < ANGLE_STEPS; ++i) + { + int const sign = (i % 2 == 0) ? 1 : -1; + int const offset = (i + 1) / 2; + float const angle = baseAngle + sign * offset * (2.0f * float(M_PI) / ANGLE_STEPS); + + float const destX = bot->GetPositionX() + std::cos(angle) * RANGED_STEP; + float const destY = bot->GetPositionY() + std::sin(angle) * RANGED_STEP; + + if (TryDest(destX, destY)) + return true; + } + + return false; + } + + // Melee: search rings around the nearest spirit + static constexpr std::array MeleeRings = {4.0f, 6.0f, 9.0f, 13.0f}; + + for (float const radius : MeleeRings) + { + for (int i = 0; i < ANGLE_STEPS; ++i) + { + float const angle = (i * 2.0f * float(M_PI)) / ANGLE_STEPS; + float const destX = nearestSpirit->GetPositionX() + std::cos(angle) * radius; + float const destY = nearestSpirit->GetPositionY() + std::sin(angle) * radius; + + if (TryDest(destX, destY)) + return true; + } + } + + return false; +} + +bool IccLichKingAddsAction::HandleShamblingHorrors(Unit* /*boss*/, bool /*hasPlague*/) +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || !IsLkShambling(unit->GetEntry())) + continue; + + if (botAI->HasAura("Enrage", unit)) + { + botAI->CastSpell("Tranquilizing Shot", unit); + return true; + } + } + + return false; +} + +bool IccLichKingAddsAction::HandleAssistTankAddManagement(Unit* boss, Difficulty diff) +{ + if (!botAI->IsAssistTank(bot) || !boss) + return false; + + // Below 71%: stun all shamblings until winter starts so they don't + // shockwave the raid during the transition gap. + if (boss->HealthBelowPct(72) && boss->HealthAbovePct(70)) + { + if (!HasAnyRemorselessWinter(boss)) + { + GuidVector const& stunTargets = AI_VALUE(GuidVector, "possible targets"); + for (ObjectGuid const& guid : stunTargets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + if (!IsLkShambling(unit->GetEntry())) + continue; + if (!unit->HasAura(SPELL_HAMMER_OF_JUSTICE)) + bot->AddAura(SPELL_HAMMER_OF_JUSTICE, unit); + } + } + + return false; + } + + Position const& holdPos = IsHeroicLk(diff) + ? ICC_LICH_KING_ASSISTHC_POSITION + : ICC_LICH_KING_ADDS_POSITION; + + // Class-specific taunt with forced cooldown reset + auto CastClassTaunt = [&](Unit* target) -> bool + { + if (!target || !target->IsAlive()) + return false; + + switch (bot->getClass()) + { + case CLASS_PALADIN: + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + case CLASS_DEATH_KNIGHT: + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + case CLASS_DRUID: + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + case CLASS_WARRIOR: + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; + }; + + // Categorise visible adds + GuidVector const& targets = AI_VALUE(GuidVector, "possible targets"); + + std::vector addsOnUs; + std::vector addsElsewhere; + + for (ObjectGuid const& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!IsLkCollectibleAdd(unit)) + continue; + + if (unit->GetVictim() == bot) + addsOnUs.push_back(unit); + else + addsElsewhere.push_back(unit); + } + + bool const isHeroic = IsHeroicLk(diff); + + // Non-winter Raging Spirit pickup: AT grabs ALL spirits before joining MT. + if (!HasAnyRemorselessWinter(boss)) + { + std::vector looseSpirits; + std::vector spiritsOnUs; + + for (Unit* add : addsOnUs) + { + if (IsLkRagingSpirit(add->GetEntry())) + spiritsOnUs.push_back(add); + } + + for (Unit* add : addsElsewhere) + { + if (!IsLkRagingSpirit(add->GetEntry())) + continue; + if (bot->GetExactDist2d(add) > 60.0f) + continue; + looseSpirits.push_back(add); + } + + if (!looseSpirits.empty() || !spiritsOnUs.empty()) + { + // Taunt every loose spirit that we can reach + Unit* nearestLoose = nullptr; + float nearestLooseDist = FLT_MAX; + for (Unit* spirit : looseSpirits) + { + CastClassTaunt(spirit); + float const d = bot->GetExactDist2d(spirit); + if (d < nearestLooseDist) + { + nearestLooseDist = d; + nearestLoose = spirit; + } + } + + // If any spirit is loose, go fetch the nearest loose one (do not drag yet) + if (nearestLoose) + { + bot->SetTarget(nearestLoose->GetGUID()); + bot->SetFacingToObject(nearestLoose); + Attack(nearestLoose); + + if (nearestLooseDist > 4.0f) + { + float const dx = nearestLoose->GetPositionX() - bot->GetPositionX(); + float const dy = nearestLoose->GetPositionY() - bot->GetPositionY(); + float const len = std::hypot(dx, dy); + if (len > 0.1f) + { + float const step = std::min(7.0f, len - 3.0f); + if (step > 0.0f) + { + float const goalX = bot->GetPositionX() + (dx / len) * step; + float const goalY = bot->GetPositionY() + (dy / len) * step; + float goalZ = bot->GetPositionZ(); + bot->UpdateAllowedPositionZ(goalX, goalY, goalZ); + MoveTo(bot->GetMapId(), goalX, goalY, goalZ, + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + } + return false; + } + + // All spirits are on us: drag the pack toward boss + Unit* nearestOnUs = nullptr; + float nearestOnUsDist = FLT_MAX; + for (Unit* spirit : spiritsOnUs) + { + float const d = bot->GetExactDist2d(spirit); + if (d < nearestOnUsDist) + { + nearestOnUsDist = d; + nearestOnUs = spirit; + } + } + + if (nearestOnUs) + { + bot->SetTarget(nearestOnUs->GetGUID()); + bot->SetFacingToObject(nearestOnUs); + Attack(nearestOnUs); + + if (bot->GetExactDist2d(boss) > 5.0f) + { + float const dx = boss->GetPositionX() - bot->GetPositionX(); + float const dy = boss->GetPositionY() - bot->GetPositionY(); + float const len = std::hypot(dx, dy); + if (len > 0.1f) + { + float const step = std::min(5.0f, len - 4.0f); + if (step > 0.0f) + { + float const goalX = bot->GetPositionX() + (dx / len) * step; + float const goalY = bot->GetPositionY() + (dy / len) * step; + float goalZ = bot->GetPositionZ(); + bot->UpdateAllowedPositionZ(goalX, goalY, goalZ); + MoveTo(bot->GetMapId(), goalX, goalY, goalZ, + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + } + return false; + } + } + } + + // Hold position during phase 1 (boss above 70%) + if (boss->HealthAbovePct(70)) + { + if (!isHeroic) + { + if (bot->GetExactDist2d(holdPos) > 5.0f) + { + MoveTo(bot->GetMapId(), holdPos.GetPositionX(), holdPos.GetPositionY(), + holdPos.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + else + { + Unit* mainTankStep1 = AI_VALUE(Unit*, "main tank"); + if (mainTankStep1 && mainTankStep1->IsAlive()) + { + float const distToMt = bot->GetExactDist2d(mainTankStep1); + + if (distToMt < 20.0f || distToMt > 30.0f) + { + float const axX = holdPos.GetPositionX() - mainTankStep1->GetPositionX(); + float const axY = holdPos.GetPositionY() - mainTankStep1->GetPositionY(); + float const axLen = std::hypot(axX, axY); + + if (axLen > 0.01f) + { + float const targetDist = std::max(20.0f, std::min(30.0f, distToMt)); + float const goalX = mainTankStep1->GetPositionX() + (axX / axLen) * targetDist; + float const goalY = mainTankStep1->GetPositionY() + (axY / axLen) * targetDist; + MoveTo(bot->GetMapId(), goalX, goalY, holdPos.GetPositionZ(), + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + } + } + } + + // Taunt loose adds: shamblings first, then others; nearest wins within tier. + Unit* tauntTargetShambling = nullptr; + float tauntDistShambling = FLT_MAX; + Unit* tauntTargetOther = nullptr; + float tauntDistOther = FLT_MAX; + + for (Unit* add : addsElsewhere) + { + float const dist = bot->GetExactDist2d(add); + if (dist > 30.0f) + continue; + + if (IsLkShambling(add->GetEntry())) + { + if (dist < tauntDistShambling) + { + tauntDistShambling = dist; + tauntTargetShambling = add; + } + } + else if (dist < tauntDistOther) + { + tauntDistOther = dist; + tauntTargetOther = add; + } + } + + Unit* tauntTarget = tauntTargetShambling ? tauntTargetShambling : tauntTargetOther; + if (tauntTarget) + CastClassTaunt(tauntTarget); + + // No adds at all — stay at hold position + if (addsOnUs.empty() && addsElsewhere.empty()) + return boss->HealthAbovePct(70); + + // Target priority by mob type (stable, not victim-dependent): + // Tier 1 — Shambling Horror + // Tier 0 — Ghoul / Raging Spirit / other collectible add + // Sticky: never switch away from a same-or-higher tier target. + auto TargetTier = [](Unit* t) -> int + { + if (!t || !t->IsAlive()) + return -1; + if (!IsLkCollectibleAdd(t)) + return -1; + return IsLkShambling(t->GetEntry()) ? 1 : 0; + }; + + Unit* currentTarget = bot->GetVictim(); + int const currentTier = TargetTier(currentTarget); + + // Current target is a valid add — keep it unless a higher-tier add exists + if (currentTier >= 0) + { + if (currentTier < 1) + { + for (Unit* add : addsOnUs) + { + if (IsLkShambling(add->GetEntry())) + { + bot->SetTarget(add->GetGUID()); + bot->SetFacingToObject(add); + Attack(add); + currentTarget = add; + break; + } + } + } + + if (currentTarget) + { + bot->SetFacingToObject(currentTarget); + Attack(currentTarget); + } + } + else + { + // Current target is dead/invalid — pick a new one. + // Prefer adds already on us, then nearby adds within melee reach. + Unit* bestTarget = nullptr; + int bestTier = -1; + float bestDist = FLT_MAX; + + for (Unit* add : addsOnUs) + { + int const tier = TargetTier(add); + if (tier < 0) + continue; + float const dist = bot->GetExactDist2d(add); + if (tier > bestTier || (tier == bestTier && dist < bestDist)) + { + bestTier = tier; + bestDist = dist; + bestTarget = add; + } + } + + if (!bestTarget) + { + for (Unit* add : addsElsewhere) + { + int const tier = TargetTier(add); + if (tier < 0) + continue; + float const dist = bot->GetExactDist2d(add); + if (dist > 8.0f) + continue; + if (tier > bestTier || (tier == bestTier && dist < bestDist)) + { + bestTier = tier; + bestDist = dist; + bestTarget = add; + } + } + } + + if (bestTarget) + { + bot->SetTarget(bestTarget->GetGUID()); + bot->SetFacingToObject(bestTarget); + Attack(bestTarget); + } + } + + // Face the nearest high-priority add (even when not actively attacking) + { + Unit* faceTarget = nullptr; + int faceTier = -1; + float faceDist = FLT_MAX; + + auto const& faceList = addsOnUs.empty() ? addsElsewhere : addsOnUs; + for (Unit* add : faceList) + { + int const tier = TargetTier(add); + if (tier < 0) + continue; + float const dist = bot->GetExactDist2d(add); + if (tier > faceTier || (tier == faceTier && dist < faceDist)) + { + faceTier = tier; + faceDist = dist; + faceTarget = add; + } + } + + if (faceTarget) + bot->SetFacingToObject(faceTarget); + } + + // Heroic: rotate any Shambling that is facing the MT + if (isHeroic && !addsOnUs.empty()) + { + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + if (mainTank && mainTank->IsAlive()) + { + Unit* worstShambling = nullptr; + float worstDot = -2.0f; + + for (Unit* add : addsOnUs) + { + if (!IsLkShambling(add->GetEntry())) + continue; + + float const toMtX = mainTank->GetPositionX() - add->GetPositionX(); + float const toMtY = mainTank->GetPositionY() - add->GetPositionY(); + float const toMtLen = std::hypot(toMtX, toMtY); + if (toMtLen < 0.01f) + continue; + + float const dot = (std::cos(add->GetOrientation()) * toMtX + + std::sin(add->GetOrientation()) * toMtY) / toMtLen; + if (dot > worstDot) + { + worstDot = dot; + worstShambling = add; + } + } + + // dot > 0.3: Shambling is facing toward MT — reposition to turn it away + if (worstShambling && worstDot > 0.3f) + { + float const axisX = worstShambling->GetPositionX() - mainTank->GetPositionX(); + float const axisY = worstShambling->GetPositionY() - mainTank->GetPositionY(); + float const axisLen = std::hypot(axisX, axisY); + + if (axisLen > 0.01f) + { + float const goalX = worstShambling->GetPositionX() + (axisX / axisLen) * 5.0f; + float const goalY = worstShambling->GetPositionY() + (axisY / axisLen) * 5.0f; + + if (std::hypot(goalX - bot->GetPositionX(), goalY - bot->GetPositionY()) > 1.0f) + { + MoveTo(bot->GetMapId(), goalX, goalY, bot->GetPositionZ(), + false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + } + } + } + + return false; +} + +bool IccLichKingAddsAction::HandleMeleePositioning(Unit* boss, bool hasPlague, Difficulty diff) +{ + if (!boss || !botAI->IsMelee(bot) || botAI->IsAssistTank(bot) || + boss->HealthBelowPct(71) || hasPlague || IsHeroicLk(diff)) + return false; + + float const distToPos = bot->GetDistance(ICC_LICH_KING_MELEE_POSITION); + if (distToPos <= 6.0f) + return false; + + if (!botAI->IsMainTank(bot)) + { + MoveTo(bot->GetMapId(), + ICC_LICH_KING_MELEE_POSITION.GetPositionX(), + ICC_LICH_KING_MELEE_POSITION.GetPositionY(), + ICC_LICH_KING_MELEE_POSITION.GetPositionZ(), + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + return true; + } + + // Main tank: step toward position in 3-yard increments to avoid overshooting + if (boss->GetVictim() != bot) + return false; + + float const dx = ICC_LICH_KING_MELEE_POSITION.GetPositionX() - bot->GetPositionX(); + float const dy = ICC_LICH_KING_MELEE_POSITION.GetPositionY() - bot->GetPositionY(); + float const len = std::hypot(dx, dy); + if (len < 0.1f) + return false; + + float const step = std::min(3.0f, len - 1.0f); + if (step <= 0.0f) + { + MoveTo(bot->GetMapId(), + ICC_LICH_KING_MELEE_POSITION.GetPositionX(), + ICC_LICH_KING_MELEE_POSITION.GetPositionY(), + bot->GetPositionZ(), + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + return true; + } + + MoveTo(bot->GetMapId(), + bot->GetPositionX() + (dx / len) * step, + bot->GetPositionY() + (dy / len) * step, + bot->GetPositionZ(), + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + + return true; +} + +bool IccLichKingAddsAction::HandleMainTankTargeting(Unit* boss, Difficulty diff) +{ + if (!botAI->IsMainTank(bot) || !boss) + return false; + + if (boss->GetVictim() == bot) + return false; + + // Non-winter Raging Spirit case: stay on boss only when an assist tank is alive + // to take the spirit. If AT is dead, fall through so MT can swap. + if (!HasAnyRemorselessWinter(boss)) + { + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + bool spiritAlive = false; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && IsLkRagingSpirit(unit->GetEntry())) + { + spiritAlive = true; + break; + } + } + + if (spiritAlive) + { + bool assistTankAlive = false; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->IsAlive() && member != bot && + botAI->IsAssistTank(member)) + { + assistTankAlive = true; + break; + } + } + } + + if (assistTankAlive) + { + bot->SetTarget(boss->GetGUID()); + bot->SetFacingToObject(boss); + Attack(boss); + return true; + } + } + } + + if (!IsHeroicLk(diff) || boss->HealthBelowPct(71)) + return false; + + bot->SetTarget(boss->GetGUID()); + bot->SetFacingToObject(boss); + Attack(boss); + + return true; +} + +bool IccLichKingAddsAction::HandleNonTankHeroicPositioning(Unit* boss, Difficulty diff, bool hasPlague) +{ + if (botAI->IsTank(bot) || !boss || !IsHeroicLk(diff)) + return false; + + if (boss->HealthBelowPct(71) || hasPlague) + return false; + + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + if (!mainTank) + return false; + + float const distToTank = bot->GetDistance2d(mainTank); + + if (bot->getClass() == CLASS_HUNTER) + { + // Hunters stay within 10 yd of MT but away from the assist tank position + if (distToTank > 10.0f) + { + float goalX = mainTank->GetPositionX(); + float goalY = mainTank->GetPositionY(); + + float const atDx = ICC_LICH_KING_ASSISTHC_POSITION.GetPositionX() - mainTank->GetPositionX(); + float const atDy = ICC_LICH_KING_ASSISTHC_POSITION.GetPositionY() - mainTank->GetPositionY(); + float const atLen = std::hypot(atDx, atDy); + if (atLen > 0.01f) + { + goalX -= (atDx / atLen) * 8.0f; + goalY -= (atDy / atLen) * 8.0f; + } + + MoveTo(bot->GetMapId(), goalX, goalY, bot->GetPositionZ(), + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + return true; + } + } + else + { + // Everyone else stacks on main tank + if (distToTank > 3.0f) + { + MoveTo(bot->GetMapId(), mainTank->GetPositionX(), mainTank->GetPositionY(), + bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + return true; + } + } + + return false; +} + +bool IccLichKingAddsAction::HandleRangedPositioning(Unit* boss, bool hasPlague, Difficulty diff) +{ + if (!boss || !botAI->IsRanged(bot) || boss->HealthBelowPct(71) || + hasPlague || IsHeroicLk(diff)) + return false; + + if (bot->GetDistance(ICC_LICH_KING_RANGED_POSITION) > 2.0f) + { + MoveTo(bot->GetMapId(), + ICC_LICH_KING_RANGED_POSITION.GetPositionX(), + ICC_LICH_KING_RANGED_POSITION.GetPositionY(), + ICC_LICH_KING_RANGED_POSITION.GetPositionZ(), + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + return true; + } + + return false; +} + +bool IccLichKingAddsAction::HandleCenterStacking(Unit* boss, Difficulty diff) +{ + if (!boss || !boss->HealthBelowPct(67) || HasAnyRemorselessWinter(boss)) + return false; + + // Defile target: let HandleDefileMechanics() handle movement + // (perpendicular run). Don't override with slot/center movement. + auto const defileIt = IcecrownHelpers::defileCast.find(bot->GetInstanceId()); + if (defileIt != IcecrownHelpers::defileCast.end()) + { + auto const& defileInfo = defileIt->second; + if (!defileInfo.targetGuid.IsEmpty() && + getMSTimeDiff(defileInfo.castTime, getMSTime()) <= 3000 && + defileInfo.targetGuid == bot->GetGUID()) + { + return false; + } + } + + // Marked Val'kyrs (Skull/Cross/Star) are being kited by assist - bots + // assigned to them must not be locked to the center stack. + if (Group* group = bot->GetGroup()) + { + static constexpr std::array ValkyrIcons = {7, 6, 0}; + for (uint8 const iconIdx : ValkyrIcons) + { + Unit* marked = botAI->GetUnit(group->GetTargetIcon(iconIdx)); + if (marked && marked->IsAlive() && IsLkValkyr(marked)) + return false; + } + } + + static constexpr float HUNTER_DISTANCE = 8.0f; + static constexpr float OTHER_DISTANCE = 5.0f; + // Worst-case stack radius: hunters sit furthest out, plus safety margin. + // Defile reach = dist - radius; unsafe if reach < stack + safety. + static constexpr float STACK_SAFETY = 3.0f; + static constexpr float STACK_DEFILE_BUFFER = HUNTER_DISTANCE + STACK_SAFETY; + + bool const lastPhase = boss->HealthBelowPct(37); + bool const spiritPhase = boss->HealthBelowPct(43) && boss->HealthAbovePct(37); + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector defiles; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (IsLkRagingSpirit(unit->GetEntry()) || IsLkVileSpirit(unit) || + IsIceSphere(unit->GetEntry())) + return false; + + if (unit->GetEntry() == DEFILE_NPC_ID) + defiles.push_back(unit); + } + + Position dest; + if (spiritPhase || lastPhase) + { + // Phase 3: pick the safe vile-spirit slot closest to the bot. + Position const slots[3] = { + ICC_LK_VILE_SPIRIT1_POSITION, + ICC_LK_VILE_SPIRIT2_POSITION, + ICC_LK_VILE_SPIRIT3_POSITION, + }; + + auto IsSlotSafe = [&](Position const& slot) -> bool + { + for (Unit const* defile : defiles) + { + float const radius = GetDefileEffectiveRadius(defile, diff); + float const d = std::hypot(slot.GetPositionX() - defile->GetPositionX(), + slot.GetPositionY() - defile->GetPositionY()); + if (d < radius + STACK_DEFILE_BUFFER) + return false; + } + return true; + }; + + // Among safe slots (0 and 2), pick the one closest to the group + // centroid. Centroid is identical for every bot in the raid so all + // bots converge on the same anchor. Cached per boss GUID for 2s to + // dampen flicker if defile state shifts between ticks. + struct StackChoice { uint32 evaluatedMs; int slotIdx; }; + static std::map, StackChoice> s_stackChoice; + static constexpr uint32 STACK_CHOICE_TTL_MS = 2000; + auto const stackKey = std::make_pair(boss->GetInstanceId(), boss->GetGUID()); + + uint32 const now = getMSTime(); + int chosen = -1; + auto cacheIt = s_stackChoice.find(stackKey); + if (cacheIt != s_stackChoice.end() && + getMSTimeDiff(cacheIt->second.evaluatedMs, now) < STACK_CHOICE_TTL_MS) + { + chosen = cacheIt->second.slotIdx; + } + else + { + Position const centroid = ComputeGroupCentroid(bot); + float bestDist = std::numeric_limits::max(); + for (int const i : {0, 2}) + { + if (!IsSlotSafe(slots[i])) + continue; + + float const d = std::hypot(centroid.GetPositionX() - slots[i].GetPositionX(), + centroid.GetPositionY() - slots[i].GetPositionY()); + if (d < bestDist) + { + bestDist = d; + chosen = i; + } + } + s_stackChoice[stackKey] = {now, chosen}; + } + + if (chosen < 0) + return false; + + dest = slots[chosen]; + } + else + { + // Phase 2: stack on platform center if it's safe from defiles. + for (Unit const* defile : defiles) + { + float const radius = GetDefileEffectiveRadius(defile, diff); + float const d = std::hypot( + defile->GetPositionX() - ICC_LICH_KING_CENTER_POSITION.GetPositionX(), + defile->GetPositionY() - ICC_LICH_KING_CENTER_POSITION.GetPositionY()); + if (d < radius + STACK_DEFILE_BUFFER) + return false; + } + dest = ICC_LICH_KING_CENTER_POSITION; + } + + // Main tank only moves to the stack point if he's still the boss's victim. + if (botAI->IsMainTank(bot) && boss->GetVictim() != bot) + return false; + + float const distToDest = bot->GetDistance2d(dest.GetPositionX(), dest.GetPositionY()); + float const threshold = (bot->getClass() == CLASS_HUNTER) ? HUNTER_DISTANCE : OTHER_DISTANCE; + if (distToDest <= threshold) + return false; + + auto const [stepX, stepY] = DefileAwareStep(dest.GetPositionX(), dest.GetPositionY(), defiles, diff); + float stepZ = dest.GetPositionZ(); + bot->UpdateAllowedPositionZ(stepX, stepY, stepZ); + + MoveTo(bot->GetMapId(), stepX, stepY, stepZ, + false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + return true; +} + +bool IccLichKingAddsAction::HandleDefileMechanics(Unit* boss, Difficulty diff) +{ + if (!boss) + return false; + + static constexpr float SAFETY_MARGIN = 3.0f; + static constexpr float MOVE_DISTANCE = 5.0f; + static constexpr float FIXED_Z = 840.857f; + static constexpr float MAX_HEIGHT_DIFF = 5.0f; + static constexpr int ANGLE_TESTS = 16; + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + std::vector defiles; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == DEFILE_NPC_ID) + defiles.push_back(unit); + } + + if (!defiles.empty()) + { + float const botX = bot->GetPositionX(); + float const botY = bot->GetPositionY(); + + bool needToMove = false; + for (Unit const* defile : defiles) + { + float const radius = GetDefileEffectiveRadius(defile, diff); + float const dist = std::hypot(botX - defile->GetPositionX(), + botY - defile->GetPositionY()); + if (dist < radius + SAFETY_MARGIN) + { + needToMove = true; + break; + } + } + + if (needToMove) + { + float bestScore = 0.0f; + float bestAngle = 0.0f; + bool found = false; + + for (int i = 0; i < ANGLE_TESTS; ++i) + { + float const angle = i * float(M_PI) / 8.0f; + float const testX = botX + MOVE_DISTANCE * std::cos(angle); + float const testY = botY + MOVE_DISTANCE * std::sin(angle); + float testZ = FIXED_Z; + + bot->UpdateAllowedPositionZ(testX, testY, testZ); + + if (!bot->IsWithinLOS(testX, testY, testZ) || + std::abs(testZ - bot->GetPositionZ()) >= MAX_HEIGHT_DIFF) + continue; + + float minDefileDist = std::numeric_limits::max(); + for (Unit const* defile : defiles) + { + float const d = std::hypot(testX - defile->GetPositionX(), + testY - defile->GetPositionY()); + minDefileDist = std::min(minDefileDist, d); + } + + float const bossProximity = 100.0f - std::min(100.0f, boss->GetDistance2d(testX, testY)); + float const score = minDefileDist + bossProximity * 0.5f; + + if (score > bestScore) + { + bestScore = score; + bestAngle = angle; + found = true; + } + } + + if (found) + { + float moveX = botX + MOVE_DISTANCE * std::cos(bestAngle); + float moveY = botY + MOVE_DISTANCE * std::sin(bestAngle); + float moveZ = FIXED_Z; + + if (bot->HasUnitState(UNIT_STATE_CASTING)) + bot->InterruptNonMeleeSpells(false); + + bot->UpdateAllowedPositionZ(moveX, moveY, moveZ); + MoveTo(bot->GetMapId(), moveX, moveY, moveZ, + false, false, false, true, MovementPriority::MOVEMENT_FORCED); + return true; + } + } + } + + if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(DEFILE_CAST_ID)) + return false; + + // Boss casting Defile — only the targeted player runs out. Target is + // stamped by IccLichKingListenerScript at OnSpellPrepare time (cast start). + auto const defileIt = IcecrownHelpers::defileCast.find(bot->GetInstanceId()); + if (defileIt == IcecrownHelpers::defileCast.end()) + return false; + auto const& info = defileIt->second; + if (info.targetGuid.IsEmpty() || getMSTimeDiff(info.castTime, getMSTime()) > 3000) + return false; + + Player* target = ObjectAccessor::FindPlayer(info.targetGuid); + if (!target || !target->IsAlive()) + return false; + + // Main tank yells once per cast. + static std::map s_lastYellMs; + uint32& lastYellMs = s_lastYellMs[bot->GetInstanceId()]; + if (botAI->IsMainTank(bot) && info.castTime != lastYellMs) + { + botAI->Yell("Defile on " + target->GetName() + " - move to the edge!"); + lastYellMs = info.castTime; + } + + if (!target->HasAura(SPELL_NITRO_BOOSTS)) + target->AddAura(SPELL_NITRO_BOOSTS, target); + + // Real players run themselves; only the targeted bot moves. + if (target != bot) + return false; + + // During Vile Spirit phase, run perpendicular (left/right) to the line from + // spirit centroid -> this bot, so defile drops sideways instead of into the + // raid stack or into the spirit cluster. + float spiritSumX = 0.0f; + float spiritSumY = 0.0f; + uint32 spiritCount = 0; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || !IsLkVileSpirit(unit)) + continue; + spiritSumX += unit->GetPositionX(); + spiritSumY += unit->GetPositionY(); + ++spiritCount; + } + + float baseAngle; + if (spiritCount > 0) + { + float const cx = spiritSumX / spiritCount; + float const cy = spiritSumY / spiritCount; + float const sx = bot->GetPositionX() - cx; + float const sy = bot->GetPositionY() - cy; + float const sLen = std::hypot(sx, sy); + if (sLen < 0.01f) + baseAngle = (bot->GetGUID().GetCounter() % 16) * (float(M_PI) / 8.0f); + else + { + float const radial = std::atan2(sy, sx); + // Pick left or right perpendicular based on bot GUID for stable choice + float const sign = (bot->GetGUID().GetCounter() & 1) ? 1.0f : -1.0f; + baseAngle = radial + sign * (float(M_PI) / 2.0f); + } + } + else + { + Position centroid = ComputeGroupCentroid(bot); + float const dx = bot->GetPositionX() - centroid.GetPositionX(); + float const dy = bot->GetPositionY() - centroid.GetPositionY(); + float const len = std::hypot(dx, dy); + if (len < 0.01f) + baseAngle = (bot->GetGUID().GetCounter() % 16) * (float(M_PI) / 8.0f); + else + baseAngle = std::atan2(dy, dx); + } + + // Reject candidates landing inside any defile (radius from grow stacks). + auto IsSafeFromDefiles = [&](float x, float y) -> bool + { + for (Unit const* defile : defiles) + { + float const radius = GetDefileEffectiveRadius(defile, diff); + float const d = std::hypot(x - defile->GetPositionX(), + y - defile->GetPositionY()); + if (d < radius + SAFETY_MARGIN) + return false; + } + return true; + }; + + // Try base direction first, then fan out in +/- 22.5 deg increments. + float destX = 0.0f; + float destY = 0.0f; + bool found = false; + for (int offset = 0; offset <= 8 && !found; ++offset) + { + for (int dir : {-1, 1}) + { + if (offset == 0 && dir > 0) + continue; + + float const angle = baseAngle + dir * offset * float(M_PI) / 8.0f; + float const tx = bot->GetPositionX() + 10.0f * std::cos(angle); + float const ty = bot->GetPositionY() + 10.0f * std::sin(angle); + if (!IsSafeFromDefiles(tx, ty)) + continue; + + destX = tx; + destY = ty; + found = true; + break; + } + } + + if (!found) + return false; + + float destZ = bot->GetPositionZ(); + bot->UpdateAllowedPositionZ(destX, destY, destZ); + + if (bot->HasUnitState(UNIT_STATE_CASTING)) + bot->InterruptNonMeleeSpells(false); + + MoveTo(bot->GetMapId(), destX, destY, destZ, + false, false, false, true, MovementPriority::MOVEMENT_FORCED); + return true; +} + +bool IccLichKingAddsAction::HandleValkyrMechanics(Difficulty diff) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + Group* group = bot->GetGroup(); + if (!group) + return false; + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + std::vector grabbingValkyrs; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || !IsLkValkyr(unit)) + continue; + + // Heroic: only target Val'kyrs above 49% health + bool const isGrabbing = unit->HasAura(SPELL_HARVEST_SOUL_VALKYR) && + (!IsHeroicLk(diff) || unit->HealthAbovePct(49)); + + if (isGrabbing) + grabbingValkyrs.push_back(unit); + } + + // No active valkyrs — reset bots still targeting an excluded valkyr + if (grabbingValkyrs.empty() || (boss && boss->HealthBelowPct(40))) + { + Unit* currentTarget = bot->GetVictim(); + if (currentTarget && IsLkValkyr(currentTarget) && boss) + { + bot->SetTarget(boss->GetGUID()); + context->GetValue("rti")->Set("skull"); + } + return false; + } + + if (botAI->IsMainTank(bot)) + return false; + + // Defile target: let HandleDefileMechanics() handle movement + // (perpendicular run). Don't override with Val'kyr chase. + auto const defileIt = IcecrownHelpers::defileCast.find(bot->GetInstanceId()); + if (defileIt != IcecrownHelpers::defileCast.end()) + { + auto const& defileInfo = defileIt->second; + if (!defileInfo.targetGuid.IsEmpty() && getMSTimeDiff(defileInfo.castTime, getMSTime()) <= 3000 && defileInfo.targetGuid == bot->GetGUID()) + return false; + } + + HandleValkyrMarking(grabbingValkyrs, diff); + HandleValkyrAssignment(grabbingValkyrs); + + return true; +} + +bool IccLichKingAddsAction::HandleValkyrMarking(std::vector const& grabbingValkyrs, + Difficulty diff) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector sorted = grabbingValkyrs; + std::sort(sorted.begin(), sorted.end(), + [](Unit* a, Unit* b) { return a->GetGUID() < b->GetGUID(); }); + + static constexpr std::array Icons = {7, 6, 0}; // Skull, Cross, Star + + // Heroic: clear stale markers for Val'kyrs no longer grabbing or at wrong Z + if (IsHeroicLk(diff)) + { + for (uint8 const iconIdx : Icons) + { + Unit* marked = botAI->GetUnit(group->GetTargetIcon(iconIdx)); + if (!marked || !IsLkValkyr(marked)) + continue; + + bool const stale = !marked->HasAura(SPELL_HARVEST_SOUL_VALKYR) || + std::abs(marked->GetPositionZ() - bot->GetPositionZ()) > 5.0f; + if (stale) + group->SetTargetIcon(iconIdx, bot->GetGUID(), ObjectGuid::Empty); + } + } + + // Clear icon slots beyond the current Val'kyr count + for (size_t i = sorted.size(); i < Icons.size(); ++i) + { + if (!group->GetTargetIcon(Icons[i]).IsEmpty()) + group->SetTargetIcon(Icons[i], bot->GetGUID(), ObjectGuid::Empty); + } + + // Assign an icon to each active Val'kyr. + // Skip skull if a Raging Spirit currently owns it. + for (size_t i = 0; i < sorted.size() && i < Icons.size(); ++i) + { + uint8 const iconIdx = Icons[i]; + + if (iconIdx == 7) + { + Unit* currentSkull = botAI->GetUnit(group->GetTargetIcon(7)); + if (currentSkull && currentSkull->IsAlive() && IsLkRagingSpirit(currentSkull->GetEntry())) + continue; + } + + Unit* marked = botAI->GetUnit(group->GetTargetIcon(iconIdx)); + if (!marked || marked != sorted[i]) + group->SetTargetIcon(iconIdx, bot->GetGUID(), sorted[i]->GetGUID()); + } + + return true; +} + +bool IccLichKingAddsAction::HandleValkyrAssignment(std::vector const& grabbingValkyrs) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (boss && boss->HealthBelowPct(40)) + return false; + + std::vector valid; + for (Unit* valkyr : grabbingValkyrs) + { + if (valkyr && valkyr->IsAlive() && valkyr->HasAura(SPELL_HARVEST_SOUL_VALKYR)) + valid.push_back(valkyr); + } + + if (valid.empty()) + return false; + + std::sort(valid.begin(), valid.end(), + [](Unit* a, Unit* b) { return a->GetGUID() < b->GetGUID(); }); + + // Build sorted list of non-main-tank members for deterministic assignment + std::vector assistMembers; + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && !botAI->IsMainTank(member)) + assistMembers.push_back(member); + } + + if (assistMembers.empty()) + return false; + + std::sort(assistMembers.begin(), assistMembers.end(), + [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); + + auto const it = std::find(assistMembers.begin(), assistMembers.end(), bot); + if (it == assistMembers.end()) + return false; + + size_t const myIndex = std::distance(assistMembers.begin(), it); + auto const groupSizes = CalculateBalancedGroupSizes(assistMembers.size(), valid.size()); + size_t const valkyrIndex = GetAssignedValkyrIndex(myIndex, groupSizes); + + if (valkyrIndex >= valid.size()) + return false; + + Unit* myValkyr = valid[valkyrIndex]; + context->GetValue("rti")->Set(GetRTIValueForValkyr(valkyrIndex)); + + Attack(myValkyr); + + Difficulty const diff = bot->GetRaidDifficulty(); + if (sPlayerbotAIConfig.EnableICCBuffs && IsHeroicLk(diff) && + !myValkyr->HasAura(SPELL_HAMMER_OF_JUSTICE)) + bot->AddAura(SPELL_HAMMER_OF_JUSTICE, myValkyr); + + ApplyCCToValkyr(myValkyr); + + return true; +} + +std::pair IccLichKingAddsAction::DefileAwareStep(float tx, float ty, + std::vector const& defiles, + Difficulty diff) +{ + float const px = bot->GetPositionX(); + float const py = bot->GetPositionY(); + float const ddx = tx - px; + float const ddy = ty - py; + float const fullLen = std::hypot(ddx, ddy); + if (fullLen < 0.01f) + return {tx, ty}; + + static constexpr float STEP_DISTANCE = 10.0f; + static constexpr float MARGIN = 2.0f; + static constexpr int SAMPLES = 5; + + auto PathSafe = [&](float ex, float ey) -> bool + { + for (int s = 1; s <= SAMPLES; ++s) + { + float const t = float(s) / float(SAMPLES); + float const sx = px + (ex - px) * t; + float const sy = py + (ey - py) * t; + for (Unit const* defile : defiles) + { + float const radius = GetDefileEffectiveRadius(defile, diff); + float const d = std::hypot(sx - defile->GetPositionX(), sy - defile->GetPositionY()); + if (d < radius + MARGIN) + return false; + } + } + return true; + }; + + float const stepLen = std::min(STEP_DISTANCE, fullLen); + float const baseAngle = std::atan2(ddy, ddx); + + for (int offset = 0; offset <= 8; ++offset) + { + for (int dir : {-1, 1}) + { + if (offset == 0 && dir > 0) + continue; + float const angle = baseAngle + dir * offset * float(M_PI) / 8.0f; + float const ex = px + stepLen * std::cos(angle); + float const ey = py + stepLen * std::sin(angle); + if (PathSafe(ex, ey)) + return {ex, ey}; + } + } + + return {tx, ty}; +} + +bool IccLichKingAddsAction::HandleVileSpiritMechanics() +{ + static constexpr float ARRIVE_TOLERANCE = 4.0f; + + // Defile target: let HandleDefileMechanics() handle movement + // (perpendicular run). Don't override with spirit chase or slot + // movement. + auto const defileIt = IcecrownHelpers::defileCast.find(bot->GetInstanceId()); + if (defileIt != IcecrownHelpers::defileCast.end()) + { + auto const& defileInfo = defileIt->second; + if (!defileInfo.targetGuid.IsEmpty() && getMSTimeDiff(defileInfo.castTime, getMSTime()) <= 3000 && defileInfo.targetGuid == bot->GetGUID()) + return false; + } + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + uint32 spiritCount = 0; + std::vector spirits; + std::vector defiles; + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (IsLkVileSpirit(unit)) + { + ++spiritCount; + spirits.push_back(unit); + } + else if (unit->GetEntry() == DEFILE_NPC_ID) + { + defiles.push_back(unit); + } + } + + // Shared raid-wide slot choice. All bots converge on the same position so + // they stay stacked. Reset when no spirits are alive. Keyed per-instance to + // avoid cross-instance pollution when multiple ICCs run simultaneously. + static std::map s_sharedSlotByInstance; + auto sharedSlotIt = s_sharedSlotByInstance.find(bot->GetInstanceId()); + if (sharedSlotIt == s_sharedSlotByInstance.end()) + sharedSlotIt = s_sharedSlotByInstance.emplace(bot->GetInstanceId(), -1).first; + int& sharedSlot = sharedSlotIt->second; + + if (spiritCount == 0) + { + sharedSlot = -1; + return false; + } + + Difficulty const diff = bot->GetRaidDifficulty(); + auto IsSlotSafeFromDefile = [&](Position const& slot) -> bool + { + static constexpr float SAFETY_MARGIN = 2.0f; + for (Unit const* defile : defiles) + { + float const radius = GetDefileEffectiveRadius(defile, diff); + float const d = + std::hypot(slot.GetPositionX() - defile->GetPositionX(), slot.GetPositionY() - defile->GetPositionY()); + if (d < radius + SAFETY_MARGIN) + return false; + } + return true; + }; + + static constexpr float SPIRIT_NEAR_SLOT = 15.0f; + auto IsSlotSafeFromSpirits = [&](Position const& slot) -> bool + { + for (Unit const* spirit : spirits) + { + float const d = + std::hypot(slot.GetPositionX() - spirit->GetPositionX(), slot.GetPositionY() - spirit->GetPositionY()); + if (d < SPIRIT_NEAR_SLOT) + return false; + } + return true; + }; + + Position const slots[3] = { + ICC_LK_VILE_SPIRIT1_POSITION, + ICC_LK_VILE_SPIRIT2_POSITION, + ICC_LK_VILE_SPIRIT3_POSITION, + }; + + // Spirits beyond this radius from bot = previous wave still alive, block move + static constexpr float OLD_SPIRIT_RADIUS = 20.0f; + // Priority order: pos1 (0), pos3 (2), pos2 (1) + static constexpr int SLOT_PRIORITY[3] = {0, 2, 1}; + + auto HasOldSpirits = [&]() -> bool + { + for (Unit const* spirit : spirits) + { + float const d = std::hypot(bot->GetPositionX() - spirit->GetPositionX(), + bot->GetPositionY() - spirit->GetPositionY()); + if (d > OLD_SPIRIT_RADIUS) + return true; + } + return false; + }; + + if (sharedSlot >= 0 && sharedSlot < 3) + { + bool const defileHit = !IsSlotSafeFromDefile(slots[sharedSlot]); + bool const spiritHit = !IsSlotSafeFromSpirits(slots[sharedSlot]); + + if (defileHit) + { + // Defile is fatal — move regardless of old spirits + sharedSlot = -1; + } + else if (spiritHit) + { + // New spirits at current slot; wait if previous-wave spirits still alive + if (HasOldSpirits()) + return false; + + sharedSlot = -1; + } + else if (sharedSlot == 1) + { + // On fallback slot (pos2); upgrade to pos1 or pos3 if now safe + for (int pri : SLOT_PRIORITY) + { + if (pri == 1) + break; + if (IsSlotSafeFromDefile(slots[pri]) && IsSlotSafeFromSpirits(slots[pri])) + { + sharedSlot = pri; + break; + } + } + } + } + + if (sharedSlot < 0) + { + for (int pri : SLOT_PRIORITY) + { + if (!IsSlotSafeFromDefile(slots[pri])) + continue; + if (!IsSlotSafeFromSpirits(slots[pri])) + continue; + sharedSlot = pri; + break; + } + + if (sharedSlot < 0) + return false; + } + + if (sharedSlot < 0 || sharedSlot >= 3) + return false; + + int const chosen = sharedSlot; + Position const& slotPos = slots[chosen]; + + // Assist tank: stacks with the raid at the chosen vile slot, but allowed + // a 40y leash to intercept the spirit nearest the slot. Keeps nitro boost + // for chase speed. + if (botAI->IsAssistTank(bot)) + { + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + if (!bot->HasAura(SPELL_PAIN_SUPPRESION)) + bot->AddAura(SPELL_PAIN_SUPPRESION, bot); + if (!bot->HasAura(SPELL_AGEIS_OF_DALARAN)) + bot->AddAura(SPELL_AGEIS_OF_DALARAN, bot); + + static constexpr float LEASH_RADIUS = 40.0f; + + float const anchorX = slotPos.GetPositionX(); + float const anchorY = slotPos.GetPositionY(); + + // Find spirit closest to the slot (not to bot) — that's the spirit + // most likely to reach the raid stack first. + Unit* chaseTarget = nullptr; + float chaseTargetDist = std::numeric_limits::max(); + for (Unit* spirit : spirits) + { + float const d = std::hypot(spirit->GetPositionX() - anchorX, spirit->GetPositionY() - anchorY); + if (d < chaseTargetDist) + { + chaseTargetDist = d; + chaseTarget = spirit; + } + } + + float ax = anchorX; + float ay = anchorY; + if (chaseTarget) + { + float tx = chaseTarget->GetPositionX(); + float ty = chaseTarget->GetPositionY(); + float const dxc = tx - anchorX; + float const dyc = ty - anchorY; + float const lenc = std::hypot(dxc, dyc); + if (lenc > LEASH_RADIUS) + { + // Spirit too far from slot — clamp chase point to leash radius + tx = anchorX + dxc * LEASH_RADIUS / lenc; + ty = anchorY + dyc * LEASH_RADIUS / lenc; + } + ax = tx; + ay = ty; + } + + float az = slotPos.GetPositionZ(); + + float const distToAnchor = std::hypot(bot->GetPositionX() - ax, bot->GetPositionY() - ay); + if (distToAnchor <= ARRIVE_TOLERANCE) + return false; + + auto const [sx, sy] = DefileAwareStep(ax, ay, defiles, diff); + float sz = az; + bot->UpdateAllowedPositionZ(sx, sy, sz); + + return MoveTo(bot->GetMapId(), sx, sy, sz, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, + false); + } + + // Hunter trap duty: only the lowest-GUID alive bot hunter handles it so + // multiple hunters don't pile up at center. Real-player hunters are skipped + // — they may not know the strategy, so a bot owns the role. + bool isDesignatedHunter = false; + if (bot->getClass() == CLASS_HUNTER) + { + Group* hunterGroup = bot->GetGroup(); + if (hunterGroup) + { + ObjectGuid bestGuid; + for (GroupReference* ref = hunterGroup->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + if (member->getClass() != CLASS_HUNTER) + continue; + if (!GET_PLAYERBOT_AI(member)) + continue; + + if (bestGuid.IsEmpty() || member->GetGUID() < bestGuid) + bestGuid = member->GetGUID(); + } + isDesignatedHunter = (!bestGuid.IsEmpty() && bot->GetGUID() == bestGuid); + } + } + + // Skip the spirit-flee logic entirely if boss is casting Harvest Soul(s). + // The harvested player must stay alive — flee movement breaks range for + // healers and gets the soul victim killed, buffing LK and wiping raid. + Unit* Boss = AI_VALUE2(Unit*, "find target", "the lich king"); + bool const bossCastingHarvest = Boss && Boss->HasUnitState(UNIT_STATE_CASTING) && + (Boss->FindCurrentSpellBySpellId(SPELL_HARVEST_SOUL_LK) || + Boss->FindCurrentSpellBySpellId(SPELL_HARVEST_SOULS_LK_25) || + Boss->FindCurrentSpellBySpellId(SPELL_HARVEST_SOULS_LK_H1) || + Boss->FindCurrentSpellBySpellId(SPELL_HARVEST_SOULS_LK_H2) || + Boss->FindCurrentSpellBySpellId(SPELL_HARVEST_SOULS_LK_H3)); + + if (!botAI->IsTank(bot) && !bossCastingHarvest) + { + // Flee to MT if a spirit is targeting this bot OR is within FLEE_RANGE. + // Either condition is enough — proximity catches spirits that haven't + // committed a target yet, targeting catches faraway chasers. + static constexpr float FLEE_RANGE = 15.0f; + + Unit* chaser = nullptr; + for (Unit* spirit : spirits) + { + bool const isTargetingBot = spirit->GetVictim() && + spirit->GetVictim()->GetGUID() == bot->GetGUID(); + bool const isClose = bot->GetDistance2d(spirit) < FLEE_RANGE; + if (isTargetingBot || isClose) + { + chaser = spirit; + break; + } + } + + if (chaser) + { + // Flee toward main tank in 10y increments, leash ignored + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + if (mainTank && mainTank->IsAlive()) + { + auto const [fx, fy] = DefileAwareStep(mainTank->GetPositionX(), mainTank->GetPositionY(), defiles, diff); + float fz = slotPos.GetPositionZ(); + bot->UpdateAllowedPositionZ(fx, fy, fz); + return MoveTo(bot->GetMapId(), fx, fy, fz, false, false, false, true, MovementPriority::MOVEMENT_FORCED, + true, false); + } + // No MT — fall through to leash logic + } + } + + // Designated hunter: anchored at platform center with 30y leash. Drops + // Frost Trap to slow spirits. If a spirit is targeting the hunter, flee + // toward main tank in 10y steps (leash ignored, trap not dropped). + if (isDesignatedHunter) + { + static constexpr float HUNTER_CENTER_X = 503.62f; + static constexpr float HUNTER_CENTER_Y = -2124.73f; + static constexpr float HUNTER_LEASH = 4.0f; + static constexpr float HUNTER_DROP_TOLERANCE = 5.0f; + + float const dxh = bot->GetPositionX() - HUNTER_CENTER_X; + float const dyh = bot->GetPositionY() - HUNTER_CENTER_Y; + float const distToCenter = std::hypot(dxh, dyh); + + if (distToCenter > HUNTER_LEASH) + { + auto const [hx, hy] = DefileAwareStep(HUNTER_CENTER_X, HUNTER_CENTER_Y, defiles, diff); + float hz = slotPos.GetPositionZ(); + bot->UpdateAllowedPositionZ(hx, hy, hz); + return MoveTo(bot->GetMapId(), hx, hy, hz, false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + if (distToCenter <= HUNTER_DROP_TOLERANCE && !bot->HasSpellCooldown(SPELL_FROST_TRAP1)) + { + if (botAI->CastSpell("frost trap", bot)) + return true; + } + + return false; + } + + // Healers may stay up to 20y from the slot so they can heal the assist tank + float const arriveTol = botAI->IsHeal(bot) ? 20.0f : ARRIVE_TOLERANCE; + + float const tx = slotPos.GetPositionX(); + float const ty = slotPos.GetPositionY(); + float const distToSlot = std::hypot(bot->GetPositionX() - tx, bot->GetPositionY() - ty); + if (distToSlot <= arriveTol) + return false; + + auto const [sx, sy] = DefileAwareStep(tx, ty, defiles, diff); + float sz = slotPos.GetPositionZ(); + bot->UpdateAllowedPositionZ(sx, sy, sz); + + return MoveTo(bot->GetMapId(), sx, sy, sz, false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); +} + +bool IccLichKingAddsAction::HandleIceSphereMechanics() +{ + if (!botAI->IsRangedDps(bot)) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + static constexpr uint8 SPHERE_ICON = 1; // Diamond + + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + Unit* currentMark = botAI->GetUnit(group->GetTargetIcon(SPHERE_ICON)); + bool const sphereMarked = currentMark && currentMark->IsAlive() && + IsIceSphere(currentMark->GetEntry()); + + if (!sphereMarked) + { + Unit* nearestSphere = nullptr; + float nearestDist = std::numeric_limits::max(); + + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || !IsIceSphere(unit->GetEntry())) + continue; + + float const dist = bot->GetDistance(unit); + if (dist < nearestDist) + { + nearestDist = dist; + nearestSphere = unit; + } + } + + if (nearestSphere) + group->SetTargetIcon(SPHERE_ICON, bot->GetGUID(), nearestSphere->GetGUID()); + else + { + if (!group->GetTargetIcon(SPHERE_ICON).IsEmpty()) + group->SetTargetIcon(SPHERE_ICON, bot->GetGUID(), ObjectGuid::Empty); + return false; + } + + currentMark = botAI->GetUnit(group->GetTargetIcon(SPHERE_ICON)); + } + + if (currentMark && currentMark->IsAlive()) + { + bot->SetTarget(currentMark->GetGUID()); + bot->SetFacingToObject(currentMark); + Attack(currentMark); + return true; + } + + return false; +} + +bool IccLichKingAddsAction::IsValkyr(Unit* unit) +{ + return IsLkValkyr(unit); +} + +std::vector IccLichKingAddsAction::CalculateBalancedGroupSizes(size_t totalAssist, + size_t numValkyrs) +{ + std::vector groupSizes(numValkyrs, 0); + if (numValkyrs == 0) + return groupSizes; + + size_t const baseSize = totalAssist / numValkyrs; + size_t const remainder = totalAssist % numValkyrs; + + for (size_t i = 0; i < numValkyrs; ++i) + { + groupSizes[i] = baseSize; + if (i < remainder) + ++groupSizes[i]; + } + + return groupSizes; +} + +size_t IccLichKingAddsAction::GetAssignedValkyrIndex(size_t assistIndex, + std::vector const& groupSizes) +{ + size_t cursor = 0; + for (size_t valkyrIndex = 0; valkyrIndex < groupSizes.size(); ++valkyrIndex) + { + if (assistIndex < cursor + groupSizes[valkyrIndex]) + return valkyrIndex; + cursor += groupSizes[valkyrIndex]; + } + + return 0; // fallback +} + +std::string IccLichKingAddsAction::GetRTIValueForValkyr(size_t valkyrIndex) +{ + switch (valkyrIndex) + { + case 0: + return "skull"; + case 1: + return "cross"; + case 2: + return "star"; + default: + return "skull"; + } +} + +bool IccLichKingAddsAction::ApplyCCToValkyr(Unit* valkyr) +{ + switch (bot->getClass()) + { + case CLASS_MAGE: + if (!botAI->HasAura("Deep Freeze", valkyr) && botAI->CanCastSpell("Deep Freeze", valkyr)) + return botAI->CastSpell("Deep Freeze", valkyr); + if (!botAI->HasAura("Frost Nova", valkyr) && botAI->CanCastSpell("Frost Nova", valkyr)) + return botAI->CastSpell("Frost Nova", valkyr); + if (!botAI->HasAura("Cone of Cold", valkyr) && botAI->CanCastSpell("Cone of Cold", valkyr)) + return botAI->CastSpell("Cone of Cold", valkyr); + if (!botAI->HasAura("Frostbolt", valkyr) && botAI->CanCastSpell("Frostbolt", valkyr)) + return botAI->CastSpell("Frostbolt", valkyr); + if (!botAI->HasAura("Slow", valkyr) && botAI->CanCastSpell("Slow", valkyr)) + return botAI->CastSpell("Slow", valkyr); + break; + case CLASS_DRUID: + if (!botAI->HasAura("Bash", valkyr) && botAI->CanCastSpell("Bash", valkyr)) + return botAI->CastSpell("Bash", valkyr); + if (!botAI->HasAura("Maim", valkyr) && botAI->CanCastSpell("Maim", valkyr)) + return botAI->CastSpell("Maim", valkyr); + break; + case CLASS_PALADIN: + if (!botAI->HasAura("Hammer of Justice", valkyr) && botAI->CanCastSpell("Hammer of Justice", valkyr)) + return botAI->CastSpell("Hammer of Justice", valkyr); + break; + case CLASS_WARRIOR: + if (!botAI->HasAura("Concussion Blow", valkyr) && botAI->CanCastSpell("Concussion Blow", valkyr)) + return botAI->CastSpell("Concussion Blow", valkyr); + if (!botAI->HasAura("Shockwave", valkyr) && botAI->CanCastSpell("Shockwave", valkyr)) + return botAI->CastSpell("Shockwave", valkyr); + if (!botAI->HasAura("Intercept", valkyr) && botAI->CanCastSpell("Intercept", valkyr)) + return botAI->CastSpell("Intercept", valkyr); + if (!botAI->HasAura("Charge", valkyr) && botAI->CanCastSpell("Charge", valkyr)) + return botAI->CastSpell("Charge", valkyr); + if (!botAI->HasAura("Hamstring", valkyr) && botAI->CanCastSpell("Hamstring", valkyr)) + return botAI->CastSpell("Hamstring", valkyr); + if (!botAI->HasAura("Piercing Howl", valkyr) && botAI->CanCastSpell("Piercing Howl", valkyr)) + return botAI->CastSpell("Piercing Howl", valkyr); + break; + case CLASS_HUNTER: + if (!botAI->HasAura("Intimidation", valkyr) && botAI->CanCastSpell("Intimidation", valkyr)) + return botAI->CastSpell("Intimidation", valkyr); + if (!botAI->HasAura("Concussive Shot", valkyr) && botAI->CanCastSpell("Concussive Shot", valkyr)) + return botAI->CastSpell("Concussive Shot", valkyr); + if (!botAI->HasAura("Wing Clip", valkyr) && botAI->CanCastSpell("Wing Clip", valkyr)) + return botAI->CastSpell("Wing Clip", valkyr); + if (!botAI->HasAura("Freezing Trap", valkyr) && botAI->CanCastSpell("Freezing Trap", valkyr)) + return botAI->CastSpell("Freezing Trap", valkyr); + break; + case CLASS_ROGUE: + if (!botAI->HasAura("Kidney Shot", valkyr) && botAI->CanCastSpell("Kidney Shot", valkyr)) + return botAI->CastSpell("Kidney Shot", valkyr); + if (!botAI->HasAura("Gouge", valkyr) && botAI->CanCastSpell("Gouge", valkyr)) + return botAI->CastSpell("Gouge", valkyr); + if (!botAI->HasAura("Blind", valkyr) && botAI->CanCastSpell("Blind", valkyr)) + return botAI->CastSpell("Blind", valkyr); + if (!botAI->HasAura("Deadly Throw", valkyr) && botAI->CanCastSpell("Deadly Throw", valkyr)) + return botAI->CastSpell("Deadly Throw", valkyr); + break; + case CLASS_SHAMAN: + if (!botAI->HasAura("Thunderstorm", valkyr) && botAI->CanCastSpell("Thunderstorm", valkyr)) + return botAI->CastSpell("Thunderstorm", valkyr); + if (!botAI->HasAura("Frost Shock", valkyr) && botAI->CanCastSpell("Frost Shock", valkyr)) + return botAI->CastSpell("Frost Shock", valkyr); + if (!botAI->HasAura("Earthbind Totem", valkyr) && botAI->CanCastSpell("Earthbind Totem", valkyr)) + return botAI->CastSpell("Earthbind Totem", valkyr); + break; + case CLASS_DEATH_KNIGHT: + if (!botAI->HasAura("Hungering Cold", valkyr) && botAI->CanCastSpell("Hungering Cold", valkyr)) + return botAI->CastSpell("Hungering Cold", valkyr); + if (!botAI->HasAura("Gnaw", valkyr) && botAI->CanCastSpell("Gnaw", valkyr)) + return botAI->CastSpell("Gnaw", valkyr); + if (!botAI->HasAura("Chains of Ice", valkyr) && botAI->CanCastSpell("Chains of Ice", valkyr)) + return botAI->CastSpell("Chains of Ice", valkyr); + if (!botAI->HasAura("Desecration", valkyr) && botAI->CanCastSpell("Desecration", valkyr)) + return botAI->CastSpell("Desecration", valkyr); + break; + case CLASS_PRIEST: + if (!botAI->HasAura("Psychic Horror", valkyr) && botAI->CanCastSpell("Psychic Horror", valkyr)) + return botAI->CastSpell("Psychic Horror", valkyr); + if (!botAI->HasAura("Mind Flay", valkyr) && botAI->CanCastSpell("Mind Flay", valkyr)) + return botAI->CastSpell("Mind Flay", valkyr); + break; + case CLASS_WARLOCK: + if (!botAI->HasAura("Shadowfury", valkyr) && botAI->CanCastSpell("Shadowfury", valkyr)) + return botAI->CastSpell("Shadowfury", valkyr); + if (!botAI->HasAura("Death Coil", valkyr) && botAI->CanCastSpell("Death Coil", valkyr)) + return botAI->CastSpell("Death Coil", valkyr); + if (!botAI->HasAura("Curse of Exhaustion", valkyr) && botAI->CanCastSpell("Curse of Exhaustion", valkyr)) + return botAI->CastSpell("Curse of Exhaustion", valkyr); + break; + default: + break; + } + + return false; +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_LM.cpp b/src/Ai/Raid/ICC/Action/ICCActions_LM.cpp new file mode 100644 index 00000000000..af1dff47d81 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_LM.cpp @@ -0,0 +1,601 @@ +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "Multiplier.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "ICCActions.h" +#include "ICCTriggers.h" +#include "RtiValue.h" +#include "Vehicle.h" + +// Lord Marrowgar + +// Group iteration filter for same-instance, alive, in-world members. +static bool IsValidLmMember(Player* member, Player* bot) +{ + if (!member || !member->IsInWorld() || !member->IsAlive()) + return false; + if (member->GetMapId() != bot->GetMapId()) + return false; + if (member->GetInstanceId() != bot->GetInstanceId()) + return false; + if (member->HasAura(SPELL_LM_IMPALED)) + return false; + return true; +} + +// Up to two lowest-GUID ranged bots in same instance, hunter-priority. +static std::vector PickBoneStormRangedTargets(Player* bot, PlayerbotAI* botAI) +{ + std::vector result; + + Group* group = bot->GetGroup(); + if (!group) + return result; + + std::vector ranged; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!IsValidLmMember(member, bot)) + continue; + if (botAI->IsTank(member)) + continue; + if (!botAI->IsRanged(member)) + continue; + + ranged.push_back(member); + } + + if (ranged.empty()) + return result; + + std::sort(ranged.begin(), ranged.end(), + [](Player const* a, Player const* b) { return a->GetGUID() < b->GetGUID(); }); + + for (Player* p : ranged) + { + if (p->getClass() != CLASS_HUNTER) + continue; + result.push_back(p); + if (result.size() == 2) + return result; + } + + for (Player* p : ranged) + { + if (p->getClass() == CLASS_HUNTER) + continue; + result.push_back(p); + if (result.size() == 2) + return result; + } + + return result; +} + +// True if any coldflame line sits within 10f of the anchor position. +// Used to widen the tank's "stay-put" tolerance so AvoidAoe can move them +// off the line without IccLmTankPositionAction dragging them back. +static bool ColdflameNearAnchor(Player* bot, Position const& anchor, float leash) +{ + std::list coldflames; + bot->GetCreatureListWithEntryInGrid(coldflames, NPC_COLDFLAME, 200.0f); + for (Creature* c : coldflames) + if (c->GetExactDist2d(anchor.GetPositionX(), anchor.GetPositionY()) < leash) + return true; + return false; +} + +bool IccLmTankPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar"); + if (!boss) + return false; + + bool const isBossInBoneStorm = botAI->GetAura("Bone Storm", boss) != nullptr; + float const maxDistanceThreshold = 3.0f; + + if (isBossInBoneStorm) + { + std::vector const rangedTargets = PickBoneStormRangedTargets(bot, botAI); + if (std::find(rangedTargets.begin(), rangedTargets.end(), bot) != rangedTargets.end()) + { + float const anchorDist = bot->GetExactDist2d(ICC_LM_BONE_STORM_AT_POSITION.GetPositionX(), + ICC_LM_BONE_STORM_AT_POSITION.GetPositionY()); + + float const bossDist = bot->GetExactDist2d(boss); + float const proximityTrigger = 20.0f; + float const leash = 10.0f; + + // Boss too close or standing in coldflame: reposition within leash from anchor + bool const bossNear = bossDist < proximityTrigger; + bool const inColdflame = [&]() + { + std::list coldflames; + bot->GetCreatureListWithEntryInGrid(coldflames, NPC_COLDFLAME, 4.0f); + return !coldflames.empty(); + }(); + + if (bossNear || inColdflame) + { + // Try eight candidate offsets from the anchor at the leash radius; + // pick first one that is far from boss and clear of coldflames. + float bestX = bot->GetPositionX(); + float bestY = bot->GetPositionY(); + float bestScore = -1.0f; + bool found = false; + + for (int i = 0; i < 8; ++i) + { + float const angle = (float)i * (float)M_PI / 4.0f; + float const cx = ICC_LM_BONE_STORM_AT_POSITION.GetPositionX() + std::cos(angle) * leash; + float const cy = ICC_LM_BONE_STORM_AT_POSITION.GetPositionY() + std::sin(angle) * leash; + + float const dx = cx - boss->GetPositionX(); + float const dy = cy - boss->GetPositionY(); + float const distToBoss = std::sqrt(dx * dx + dy * dy); + + std::list coldflames; + bot->GetCreatureListWithEntryInGrid(coldflames, NPC_COLDFLAME, 200.0f); + bool hitColdflame = false; + for (Creature* c : coldflames) + { + if (c->GetExactDist2d(cx, cy) < 4.0f) + { + hitColdflame = true; + break; + } + } + if (hitColdflame) + continue; + + if (distToBoss > bestScore) + { + bestScore = distToBoss; + bestX = cx; + bestY = cy; + found = true; + } + } + + if (found) + return MoveTo(bot->GetMapId(), bestX, bestY, ICC_LM_BONE_STORM_AT_POSITION.GetPositionZ(), false, + false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + + if (anchorDist > maxDistanceThreshold) + return MoveTo(bot->GetMapId(), ICC_LM_BONE_STORM_AT_POSITION.GetPositionX(), + ICC_LM_BONE_STORM_AT_POSITION.GetPositionY(), + ICC_LM_BONE_STORM_AT_POSITION.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + return true; + } + + float const tankLeash = + ColdflameNearAnchor(bot, ICC_LM_TANK_POSITION, 10.0f) ? 10.0f : maxDistanceThreshold; + + if (botAI->IsMainTank(bot)) + { + float const distance = + bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY()); + if (distance > tankLeash) + return MoveTowardPosition(ICC_LM_TANK_POSITION, maxDistanceThreshold); + return false; + } + + if (botAI->IsAssistTank(bot)) + { + float const distance = + bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY()); + if (distance > tankLeash) + return MoveTo(bot->GetMapId(), ICC_LM_TANK_POSITION.GetPositionX(), + ICC_LM_TANK_POSITION.GetPositionY(), ICC_LM_TANK_POSITION.GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT); + return false; + } + + // Non-tanks: if too far from mid position, move toward it + float const distance = + bot->GetExactDist2d(ICC_LM_MID_POSITION.GetPositionX(), ICC_LM_MID_POSITION.GetPositionY()); + if (distance > 35.0f) + return MoveTowardPosition(ICC_LM_MID_POSITION, 15.0f); + + return false; + } + + float const tankLeash = + ColdflameNearAnchor(bot, ICC_LM_TANK_POSITION, 10.0f) ? 10.0f : maxDistanceThreshold; + + if (botAI->HasAggro(boss) && botAI->IsMainTank(bot) && boss->GetVictim() == bot) + { + float const distance = + bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY()); + + if (distance > tankLeash) + return MoveTowardPosition(ICC_LM_TANK_POSITION, maxDistanceThreshold); + } + + if (botAI->IsAssistTank(bot)) + { + float const distance = + bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY()); + + if (distance > tankLeash) + return MoveTo(bot->GetMapId(), ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY(), + ICC_LM_TANK_POSITION.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + + if (distance < maxDistanceThreshold) + { + bot->SetFacingToObject(boss); + return true; + } + } + + return false; +} + +bool IccLmTankPositionAction::MoveTowardPosition(Position const& position, float incrementSize) +{ + float const dirX = position.GetPositionX() - bot->GetPositionX(); + float const dirY = position.GetPositionY() - bot->GetPositionY(); + float const length = std::sqrt(dirX * dirX + dirY * dirY); + + float const normalizedDirX = dirX / length; + float const normalizedDirY = dirY / length; + + float const moveX = bot->GetPositionX() + normalizedDirX * incrementSize; + float const moveY = bot->GetPositionY() + normalizedDirY * incrementSize; + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); +} + +bool IccSpikeAction::Execute(Event /*event*/) +{ + if (bot->HasAura(SPELL_LM_IMPALED)) + return false; + + Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar"); + if (!boss) + return false; + + bool const isBossInBoneStorm = botAI->GetAura("Bone Storm", boss) != nullptr; + std::vector const spikes = FindAliveSpikes(); + + if (!spikes.empty()) + { + HandleSpikeMarking(spikes, boss); + return HandleSpikeAssignment(spikes, boss); + } + + // No spikes alive -- skull on boss, clear cross, all bots on skull + HandleNoSpikesMarking(boss); + + // Melee non-tanks in front of boss should reposition + if (boss->isInFront(bot) && !botAI->IsTank(bot) && !isBossInBoneStorm) + { + Position const safePosition = {-390.6757f, 2230.5283f, 0.0f}; + float const distance = bot->GetExactDist2d(safePosition.GetPositionX(), safePosition.GetPositionY()); + if (distance > 3.0f) + return MoveTowardPosition(safePosition, 3.0f); + } + + return false; +} + +std::vector IccSpikeAction::FindAliveSpikes() +{ + // All difficulty variants — AzerothCore spawns a different entry per difficulty mode. + // Bonespike NPCs have UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_IMMUNE_TO_PC so they + // never appear in "possible targets no los". Use a direct grid search instead. + static uint32 const spikeEntries[] = { + NPC_SPIKE1, NPC_SPIKE1_10H, NPC_SPIKE1_25N, NPC_SPIKE1_25H, + NPC_SPIKE2, NPC_SPIKE2_10H, NPC_SPIKE2_25N, NPC_SPIKE2_25H, + NPC_SPIKE3, NPC_SPIKE3_10H, NPC_SPIKE3_25N, NPC_SPIKE3_25H + }; + + std::vector spikes; + for (uint32 const entry : spikeEntries) + { + std::list found; + bot->GetCreatureListWithEntryInGrid(found, entry, 200.0f); + for (Creature* c : found) + { + if (c && c->IsAlive()) + spikes.push_back(c); + } + } + + std::sort(spikes.begin(), spikes.end(), [](Unit const* a, Unit const* b) { return a->GetGUID() < b->GetGUID(); }); + return spikes; +} + +bool IccSpikeAction::HandleSpikeMarking(std::vector const& spikes, Unit* boss) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + static uint8 const Icons[] = {7, 6, 0}; // Skull, Cross, Star + + std::vector aliveSpikeGuids; + aliveSpikeGuids.reserve(spikes.size()); + for (Unit* spike : spikes) + aliveSpikeGuids.push_back(spike->GetGUID()); + + for (uint8 const iconIdx : Icons) + { + ObjectGuid const iconGuid = group->GetTargetIcon(iconIdx); + if (iconGuid.IsEmpty()) + continue; + if (std::find(aliveSpikeGuids.begin(), aliveSpikeGuids.end(), iconGuid) != aliveSpikeGuids.end()) + continue; + + Unit* marked = botAI->GetUnit(iconGuid); + if (marked && !marked->IsAlive()) + group->SetTargetIcon(iconIdx, bot->GetGUID(), ObjectGuid::Empty); + } + + // Check if the only spike left is a tank spike + Player* firstSpikeVictim = spikes.size() == 1 ? GetSpikeVictim(spikes[0]) : nullptr; + bool const onlyTankSpike = firstSpikeVictim && botAI->IsTank(firstSpikeVictim); + + if (onlyTankSpike) + { + // Skull on spike, Cross on boss + if (group->GetTargetIcon(7) != spikes[0]->GetGUID()) + group->SetTargetIcon(7, bot->GetGUID(), spikes[0]->GetGUID()); + + if (group->GetTargetIcon(6) != boss->GetGUID()) + group->SetTargetIcon(6, bot->GetGUID(), boss->GetGUID()); + + if (!group->GetTargetIcon(0).IsEmpty()) + group->SetTargetIcon(0, bot->GetGUID(), ObjectGuid::Empty); + + return true; + } + + // Clear icon slots beyond the current spike count + for (size_t i = spikes.size(); i < sizeof(Icons); ++i) + { + if (!group->GetTargetIcon(Icons[i]).IsEmpty()) + group->SetTargetIcon(Icons[i], bot->GetGUID(), ObjectGuid::Empty); + } + + // Assign Skull/Cross/Star to each alive spike + for (size_t i = 0; i < spikes.size() && i < sizeof(Icons); ++i) + { + uint8 const iconIdx = Icons[i]; + if (group->GetTargetIcon(iconIdx) != spikes[i]->GetGUID()) + group->SetTargetIcon(iconIdx, bot->GetGUID(), spikes[i]->GetGUID()); + } + + return true; +} + +bool IccSpikeAction::HandleNoSpikesMarking(Unit* boss) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + // Clear cross and star + for (uint8 const iconIdx : {uint8(6), uint8(0)}) + { + if (!group->GetTargetIcon(iconIdx).IsEmpty()) + group->SetTargetIcon(iconIdx, bot->GetGUID(), ObjectGuid::Empty); + } + + // Skull on boss + if (group->GetTargetIcon(7) != boss->GetGUID()) + group->SetTargetIcon(7, bot->GetGUID(), boss->GetGUID()); + + // Per-bot context value -- every bot needs this for its own ChooseTarget. + context->GetValue("rti")->Set("skull"); + return true; +} + +bool IccSpikeAction::HandleSpikeAssignment(std::vector const& spikes, Unit* boss) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + bool const isMelee = botAI->IsMelee(bot) && !botAI->IsTank(bot); + bool const isAssistTank = botAI->IsAssistTank(bot); + + auto isTankSpike = [&](Unit* spike) -> bool + { + Player* victim = GetSpikeVictim(spike); + return victim && botAI->IsTank(victim); + }; + + // Assist tank: only attack tank spike, ignore all others + if (isAssistTank) + { + for (Unit* spike : spikes) + { + if (isTankSpike(spike)) + { + Attack(spike); + return false; + } + } + return false; + } + + // Only tank spike left -- ranged go skull (spike), melee go cross (boss) + bool const onlyTankSpike = spikes.size() == 1 && isTankSpike(spikes[0]); + if (onlyTankSpike) + { + if (isMelee) + { + context->GetValue("rti")->Set("cross"); + Attack(boss); + } + else + { + context->GetValue("rti")->Set("skull"); + Attack(spikes[0]); + } + return false; + } + + // Melee DPS: pick closest safe spike within 20y, never tank spikes + if (isMelee) + { + Unit* bestSpike = nullptr; + float bestDist = 20.0f; + for (Unit* spike : spikes) + { + if (isTankSpike(spike)) + continue; + + if (boss->isInFront(spike, 7.0f)) + continue; + + if (IsSpikeInColdFlame(spike)) + continue; + + float const dist = bot->GetExactDist2d(spike); + if (dist < bestDist) + { + bestDist = dist; + bestSpike = spike; + } + } + + if (bestSpike) + Attack(bestSpike); + + return false; + } + + // Ranged / healers: balanced assignment across all spikes + std::vector rangedMembers; + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || member->HasAura(SPELL_LM_IMPALED)) + continue; + + if (botAI->IsMainTank(member) || botAI->IsAssistTank(member)) + continue; + + if (botAI->IsMelee(member) && !botAI->IsTank(member)) + continue; + + rangedMembers.push_back(member); + } + + if (rangedMembers.empty()) + return false; + + std::sort(rangedMembers.begin(), rangedMembers.end(), + [](Player const* a, Player const* b) { return a->GetGUID() < b->GetGUID(); }); + + auto const it = std::find(rangedMembers.begin(), rangedMembers.end(), bot); + if (it == rangedMembers.end()) + return false; + + size_t const myIndex = std::distance(rangedMembers.begin(), it); + std::vector const groupSizes = CalculateBalancedGroupSizes(rangedMembers.size(), spikes.size()); + size_t const spikeIndex = GetAssignedSpikeIndex(myIndex, groupSizes); + + if (spikeIndex >= spikes.size()) + return false; + + Unit* mySpike = spikes[spikeIndex]; + context->GetValue("rti")->Set(GetRTIValueForSpike(spikeIndex)); + + Attack(mySpike); + return false; +} + +bool IccSpikeAction::MoveTowardPosition(Position const& position, float incrementSize) +{ + float const dirX = position.GetPositionX() - bot->GetPositionX(); + float const dirY = position.GetPositionY() - bot->GetPositionY(); + float const length = std::sqrt(dirX * dirX + dirY * dirY); + + float const normalizedDirX = dirX / length; + float const normalizedDirY = dirY / length; + + float const moveX = bot->GetPositionX() + normalizedDirX * incrementSize; + float const moveY = bot->GetPositionY() + normalizedDirY * incrementSize; + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); +} + +std::vector IccSpikeAction::CalculateBalancedGroupSizes(size_t totalMembers, size_t numSpikes) +{ + std::vector groupSizes(numSpikes, 0); + if (numSpikes == 0) + return groupSizes; + + size_t const baseSize = totalMembers / numSpikes; + size_t const remainder = totalMembers % numSpikes; + + for (size_t i = 0; i < numSpikes; ++i) + { + groupSizes[i] = baseSize; + if (i < remainder) + ++groupSizes[i]; + } + + return groupSizes; +} + +size_t IccSpikeAction::GetAssignedSpikeIndex(size_t memberIndex, std::vector const& groupSizes) +{ + size_t cursor = 0; + for (size_t spikeIndex = 0; spikeIndex < groupSizes.size(); ++spikeIndex) + { + if (memberIndex < cursor + groupSizes[spikeIndex]) + return spikeIndex; + cursor += groupSizes[spikeIndex]; + } + + return 0; +} + +std::string IccSpikeAction::GetRTIValueForSpike(size_t spikeIndex) +{ + switch (spikeIndex) + { + case 0: + return "skull"; + case 1: + return "cross"; + case 2: + return "star"; + default: + return "skull"; + } +} + +Player* IccSpikeAction::GetSpikeVictim(Unit* spike) +{ + // Spike holds player via vehicle; GetVictim() is unreliable (NPC not in combat) + if (Vehicle* veh = spike->GetVehicleKit()) + { + for (auto const& [seatId, seatInfo] : veh->Seats) + { + if (Unit* passenger = ObjectAccessor::GetUnit(*spike, seatInfo.Passenger.Guid)) + return passenger->ToPlayer(); + } + } + return nullptr; +} + +bool IccSpikeAction::IsSpikeInColdFlame(Unit* spike) +{ + float const checkRadius = 6.0f; + std::list coldflames; + spike->GetCreatureListWithEntryInGrid(coldflames, NPC_COLDFLAME, checkRadius); + return !coldflames.empty(); +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_PP.cpp b/src/Ai/Raid/ICC/Action/ICCActions_PP.cpp new file mode 100644 index 00000000000..f344248474a --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_PP.cpp @@ -0,0 +1,1733 @@ +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "Multiplier.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "ICCActions.h" +#include "ICCTriggers.h" +#include "ICCScripts.h" +#include "RtiValue.h" +#include "Vehicle.h" +#include +#include +#include +#include + +namespace +{ + // Per-bot last flee direction during Gaseous Bloat. Used to prevent + // backtracking — once a bot commits to a direction, candidate angles + // pointing backward (negative dot product) are rejected so the cloud + // can't trap it in a back-and-forth loop. Cleared when the aura drops. + struct BloatDir { float x; float y; }; + std::unordered_map g_bloatLastDir; +} + +// Professor Putricide +bool IccPutricideMutatedPlagueAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (!boss) + return false; + + if (!botAI->IsTank(bot)) + return false; + + if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + if (bot->GetTarget()) + bot->SetTarget(ObjectGuid::Empty); + return false; + } + + auto CastClassTaunt = [&](Unit* target) -> bool + { + if (!target || !target->IsAlive()) + return false; + + switch (bot->getClass()) + { + case CLASS_PALADIN: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + } + case CLASS_DEATH_KNIGHT: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + } + case CLASS_DRUID: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + } + case CLASS_WARRIOR: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + } + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; + }; + + auto GetPlagueStacks = [&](Unit* unit) -> uint32 + { + if (!unit) + return 0; + + Aura* a = botAI->GetAura("mutated plague", unit, false, true); + return a ? a->GetStackAmount() : 0; + }; + + uint32 const myStacks = GetPlagueStacks(bot); + + bool shouldTaunt = false; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || member == bot || !member->IsAlive() || !member->IsInWorld()) + continue; + + if (!PlayerbotAI::IsTank(member)) + continue; + + if (myStacks < GetPlagueStacks(member)) + { + shouldTaunt = true; + break; + } + } + } + + if (shouldTaunt && boss->GetVictim() != bot) + CastClassTaunt(boss); + + return false; +} + +bool IccPutricideGrowingOozePuddleAction::Execute(Event /*event*/) +{ + if (botAI->IsMainTank(bot) && + bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + if (bot->GetTarget()) + bot->SetTarget(ObjectGuid::Empty); + if (Unit* master = botAI->GetMaster()) + Follow(master); + return true; + } + + // Phase 3: only MT avoids hazards. Non-MT bots stack on MT blindly so + // they don't scatter when a puddle drops on the stack. + if (!botAI->IsMainTank(bot)) + { + Unit* bossP3 = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (bossP3 && bossP3->HealthBelowPct(35)) + return false; + } + + // Main tank rotation kite: when one or more active Growing Ooze Puddles + // sit close to the boss, walk the boss to a position safe from ALL of + // them. Tank picks an angle around the boss whose forward arc clears + // every nearby puddle. Boss turns to face the tank, rotating its frontal + // cone away from the puddle field. Stack bots line up behind the boss. + // Phase 3: skip kite (MT idles near boss). Flee logic below still runs so + // MT steps out of puddles. + if (botAI->IsMainTank(bot)) + { + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (boss && boss->IsAlive() && boss->GetVictim() == bot && !boss->HealthBelowPct(35)) + { + constexpr float puddleNearBossRange = 8.0f; + constexpr float puddleSafeRadius = 10.0f; + constexpr float bossReach = 4.0f; + constexpr int kiteAngleSteps = 24; + + std::vector nearPuddles; + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& g : npcs) + { + Unit* u = botAI->GetUnit(g); + if (!u || !u->IsAlive() || u->GetEntry() != NPC_GROWING_OOZE_PUDDLE) + continue; + if (u->GetExactDist2d(boss) <= puddleNearBossRange) + nearPuddles.push_back(u); + } + + if (!nearPuddles.empty()) + { + // Centroid of near puddles → seed direction is boss-from-centroid. + float cx = 0.0f, cy = 0.0f; + for (Unit* p : nearPuddles) + { + cx += p->GetPositionX(); + cy += p->GetPositionY(); + } + cx /= static_cast(nearPuddles.size()); + cy /= static_cast(nearPuddles.size()); + + float seedX = boss->GetPositionX() - cx; + float seedY = boss->GetPositionY() - cy; + float seedLen = std::sqrt(seedX * seedX + seedY * seedY); + float seedAngle = (seedLen > 0.01f) ? std::atan2(seedY, seedX) : 0.0f; + + float bossX = boss->GetPositionX(); + float bossY = boss->GetPositionY(); + float botZ = bot->GetPositionZ(); + + // Scan angles around the boss starting from the seed (away + // from centroid) and spiraling outward. Pick the first angle + // whose tank-stand point clears every near puddle. + float bestGoalX = 0.0f, bestGoalY = 0.0f; + float bestMinDist = -FLT_MAX; + bool found = false; + bool foundClean = false; + + for (int i = 0; i < kiteAngleSteps; ++i) + { + // Alternate +/- around seedAngle to prefer minimal rotation. + int sign = (i % 2 == 0) ? 1 : -1; + int step = (i + 1) / 2; + float angle = seedAngle + sign * step * + (2.0f * static_cast(M_PI) / kiteAngleSteps); + + float gx = bossX + std::cos(angle) * bossReach; + float gy = bossY + std::sin(angle) * bossReach; + + float minDist = FLT_MAX; + for (Unit* p : nearPuddles) + { + float dd = p->GetDistance2d(gx, gy); + if (dd < minDist) + minDist = dd; + } + + if (!bot->IsWithinLOS(gx, gy, botZ)) + continue; + + if (minDist >= puddleSafeRadius) + { + bestGoalX = gx; + bestGoalY = gy; + foundClean = true; + found = true; + break; + } + + if (minDist > bestMinDist) + { + bestMinDist = minDist; + bestGoalX = gx; + bestGoalY = gy; + found = true; + } + } + + if (found && bot->GetExactDist2d(bestGoalX, bestGoalY) > 1.0f) + { + return MoveTo(bot->GetMapId(), bestGoalX, bestGoalY, botZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + + // No clean angle and already at best position: fall through + // to puddle-flee logic below so tank still avoids damage. + (void)foundClean; + } + } + } + + Unit* closestPuddle = FindClosestThreateningPuddle(); + if (!closestPuddle) + return false; + + Position movePosition = CalculateSafeMovePosition(closestPuddle); + return MoveTo(bot->GetMapId(), movePosition.GetPositionX(), movePosition.GetPositionY(), + movePosition.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); +} + +Unit* IccPutricideGrowingOozePuddleAction::FindClosestThreateningPuddle() +{ + constexpr float baseRadius = 2.0f; + constexpr float stackMultiplier = 0.8f; + constexpr float mainTankSafeDistance = 10.0f; + constexpr float minDistance = 0.1f; + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + if (npcs.empty()) + return nullptr; + + // Phase 3: MT no longer kites, treat as regular bot for puddle avoidance. + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + bool const isMainTank = botAI->IsMainTank(bot) && !(boss && boss->HealthBelowPct(35)); + + Unit* closestPuddle = nullptr; + float closestDistance = FLT_MAX; + + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || unit->GetEntry() != NPC_GROWING_OOZE_PUDDLE) + continue; + + float currentDistance = std::max(minDistance, bot->GetExactDist2d(unit)); + float safeDistance = isMainTank ? mainTankSafeDistance : baseRadius; + + if (!isMainTank) + { + if (Aura* grow = unit->GetAura(SPELL_GROW_AURA)) + safeDistance += (grow->GetStackAmount() * stackMultiplier); + } + + if (currentDistance < safeDistance && currentDistance < closestDistance) + { + closestDistance = currentDistance; + closestPuddle = unit; + } + } + + return closestPuddle; +} + +Position IccPutricideGrowingOozePuddleAction::CalculateSafeMovePosition(Unit* closestPuddle) +{ + constexpr float baseRadius = 2.0f; + constexpr float stackMultiplier = 0.8f; + constexpr float mainTankSafeDistance = 10.0f; + constexpr float bufferDistance = 2.0f; + constexpr float minDistance = 0.1f; + constexpr int numAnglesToTest = 8; + constexpr float tankShoveDistance = 6.0f; + + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + bool const isMainTank = botAI->IsMainTank(bot) && !(boss && boss->HealthBelowPct(35)); + bool const isP3Tank = botAI->IsMainTank(bot) && boss && boss->HealthBelowPct(35); + + float botX = bot->GetPositionX(); + float botY = bot->GetPositionY(); + float botZ = bot->GetPositionZ(); + + float gateDx = 0.0f, gateDy = 0.0f, gateLen = 0.0f; + if (isP3Tank) + { + gateDx = ICC_PUTRICIDE_GATE_POSITION.GetPositionX() - botX; + gateDy = ICC_PUTRICIDE_GATE_POSITION.GetPositionY() - botY; + gateLen = std::sqrt(gateDx * gateDx + gateDy * gateDy); + if (gateLen > 0.01f) + { + gateDx /= gateLen; + gateDy /= gateLen; + } + } + + float currentDistance = std::max(minDistance, bot->GetExactDist2d(closestPuddle)); + bool const useTankSafeDistance = isMainTank || isP3Tank; + float safeDistance = useTankSafeDistance ? mainTankSafeDistance : baseRadius; + if (!useTankSafeDistance) + { + if (Aura* grow = closestPuddle->GetAura(SPELL_GROW_AURA)) + safeDistance += (grow->GetStackAmount() * stackMultiplier); + } + + float dx = botX - closestPuddle->GetPositionX(); + float dy = botY - closestPuddle->GetPositionY(); + float dist = std::max(minDistance, std::sqrt(dx * dx + dy * dy)); + + if (dist < minDistance * 2.0f) + { + float randomAngle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * static_cast(M_PI); + dx = std::cos(randomAngle); + dy = std::sin(randomAngle); + } + else + { + dx /= dist; + dy /= dist; + } + + float moveDistance = safeDistance - currentDistance + bufferDistance; + + // If the bot is already inside the puddle, anchor candidates on the + // puddle's safe-radius circle instead of rotating around the bot's + // current (unsafe) position. Otherwise a rotated move can land deeper + // into the puddle. Outward radial from puddle center is always safe. + bool const insidePuddle = currentDistance < safeDistance; + + for (int i = 0; i < numAnglesToTest; ++i) + { + float angle = (2.0f * static_cast(M_PI) * i) / numAnglesToTest; + float rotatedDx = dx * std::cos(angle) - dy * std::sin(angle); + float rotatedDy = dx * std::sin(angle) + dy * std::cos(angle); + + float testX, testY; + if (insidePuddle) + { + // Stand on safe ring around puddle, rotated by `angle` from the + // bot-relative radial. i=0 → straight outward from puddle center. + float radius = safeDistance + bufferDistance; + testX = closestPuddle->GetPositionX() + rotatedDx * radius; + testY = closestPuddle->GetPositionY() + rotatedDy * radius; + } + else + { + testX = botX + rotatedDx * moveDistance; + testY = botY + rotatedDy * moveDistance; + } + + // Reject any candidate that still sits inside the closest puddle. + float candDist = closestPuddle->GetDistance2d(testX, testY); + if (candDist < safeDistance) + continue; + + if (!IsPositionTooCloseToOtherPuddles(testX, testY, closestPuddle) && bot->IsWithinLOS(testX, testY, botZ)) + { + if (isP3Tank && gateLen > 0.01f) + { + float moveDx = testX - botX; + float moveDy = testY - botY; + if (moveDx * gateDx + moveDy * gateDy <= 0.0f) + continue; + } + + if (PathCrossesAnyPuddle(botX, botY, testX, testY, nullptr)) + continue; + + if (botAI->IsTank(bot)) + { + float awayDx = testX - closestPuddle->GetPositionX(); + float awayDy = testY - closestPuddle->GetPositionY(); + float awayDist = std::sqrt(awayDx * awayDx + awayDy * awayDy); + if (awayDist > 0.001f) + { + awayDx /= awayDist; + awayDy /= awayDist; + testX += awayDx * tankShoveDistance; + testY += awayDy * tankShoveDistance; + } + } + return Position(testX, testY, botZ); + } + } + + // Fallback: straight outward from puddle center on its safe ring. + float fallbackRadius = safeDistance + bufferDistance; + float fallbackX = insidePuddle ? (closestPuddle->GetPositionX() + dx * fallbackRadius) + : (botX + dx * moveDistance); + float fallbackY = insidePuddle ? (closestPuddle->GetPositionY() + dy * fallbackRadius) + : (botY + dy * moveDistance); + if (isP3Tank && gateLen > 0.01f) + { + float fbDx = fallbackX - botX; + float fbDy = fallbackY - botY; + if (fbDx * gateDx + fbDy * gateDy <= 0.0f) + { + fallbackX = closestPuddle->GetPositionX() + gateDx * fallbackRadius; + fallbackY = closestPuddle->GetPositionY() + gateDy * fallbackRadius; + } + } + if (botAI->IsTank(bot)) + { + float awayDx = fallbackX - closestPuddle->GetPositionX(); + float awayDy = fallbackY - closestPuddle->GetPositionY(); + float awayDist = std::sqrt(awayDx * awayDx + awayDy * awayDy); + if (awayDist > 0.001f) + { + awayDx /= awayDist; + awayDy /= awayDist; + fallbackX += awayDx * tankShoveDistance; + fallbackY += awayDy * tankShoveDistance; + } + } + return Position(fallbackX, fallbackY, botZ); +} + +bool IccPutricideGrowingOozePuddleAction::PathCrossesAnyPuddle(float fromX, float fromY, float toX, float toY, Unit* ignorePuddle) +{ + constexpr float baseRadius = 2.0f; + constexpr float stackMultiplier = 0.8f; + constexpr float mainTankSafeDistance = 10.0f; + + bool const useTankSafeDistance = botAI->IsMainTank(bot); + + float segDx = toX - fromX; + float segDy = toY - fromY; + float segLenSq = segDx * segDx + segDy * segDy; + if (segLenSq < 0.01f) + return false; + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || unit == ignorePuddle || unit->GetEntry() != NPC_GROWING_OOZE_PUDDLE) + continue; + + float radius = useTankSafeDistance ? mainTankSafeDistance : baseRadius; + if (!useTankSafeDistance) + { + if (Aura* grow = unit->GetAura(SPELL_GROW_AURA)) + radius += (grow->GetStackAmount() * stackMultiplier); + } + + float px = unit->GetPositionX(); + float py = unit->GetPositionY(); + float t = ((px - fromX) * segDx + (py - fromY) * segDy) / segLenSq; + if (t < 0.0f) t = 0.0f; + else if (t > 1.0f) t = 1.0f; + + float closestX = fromX + segDx * t; + float closestY = fromY + segDy * t; + float ddx = closestX - px; + float ddy = closestY - py; + if (ddx * ddx + ddy * ddy < radius * radius) + return true; + } + + return false; +} + +bool IccPutricideGrowingOozePuddleAction::IsPositionTooCloseToOtherPuddles(float x, float y, Unit* ignorePuddle) +{ + constexpr float baseRadius = 2.0f; + constexpr float stackMultiplier = 0.8f; + constexpr float mainTankSafeDistance = 10.0f; + + bool const isMainTank = botAI->IsMainTank(bot); + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || unit == ignorePuddle || unit->GetEntry() != NPC_GROWING_OOZE_PUDDLE) + continue; + + float safeDistance = isMainTank ? mainTankSafeDistance : baseRadius; + if (!isMainTank) + { + if (Aura* grow = unit->GetAura(SPELL_GROW_AURA)) + safeDistance += (grow->GetStackAmount() * stackMultiplier); + } + + float dist = unit->GetDistance2d(x, y); + if (dist < safeDistance) + return true; + } + + return false; +} + +bool IccPutricideVolatileOozeAction::Execute(Event /*event*/) +{ + constexpr float stackDistance = 7.0f; + + Unit* ooze = AI_VALUE2(Unit*, "find target", "volatile ooze"); + if (!ooze) + return false; + + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (!boss) + return false; + + if (botAI->IsMainTank(bot) && + bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FOLLOW_MOTION_TYPE && + bot->GetExactDist2d(ICC_PUTRICIDE_TANK_POSITION) > 20.0f && + !boss->HealthBelowPct(36) && boss->GetVictim() == bot) + return MoveTo(bot->GetMapId(), ICC_PUTRICIDE_TANK_POSITION.GetPositionX(), + ICC_PUTRICIDE_TANK_POSITION.GetPositionY(), ICC_PUTRICIDE_TANK_POSITION.GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + + if (botAI->HasAura("Gaseous Bloat", bot) || botAI->HasAura("Unbound Plague", bot)) + return false; + + std::vector aliveOozes; + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == ooze->GetEntry()) + aliveOozes.push_back(unit); + } + + if (aliveOozes.size() > 1) + { + for (size_t i = 0; i < aliveOozes.size() - 1; ++i) + bot->Kill(bot, aliveOozes[i]); + } + + MarkOozeWithSkull(ooze); + + if (botAI->IsMelee(bot) && !botAI->IsMainTank(bot)) + { + if (bot->IsWithinMeleeRange(ooze)) + { + Attack(ooze); + return false; + } + + // If we're closer to the ooze than the targeted stack player is, + // running to the stack point just drags the ooze further. Go attack + // the ooze directly — the stack target will end up here anyway. + Unit* stackTarget = FindAuraTarget(); + if (stackTarget) + { + float botToOoze = bot->GetDistance2d(ooze); + float stackToOoze = stackTarget->GetDistance2d(ooze); + if (botToOoze < stackToOoze) + { + Attack(ooze); + return false; + } + } + } + + if (botAI->IsRanged(bot) || botAI->IsHeal(bot)) + { + constexpr float nearbyStackRange = 20.0f; + + Unit* stackTarget = FindAuraTarget(); + float distToStack = stackTarget ? bot->GetDistance2d(stackTarget) : 0.0f; + + // Only run to the stack target if it is reasonably close. Otherwise + // we'd chase across the room and barely attack. If it is far, check + // whether any other group member is already stacking near it — if + // yes, join them; if no, just stay put and attack from range. + bool shouldMoveToStack = false; + if (stackTarget && distToStack > stackDistance) + { + if (distToStack <= nearbyStackRange) + { + shouldMoveToStack = true; + } + else if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || member == bot || member == stackTarget) + continue; + if (member->GetDistance2d(stackTarget) <= stackDistance && + bot->GetDistance2d(member) <= nearbyStackRange) + { + shouldMoveToStack = true; + break; + } + } + } + } + + if (shouldMoveToStack) + { + return MoveTo(bot->GetMapId(), stackTarget->GetPositionX(), stackTarget->GetPositionY(), + stackTarget->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + + if (ooze && !botAI->IsHeal(bot)) + { + bot->SetTarget(ooze->GetGUID()); + bot->SetFacingToObject(ooze); + if (bot->IsWithinRange(ooze, 25.0f)) + { + Attack(ooze); + return false; + } + } + } + + return false; +} + +bool IccPutricideVolatileOozeAction::MarkOozeWithSkull(Unit* ooze) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + constexpr uint8 skullIconId = 7; + ObjectGuid skullGuid = group->GetTargetIcon(skullIconId); + Unit* markedUnit = botAI->GetUnit(skullGuid); + + if (markedUnit && (!markedUnit->IsAlive() || (ooze && markedUnit != ooze))) + group->SetTargetIcon(skullIconId, bot->GetGUID(), ObjectGuid::Empty); + + if (ooze && ooze->IsAlive() && (!skullGuid || !markedUnit)) + group->SetTargetIcon(skullIconId, bot->GetGUID(), ooze->GetGUID()); + + return false; +} + +Unit* IccPutricideVolatileOozeAction::FindAuraTarget() +{ + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + + if (botAI->HasAura("Volatile Ooze Adhesive", member)) + return member; + } + + return nullptr; +} + +bool IccPutricideGasCloudAction::Execute(Event /*event*/) +{ + Unit* gasCloud = AI_VALUE2(Unit*, "find target", "gas cloud"); + if (!gasCloud) + return false; + + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (!boss) + return false; + + if (botAI->IsTank(bot) && + bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FOLLOW_MOTION_TYPE && + bot->GetExactDist2d(ICC_PUTRICIDE_TANK_POSITION) > 20.0f && !boss->HealthBelowPct(36) && + boss->GetVictim() == bot) + return MoveTo(bot->GetMapId(), ICC_PUTRICIDE_TANK_POSITION.GetPositionX(), + ICC_PUTRICIDE_TANK_POSITION.GetPositionY(), ICC_PUTRICIDE_TANK_POSITION.GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + + if (botAI->IsMainTank(bot)) + return false; + + bool hasGaseousBloat = botAI->HasAura("Gaseous Bloat", bot); + Unit* volatileOoze = AI_VALUE2(Unit*, "find target", "volatile ooze"); + + std::vector aliveGasCloud; + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == gasCloud->GetEntry()) + aliveGasCloud.push_back(unit); + } + + if (aliveGasCloud.size() > 1) + { + for (size_t i = 0; i < aliveGasCloud.size() - 1; ++i) + bot->Kill(bot, aliveGasCloud[i]); + } + + if (!hasGaseousBloat && volatileOoze) + return false; + + if (hasGaseousBloat) + return HandleGaseousBloatMovement(gasCloud); + + return HandleGroupAuraSituation(gasCloud); +} + +bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) +{ + if (!botAI->HasAura("Gaseous Bloat", bot)) + { + g_bloatLastDir.erase(bot->GetGUID().GetRawValue()); + return false; + } + + // Lookup prior committed flee direction (if any) so we can reject + // backtracking candidates this tick. + uint64 botKey = bot->GetGUID().GetRawValue(); + auto lastDirIt = g_bloatLastDir.find(botKey); + bool hasLastDir = lastDirIt != g_bloatLastDir.end(); + float lastDirX = hasLastDir ? lastDirIt->second.x : 0.0f; + float lastDirY = hasLastDir ? lastDirIt->second.y : 0.0f; + + auto isBacktrack = [&](float candDx, float candDy) -> bool + { + if (!hasLastDir) + return false; + // Require forward progress: dot must be positive (>0). This still + // allows up to ~89deg turns but blocks any move with a backward + // component. + return (candDx * lastDirX + candDy * lastDirY) <= 0.0f; + }; + + auto commitDir = [&](float fromX, float fromY, float toX, float toY) + { + float ddx = toX - fromX; + float ddy = toY - fromY; + float l = std::sqrt(ddx * ddx + ddy * ddy); + if (l < 0.01f) + return; + g_bloatLastDir[botKey] = { ddx / l, ddy / l }; + }; + + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + + constexpr int numAngles = 32; + constexpr float gasBombSafeDist = 6.0f; + constexpr float movementIncrement = 5.0f; + constexpr float maxTestDist = 30.0f; + constexpr int checkDirs = 16; + constexpr float checkDist = 8.0f; + constexpr float minFreedomScore = 0.75f; + + Position botPos = bot->GetPosition(); + Position cloudPos = gasCloud->GetPosition(); + float cloudDist = gasCloud->GetExactDist2d(botPos); + + // Detect if bot is trapped near walls by testing 8 directions at 40yd. + // Threshold for "corner-ish" is intentionally low so we trigger the + // corner-escape branch before the bot fully parks against a wall. + constexpr int cornerCheckDirs = 8; + constexpr float cornerCheckDist = 40.0f; + int blockedAtStart = 0; + for (int i = 0; i < cornerCheckDirs; ++i) + { + float cA = (2.0f * static_cast(M_PI) * i) / cornerCheckDirs; + if (!bot->IsWithinLOS(botPos.GetPositionX() + std::cos(cA) * cornerCheckDist, + botPos.GetPositionY() + std::sin(cA) * cornerCheckDist, botPos.GetPositionZ())) + blockedAtStart++; + } + + // No distance early-exit: bloated bots must kite continuously because + // the gas cloud actively chases them. Stopping even briefly lets it catch up. + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector gasBombs; + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_CHOKING_GAS_BOMB) + gasBombs.push_back(unit); + } + + float dx = botPos.GetPositionX() - cloudPos.GetPositionX(); + float dy = botPos.GetPositionY() - cloudPos.GetPositionY(); + float dist = std::max(0.1f, std::sqrt(dx * dx + dy * dy)); + dx /= dist; + dy /= dist; + + Position bestPos; + bool foundPath = false; + float bestScore = 0.0f; + + // Corner escape: test all 32 angles at a fixed medium distance with strict requirements. + // Trigger at >=3 blocked to catch edge-of-wall situations before parking. + if (blockedAtStart >= 3) + { + constexpr float escRadius = 10.0f; + for (int i = 0; i < 32; ++i) + { + float escAngle = (2.0f * static_cast(M_PI) * i) / 32; + float escX = botPos.GetPositionX() + std::cos(escAngle) * escRadius; + float escY = botPos.GetPositionY() + std::sin(escAngle) * escRadius; + float escZ = botPos.GetPositionZ(); + + if (!bot->IsWithinLOS(escX, escY, escZ)) + continue; + + if (isBacktrack(escX - botPos.GetPositionX(), escY - botPos.GetPositionY())) + continue; + + int freeDirs = 0; + for (int j = 0; j < checkDirs; ++j) + { + float cA = (2.0f * static_cast(M_PI) * j) / checkDirs; + if (bot->IsWithinLOS(escX + std::cos(cA) * checkDist, escY + std::sin(cA) * checkDist, escZ)) + freeDirs++; + } + + // Count long-range openness (25f rays) — reject positions near walls. + int farFreeDirs = 0; + for (int k = 0; k < 8; ++k) + { + float cA = (2.0f * static_cast(M_PI) * k) / 8; + if (bot->IsWithinLOS(escX + std::cos(cA) * 25.0f, escY + std::sin(cA) * 25.0f, escZ)) + farFreeDirs++; + } + + float escCloudDist = cloudPos.GetExactDist2d(escX, escY); + + // Must be moving away, open locally, and open at range. + if (escCloudDist > cloudDist && freeDirs >= 12 && farFreeDirs >= 6) + { + commitDir(botPos.GetPositionX(), botPos.GetPositionY(), escX, escY); + botAI->Reset(); + return MoveTo(bot->GetMapId(), escX, escY, escZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + } + + // Hard-corner relief: no strict candidate found. Re-scan and pick the + // angle with the most local openness regardless of cloud direction — + // even briefly stepping toward the cloud is preferable to standing + // still in a wedge while it walks into us. + int bestRelOpen = -1; + float bestRelX = 0.0f, bestRelY = 0.0f, bestRelZ = botPos.GetPositionZ(); + for (int i = 0; i < 32; ++i) + { + float escAngle = (2.0f * static_cast(M_PI) * i) / 32; + float escX = botPos.GetPositionX() + std::cos(escAngle) * escRadius; + float escY = botPos.GetPositionY() + std::sin(escAngle) * escRadius; + float escZ = botPos.GetPositionZ(); + + if (!bot->IsWithinLOS(escX, escY, escZ)) + continue; + + if (isBacktrack(escX - botPos.GetPositionX(), escY - botPos.GetPositionY())) + continue; + + int openCount = 0; + for (int j = 0; j < checkDirs; ++j) + { + float cA = (2.0f * static_cast(M_PI) * j) / checkDirs; + if (bot->IsWithinLOS(escX + std::cos(cA) * checkDist, + escY + std::sin(cA) * checkDist, escZ)) + openCount++; + } + if (openCount > bestRelOpen) + { + bestRelOpen = openCount; + bestRelX = escX; + bestRelY = escY; + bestRelZ = escZ; + } + } + if (bestRelOpen >= 0) + { + commitDir(botPos.GetPositionX(), botPos.GetPositionY(), bestRelX, bestRelY); + botAI->Reset(); + return MoveTo(bot->GetMapId(), bestRelX, bestRelY, bestRelZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + } + + // Normal search: scan 32 angles × distance increments, score each valid position. + for (int i = 0; i < numAngles; ++i) + { + float angle = (2.0f * static_cast(M_PI) * i) / numAngles; + float rotatedDx = dx * std::cos(angle) - dy * std::sin(angle); + float rotatedDy = dx * std::sin(angle) + dy * std::cos(angle); + + for (float testDist = movementIncrement; testDist <= maxTestDist; testDist += movementIncrement) + { + float testX = botPos.GetPositionX() + rotatedDx * testDist; + float testY = botPos.GetPositionY() + rotatedDy * testDist; + float testZ = botPos.GetPositionZ(); + + float newCloudDist = cloudPos.GetExactDist2d(testX, testY); + + // Reject positions on the far side of the cloud (through it). + // The move direction must point generally away from the cloud. + float toTestX = testX - botPos.GetPositionX(); + float toTestY = testY - botPos.GetPositionY(); + if (toTestX * dx + toTestY * dy <= 0.0f) + continue; + + // Anti-backtrack: don't pick a candidate that walks against the + // direction we already committed to this Bloat session. + if (isBacktrack(toTestX, toTestY)) + continue; + + float minGasBombDist = FLT_MAX; + for (Unit* bomb : gasBombs) + { + float bombDist = bomb->GetDistance2d(testX, testY); + if (bombDist < minGasBombDist) + minGasBombDist = bombDist; + } + + if (newCloudDist > cloudDist && minGasBombDist >= gasBombSafeDist && + bot->IsWithinLOS(testX, testY, testZ)) + { + int freeDirections = 0; + for (int j = 0; j < checkDirs; ++j) + { + float checkAngle = (2.0f * static_cast(M_PI) * j) / checkDirs; + float checkX = testX + std::cos(checkAngle) * checkDist; + float checkY = testY + std::sin(checkAngle) * checkDist; + if (bot->IsWithinLOS(checkX, checkY, testZ)) + freeDirections++; + } + + float freedomScore = static_cast(freeDirections) / static_cast(checkDirs); + if (freedomScore < minFreedomScore) + continue; + + // Long-range corner check: 8 rays at 25f. If too many blocked, skip. + int farFreeDirs = 0; + for (int k = 0; k < 8; ++k) + { + float cA = (2.0f * static_cast(M_PI) * k) / 8; + if (bot->IsWithinLOS(testX + std::cos(cA) * 25.0f, testY + std::sin(cA) * 25.0f, testZ)) + farFreeDirs++; + } + if (farFreeDirs < 5) + continue; + + // Score: cloud distance, freedom, gas bomb distance, continuity. + bool canContinueMoving = false; + { + float continueX = testX + rotatedDx * movementIncrement; + float continueY = testY + rotatedDy * movementIncrement; + if (bot->IsWithinLOS(continueX, continueY, testZ) && + farFreeDirs >= 6) + canContinueMoving = true; + } + + float continuity = canContinueMoving ? 5.0f : 0.0f; + float combinedScore = newCloudDist + (freedomScore * 15.0f) + minGasBombDist + + static_cast(farFreeDirs) * 2.0f + continuity; + + if (!foundPath || combinedScore > bestScore) + { + bestPos = Position(testX, testY, testZ); + bestScore = combinedScore; + foundPath = true; + } + } + } + } + + if (foundPath) + { + commitDir(botPos.GetPositionX(), botPos.GetPositionY(), + bestPos.GetPositionX(), bestPos.GetPositionY()); + botAI->Reset(); + return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, + false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + + // Fallback pass: no candidate met the strict freedom/openness thresholds. + // Relax requirements and pick the most-open position that still moves us + // away from the cloud. Better to make imperfect progress than to stand + // still while the cloud walks into us. + float bestFallbackScore = -FLT_MAX; + Position bestFallbackPos; + bool foundFallback = false; + for (int i = 0; i < numAngles; ++i) + { + float angle = (2.0f * static_cast(M_PI) * i) / numAngles; + float rotatedDx = dx * std::cos(angle) - dy * std::sin(angle); + float rotatedDy = dx * std::sin(angle) + dy * std::cos(angle); + + for (float testDist = movementIncrement; testDist <= maxTestDist; testDist += movementIncrement) + { + float testX = botPos.GetPositionX() + rotatedDx * testDist; + float testY = botPos.GetPositionY() + rotatedDy * testDist; + float testZ = botPos.GetPositionZ(); + + // Must be moving away from the cloud. + float toTestX = testX - botPos.GetPositionX(); + float toTestY = testY - botPos.GetPositionY(); + if (toTestX * dx + toTestY * dy <= 0.0f) + continue; + + if (isBacktrack(toTestX, toTestY)) + continue; + + float newCloudDist = cloudPos.GetExactDist2d(testX, testY); + if (newCloudDist <= cloudDist) + continue; + + if (!bot->IsWithinLOS(testX, testY, testZ)) + continue; + + // Gas bomb hard-reject only. + float minGasBombDist = FLT_MAX; + for (Unit* bomb : gasBombs) + { + float bombDist = bomb->GetDistance2d(testX, testY); + if (bombDist < minGasBombDist) + minGasBombDist = bombDist; + } + if (minGasBombDist < gasBombSafeDist) + continue; + + // Score by cloud distance + local openness, no hard threshold. + int freeDirections = 0; + for (int j = 0; j < checkDirs; ++j) + { + float checkAngle = (2.0f * static_cast(M_PI) * j) / checkDirs; + if (bot->IsWithinLOS(testX + std::cos(checkAngle) * checkDist, + testY + std::sin(checkAngle) * checkDist, testZ)) + freeDirections++; + } + + float score = newCloudDist + static_cast(freeDirections) * 2.0f; + if (score > bestFallbackScore) + { + bestFallbackScore = score; + bestFallbackPos = Position(testX, testY, testZ); + foundFallback = true; + } + } + } + + if (foundFallback) + { + commitDir(botPos.GetPositionX(), botPos.GetPositionY(), + bestFallbackPos.GetPositionX(), bestFallbackPos.GetPositionY()); + botAI->Reset(); + return MoveTo(bot->GetMapId(), bestFallbackPos.GetPositionX(), bestFallbackPos.GetPositionY(), + bestFallbackPos.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + + // Last resort: move directly away from the cloud ignoring everything. + // Better to slide along a wall than to stand and eat it. + float lastX = botPos.GetPositionX() + dx * movementIncrement; + float lastY = botPos.GetPositionY() + dy * movementIncrement; + commitDir(botPos.GetPositionX(), botPos.GetPositionY(), lastX, lastY); + botAI->Reset(); + return MoveTo(bot->GetMapId(), lastX, lastY, botPos.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); +} + +bool IccPutricideGasCloudAction::HandleGroupAuraSituation(Unit* gasCloud) +{ + Group* group = bot->GetGroup(); + if (!group || botAI->IsHeal(bot)) + return false; + + constexpr float rangeMinSafeDistance = 15.0f; + constexpr float rangedMaxDistance = 25.0f; + constexpr float meleeRange = 5.0f; + constexpr uint8 skullIconId = 7; + + Unit* volatileOoze = AI_VALUE2(Unit*, "find target", "volatile ooze"); + if ((!volatileOoze || !volatileOoze->IsAlive()) && gasCloud && gasCloud->IsAlive()) + { + ObjectGuid currentSkull = group->GetTargetIcon(skullIconId); + Unit* markedUnit = botAI->GetUnit(currentSkull); + if (!markedUnit || !markedUnit->IsAlive() || markedUnit != gasCloud) + group->SetTargetIcon(skullIconId, bot->GetGUID(), gasCloud->GetGUID()); + } + + float currentDist = gasCloud ? bot->GetDistance(gasCloud) : 0.0f; + + if (!GroupHasGaseousBloat(group) && gasCloud && currentDist < rangeMinSafeDistance) + { + float dx = bot->GetPositionX() - gasCloud->GetPositionX(); + float dy = bot->GetPositionY() - gasCloud->GetPositionY(); + float dist = std::max(0.1f, std::sqrt(dx * dx + dy * dy)); + dx /= dist; + dy /= dist; + + float step = std::min(5.0f, rangeMinSafeDistance - currentDist); + return MoveTo(bot->GetMapId(), bot->GetPositionX() + dx * step, bot->GetPositionY() + dy * step, + bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + + return false; +} + +bool IccPutricideGasCloudAction::GroupHasGaseousBloat(Group* group) +{ + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && botAI->HasAura("Gaseous Bloat", member)) + return true; + } + return false; +} + +bool IccPutricideAvoidMalleableGooAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (!boss) + return false; + + if (botAI->IsTank(bot) && + bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + if (bot->GetTarget()) + bot->SetTarget(ObjectGuid::Empty); + if (Unit* master = botAI->GetMaster()) + Follow(master); + return true; + } + + // Reactive avoidance: find a position safe from ALL active Malleable Goo + // impact points (boss casts up to 3 simultaneously and they may be near + // each other - fleeing one can land in another). We sample a ring of + // candidates around the bot and score by minimum distance to every active + // hazard (goo impacts, ooze puddles, choking gas bombs). The best-scoring + // candidate that clears the danger radius for all goos is chosen. + // Only active in phase 1/2 (>35% HP) - in phase 3 bots must stack on the + // boss for Mutated Plague healing. + if (!boss->HealthBelowPct(35)) + { + constexpr uint32 impactLifetimeMs = 6000; + constexpr float gooDangerRadius = 8.0f; // 5yd AoE + 3yd safety + constexpr float puddleAvoidRadius = 6.0f; + constexpr float bombAvoidRadius = 6.0f; + + uint32 now = getMSTime(); + float botX = bot->GetPositionX(); + float botY = bot->GetPositionY(); + float botZ = bot->GetPositionZ(); + + // Collect active goo impacts + std::vector goos; + goos.reserve(4); + bool botInDanger = false; + auto impactIt = IcecrownHelpers::malleableGooImpacts.find(bot->GetMap()->GetInstanceId()); + if (impactIt != IcecrownHelpers::malleableGooImpacts.end()) + { + for (auto const& impact : impactIt->second) + { + if (getMSTimeDiff(impact.castTime, now) > impactLifetimeMs) + continue; + goos.push_back(impact.position); + + float dx = botX - impact.position.GetPositionX(); + float dy = botY - impact.position.GetPositionY(); + if (dx * dx + dy * dy < gooDangerRadius * gooDangerRadius) + botInDanger = true; + } + } + + if (botInDanger) + { + // Gather puddle / bomb obstacle positions to avoid in scoring + std::vector obstacles; + std::list nearbyUnits; + bot->GetCreatureListWithEntryInGrid(nearbyUnits, NPC_GROWING_OOZE_PUDDLE, 40.0f); + for (Creature* c : nearbyUnits) + if (c && c->IsAlive()) + obstacles.push_back(c->GetPosition()); + nearbyUnits.clear(); + bot->GetCreatureListWithEntryInGrid(nearbyUnits, NPC_CHOKING_GAS_BOMB, 40.0f); + for (Creature* c : nearbyUnits) + if (c && c->IsAlive()) + obstacles.push_back(c->GetPosition()); + + Position fromPos = bot->GetPosition(); + float bestScore = -1.0f; + float bestX = botX, bestY = botY; + bool found = false; + + // Sample ring: multiple radii × multiple angles. Prefer minimum + // displacement (closer rings tried first via scoring bias). + constexpr int angleSteps = 24; + float const radii[] = {8.0f, 11.0f, 14.0f, 17.0f}; + for (float r : radii) + { + for (int i = 0; i < angleSteps; ++i) + { + float a = (2.0f * float(M_PI) * i) / angleSteps; + float cx = botX + std::cos(a) * r; + float cy = botY + std::sin(a) * r; + Position toPos(cx, cy, botZ); + + // Must clear every goo's danger radius + float minGooDistSq = std::numeric_limits::max(); + bool safe = true; + for (Position const& g : goos) + { + float gdx = cx - g.GetPositionX(); + float gdy = cy - g.GetPositionY(); + float d2 = gdx * gdx + gdy * gdy; + if (d2 < gooDangerRadius * gooDangerRadius) + { + safe = false; + break; + } + if (d2 < minGooDistSq) + minGooDistSq = d2; + } + if (!safe) + continue; + + // Reachability checks + if (!bot->IsWithinLOS(cx, cy, botZ)) + continue; + if (HasObstacleBetween(fromPos, toPos)) + continue; + + // Score = min distance to nearest hazard at the candidate + // (goo, puddle, bomb), penalize travel distance lightly. + float minHazard = std::sqrt(minGooDistSq); + for (Position const& o : obstacles) + { + float odx = cx - o.GetPositionX(); + float ody = cy - o.GetPositionY(); + float od = std::sqrt(odx * odx + ody * ody); + // Treat obstacles as having a minimum stand-off + float effective = od - (puddleAvoidRadius - 4.0f); + if (effective < minHazard) + minHazard = effective; + } + + float travel = std::sqrt((cx - botX) * (cx - botX) + + (cy - botY) * (cy - botY)); + float score = minHazard - travel * 0.1f; + + if (score > bestScore) + { + bestScore = score; + bestX = cx; + bestY = cy; + found = true; + } + } + // If we found a clean candidate at this radius, stop expanding + if (found) + break; + } + + if (found) + { + return MoveTo(bot->GetMapId(), bestX, bestY, botZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + } + } + + if (HandleTankPositioning(boss)) + return false; + + if (AI_VALUE2(Unit*, "find target", "volatile ooze") || AI_VALUE2(Unit*, "find target", "gas cloud")) + return false; + + if (HandleUnboundPlague(boss)) + return false; + + // Only stack at boss in phase 3 (<=35% HP). Above 35% bots may stand + // anywhere; the goo flee block above handles emergencies. + if (!boss->HealthBelowPct(35)) + return false; + + return HandleBossPositioning(boss); +} + +bool IccPutricideAvoidMalleableGooAction::HandleTankPositioning(Unit* boss) +{ + if (!botAI->IsTank(bot)) + return false; + + if (boss && boss->IsAlive() && + !AI_VALUE2(Unit*, "find target", "volatile ooze") && !AI_VALUE2(Unit*, "find target", "gas cloud")) + { + if (Group* group = bot->GetGroup()) + { + constexpr uint8 skullIconId = 7; + ObjectGuid skullGuid = group->GetTargetIcon(skullIconId); + Unit* markedUnit = botAI->GetUnit(skullGuid); + if (!skullGuid || !markedUnit || !markedUnit->IsAlive()) + group->SetTargetIcon(skullIconId, bot->GetGUID(), boss->GetGUID()); + } + } + + constexpr float bombSearchRange = 100.0f; + constexpr float safeDistance = 15.0f; + + Unit* bomb = bot->FindNearestCreature(NPC_CHOKING_GAS_BOMB, bombSearchRange); + if (!bomb) + return false; + + float currentDistance = bot->GetDistance2d(bomb); + + if (currentDistance < safeDistance) + return MoveAway(bomb, safeDistance - currentDistance); + + return false; +} + +bool IccPutricideAvoidMalleableGooAction::HandleUnboundPlague(Unit* boss) +{ + if (boss && boss->HealthBelowPct(35)) + return false; + + if (!botAI->HasAura("Unbound Plague", bot)) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + constexpr float unboundPlagueDistance = 20.0f; + constexpr float unboundPlagueBuffer = 2.0f; + float closestDistance = unboundPlagueDistance; + Unit* closestPlayer = nullptr; + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + + float dist = bot->GetDistance2d(member); + if (dist < closestDistance) + { + closestDistance = dist; + closestPlayer = member; + } + } + + if (!closestPlayer || closestDistance >= unboundPlagueDistance) + { + bot->Kill(bot, bot); + return true; + } + + float dx = bot->GetPositionX() - closestPlayer->GetPositionX(); + float dy = bot->GetPositionY() - closestPlayer->GetPositionY(); + float dist = std::sqrt(dx * dx + dy * dy); + + if (dist <= 0.0f) + return false; + + dx /= dist; + dy /= dist; + float moveDistance = unboundPlagueDistance - closestDistance + unboundPlagueBuffer; + + float moveX = bot->GetPositionX() + dx * moveDistance; + float moveY = bot->GetPositionY() + dy * moveDistance; + + if (bot->IsWithinLOS(moveX, moveY, bot->GetPositionZ())) + { + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + + return false; +} + +bool IccPutricideAvoidMalleableGooAction::HandleBossPositioning(Unit* boss) +{ + if (botAI->IsMainTank(bot)) + return false; + + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + if (!mainTank || !mainTank->IsAlive()) + return false; + + constexpr float stackTolerance = 2.0f; + constexpr float hunterMaxDistance = 12.0f; + constexpr float frontalConeHalfAngle = float(M_PI) / 3.0f; // 60 degrees -> 120 deg cone + + float distToTank = bot->GetExactDist2d(mainTank); + + if (bot->getClass() == CLASS_HUNTER) + { + // Hunter slack: stay up to 12f from main tank, but only if standing in + // the boss's frontal cone. Outside the cone or beyond 12f -> move to MT. + float bossOrient = boss->GetOrientation(); + float toBotX = bot->GetPositionX() - boss->GetPositionX(); + float toBotY = bot->GetPositionY() - boss->GetPositionY(); + float toBotLen = std::sqrt(toBotX * toBotX + toBotY * toBotY); + bool inFrontCone = false; + if (toBotLen > 0.01f) + { + float bearing = std::atan2(toBotY, toBotX); + float delta = bearing - bossOrient; + while (delta > float(M_PI)) delta -= 2.0f * float(M_PI); + while (delta < -float(M_PI)) delta += 2.0f * float(M_PI); + inFrontCone = std::fabs(delta) <= frontalConeHalfAngle; + } + + if (distToTank <= hunterMaxDistance && inFrontCone) + return false; + } + else if (distToTank <= stackTolerance) + { + return false; + } + + bot->SetFacingToObject(boss); + return MoveTo(bot->GetMapId(), mainTank->GetPositionX(), mainTank->GetPositionY(), mainTank->GetPositionZ(), + false, false, false, botAI->IsRanged(bot), MovementPriority::MOVEMENT_COMBAT); +} + +bool IccPutricideAvoidMalleableGooAction::HasObstacleBetween(Position const& from, Position const& to) +{ + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + if (unit->GetEntry() == NPC_GROWING_OOZE_PUDDLE || unit->GetEntry() == NPC_CHOKING_GAS_BOMB) + { + if (IsOnPath(from, to, unit->GetPosition(), 3.0f)) + return true; + } + } + return false; +} + +bool IccPutricideAvoidMalleableGooAction::IsOnPath(Position const& from, Position const& to, Position const& point, + float threshold) +{ + float pathX = to.GetPositionX() - from.GetPositionX(); + float pathY = to.GetPositionY() - from.GetPositionY(); + float pathLen = std::sqrt(pathX * pathX + pathY * pathY); + + if (pathLen < 0.1f) + return false; + + float normX = pathX / pathLen; + float normY = pathY / pathLen; + + float toPointX = point.GetPositionX() - from.GetPositionX(); + float toPointY = point.GetPositionY() - from.GetPositionY(); + float proj = toPointX * normX + toPointY * normY; + + if (proj < 0.0f || proj > pathLen) + return false; + + float closestX = from.GetPositionX() + normX * proj; + float closestY = from.GetPositionY() + normY * proj; + float distToPath = std::sqrt((point.GetPositionX() - closestX) * (point.GetPositionX() - closestX) + + (point.GetPositionY() - closestY) * (point.GetPositionY() - closestY)); + + return distToPath < threshold; +} + +bool IccPutricideAbominationAction::IsSomeoneAlreadyPiloting() +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* m = itr->GetSource(); + if (!m || m == bot || !m->IsAlive()) + continue; + if (Unit* vb = m->GetVehicleBase()) + { + uint32 e = vb->GetEntry(); + if (e == NPC_MUTATED_ABOMINATION_10 || e == NPC_MUTATED_ABOMINATION_25) + return true; + } + } + return false; +} + +Unit* IccPutricideAbominationAction::FindClosestPuddle(float maxRange) +{ + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + Unit* best = nullptr; + float bestDist = maxRange; + for (auto const& g : npcs) + { + Unit* u = botAI->GetUnit(g); + if (!u || !u->IsAlive() || u->GetEntry() != NPC_GROWING_OOZE_PUDDLE) + continue; + float d = bot->GetExactDist2d(u); + if (d < bestDist) + { + bestDist = d; + best = u; + } + } + return best; +} + +bool IccPutricideAbominationAction::BecomeAbomination() +{ + GameObject* go = bot->FindNearestGameObject(GO_PUTRICIDE_DRINK_ME, 100.0f); + if (!go || !go->isSpawned()) + return false; + + float dist = bot->GetDistance(go); + if (dist > INTERACTION_DISTANCE) + { + return MoveTo(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + + bot->GetMotionMaster()->Clear(); + bot->StopMoving(); + bot->SetFacingToObject(go); + bool hasAura = botAI->HasAura("Mutated Transformation", bot); + if (!hasAura) + { + go->Use(bot); + return true; + } + return false; +} + +Unit* IccPutricideAbominationAction::PickSlashTarget(Unit* boss) +{ + Unit* volatileOoze = AI_VALUE2(Unit*, "find target", "volatile ooze"); + if (volatileOoze && volatileOoze->IsAlive()) + return volatileOoze; + + Unit* gasCloud = AI_VALUE2(Unit*, "find target", "gas cloud"); + if (gasCloud && gasCloud->IsAlive()) + return gasCloud; + + return boss; +} + +bool IccPutricideAbominationAction::TryRegurgitate(Unit* abo, Unit* target) +{ + if (!abo || !target || !target->IsAlive()) + return false; + + if (abo->GetExactDist2d(target) > 50.0f) + return false; + + if (botAI->HasAura("Regurgitated Ooze", target)) + return false; + + uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", "Regurgitated Ooze"); + if (!spellId) + return false; + + if (abo->HasSpellCooldown(spellId)) + return false; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + return false; + + if (!abo->HasInArc(CAST_ANGLE_IN_FRONT, target, 100.0f)) + { + abo->SetFacingToObject(target); + return false; + } + + Spell* spell = new Spell(abo, spellInfo, TRIGGERED_IGNORE_POWER_AND_REAGENT_COST); + SpellCastTargets targets; + targets.SetUnitTarget(target); + spell->prepare(&targets); + + abo->AddSpellCooldown(spellId, 0, 1000); + return true; +} + +bool IccPutricideAbominationAction::TryEatOoze(Unit* abo, Unit* puddle) +{ + if (!abo || !puddle || !puddle->IsAlive()) + return false; + + constexpr float eatRange = 4.0f; + if (abo->GetExactDist2d(puddle) > eatRange) + { + return MoveTo(bot->GetMapId(), puddle->GetPositionX(), puddle->GetPositionY(), puddle->GetPositionZ(), false, + false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + + // In range of puddle — hold position until it dies, even on cooldown + uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", "Eat Ooze"); + if (!spellId) + return true; + + if (abo->HasSpellCooldown(spellId)) + return true; + + if (botAI->CanCastVehicleSpell(spellId, puddle) && botAI->CastVehicleSpell(spellId, puddle)) + abo->AddSpellCooldown(spellId, 0, 1000); + + return true; +} + +bool IccPutricideAbominationAction::Execute(Event /*event*/) +{ + if (!botAI->IsAssistTank(bot)) + return false; + + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (!boss) + return false; + + Unit* abo = bot->GetVehicleBase(); + bool piloting = + abo && (abo->GetEntry() == NPC_MUTATED_ABOMINATION_10 || abo->GetEntry() == NPC_MUTATED_ABOMINATION_25); + + if (!piloting) + { + if (boss->HealthBelowPct(35)) + return false; + if (IsSomeoneAlreadyPiloting()) + return false; + Unit* nearestPuddle = FindClosestPuddle(50.0f); + if (!nearestPuddle) + return false; + return BecomeAbomination(); + } + + Unit* volatileOoze = AI_VALUE2(Unit*, "find target", "volatile ooze"); + Unit* gasCloud = AI_VALUE2(Unit*, "find target", "gas cloud"); + Unit* puddle = FindClosestPuddle(100.0f); + + // Priority 1: Regurgitated Ooze on volatile ooze (aura-gated, energy bypassed) + if (volatileOoze && volatileOoze->IsAlive() && + !botAI->HasAura("Regurgitated Ooze", volatileOoze)) + { + if (TryRegurgitate(abo, volatileOoze)) + return true; + } + + // Priority 2: Regurgitated Ooze on gas cloud (aura-gated, energy bypassed) + if (gasCloud && gasCloud->IsAlive() && + !botAI->HasAura("Regurgitated Ooze", gasCloud)) + { + if (TryRegurgitate(abo, gasCloud)) + return true; + } + + // Priority 3: actively seek and eat puddle + if (puddle) + { + if (TryEatOoze(abo, puddle)) + return true; + } + + // Priority 4: slash target volatile > gas > boss + Unit* slashTarget = PickSlashTarget(boss); + if (slashTarget) + { + if (abo->GetExactDist2d(slashTarget) > 5.0f) + { + return MoveTo(bot->GetMapId(), slashTarget->GetPositionX(), slashTarget->GetPositionY(), + slashTarget->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + + if (abo) + { + uint32 msSpellId = AI_VALUE2(uint32, "vehicle spell id", "Mutated Slash"); + if (msSpellId && !abo->HasSpellCooldown(msSpellId) && botAI->CanCastVehicleSpell(msSpellId, slashTarget) && + botAI->CastVehicleSpell(msSpellId, slashTarget)) + { + abo->AddSpellCooldown(msSpellId, 0, 1000); + return true; + } + } + + if (bot->GetTarget() != slashTarget->GetGUID()) + bot->SetTarget(slashTarget->GetGUID()); + Attack(slashTarget); + } + + return true; +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_RF.cpp b/src/Ai/Raid/ICC/Action/ICCActions_RF.cpp new file mode 100644 index 00000000000..30cab598ee3 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_RF.cpp @@ -0,0 +1,1091 @@ +#include +#include +#include +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "Multiplier.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "ICCActions.h" +#include "ICCScripts.h" +#include "ICCTriggers.h" +#include "RtiValue.h" +#include "Timer.h" +#include "Vehicle.h" + +// Rotface +bool IccRotfaceTankPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "rotface"); + if (!boss) + return false; + + Unit* smallOoze = AI_VALUE2(Unit*, "find target", "little ooze"); + + MarkBossWithSkull(boss); + + if (botAI->IsMainTank(bot)) + { + bool assistTankAlive = false; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->IsAlive() && member != bot && botAI->IsAssistTank(member)) + { + assistTankAlive = true; + break; + } + } + } + + Unit* bigOoze = bot->FindNearestCreature(NPC_BIG_OOZE, 100.0f); + bool const bigOozeHandled = bigOoze && bigOoze->IsAlive() && bigOoze->GetVictim() && + bigOoze->GetVictim()->IsPlayer() && + botAI->IsAssistTank(bigOoze->GetVictim()->ToPlayer()); + + if (bigOoze && bigOoze->IsAlive() && !assistTankAlive && !bigOozeHandled) + return HandleAssistTankPositioning(boss); + + return PositionMainTankAndMelee(boss, smallOoze); + } + + if (botAI->IsAssistTank(bot)) + return HandleAssistTankPositioning(boss); + + return false; +} + +bool IccRotfaceTankPositionAction::MarkBossWithSkull(Unit* boss) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + constexpr uint8 skullIconId = 7; + ObjectGuid skullGuid = group->GetTargetIcon(skullIconId); + if (skullGuid != boss->GetGUID()) + group->SetTargetIcon(skullIconId, bot->GetGUID(), boss->GetGUID()); + + return false; +} + +bool IccRotfaceTankPositionAction::PositionMainTankAndMelee(Unit* boss, Unit* smallOoze) +{ + bool isBossCasting = false; + if (boss && boss->HasUnitState(UNIT_STATE_CASTING)) + isBossCasting = true; + + if (botAI->IsMainTank(bot) && boss && boss->GetVictim() == bot) + { + bool const bossInPosition = boss->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION_BOSS) <= 4.0f; + + if (!bossInPosition) + { + // Step 2y in the direction from boss toward center — tank leads, boss follows + float dirX = ICC_ROTFACE_CENTER_POSITION_BOSS.GetPositionX() - boss->GetPositionX(); + float dirY = ICC_ROTFACE_CENTER_POSITION_BOSS.GetPositionY() - boss->GetPositionY(); + float const len = std::sqrt(dirX * dirX + dirY * dirY); + if (len > 0.5f) + { + dirX /= len; + dirY /= len; + } + float const destX = bot->GetPositionX() + dirX * 2.0f; + float const destY = bot->GetPositionY() + dirY * 2.0f; + MoveTo(bot->GetMapId(), destX, destY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + else if (bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION) > 7.0f) + { + MoveTo(bot->GetMapId(), ICC_ROTFACE_CENTER_POSITION.GetPositionX(), + ICC_ROTFACE_CENTER_POSITION.GetPositionY(), ICC_ROTFACE_CENTER_POSITION.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT); + } + else + { + if (bot->GetVictim() != boss) + bot->Attack(boss, true); + } + } + + if (boss && isBossCasting && !botAI->IsTank(bot)) + { + float const x = boss->GetPositionX(); + float const y = boss->GetPositionY(); + float const z = boss->GetPositionZ(); + + if (bot->GetExactDist2d(x, y) > 0.5f) + MoveTo(bot->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, + false); + } + + return false; +} + +bool IccRotfaceTankPositionAction::HandleAssistTankPositioning(Unit* boss) +{ + GuidVector bigOozes = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector activeBigOozes; + + for (auto const& guid : bigOozes) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_BIG_OOZE && unit->IsVisible()) + activeBigOozes.push_back(unit); + } + + if (activeBigOozes.empty()) + return false; + + auto CastClassTaunt = [&](Unit* target) -> bool + { + if (!target || !target->IsAlive()) + return false; + + if (!bot->HasAura(SPELL_SPITEFULL_FURY)) + bot->AddAura(SPELL_SPITEFULL_FURY, bot); + + switch (bot->getClass()) + { + case CLASS_PALADIN: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + } + case CLASS_DEATH_KNIGHT: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + } + case CLASS_DRUID: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + } + case CLASS_WARRIOR: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + } + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; + }; + + Unit* uncollectedOoze = nullptr; + float minUncollectedDist = FLT_MAX; + for (Unit* ooze : activeBigOozes) + { + if (ooze->GetVictim() == bot) + continue; + + CastClassTaunt(ooze); + + float const dist = bot->GetExactDist2d(ooze); + if (dist < minUncollectedDist) + { + minUncollectedDist = dist; + uncollectedOoze = ooze; + } + } + + if (uncollectedOoze) + { + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + return MoveTo(bot->GetMapId(), uncollectedOoze->GetPositionX(), uncollectedOoze->GetPositionY(), + uncollectedOoze->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + + Unit* targetOoze = nullptr; + float minDist = FLT_MAX; + for (Unit* ooze : activeBigOozes) + { + float const dist = bot->GetExactDist2d(ooze); + if (dist < minDist) + { + minDist = dist; + targetOoze = ooze; + } + } + + if (!targetOoze) + return false; + + return HandleBigOozeKiting(targetOoze); +} + +Unit* IccRotfaceTankPositionAction::FindAssignedBigOoze(Unit* /*boss*/, std::vector& bigOozes) +{ + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + Unit* bestOoze = nullptr; + float minDistance = FLT_MAX; + + for (Unit* ooze : bigOozes) + { + if (!ooze || !ooze->IsAlive() || !ooze->IsVisible()) + continue; + + ObjectGuid oozeGuid = ooze->GetGUID(); + + bool isAssignedToOther = false; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsInWorld() || member == bot) + continue; + + Unit* memberTarget = botAI->GetUnit(member->GetTarget()); + if (memberTarget && memberTarget->GetGUID() == oozeGuid) + { + isAssignedToOther = true; + break; + } + } + + if (!isAssignedToOther) + { + float const dist = bot->GetExactDist2d(ooze); + if (dist < minDistance) + { + minDistance = dist; + bestOoze = ooze; + } + } + } + + return bestOoze; +} + +bool IccRotfaceTankPositionAction::HandleBigOozeKiting(Unit* bigOoze) +{ + auto CastClassTaunt = [&](Unit* target) -> bool + { + if (!target || !target->IsAlive()) + return false; + + if (!bot->HasAura(SPELL_SPITEFULL_FURY)) + bot->AddAura(SPELL_SPITEFULL_FURY, bot); + + switch (bot->getClass()) + { + case CLASS_PALADIN: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + } + case CLASS_DEATH_KNIGHT: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + } + case CLASS_DRUID: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + } + case CLASS_WARRIOR: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + } + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; + }; + + if (bigOoze->GetVictim() != bot && bigOoze->IsAlive()) + CastClassTaunt(bigOoze); + + float const oozeDistance = bot->GetExactDist2d(bigOoze); + + if (oozeDistance > 12.0f) + { + bot->SetTarget(bigOoze->GetGUID()); + bot->SetFacingToObject(bigOoze); + if (bigOoze->GetVictim() != bot) + CastClassTaunt(bigOoze); + return false; + } + + float const minRadius = 24.0f; + float const maxRadius = 34.0f; + float const safeDistanceFromOoze = 13.0f; + + float const currentDistance = bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION); + + if (currentDistance < minRadius || currentDistance > maxRadius) + { + float dirX = bot->GetPositionX() - ICC_ROTFACE_CENTER_POSITION.GetPositionX(); + float dirY = bot->GetPositionY() - ICC_ROTFACE_CENTER_POSITION.GetPositionY(); + float length = std::sqrt(dirX * dirX + dirY * dirY); + dirX /= length; + dirY /= length; + + float const targetX = ICC_ROTFACE_CENTER_POSITION.GetPositionX() + dirX * maxRadius; + float const targetY = ICC_ROTFACE_CENTER_POSITION.GetPositionY() + dirY * maxRadius; + + if (bigOoze->GetExactDist2d(targetX, targetY) >= safeDistanceFromOoze) + return MoveTo(bot->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + + float currentAngle = std::atan2(bot->GetPositionY() - ICC_ROTFACE_CENTER_POSITION.GetPositionY(), + bot->GetPositionX() - ICC_ROTFACE_CENTER_POSITION.GetPositionX()); + + for (int32 i = 0; i < 16; ++i) + { + float const angleOffset = (i % 2 == 0 ? 1 : -1) * (M_PI / 16.0f) * (i / 2.0f); + float const newAngle = currentAngle + angleOffset; + + float const newX = ICC_ROTFACE_CENTER_POSITION.GetPositionX() + maxRadius * std::cos(newAngle); + float const newY = ICC_ROTFACE_CENTER_POSITION.GetPositionY() + maxRadius * std::sin(newAngle); + + if (bigOoze->GetExactDist2d(newX, newY) >= safeDistanceFromOoze) + { + GuidVector puddles = AI_VALUE(GuidVector, "nearest hostile npcs"); + bool isSafeFromPuddles = true; + + for (auto const& puddleGuid : puddles) + { + Unit* puddle = botAI->GetUnit(puddleGuid); + if (puddle && botAI->GetAura("Ooze Flood", puddle)) + { + float const puddleDistance = puddle->GetDistance2d(newX, newY); + if (puddleDistance < 30.0f) + { + isSafeFromPuddles = false; + break; + } + } + } + + if (isSafeFromPuddles) + { + MoveTo(bot->GetMapId(), newX, newY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + return true; + } + } + } + + return false; +} + +bool IccRotfaceGroupPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "rotface"); + if (!boss) + return false; + + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + bool const hasOozeFlood = botAI->HasAura("Ooze Flood", bot); + Unit* smallOoze = AI_VALUE2(Unit*, "find target", "little ooze"); + + if (!botAI->IsTank(bot) && HandlePuddleAvoidance(boss)) + return true; + + if (HandleOozeTargeting()) + return true; + + if (!(smallOoze && smallOoze->GetVictim() == bot) && !hasOozeFlood && PositionRangedAndHealers(boss, smallOoze)) + return true; + + return false; +} + +bool IccRotfaceGroupPositionAction::HandlePuddleAvoidance(Unit* boss) +{ + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !botAI->HasAura("Ooze Flood", unit)) + continue; + + float const puddleDistance = bot->GetExactDist2d(unit); + float const bossDistance = bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION); + + if (bossDistance < 15.0f) + return false; + + if (puddleDistance < 30.0f) + return MoveAwayFromPuddle(boss, unit, puddleDistance); + } + + return false; +} + +bool IccRotfaceGroupPositionAction::MoveAwayFromPuddle(Unit* boss, Unit* puddle, float) +{ + if (!boss || !puddle) + return false; + + float const dx = puddle->GetPositionX() - bot->GetPositionX(); + float const dy = puddle->GetPositionY() - bot->GetPositionY(); + float const angle = std::atan2(dy, dx); + + float const stepSize = 7.0f; + float const minPuddleDistance = 30.0f; + float const minCenterDistance = 15.0f; + float const maxCenterDistance = 25.0f; + int32 const directions = 8; + + for (int32 i = 0; i < directions; ++i) + { + float const testAngle = angle + M_PI + (i * M_PI / 4); + float const moveX = bot->GetPositionX() + stepSize * std::cos(testAngle); + float const moveY = bot->GetPositionY() + stepSize * std::sin(testAngle); + float const moveZ = bot->GetPositionZ(); + + float const newPuddleDistance = puddle->GetDistance2d(moveX, moveY); + float const newCenterDistance = std::sqrt(std::pow(moveX - ICC_ROTFACE_CENTER_POSITION.GetPositionX(), 2) + + std::pow(moveY - ICC_ROTFACE_CENTER_POSITION.GetPositionY(), 2)); + + if (newPuddleDistance >= minPuddleDistance && newCenterDistance >= minCenterDistance && + newCenterDistance <= maxCenterDistance && bot->IsWithinLOS(moveX, moveY, moveZ)) + { + return MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + } + + return false; +} + +bool IccRotfaceGroupPositionAction::HandleOozeTargeting() +{ + if (botAI->IsMainTank(bot)) + return false; + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + Player* mainTankWithOoze = nullptr; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || !botAI->IsMainTank(member)) + continue; + + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SMALL_OOZE && unit->GetVictim() == member) + { + mainTankWithOoze = member; + break; + } + } + break; + } + } + + Aura* infectionAura = botAI->GetAura("Mutated Infection", bot, false, false); + + // Find assist tank currently kiting a Big Ooze — preferred merge point + Player* assistTankKiting = nullptr; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || !botAI->IsAssistTank(member)) + continue; + + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_BIG_OOZE && unit->GetVictim() == member) + { + assistTankKiting = member; + break; + } + } + if (assistTankKiting) + break; + } + } + + bool const hasSmallOoze = [&]() -> bool { + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SMALL_OOZE && unit->GetVictim() == bot) + return true; + } + return false; + }(); + + bool const needsMerge = hasSmallOoze || (infectionAura && infectionAura->GetDuration() <= 4000); + + if (needsMerge) + { + if (assistTankKiting && bot->GetExactDist2d(assistTankKiting) > 2.0f) + return MoveTo(bot->GetMapId(), assistTankKiting->GetPositionX(), assistTankKiting->GetPositionY(), + assistTankKiting->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED); + + if (!assistTankKiting) + { + if (mainTankWithOoze && bot->GetExactDist2d(mainTankWithOoze) > 2.0f) + return MoveTo(bot->GetMapId(), mainTankWithOoze->GetPositionX(), mainTankWithOoze->GetPositionY(), + mainTankWithOoze->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED); + + return HandleOozeMemberPositioning(nullptr); + } + } + + return false; +} + +bool IccRotfaceGroupPositionAction::HandleOozeMemberPositioning(Unit* /*mySmallOoze*/) +{ + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector bigOozes; + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->IsVisible() && unit->GetEntry() == NPC_BIG_OOZE) + bigOozes.push_back(unit); + } + + if (!bigOozes.empty()) + { + Unit* target = nullptr; + float minDist = FLT_MAX; + + for (Unit* ooze : bigOozes) + { + Unit* victim = ooze->GetVictim(); + if (victim && victim->IsPlayer() && botAI->IsAssistTank(victim->ToPlayer())) + { + float const dist = bot->GetExactDist2d(ooze); + if (dist < minDist) + { + minDist = dist; + target = ooze; + } + } + } + + if (!target) + { + for (Unit* ooze : bigOozes) + { + float const dist = bot->GetExactDist2d(ooze); + if (dist < minDist) + { + minDist = dist; + target = ooze; + } + } + } + + if (target && bot->GetExactDist2d(target) > 2.0f && !botAI->IsAssistTank(bot)) + return MoveTo(bot->GetMapId(), target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED); + + return false; + } + + if (bot->GetExactDist2d(ICC_ROTFACE_BIG_OOZE_POSITION) > 5.0f) + return MoveTo(bot->GetMapId(), ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionX(), + ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionY(), ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_FORCED); + + return false; +} + +bool IccRotfaceGroupPositionAction::PositionRangedAndHealers(Unit* boss, Unit* smallOoze) +{ + if (!(botAI->IsRanged(bot) || botAI->IsHeal(bot))) + return false; + + if (smallOoze && smallOoze->GetVictim() == bot) + return false; + + Difficulty const diff = bot->GetRaidDifficulty(); + bool isBossCasting = false; + if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_SLIME_SPRAY)) + isBossCasting = true; + + bool const isHeroic = (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC); + + if (boss && isBossCasting && !isHeroic) + { + float const x = boss->GetPositionX(); + float const y = boss->GetPositionY(); + float const z = boss->GetPositionZ(); + + if (bot->GetExactDist2d(x, y) > 0.5f) + { + MoveTo(bot->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, + false); + } + return false; + } + + if (!isHeroic && !isBossCasting && boss && bot->getClass() != CLASS_HUNTER && + (bot->GetExactDist2d(boss->GetPositionX(), boss->GetPositionY()) < 2.0f || + bot->GetExactDist2d(boss->GetPositionX(), boss->GetPositionY()) > 5.0f)) + { + float const angle = + std::atan2(bot->GetPositionY() - boss->GetPositionY(), bot->GetPositionX() - boss->GetPositionX()); + float const destX = boss->GetPositionX() + 3.5f * std::cos(angle); + float const destY = boss->GetPositionY() + 3.5f * std::sin(angle); + return MoveTo(bot->GetMapId(), destX, destY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + + if (!isHeroic) + return false; + + return PositionHeroicGrid(boss); +} + +bool IccRotfaceGroupPositionAction::PositionHeroicGrid(Unit* boss) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + static Position const rangedSpots[] = { + ICC_ROTFACE_RANGED_POSITION_HC_1, ICC_ROTFACE_RANGED_POSITION_HC_2, ICC_ROTFACE_RANGED_POSITION_HC_3, + ICC_ROTFACE_RANGED_POSITION_HC_4, ICC_ROTFACE_RANGED_POSITION_HC_5, ICC_ROTFACE_RANGED_POSITION_HC_6, + ICC_ROTFACE_RANGED_POSITION_HC_7, ICC_ROTFACE_RANGED_POSITION_HC_8, ICC_ROTFACE_RANGED_POSITION_HC_9, + ICC_ROTFACE_RANGED_POSITION_HC_10, ICC_ROTFACE_RANGED_POSITION_HC_11, ICC_ROTFACE_RANGED_POSITION_HC_12, + ICC_ROTFACE_RANGED_POSITION_HC_13, ICC_ROTFACE_RANGED_POSITION_HC_14, ICC_ROTFACE_RANGED_POSITION_HC_15, + ICC_ROTFACE_RANGED_POSITION_HC_16, ICC_ROTFACE_RANGED_POSITION_HC_17, ICC_ROTFACE_RANGED_POSITION_HC_18, + ICC_ROTFACE_RANGED_POSITION_HC_19, ICC_ROTFACE_RANGED_POSITION_HC_20, ICC_ROTFACE_RANGED_POSITION_HC_21, + ICC_ROTFACE_RANGED_POSITION_HC_22, + }; + constexpr int32 totalSpots = 22; + + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector floodPuddles; + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit && botAI->HasAura("Ooze Flood", unit)) + floodPuddles.push_back(unit); + } + + bool spotFlooded[totalSpots] = {}; + for (int32 i = 0; i < totalSpots; ++i) + { + for (Unit* puddle : floodPuddles) + { + if (puddle->GetExactDist2d(rangedSpots[i].GetPositionX(), rangedSpots[i].GetPositionY()) < 30.0f) + { + spotFlooded[i] = true; + break; + } + } + } + + std::vector hunterGuids; + std::vector rangedGuids; + std::vector healerGuids; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || botAI->IsTank(member)) + continue; + + if (!GET_PLAYERBOT_AI(member)) + continue; + + ObjectGuid const guid = member->GetGUID(); + if (botAI->IsHeal(member)) + healerGuids.push_back(guid); + else if (botAI->IsRanged(member)) + { + if (member->getClass() == CLASS_HUNTER) + hunterGuids.push_back(guid); + else + rangedGuids.push_back(guid); + } + } + std::sort(hunterGuids.begin(), hunterGuids.end()); + std::sort(rangedGuids.begin(), rangedGuids.end()); + std::sort(healerGuids.begin(), healerGuids.end()); + + std::vector ordered; + for (auto const& g : hunterGuids) + ordered.push_back(g); + for (auto const& g : rangedGuids) + ordered.push_back(g); + for (auto const& g : healerGuids) + ordered.push_back(g); + + auto it = std::find(ordered.begin(), ordered.end(), bot->GetGUID()); + if (it == ordered.end()) + return false; + + int32 const botRank = static_cast(std::distance(ordered.begin(), it)); + int32 const totalBots = static_cast(ordered.size()); + + bool const hasHomeSpot = (botRank < totalSpots); + bool const homeSpotSafe = hasHomeSpot && !spotFlooded[botRank]; + + auto moveTowardSpot = [&](Position const& spot) -> bool + { + float const dx = spot.GetPositionX() - bot->GetPositionX(); + float const dy = spot.GetPositionY() - bot->GetPositionY(); + float const dist = std::sqrt(dx * dx + dy * dy); + if (dist <= 1.0f) + return false; + constexpr float moveStep = 7.0f; + float const step = std::min(moveStep, dist); + float const targetX = bot->GetPositionX() + (dx / dist) * step; + float const targetY = bot->GetPositionY() + (dy / dist) * step; + + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + + return MoveTo(bot->GetMapId(), targetX, targetY, spot.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + }; + + if (homeSpotSafe) + return moveTowardSpot(rangedSpots[botRank]); + + std::vector tempSpots; + for (int32 i = 0; i < totalSpots; ++i) + { + if (i >= totalBots && !spotFlooded[i]) + tempSpots.push_back(i); + } + + if (hasHomeSpot) + { + int32 displacedRank = 0; + for (int32 rank = 0; rank < totalSpots && rank < totalBots; ++rank) + { + if (rank == botRank) + break; + if (spotFlooded[rank]) + displacedRank++; + } + + if (displacedRank < static_cast(tempSpots.size())) + return moveTowardSpot(rangedSpots[tempSpots[displacedRank]]); + } + + if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && !botAI->IsTank(bot)) + { + float const bx = boss->GetPositionX(); + float const by = boss->GetPositionY(); + float const bz = boss->GetPositionZ(); + if (bot->GetExactDist2d(bx, by) > 0.5f) + return MoveTo(bot->GetMapId(), bx, by, bz, false, false, false, false, MovementPriority::MOVEMENT_FORCED, + true, false); + return false; + } + + if (Group* grp = bot->GetGroup()) + { + for (GroupReference* itr = grp->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + if (botAI->IsMainTank(member)) + { + if (bot->getClass() == CLASS_HUNTER) + { + float const distToTank = bot->GetExactDist2d(member->GetPositionX(), member->GetPositionY()); + if (distToTank <= 12.0f) + return false; + + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + + return MoveTo(bot->GetMapId(), member->GetPositionX(), member->GetPositionY(), + member->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + + Position const mainTankPos(member->GetPositionX(), member->GetPositionY(), member->GetPositionZ()); + return moveTowardSpot(mainTankPos); + } + } + } + return false; +} + +bool IccRotfaceMoveAwayFromExplosionAction::Execute(Event /*event*/) +{ + if (botAI->IsTank(bot)) + return false; + + Creature* bigOoze = bot->FindNearestCreature(NPC_BIG_OOZE, 100.0f); + bool const castingNow = bigOoze && bigOoze->IsAlive() && bigOoze->HasUnitState(UNIT_STATE_CASTING) && + bigOoze->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION); + + uint32 const now = getMSTime(); + bool const stillHolding = _hasEscape && _holdUntil != 0 && now < _holdUntil; + + if (!castingNow && !stillHolding) + { + _hasEscape = false; + _holdUntil = 0; + return false; + } + + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + + Position const anchor = ICC_ROTFACE_CENTER_POSITION_BOSS; + + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector puddles; + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && botAI->HasAura("Ooze Flood", unit)) + puddles.push_back(unit); + } + + auto isSlotSafe = [&](float x, float y) -> bool + { + for (Unit* puddle : puddles) + { + if (puddle->GetDistance2d(x, y) < 30.0f) + return false; + } + return true; + }; + + constexpr uint32 TOTAL_SLOTS = 25; + constexpr float ESCAPE_RADIUS = 40.0f; + + auto slotPos = [&](uint32 i) -> std::pair + { + float const angle = static_cast(i) * (2.0f * static_cast(M_PI) / static_cast(TOTAL_SLOTS)); + return {anchor.GetPositionX() + ESCAPE_RADIUS * std::cos(angle), + anchor.GetPositionY() + ESCAPE_RADIUS * std::sin(angle)}; + }; + + static std::map, int32> sExplosionSlotMemory; + + uint32 const instanceId = bot->GetMap()->GetInstanceId(); + auto const myKey = std::make_pair(instanceId, bot->GetGUID()); + + // count how many OTHER bots in this instance occupy each slot + std::array otherCount{}; + for (auto const& [key, slot] : sExplosionSlotMemory) + { + if (key.first == instanceId && key.second != bot->GetGUID()) + ++otherCount[slot]; + } + + auto assignSlot = [&]() -> int32 + { + int32 bestSlot = -1; + float bestDist = FLT_MAX; + for (uint32 i = 0; i < TOTAL_SLOTS; ++i) + { + if (otherCount[i] >= 2) + continue; + auto [x, y] = slotPos(i); + if (!isSlotSafe(x, y)) + continue; + float const d = bot->GetExactDist2d(x, y); + if (d < bestDist) + { + bestDist = d; + bestSlot = static_cast(i); + } + } + return bestSlot; + }; + + auto currentSlotIt = sExplosionSlotMemory.find(myKey); + bool needsAssignment = true; + + if (currentSlotIt != sExplosionSlotMemory.end()) + { + int32 const mySlot = currentSlotIt->second; + auto [x, y] = slotPos(mySlot); + if (otherCount[mySlot] < 2 && isSlotSafe(x, y)) + needsAssignment = false; + } + + if (needsAssignment) + { + int32 const slot = assignSlot(); + if (slot >= 0) + sExplosionSlotMemory[myKey] = slot; + else + sExplosionSlotMemory.erase(myKey); + } + + auto finalSlotIt = sExplosionSlotMemory.find(myKey); + + Position escapePos; + if (finalSlotIt != sExplosionSlotMemory.end()) + { + auto [x, y] = slotPos(finalSlotIt->second); + escapePos = Position(x, y, anchor.GetPositionZ()); + } + else + { + escapePos = anchor; + } + + _escapePosition = escapePos; + _hasEscape = true; + + if (castingNow) + { + _holdUntil = now + 2000; + if (bot->GetExactDist2d(_escapePosition) > 2.0f) + return MoveTo(bot->GetMapId(), _escapePosition.GetPositionX(), _escapePosition.GetPositionY(), + _escapePosition.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED); + return false; + } + + // Cast finished but still inside 2s hold window: stay at escape spot. + if (bot->GetExactDist2d(_escapePosition) <= 2.0f) + return true; + + return false; +} + +bool IccRotfaceAvoidVileGasAction::Execute(Event /*event*/) +{ + uint32 const now = getMSTime(); + + auto vgIt = IcecrownHelpers::rotfaceVileGas.find(bot->GetMap()->GetInstanceId()); + bool const isVictim = + vgIt != IcecrownHelpers::rotfaceVileGas.end() && + vgIt->second.victimGuid == bot->GetGUID() && + getMSTimeDiff(vgIt->second.castTime, now) < 8000; + bool const hasAura = botAI->HasAura("Vile Gas", bot); + + auto& waitMap = IcecrownHelpers::rotfaceVileGasWaitUntil; + auto waitIt = waitMap.find(bot->GetGUID()); + bool const inWait = waitIt != waitMap.end() && now < waitIt->second; + + if (!isVictim && !hasAura && !inWait) + { + if (waitIt != waitMap.end()) + waitMap.erase(waitIt); + _hasSafeSpot = false; + return false; + } + + Position const anchor = ICC_ROTFACE_CENTER_POSITION_BOSS; + + Creature* bigOoze = bot->FindNearestCreature(NPC_BIG_OOZE, 100.0f); + + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector puddles; + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && botAI->HasAura("Ooze Flood", unit)) + puddles.push_back(unit); + } + + auto isSafe = [&](float x, float y, float z) -> bool + { + if (bigOoze && bigOoze->IsAlive() && bigOoze->GetDistance2d(x, y) < 20.0f) + return false; + + for (Unit* puddle : puddles) + { + if (puddle->GetDistance2d(x, y) < 30.0f) + return false; + } + + if (!bot->IsWithinLOS(x, y, z)) + return false; + + return true; + }; + + if (!_hasSafeSpot) + { + float const baseAngle = std::atan2(bot->GetPositionY() - anchor.GetPositionY(), + bot->GetPositionX() - anchor.GetPositionX()); + + constexpr float escapeDistance = 40.0f; + constexpr int32 sweepSteps = 24; + constexpr float stepAngle = 2.0f * static_cast(M_PI) / sweepSteps; + + float chosenAngle = baseAngle; + for (int32 i = 0; i < sweepSteps; ++i) + { + int32 const sign = (i % 2 == 0) ? 1 : -1; + int32 const magnitude = (i + 1) / 2; + float const angle = baseAngle + sign * magnitude * stepAngle; + float const x = anchor.GetPositionX() + escapeDistance * std::cos(angle); + float const y = anchor.GetPositionY() + escapeDistance * std::sin(angle); + float const z = anchor.GetPositionZ(); + if (isSafe(x, y, z)) + { + chosenAngle = angle; + break; + } + } + + _safeSpot = Position(anchor.GetPositionX() + escapeDistance * std::cos(chosenAngle), + anchor.GetPositionY() + escapeDistance * std::sin(chosenAngle), + anchor.GetPositionZ()); + _hasSafeSpot = true; + } + + if (bot->GetExactDist2d(_safeSpot) > 2.0f) + { + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + + return MoveTo(bot->GetMapId(), _safeSpot.GetPositionX(), _safeSpot.GetPositionY(), + _safeSpot.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED); + } + + if (!inWait) + waitMap[bot->GetGUID()] = now + 4000; + + return true; +} diff --git a/src/Ai/Raid/ICC/Action/ICCActions_SG.cpp b/src/Ai/Raid/ICC/Action/ICCActions_SG.cpp new file mode 100644 index 00000000000..53096aafbf1 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_SG.cpp @@ -0,0 +1,1530 @@ +#include "ICCActions.h" +#include +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "Vehicle.h" +#include "RtiValue.h" +#include "GenericSpellActions.h" +#include "GenericActions.h" +#include "ICCTriggers.h" +#include "Multiplier.h" + +bool IccSindragosaGroupPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + // Block positioning only during the pre-combat intro fly-in. + // Once Sindragosa is in combat the air phase is handled separately, + // so ground bots must still be allowed to pre-position. + if (boss->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY) && !boss->IsInCombat()) + return false; + + Aura* aura = botAI->GetAura("mystic buffet", bot, false, true); + if (aura && aura->GetStackAmount() >= 6 && botAI->IsMainTank(bot)) + return false; + + // Route tanks to tank positioning. + // At pull GetVictim() may still be nullptr, so also check IsMainTank so the + // tank is not incorrectly sent to the melee stack before aggro is established. + bool const isTankingBoss = botAI->IsTank(bot) && (boss->GetVictim() == bot || botAI->IsMainTank(bot)); + if (isTankingBoss) + return HandleTankPositioning(boss); + + // Everyone else: boss is not targeting this bot + if (boss->GetVictim() != bot) + return HandleNonTankPositioning(); + + return false; +} + +bool IccSindragosaGroupPositionAction::HandleTankPositioning(Unit* boss) +{ + float const distBossToCenter = boss->GetExactDist2d(ICC_SINDRAGOSA_CENTER_POSITION); + float const distToTankPos = bot->GetExactDist2d(ICC_SINDRAGOSA_TANK_POSITION); + + // Compute how far the boss orientation deviates from east (PI/2) + float const targetOrientation = fmod(float(M_PI) / 2.0f + 2.0f * float(M_PI), 2.0f * float(M_PI)); + float const currentOrientation = fmod(boss->GetOrientation() + 2.0f * float(M_PI), 2.0f * float(M_PI)); + float orientationDiff = currentOrientation - targetOrientation; + + // Clamp difference to [-PI, PI] + if (orientationDiff > float(M_PI)) + orientationDiff -= 2.0f * float(M_PI); + else if (orientationDiff < -float(M_PI)) + orientationDiff += 2.0f * float(M_PI); + + // Stage 1: Drag boss toward the arena centre when it has drifted too far + if (distBossToCenter > 16.0f && distToTankPos <= 20.0f) + { + float const dirX = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionX() - boss->GetPositionX(); + float const dirY = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionY() - boss->GetPositionY(); + + // Step 4 yards past centre to keep the boss moving through it + float const moveX = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionX() + (dirX / distBossToCenter) * 8.0f; + float const moveY = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionY() + (dirY / distBossToCenter) * 8.0f; + + return MoveTo(bot->GetMapId(), moveX, moveY, boss->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + // Stage 2: Walk toward the designated tank position + if (distToTankPos > 10.0f) + { + Position const& botPos = bot->GetPosition(); + Position const& tankPos = ICC_SINDRAGOSA_TANK_POSITION; + + float const dx = tankPos.GetPositionX() - botPos.GetPositionX(); + float const dy = tankPos.GetPositionY() - botPos.GetPositionY(); + float const distance = std::hypot(dx, dy); + + // Advance one yard at a time for smooth pathing + float const scale = 1.0f / distance; + float const targetX = botPos.GetPositionX() + dx * scale; + float const targetY = botPos.GetPositionY() + dy * scale; + + return MoveTo(bot->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + // Stage 3: Arc around the boss to correct its facing toward east + if (std::abs(orientationDiff) > 0.15f) + { + float const centerX = boss->GetPositionX(); + float const centerY = boss->GetPositionY(); + float const radius = std::max(2.0f, bot->GetExactDist2d(centerX, centerY)); + + float angle = atan2(bot->GetPositionY() - centerY, bot->GetPositionX() - centerX); + + // Negative diff → step counterclockwise (north); positive → clockwise (south) + static constexpr float ARC_STEP = 0.125f; + angle += (orientationDiff < 0) ? ARC_STEP : -ARC_STEP; + + float const moveX = centerX + radius * cos(angle); + float const moveY = centerY + radius * sin(angle); + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + // Stage 4: Fine-tune Y-axis alignment with the tank position + float const yDiff = std::abs(bot->GetPositionY() - ICC_SINDRAGOSA_TANK_POSITION.GetPositionY()); + if (yDiff > 2.0f) + { + Position const& botPos = bot->GetPosition(); + Position const& tankPos = ICC_SINDRAGOSA_TANK_POSITION; + + float const newY = botPos.GetPositionY() + (tankPos.GetPositionY() > botPos.GetPositionY() ? 1.0f : -1.0f); + + return MoveTo(bot->GetMapId(), botPos.GetPositionX(), newY, botPos.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool IccSindragosaGroupPositionAction::HandleNonTankPositioning() +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + // Collect all alive raid members + std::vector raidMembers; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + + raidMembers.push_back(member); + } + + uint32 const totalMembers = static_cast(raidMembers.size()); + if (totalMembers == 0) + return false; + + // Count members currently free of Mystic Buffet + uint32 membersWithoutAura = 0; + for (Player* member : raidMembers) + { + if (!botAI->GetAura("mystic buffet", member)) + ++membersWithoutAura; + } + + // Raid is considered "clear" when 60 % or more lack the debuff stack + float const percentageWithoutAura = static_cast(membersWithoutAura) / static_cast(totalMembers); + bool const raidClear = (percentageWithoutAura >= 0.6f); + + static constexpr std::array TombEntries = {NPC_TOMB1, NPC_TOMB2, NPC_TOMB3, NPC_TOMB4}; + static constexpr uint8 SKULL_ICON_INDEX = 7; + + // Priority: if a tank is ice-tombed (ground phase), mark that tomb skull + // immediately so the raid DPSes it and frees the tank. Any bot can issue + // the mark — redundant SetTargetIcon calls are idempotent. + Unit* const bossForFlyCheck = AI_VALUE2(Unit*, "find target", "sindragosa"); + bool const bossGrounded = bossForFlyCheck && + bossForFlyCheck->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), + ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) >= 30.0f; + + Player* entombedTank = nullptr; + if (bossGrounded) + { + for (Player* member : raidMembers) + { + if (botAI->IsTank(member) && member->HasAura(SPELL_ICE_TOMB)) + { + entombedTank = member; + break; + } + } + } + + Unit* tankTomb = nullptr; + if (entombedTank) + { + GuidVector const tombGuids = AI_VALUE(GuidVector, "possible targets no los"); + float minDist = 4.0f; + for (uint32 const entry : TombEntries) + { + for (auto const& guid : tombGuids) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || unit->GetEntry() != entry || !unit->IsAlive()) + continue; + float const d = unit->GetDistance(entombedTank); + if (d < minDist) + { + minDist = d; + tankTomb = unit; + } + } + } + } + + if (tankTomb) + { + ObjectGuid const currentSkull = group->GetTargetIcon(SKULL_ICON_INDEX); + if (currentSkull != tankTomb->GetGUID()) + group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), tankTomb->GetGUID()); + } + else if (raidClear && botAI->IsTank(bot)) + { + GuidVector const tombGuids = AI_VALUE(GuidVector, "possible targets no los"); + + Unit* nearestTomb = nullptr; + float minDist = 150.0f; + + for (uint32 const entry : TombEntries) + { + for (auto const& guid : tombGuids) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || unit->GetEntry() != entry || !unit->IsAlive()) + continue; + + float const dist = bot->GetDistance(unit); + if (dist < minDist) + { + minDist = dist; + nearestTomb = unit; + } + } + } + + // Prefer the nearest tomb; fall back to marking the boss itself + Unit* targetToMark = nearestTomb; + if (!targetToMark) + { + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (boss && boss->IsAlive()) + targetToMark = boss; + } + + if (targetToMark) + { + ObjectGuid const currentSkull = group->GetTargetIcon(SKULL_ICON_INDEX); + Unit* const currentSkullUnit = botAI->GetUnit(currentSkull); + bool const needsUpdate = + !currentSkullUnit || !currentSkullUnit->IsAlive() || currentSkullUnit != targetToMark; + + if (needsUpdate) + group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), targetToMark->GetGUID()); + } + } + + context->GetValue("rti")->Set("skull"); + + if (botAI->IsRanged(bot)) + { + static constexpr float RANGED_TOLERANCE = 9.0f; + static constexpr float RANGED_MAX_STEP = 5.0f; + + if (bot->GetExactDist2d(ICC_SINDRAGOSA_RANGED_POSITION) > RANGED_TOLERANCE) + return MoveIncrementallyToPosition(ICC_SINDRAGOSA_RANGED_POSITION, RANGED_MAX_STEP); + + return false; + } + + static constexpr float MELEE_TOLERANCE = 10.0f; + static constexpr float MELEE_MAX_STEP = 5.0f; + + if (bot->GetExactDist2d(ICC_SINDRAGOSA_MELEE_POSITION) > MELEE_TOLERANCE) + return MoveIncrementallyToPosition(ICC_SINDRAGOSA_MELEE_POSITION, MELEE_MAX_STEP); + + return false; +} + +bool IccSindragosaGroupPositionAction::MoveIncrementallyToPosition(Position const& targetPos, float maxStep) +{ + float const dirX = targetPos.GetPositionX() - bot->GetPositionX(); + float const dirY = targetPos.GetPositionY() - bot->GetPositionY(); + float const length = std::hypot(dirX, dirY); + + float const stepSize = std::min(maxStep, bot->GetExactDist2d(targetPos)); + float const moveX = bot->GetPositionX() + (dirX / length) * stepSize; + float const moveY = bot->GetPositionY() + (dirY / length) * stepSize; + + return MoveTo(bot->GetMapId(), moveX, moveY, targetPos.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); +} + +bool IccSindragosaFrostBeaconAction::TryDropTombFlares(Unit const* boss) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + bool anyBeacon = false; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON)) + { + anyBeacon = true; + break; + } + } + if (!anyBeacon) + return false; + + // Phase tracking: clear flared sets on phase boundary so each phase gets fresh markers. + // Keyed per-instance so concurrent ICC raids don't share phase state. + uint32 const instanceId = bot->GetInstanceId(); + bool const phase3 = boss->HealthBelowPct(35); + bool& lastPhase3 = s_lastPhase3[instanceId]; + if (phase3 != lastPhase3) + { + s_flaredRedThisPhase[instanceId].clear(); + s_flaredBluePhase3[instanceId] = false; + lastPhase3 = phase3; + } + + // Build position list for current phase. + std::vector> targets; + if (phase3) + { + targets.emplace_back(3, &ICC_SINDRAGOSA_THOMBMB2_POSITION); + } + else + { + Difficulty const diff = bot->GetRaidDifficulty(); + bool const is25Man = (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC); + targets.emplace_back(0, &ICC_SINDRAGOSA_THOMB1_POSITION); + if (is25Man) + targets.emplace_back(1, &ICC_SINDRAGOSA_THOMB2_POSITION); + targets.emplace_back(2, &ICC_SINDRAGOSA_THOMB3_POSITION); + } + + // Build marker pool: alive non-tank bots (skip real players). One bot per position + // sidesteps the item cooldown — each marker only casts once per phase. + std::vector preferred; // non-beaconed + std::vector fallback; // beaconed allowed + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + PlayerbotAI* memberAI = sPlayerbotsMgr.GetPlayerbotAI(member); + if (!memberAI) + continue; // real player + if (memberAI->IsTank(member)) + continue; + fallback.push_back(member); + if (!member->HasAura(SPELL_FROST_BEACON)) + preferred.push_back(member); + } + + auto byGuid = [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }; + std::sort(preferred.begin(), preferred.end(), byGuid); + std::sort(fallback.begin(), fallback.end(), byGuid); + + // Assign N markers (one per position) from preferred first, then top up from fallback. + size_t const needed = targets.size(); + std::vector markers; + markers.reserve(needed); + for (Player* p : preferred) + { + if (markers.size() >= needed) + break; + markers.push_back(p); + } + if (markers.size() < needed) + { + for (Player* p : fallback) + { + if (markers.size() >= needed) + break; + if (std::find(markers.begin(), markers.end(), p) != markers.end()) + continue; + markers.push_back(p); + } + } + + // Find this bot's slot in the marker list. + auto myIt = std::find(markers.begin(), markers.end(), bot); + if (myIt == markers.end()) + return false; + size_t const mySlot = std::distance(markers.begin(), myIt); + if (mySlot >= targets.size()) + return false; + + auto const& [chosenIdx, chosenPos] = targets[mySlot]; + + // Already flared by some bot — don't duplicate. + if (phase3 && s_flaredBluePhase3[instanceId]) + return false; + if (!phase3 && s_flaredRedThisPhase[instanceId].count(chosenIdx)) + return false; + + // Skip if a tomb is already standing on this position. + static constexpr std::array tombEntries = {NPC_TOMB1, NPC_TOMB2, NPC_TOMB3, NPC_TOMB4}; + GuidVector const tombGuids = AI_VALUE(GuidVector, "possible targets no los"); + for (uint32 const entry : tombEntries) + { + for (auto const& guid : tombGuids) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || unit->GetEntry() != entry) + continue; + if (unit->GetExactDist2d(chosenPos->GetPositionX(), chosenPos->GetPositionY()) <= 5.0f) + return false; + } + } + + uint32 const itemId = phase3 ? ITEM_BLUE_SMOKE_FLARE : ITEM_RED_SMOKE_FLARE; + if (bot->GetItemCount(itemId, false) == 0) + bot->StoreNewItemInBestSlots(itemId, 10); + + Item* flare = bot->GetItemByEntry(itemId); + if (!flare) + return false; + + if (bot->IsNonMeleeSpellCast(false)) + return false; + if (bot->CanUseItem(flare) != EQUIP_ERR_OK) + return false; + + uint8 const bagIndex = flare->GetBagSlot(); + uint8 const slot = flare->GetSlot(); + uint8 const castCount = 1; + uint32 const spellId = flare->GetTemplate()->Spells[0].SpellId; + ObjectGuid const itemGuid = flare->GetGUID(); + uint32 const glyphIndex = 0; + uint8 const castFlags = 0; + + WorldPacket packet(CMSG_USE_ITEM); + packet << bagIndex << slot << castCount << spellId << itemGuid << glyphIndex << castFlags; + packet << uint32(TARGET_FLAG_DEST_LOCATION); + packet.appendPackGUID(0); + packet << chosenPos->GetPositionX() << chosenPos->GetPositionY() << chosenPos->GetPositionZ(); + + bot->GetSession()->HandleUseItemOpcode(packet); + if (phase3) + s_flaredBluePhase3[instanceId] = true; + else + s_flaredRedThisPhase[instanceId].insert(chosenIdx); + return false; +} + +// Todo not really used since tigger is bypassed +bool IccSindragosaTankSwapPositionAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + if (!botAI->IsAssistTank(bot)) + return false; + + // Keep the assist tank on the swap position with a tight tolerance + if (bot->GetExactDist2d(ICC_SINDRAGOSA_TANK_POSITION) > 3.0f) + { + return MoveTo(bot->GetMapId(), ICC_SINDRAGOSA_TANK_POSITION.GetPositionX(), + ICC_SINDRAGOSA_TANK_POSITION.GetPositionY(), ICC_SINDRAGOSA_TANK_POSITION.GetPositionZ(), false, + false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +std::map> IccSindragosaFrostBeaconAction::s_flaredRedThisPhase; +std::map IccSindragosaFrostBeaconAction::s_flaredBluePhase3; +std::map IccSindragosaFrostBeaconAction::s_lastPhase3; +uint32 IccSindragosaFrostBeaconAction::s_nextFlareMs = 0; // deprecated, kept for ABI of header + +bool IccSindragosaFrostBeaconAction::Execute(Event /*event*/) +{ + Unit* boss = bot->FindNearestCreature(NPC_SINDRAGOSA, 200.0f); + if (!boss) + return false; + + TryDropTombFlares(boss); + + HandleSupportActions(); + + if (bot->HasAura(SPELL_FROST_BEACON)) + { + return HandleBeaconedPlayer(boss); + } + + return HandleNonBeaconedPlayer(boss); +} + +bool IccSindragosaFrostBeaconAction::HandleSupportActions() +{ + Group* group = bot->GetGroup(); + + // Tank support - Paladin Hand of Freedom + if (group && bot->getClass() == CLASS_PALADIN) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || !botAI->IsTank(member)) + { + continue; + } + + if (botAI->GetAura("Frost Breath", member) && !member->HasAura(SPELL_HAND_OF_FREEDOM)) + { + botAI->CastSpell(SPELL_HAND_OF_FREEDOM, member); + break; + } + } + } + + return false; +} + +bool IccSindragosaHotAction::Execute(Event /*event*/) +{ + if (!botAI->IsHeal(bot) || bot->HasAura(SPELL_FROST_BEACON)) + return false; + + auto const members = AI_VALUE(GuidVector, "group members"); + for (auto const& memberGuid : members) + { + Unit* member = botAI->GetUnit(memberGuid); + if (!member || !member->IsAlive() || !member->HasAura(SPELL_FROST_BEACON)) + continue; + + if (member->HasAura(SPELL_ICE_TOMB)) + continue; + + uint32 spellId = 0; + switch (bot->getClass()) + { + case CLASS_PRIEST: + spellId = 48068; // Renew + break; + case CLASS_SHAMAN: + spellId = 61301; // Riptide + break; + case CLASS_DRUID: + spellId = 48441; // Rejuvenation + break; + default: + return false; + } + + if (!member->HasAura(spellId) && botAI->CanCastSpell(spellId, member)) + botAI->CastSpell(spellId, member); + } + + return false; +} + +bool IccSindragosaFrostBeaconAction::HandleBeaconedPlayer(const Unit* boss) +{ + // Phase 3 positioning (below 35% health, not flying) + if (boss->HealthBelowPct(35) && !IsBossFlying(boss)) + { + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + botAI->Reset(); + return MoveToPositionIfNeeded(ICC_SINDRAGOSA_THOMBMB2_POSITION, POSITION_TOLERANCE); + } + + // Regular beacon positioning using tomb spots + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + // Collect and sort beaconed players by GUID for deterministic assignment + std::vector beaconedPlayers; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON)) + beaconedPlayers.push_back(member); + } + + std::sort(beaconedPlayers.begin(), beaconedPlayers.end(), + [](const Player* a, const Player* b) { return a->GetGUID() < b->GetGUID(); }); + + // Find this bot's index + auto const it = std::find(beaconedPlayers.begin(), beaconedPlayers.end(), bot); + if (it == beaconedPlayers.end()) + return false; + + size_t const myIndex = std::distance(beaconedPlayers.begin(), it); + size_t const beaconCount = beaconedPlayers.size(); + + // Calculate tomb spot based on beacon count + size_t spot = 0; + switch (beaconCount) + { + case 2: + spot = (myIndex == 0) ? 0 : 2; + break; + case 5: + spot = (myIndex < 2) ? 0 : ((myIndex == 2) ? 1 : 2); + break; + case 6: + spot = myIndex / 2; + break; + default: + spot = myIndex % 3; + break; + } + + // Get tomb position and move if needed + static constexpr std::array tombPositions = { + &ICC_SINDRAGOSA_THOMB1_POSITION, &ICC_SINDRAGOSA_THOMB2_POSITION, &ICC_SINDRAGOSA_THOMB3_POSITION}; + + const Position& tombPosition = *tombPositions[std::min(spot, tombPositions.size() - 1)]; + return MoveToPositionIfNeeded(tombPosition, TOMB_POSITION_TOLERANCE); +} + +bool IccSindragosaFrostBeaconAction::HandleNonBeaconedPlayer(const Unit* boss) +{ + // Collect beaconed players + std::vector beaconedPlayers; + auto const members = AI_VALUE(GuidVector, "group members"); + for (auto const& memberGuid : members) + { + Unit* player = botAI->GetUnit(memberGuid); + if (player && player->GetGUID() != bot->GetGUID() && player->HasAura(SPELL_FROST_BEACON)) + { + beaconedPlayers.push_back(player); + } + } + + if (beaconedPlayers.empty()) + { + return false; + } + + // Air phase positioning + if (IsBossFlying(boss)) + { + if (!bot->HasAura(SPELL_FROST_BEACON)) + { + const Difficulty diff = bot->GetRaidDifficulty(); + bool is25Man = false; + if (diff && (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC)) + is25Man = true; + + const Position& safePosition = is25Man ? ICC_SINDRAGOSA_FBOMB_POSITION : ICC_SINDRAGOSA_FBOMB10_POSITION; + + float const dist = bot->GetExactDist2d(safePosition.GetPositionX(), safePosition.GetPositionY()); + if (dist > MOVE_TOLERANCE) + { + return MoveToPosition(safePosition); + } + + } + return botAI->IsHeal(bot); // Continue for healers, wait for others + } + + // Ground phase - position based on role and avoid beaconed players + bool const isRanged = botAI->IsRanged(bot) && !botAI->IsHeal(bot) /*(bot->GetExactDist2d(ICC_SINDRAGOSA_RANGED_POSITION.GetPositionX(),ICC_SINDRAGOSA_RANGED_POSITION.GetPositionY()) < + bot->GetExactDist2d(ICC_SINDRAGOSA_MELEE_POSITION.GetPositionX(),ICC_SINDRAGOSA_MELEE_POSITION.GetPositionY()))*/; + + const Position& targetPosition = isRanged ? ICC_SINDRAGOSA_RANGED_POSITION : ICC_SINDRAGOSA_MELEE_POSITION; + + float const deltaX = std::abs(targetPosition.GetPositionX() - bot->GetPositionX()); + float const deltaY = std::abs(targetPosition.GetPositionY() - bot->GetPositionY()); + if (boss && boss->GetVictim() != bot) + { + if ((deltaX > MOVE_TOLERANCE) || (deltaY > MOVE_TOLERANCE)) + { + if (bot->HasUnitState(UNIT_STATE_CASTING)) + { + botAI->Reset(); + } + return MoveToPosition(targetPosition); + } + } + return false; +} + +bool IccSindragosaFrostBeaconAction::MoveToPositionIfNeeded(const Position& position, float tolerance) +{ + float const distance = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (distance > tolerance) + { + return MoveToPosition(position); + } + return distance <= tolerance; +} + +bool IccSindragosaFrostBeaconAction::MoveToPosition(const Position& position) +{ + float posX = position.GetPositionX(); + float posY = position.GetPositionY(); + float posZ = position.GetPositionZ(); + + bot->UpdateAllowedPositionZ(posX, posY, posZ); + + return MoveTo(bot->GetMapId(), posX, posY, posZ, false, false, false, false, MovementPriority::MOVEMENT_FORCED, + true, false); +} + +bool IccSindragosaFrostBeaconAction::IsBossFlying(const Unit* boss) +{ + return boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), + ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f; +} + +bool IccSindragosaBlisteringColdAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + // Only non-tanks should move out + if (botAI->IsMainTank(bot)) + return false; + + float dist = bot->GetExactDist2d(boss->GetPositionX(), boss->GetPositionY()); + + if (dist >= 33.0f) + return false; + + Position const& targetPos = ICC_SINDRAGOSA_BLISTERING_COLD_POSITION; + + // Only move if we're too close to the boss (< 30 yards) + if (dist < 33.0f) + { + + float const STEP_SIZE = 15.0f; + float distToTarget = bot->GetDistance2d(targetPos.GetPositionX(), targetPos.GetPositionY()); + + if (distToTarget > 0.1f) // Avoid division by zero + { + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + // Calculate direction vector + float dirX = targetPos.GetPositionX() - bot->GetPositionX(); + float dirY = targetPos.GetPositionY() - bot->GetPositionY(); + + // Normalize direction vector + float length = sqrt(dirX * dirX + dirY * dirY); + dirX /= length; + dirY /= length; + + // Move STEP_SIZE yards in that direction + float moveX = bot->GetPositionX() + dirX * STEP_SIZE; + float moveY = bot->GetPositionY() + dirY * STEP_SIZE; + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + } + } + return false; +} + +bool IccSindragosaUnchainedMagicAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + Aura* aura = botAI->GetAura("Unchained Magic", bot, false, true); + if (!aura) + return false; + + Aura* aura1 = botAI->GetAura("Instability", bot, false, true); + + Difficulty diff = bot->GetRaidDifficulty(); + if (aura && (diff == RAID_DIFFICULTY_10MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_NORMAL)) + { + if (aura1 && aura1->GetStackAmount() >= 6) + return true; // Stop casting spells + } + + return false; +} + +bool IccSindragosaChilledToTheBoneAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + Aura* aura = botAI->GetAura("Chilled to the Bone", bot, false, true); + if (!aura) + return false; + + if (aura) // Chilled to the Bone + { + if (aura->GetStackAmount() >= 6) + { + botAI->Reset(); + bot->AttackStop(); + return true; + } + } + + return false; +} + +bool IccSindragosaMysticBuffetAction::Execute(Event /*event*/) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss || !bot || !bot->IsAlive()) + return false; + + // Check if we have Mystic Buffet + Aura* aura = botAI->GetAura("mystic buffet", bot, false, true); + if (!aura) + return false; + + if (boss->GetVictim() == bot) + return false; + + // Skip if we have Frost Beacon + if (bot->HasAura(SPELL_FROST_BEACON)) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + static const std::array tombEntries = {NPC_TOMB1, NPC_TOMB2, NPC_TOMB3, NPC_TOMB4}; + const GuidVector tombGuids = AI_VALUE(GuidVector, "possible targets no los"); + + Unit* nearestTomb = nullptr; + float minDist = 150.0f; + + for (auto const entry : tombEntries) + { + for (auto const& guid : tombGuids) + { + if (Unit* unit = botAI->GetUnit(guid)) + { + if (unit->GetEntry() == entry && unit->IsAlive()) + { + float dist = bot->GetDistance(unit); + if (dist < minDist) + { + minDist = dist; + nearestTomb = unit; + } + } + } + } + } + + // Check if anyone in group has Frost Beacon (SPELL_FROST_BEACON) + bool anyoneHasFrostBeacon = false; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON)) + { + anyoneHasFrostBeacon = true; + break; + } + } + + bool tombPresent = nearestTomb != nullptr; + bool atLOS2 = bot->GetExactDist2d(ICC_SINDRAGOSA_LOS2_POSITION.GetPositionX(), + ICC_SINDRAGOSA_LOS2_POSITION.GetPositionY()) <= 2.0f; + + // Move to LOS2 position if: tomb is present and no one has Frost Beacon + bool shouldMoveLOS2 = tombPresent && !anyoneHasFrostBeacon; + + if (shouldMoveLOS2) + { + // If already at LOS2: instead of idling while stacks drop, DPS the LOS + // tomb down to MYSTIC_BUFFET_TOMB_STOP_HP_PCT so the wait isn't wasted. + // Single skull icon for the whole raid — pick the tomb with lowest GUID + // so every bot converges deterministically on the same target. + if (atLOS2 && aura && !botAI->IsHeal(bot)) + { + constexpr uint8 SKULL_ICON = 7; + float MYSTIC_BUFFET_TOMB_STOP_HP_PCT = 50.0f; + Difficulty const diff = bot->GetRaidDifficulty(); + + if (diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC)) + MYSTIC_BUFFET_TOMB_STOP_HP_PCT = 90.0f; + + Unit* tombToMark = nullptr; + ObjectGuid bestGuid; + for (auto const entry : tombEntries) + { + for (auto const& guid : tombGuids) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || unit->GetEntry() != entry) + continue; + if (unit->GetHealthPct() <= MYSTIC_BUFFET_TOMB_STOP_HP_PCT) + continue; + if (!tombToMark || unit->GetGUID() < bestGuid) + { + tombToMark = unit; + bestGuid = unit->GetGUID(); + } + } + } + + if (!tombToMark) + { + ObjectGuid const currentIcon = group->GetTargetIcon(SKULL_ICON); + if (!currentIcon.IsEmpty()) + group->SetTargetIcon(SKULL_ICON, bot->GetGUID(), ObjectGuid::Empty); + return true; + } + + context->GetValue("rti")->Set("skull"); + + Unit* currentIconUnit = botAI->GetUnit(group->GetTargetIcon(SKULL_ICON)); + if (!currentIconUnit || !currentIconUnit->IsAlive() || currentIconUnit != tombToMark) + group->SetTargetIcon(SKULL_ICON, bot->GetGUID(), tombToMark->GetGUID()); + + return false; + } + + botAI->Reset(); + // Move to LOS2 position + return MoveTo(bot->GetMapId(), ICC_SINDRAGOSA_LOS2_POSITION.GetPositionX(), + ICC_SINDRAGOSA_LOS2_POSITION.GetPositionY(), ICC_SINDRAGOSA_LOS2_POSITION.GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_FORCED); + } + return false; +} + +std::map, int> IccSindragosaFrostBombAction::s_groupAssignments; +std::map, ObjectGuid> IccSindragosaFrostBombAction::s_tombAssignments; +std::set> IccSindragosaFrostBombAction::s_freedFallback; +std::map, IccSindragosaFrostBombAction::LastLosMove> + IccSindragosaFrostBombAction::s_lastLosMove; + +bool IccSindragosaFrostBombAction::Execute(Event /*event*/) +{ + if (!bot || !bot->IsAlive()) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + if (bot->HasAura(SPELL_ICE_TOMB)) + { + PinGroupToCurrentZone(); + s_freedFallback.insert(std::make_pair(bot->GetInstanceId(), bot->GetGUID())); + return false; + } + + FrostBombContext ctx; + if (!CollectContext(ctx)) + { + bot->AttackStop(); + return true; + } + + int const groupIndex = ResolveGroupIndex(group); + if (groupIndex < 0) + return false; + + Difficulty const diff = bot->GetRaidDifficulty(); + int const groupCount = (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC) ? 3 : 2; + + // Fixed tomb zone positions per group: + // 25-man (3 groups): group 0→THOMB1, group 1→THOMB2, group 2→THOMB3 + // 10-man (2 groups): group 0→THOMB1, group 1→THOMB3 (beacons skip THOMB2 in 2-beacon case) + static constexpr std::array tombZones = { + &ICC_SINDRAGOSA_THOMB1_POSITION, + &ICC_SINDRAGOSA_THOMB2_POSITION, + &ICC_SINDRAGOSA_THOMB3_POSITION + }; + int const zoneIdx = (groupCount == 2) ? (groupIndex == 0 ? 0 : 2) : groupIndex; + Position const& myZone = *tombZones[zoneIdx]; + + std::vector myTombs = SelectTombs(ctx.tombs, groupIndex, groupCount); + + bool myZoneAllProtected = false; + { + static constexpr std::array raidIcons = {7, 6, 0}; + static constexpr float STRIP_HP_PCT = 30.0f; + bool const is10Man = + (diff == RAID_DIFFICULTY_10MAN_NORMAL || diff == RAID_DIFFICULTY_10MAN_HEROIC); + float const tombStopHpPct = is10Man ? 60.0f : 40.0f; + + auto isMarked = [&](Unit* tomb) -> bool + { + for (uint8 const icon : raidIcons) + if (group->GetTargetIcon(icon) == tomb->GetGUID()) + return true; + return false; + }; + + bool anyAlive = false; + bool anyKillable = false; + for (Unit* tomb : myTombs) + { + if (!tomb || !tomb->IsAlive()) + continue; + anyAlive = true; + bool const marked = isMarked(tomb); + + if (!marked && tomb->GetHealthPct() < STRIP_HP_PCT) + { + Unit::AuraMap& auras = tomb->GetOwnedAuras(); + for (Unit::AuraMap::iterator it = auras.begin(); it != auras.end();) + { + Aura* aura = it->second; + if (aura && aura->GetDuration() != -1) + { + tomb->RemoveOwnedAura(it); + continue; + } + ++it; + } + } + (void)marked; + if (tomb->GetHealthPct() > tombStopHpPct) + anyKillable = true; + } + + myZoneAllProtected = anyAlive && !anyKillable; + + std::vector pets; + if (Pet* mainPet = bot->GetPet()) + pets.push_back(mainPet); + for (Unit* controlled : bot->m_Controlled) + { + if (Creature* c = dynamic_cast(controlled)) + { + if (std::find(pets.begin(), pets.end(), c) == pets.end()) + pets.push_back(c); + } + } + + if (myZoneAllProtected) + { + for (Creature* pet : pets) + { + if (!pet || !pet->IsAlive()) + continue; + pet->SetReactState(REACT_PASSIVE); + pet->AttackStop(); + pet->InterruptNonMeleeSpells(true); + pet->CombatStop(); + pet->SetTarget(ObjectGuid::Empty); + if (CharmInfo* ci = pet->GetCharmInfo()) + { + ci->SetPlayerReactState(REACT_PASSIVE); + pet->GetMotionMaster()->MoveFollow(bot, PET_FOLLOW_DIST, + pet->GetFollowAngle()); + ci->SetCommandState(COMMAND_FOLLOW); + ci->SetIsCommandAttack(false); + ci->SetIsAtStay(false); + ci->SetIsReturning(true); + ci->SetIsFollowing(true); + } + } + } + else + { + for (Creature* pet : pets) + { + if (pet && pet->IsAlive() && pet->GetReactState() == REACT_PASSIVE) + { + pet->SetReactState(REACT_DEFENSIVE); + if (CharmInfo* ci = pet->GetCharmInfo()) + ci->SetPlayerReactState(REACT_DEFENSIVE); + } + } + } + } + + // No tomb in zone + if (myTombs.empty()) + { + // Freed-from-tomb fallback: hide behind the nearest alive tomb anywhere + // in the arena rather than running across to our pinned zone anchor. + if (s_freedFallback.count(std::make_pair(bot->GetInstanceId(), bot->GetGUID()))) + { + Unit* nearest = nullptr; + float minDist = std::numeric_limits::max(); + for (Unit* tomb : ctx.tombs) + { + if (!tomb || !tomb->IsAlive()) + continue; + float const d = bot->GetExactDist2d(tomb); + if (d < minDist) + { + minDist = d; + nearest = tomb; + } + } + + if (nearest) + { + float const fbAngle = ctx.marker->GetAngle(nearest); + float const fbX = nearest->GetPositionX() + std::cos(fbAngle) * 6.5f; + float const fbY = nearest->GetPositionY() + std::sin(fbAngle) * 6.5f; + float const fbZ = nearest->GetPositionZ(); + + if (bot->GetDistance2d(fbX, fbY) > 0.1f) + { + botAI->Reset(); + bot->AttackStop(); + return MoveTo(bot->GetMapId(), fbX, fbY, fbZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED); + } + return false; + } + } + + // Default: pre-position behind the zone's anchor point + float const preAngle = ctx.marker->GetAngle(myZone.GetPositionX(), myZone.GetPositionY()); + float const preX = myZone.GetPositionX() + std::cos(preAngle) * 6.5f; + float const preY = myZone.GetPositionY() + std::sin(preAngle) * 6.5f; + + if (bot->GetDistance2d(preX, preY) > 3.0f) + { + botAI->Reset(); + bot->AttackStop(); + return MoveTo(bot->GetMapId(), preX, preY, myZone.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED); + } + return false; + } + + // Pinned zone has tombs again — exit fallback mode + auto const losKey = std::make_pair(bot->GetInstanceId(), bot->GetGUID()); + s_freedFallback.erase(losKey); + + Unit* losTomb = ResolveStickyTomb(myTombs); + if (!losTomb) + { + // LOS tomb died / lost mark mid-walk. If we recently issued an LOS + // move, replay it for up to 2 seconds so the bot finishes its path + // instead of freezing in the open until the next valid sticky tomb. + auto it = s_lastLosMove.find(losKey); + if (it != s_lastLosMove.end()) + { + uint32 const now = getMSTime(); + if (getMSTimeDiff(it->second.timestampMs, now) <= 2000 && + bot->GetDistance2d(it->second.x, it->second.y) > 0.1f) + { + botAI->Reset(); + bot->AttackStop(); + return MoveTo(bot->GetMapId(), it->second.x, it->second.y, it->second.z, + false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + s_lastLosMove.erase(it); + } + return false; + } + + float const angle = ctx.marker->GetAngle(losTomb); + float const posX = losTomb->GetPositionX() + std::cos(angle) * 6.5f; + float const posY = losTomb->GetPositionY() + std::sin(angle) * 6.5f; + float const posZ = losTomb->GetPositionZ(); + + float const losDist = bot->GetDistance2d(posX, posY); + if (losDist > 0.1f) + { + botAI->Reset(); + bot->AttackStop(); + // Mark the tomb early (within 5yd) so the raid converges on the kill + // target while the bot is still walking the last few yards into LOS. + if (losDist <= 10.0f) + HandleRtiMarking(group, groupIndex, myTombs, losTomb); + + // Stamp this LOS move so we can replay it for up to 2 seconds if the + // tomb dies/loses mark before we arrive. + LastLosMove& stamp = s_lastLosMove[losKey]; + stamp.timestampMs = getMSTime(); + stamp.x = posX; + stamp.y = posY; + stamp.z = posZ; + + return MoveTo(bot->GetMapId(), posX, posY, posZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + + // Reached LOS spot — clear the replay stamp. + s_lastLosMove.erase(losKey); + + // Bot is parked at LOS spot. Face away from the LOS tomb only when our + // zone has no kill-candidates left (every remaining tomb is protected). + // While extras are still up, the bot must keep facing them so it can DPS. + if (myZoneAllProtected) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->SetTarget(ObjectGuid::Empty); + bot->SetFacingTo(losTomb->GetAngle(bot)); + } + + return HandleRtiMarking(group, groupIndex, myTombs, losTomb); +} + +bool IccSindragosaFrostBombAction::CollectContext(FrostBombContext& ctx) const +{ + constexpr uint32 tombEntries[] = {NPC_TOMB1, NPC_TOMB2, NPC_TOMB3, NPC_TOMB4}; + + std::list units; + float const range = 200.0f; + Acore::AnyUnitInObjectRangeCheck check(bot, range); + Acore::UnitListSearcher searcher(bot, units, check); + Cell::VisitObjects(bot, searcher, range); + + for (Unit* unit : units) + { + if (!unit || !unit->IsAlive()) + continue; + + if (unit->HasAura(SPELL_FROST_BOMB_VISUAL)) + ctx.marker = unit; + + for (uint32 entry : tombEntries) + { + if (unit->GetEntry() == entry) + { + ctx.tombs.push_back(unit); + break; + } + } + } + + return ctx.marker && !ctx.tombs.empty(); +} + +int IccSindragosaFrostBombAction::ResolveGroupIndex(Group* group) const +{ + // Collect bot-only GUIDs from the current group snapshot. + // Real players are excluded so they are never assigned a group index and + // never become the designated marker; only bots manage icons. + uint32 const instanceId = bot->GetInstanceId(); + std::vector currentGuids; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !sPlayerbotsMgr.GetPlayerbotAI(member)) + continue; + currentGuids.push_back(member->GetGUID()); + } + std::sort(currentGuids.begin(), currentGuids.end()); + + Difficulty const diff = bot->GetRaidDifficulty(); + int const groupCount = (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC) ? 3 : 2; + + // Assign any GUIDs not yet seen, preserving all existing assignments. + // Never clear s_groupAssignments — this ensures bots that temporarily + // drop from the group (die, get entombed) keep their original group index + // and don't trigger a full reshuffle that migrates other bots. + for (ObjectGuid const& guid : currentGuids) + { + auto const key = std::make_pair(instanceId, guid); + if (s_groupAssignments.find(key) == s_groupAssignments.end()) + { + // Assign to the group with the fewest members so far (this instance only) + std::array counts = {}; + for (auto const& [k, idx] : s_groupAssignments) + if (k.first == instanceId && idx < groupCount) + ++counts[idx]; + + int minGroup = 0; + for (int g = 1; g < groupCount; ++g) + if (counts[g] < counts[minGroup]) + minGroup = g; + + s_groupAssignments[key] = minGroup; + } + } + + auto it = s_groupAssignments.find(std::make_pair(instanceId, bot->GetGUID())); + return it != s_groupAssignments.end() ? it->second : -1; +} + +void IccSindragosaFrostBombAction::PinGroupToCurrentZone() +{ + static constexpr std::array tombZones = { + &ICC_SINDRAGOSA_THOMB1_POSITION, + &ICC_SINDRAGOSA_THOMB2_POSITION, + &ICC_SINDRAGOSA_THOMB3_POSITION + }; + + Difficulty const diff = bot->GetRaidDifficulty(); + int const groupCount = + (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC) ? 3 : 2; + + // Find the tomb the bot is inside (ice tomb spawns on top of the frozen bot). + // Mapping the bot's group via the actual tomb position guarantees consistency + // with SelectTombs, which classifies tombs by nearest anchor. Falling back to + // raw bot position vs anchor would mis-pin when the boss/beacon dragged the + // bot into a different zone than where the tomb was assigned. + constexpr uint32 tombEntries[] = {NPC_TOMB1, NPC_TOMB2, NPC_TOMB3, NPC_TOMB4}; + + std::list units; + float const range = 15.0f; + Acore::AnyUnitInObjectRangeCheck check(bot, range); + Acore::UnitListSearcher searcher(bot, units, check); + Cell::VisitObjects(bot, searcher, range); + + Unit* myTomb = nullptr; + float minDist = std::numeric_limits::max(); + for (Unit* unit : units) + { + if (!unit || !unit->IsAlive()) + continue; + bool isTomb = false; + for (uint32 entry : tombEntries) + { + if (unit->GetEntry() == entry) + { + isTomb = true; + break; + } + } + if (!isTomb) + continue; + float const d = bot->GetExactDist2d(unit); + if (d < minDist) + { + minDist = d; + myTomb = unit; + } + } + + int bestGroup = 0; + float bestDist = std::numeric_limits::max(); + + // Anchor-from-tomb path (preferred): map nearest tomb -> nearest anchor -> group. + if (myTomb) + { + for (int g = 0; g < groupCount; ++g) + { + int const zoneIdx = (groupCount == 2) ? (g == 0 ? 0 : 2) : g; + float const d = myTomb->GetExactDist2d(*tombZones[zoneIdx]); + if (d < bestDist) + { + bestDist = d; + bestGroup = g; + } + } + } + else + { + // Fallback: use bot position if no tomb visible (shouldn't happen while tombed, + // but guard against odd states). + for (int g = 0; g < groupCount; ++g) + { + int const zoneIdx = (groupCount == 2) ? (g == 0 ? 0 : 2) : g; + float const d = bot->GetExactDist2d(*tombZones[zoneIdx]); + if (d < bestDist) + { + bestDist = d; + bestGroup = g; + } + } + } + + s_groupAssignments[std::make_pair(bot->GetInstanceId(), bot->GetGUID())] = bestGroup; +} + +std::vector IccSindragosaFrostBombAction::SelectTombs(std::vector const& tombs, int groupIndex, int groupCount) const +{ + if (tombs.empty()) + return {}; + + // Map group index to its intended zone anchor + static constexpr std::array tombZones = { + &ICC_SINDRAGOSA_THOMB1_POSITION, + &ICC_SINDRAGOSA_THOMB2_POSITION, + &ICC_SINDRAGOSA_THOMB3_POSITION + }; + int const zoneIdx = (groupCount == 2) ? (groupIndex == 0 ? 0 : 2) : groupIndex; + Position const& zone = *tombZones[zoneIdx]; + + static constexpr float MAX_ZONE_RADIUS = 3.0f; + std::vector zoneTombs; + for (Unit* tomb : tombs) + { + int closestZone = 0; + float closestDist = tomb->GetExactDist2d(*tombZones[0]); + for (int z = 1; z < 3; ++z) + { + // Skip zone 1 (THOMB2) in 2-group mode — it is never assigned + if (groupCount == 2 && z == 1) + continue; + float d = tomb->GetExactDist2d(*tombZones[z]); + if (d < closestDist) + { + closestDist = d; + closestZone = z; + } + } + if (closestZone != zoneIdx) + continue; + if (closestDist > MAX_ZONE_RADIUS) + continue; + zoneTombs.push_back(tomb); + } + return zoneTombs; +} + +Unit* IccSindragosaFrostBombAction::ResolveStickyTomb(std::vector const& myTombs) +{ + // Keep the previously assigned tomb while it is still a valid member of + // this group's zone. This avoids the cascading reassignment where a tomb's + // HP fluctuation causes every bot to flip to a different LOS spot each tick. + auto const key = std::make_pair(bot->GetInstanceId(), bot->GetGUID()); + auto it = s_tombAssignments.find(key); + if (it != s_tombAssignments.end()) + { + for (Unit* tomb : myTombs) + { + if (tomb->GetGUID() == it->second && tomb->IsAlive()) + return tomb; + } + } + + // No valid sticky — pick the tomb nearest to the bot so the first + // assignment snaps to where the bot already stands. + Unit* nearest = nullptr; + float minDist = std::numeric_limits::max(); + for (Unit* tomb : myTombs) + { + if (!tomb->IsAlive()) + continue; + float const d = bot->GetExactDist2d(tomb); + if (d < minDist) + { + minDist = d; + nearest = tomb; + } + } + + if (nearest) + s_tombAssignments[key] = nearest->GetGUID(); + else + s_tombAssignments.erase(key); + + return nearest; +} + +bool IccSindragosaFrostBombAction::HandleRtiMarking(Group* group, int groupIndex, std::vector const& myTombs, Unit* losTomb) +{ + constexpr uint8 SKULL_ICON = 7; + constexpr uint8 CROSS_ICON = 6; + constexpr uint8 STAR_ICON = 0; + constexpr float TOMB_STOP_HP_PCT = 40.0f; + constexpr float TOMB_STOP_HP_PCT_10_MAN = 60.0f; + + Difficulty const diff = bot->GetRaidDifficulty(); + bool const is10Man = (diff == RAID_DIFFICULTY_10MAN_NORMAL || diff == RAID_DIFFICULTY_10MAN_HEROIC); + + uint8 iconIndex = 0; + std::string rtiValue; + + switch (groupIndex) + { + case 0: iconIndex = SKULL_ICON; rtiValue = "skull"; break; + case 1: iconIndex = CROSS_ICON; rtiValue = "cross"; break; + case 2: iconIndex = STAR_ICON; rtiValue = "star"; break; + default: return false; + } + + context->GetValue("rti")->Set(rtiValue); + + Unit* currentIconUnit = botAI->GetUnit(group->GetTargetIcon(iconIndex)); + + Unit* tombToMark = nullptr; + + // Prefer to keep the current icon if it is still a valid extra in our zone. + if (currentIconUnit && currentIconUnit->IsAlive() && currentIconUnit != losTomb && + !(is10Man && currentIconUnit->GetHealthPct() <= TOMB_STOP_HP_PCT_10_MAN)) + { + for (Unit* tomb : myTombs) + { + if (tomb == currentIconUnit) + { + tombToMark = currentIconUnit; + break; + } + } + } + + // No valid current icon — pick a new extra deterministically (lowest GUID) + // so every bot in the group agrees on the same choice. + if (!tombToMark) + { + ObjectGuid bestGuid; + for (Unit* tomb : myTombs) + { + if (!tomb || !tomb->IsAlive() || tomb == losTomb) + continue; + if (is10Man && tomb->GetHealthPct() <= TOMB_STOP_HP_PCT_10_MAN) + continue; + if (!tombToMark || tomb->GetGUID() < bestGuid) + { + tombToMark = tomb; + bestGuid = tomb->GetGUID(); + } + } + } + + // No extras left — DPS the sticky down to the stop threshold + float const stickyStopPct = is10Man ? TOMB_STOP_HP_PCT_10_MAN : TOMB_STOP_HP_PCT; + if (!tombToMark && losTomb && losTomb->IsAlive() && losTomb->GetHealthPct() > stickyStopPct) + tombToMark = losTomb; + + if (!tombToMark) + { + // All tombs at/below threshold — clear icon and stand idle + ObjectGuid const currentIcon = group->GetTargetIcon(iconIndex); + if (!currentIcon.IsEmpty()) + group->SetTargetIcon(iconIndex, bot->GetGUID(), ObjectGuid::Empty); + + bot->AttackStop(); + return true; + } + + if (!currentIconUnit || !currentIconUnit->IsAlive() || currentIconUnit != tombToMark) + group->SetTargetIcon(iconIndex, bot->GetGUID(), tombToMark->GetGUID()); + + // Let combat actions fire so this bot DPSes the marked tomb + return false; +} \ No newline at end of file diff --git a/src/Ai/Raid/ICC/Action/ICCActions_SS.cpp b/src/Ai/Raid/ICC/Action/ICCActions_SS.cpp new file mode 100644 index 00000000000..1259650c444 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_SS.cpp @@ -0,0 +1,61 @@ +#include "ICCActions.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "Vehicle.h" +#include "RtiValue.h" +#include "GenericSpellActions.h" +#include "GenericActions.h" +#include "ICCTriggers.h" +#include "Multiplier.h" + +bool IccValkyreSpearAction::Execute(Event /*event*/) +{ + // Find the nearest spear + Creature* spear = bot->FindNearestCreature(NPC_SPEAR, 100.0f); + if (!spear) + return false; + + // Move to the spear if not in range + if (!spear->IsWithinDistInMap(bot, INTERACTION_DISTANCE)) + return MoveTo(spear, INTERACTION_DISTANCE); + + // Remove shapeshift forms + botAI->RemoveShapeshift(); + + // Stop movement and click the spear + bot->GetMotionMaster()->Clear(); + bot->StopMoving(); + spear->HandleSpellClick(bot); + + // Dismount if mounted + WorldPacket emptyPacket; + bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); + + return false; +} + +bool IccSisterSvalnaAction::Execute(Event /*event*/) +{ + Unit* svalna = AI_VALUE2(Unit*, "find target", "sister svalna"); + if (!svalna || !svalna->HasAura(SPELL_AETHER_SHIELD)) + return false; + + // Check if bot has the spear item + if (!botAI->HasItemInInventory(ITEM_SPEAR)) + return false; + + // Get all items from inventory + std::vector items = botAI->GetInventoryItems(); + for (Item* item : items) + { + if (item->GetEntry() == ITEM_SPEAR) + { + // Use spear on Svalna + botAI->ImbueItem(item, svalna); + return false; + } + } + + return false; +} \ No newline at end of file diff --git a/src/Ai/Raid/ICC/Action/ICCActions_VT.cpp b/src/Ai/Raid/ICC/Action/ICCActions_VT.cpp new file mode 100644 index 00000000000..9c800d9a184 --- /dev/null +++ b/src/Ai/Raid/ICC/Action/ICCActions_VT.cpp @@ -0,0 +1,1357 @@ +#include +#include + +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "Multiplier.h" +#include "NearestNpcsValue.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "ICCActions.h" +#include "ICCTriggers.h" +#include "RtiValue.h" +#include "Vehicle.h" + +namespace +{ +// How long (ms) all bots stay at a cloud before advancing to the next one. +// This window lets every portal bot teleport in and collect the cloud together. +constexpr uint32 CLOUD_SYNC_WAIT_MS = 1000; + +struct ValithriaCloudSync +{ + ObjectGuid targetCloudGuid; + uint32 moveOnAfterMs = 0; +}; + +std::unordered_map VdwCloudSync; // key: map instance ID + +// Per-instance per-healer remembered portal claim. Other healers in the same +// instance see these claims and avoid taking the same portal. Outer key is the +// map instance ID so concurrent ICC raids don't share or evict each other's claims. +std::unordered_map> VdwPortalClaim; +} + +static bool CastClassTaunt(Player* bot, PlayerbotAI* botAI, Unit* target) +{ + if (!target || !target->IsAlive()) + return false; + + switch (bot->getClass()) + { + case CLASS_PALADIN: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true); + if (botAI->CastSpell("hand of reckoning", target)) + return true; + break; + } + case CLASS_DEATH_KNIGHT: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true); + if (botAI->CastSpell("dark command", target)) + return true; + break; + } + case CLASS_DRUID: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true); + if (botAI->CastSpell("growl", target)) + return true; + break; + } + case CLASS_WARRIOR: + { + bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true); + if (botAI->CastSpell("taunt", target)) + return true; + break; + } + default: + break; + } + + if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target)) + return true; + + return false; +} + +static std::vector GetCreaturesByEntry(WorldObject* searcher, uint32 entry, float range) +{ + std::list raw; + searcher->GetCreatureListWithEntryInGrid(raw, entry, range); + + std::vector out; + out.reserve(raw.size()); + for (Creature* c : raw) + if (c && c->IsAlive()) + out.push_back(c); + + return out; +} + +static std::vector GetCreaturesByEntries(WorldObject* searcher, std::initializer_list entries, + float range) +{ + std::vector out; + for (uint32 entry : entries) + { + auto part = GetCreaturesByEntry(searcher, entry, range); + out.insert(out.end(), part.begin(), part.end()); + } + std::sort(out.begin(), out.end(), [](Creature const* a, Creature const* b) { return a->GetGUID() < b->GetGUID(); }); + out.erase(std::unique(out.begin(), out.end()), out.end()); + + return out; +} + +bool IccValithriaGroupAction::Execute(Event /*event*/) +{ + std::vector const portalList = GetCreaturesByEntries( + bot, {NPC_DREAM_PORTAL, NPC_DREAM_PORTAL_PRE_EFFECT, NPC_NIGHTMARE_PORTAL, NPC_NIGHTMARE_PORTAL_PRE_EFFECT}, + 100.0f); + Creature* portal = portalList.empty() ? nullptr : portalList.front(); + + Creature* worm = bot->FindNearestCreature(NPC_ROT_WORM, 100.0f); + Creature* zombie = bot->FindNearestCreature(NPC_BLISTERING_ZOMBIE, 100.0f); + Creature* manaVoid = bot->FindNearestCreature(NPC_MANA_VOID, 100.0f); + + // Column of Frost units - still hostile so the hostile list is fine here + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector frostColumns; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_COLUMN_OF_FROST) + frostColumns.push_back(unit); + } + + // Tanks collect stray Gluttonous Abominations / Rot Worms anywhere. + // Each tank picks NEAREST stray so two tanks naturally split work. + // In taunt range -> taunt. Out of range -> step toward add to close gap. + if (botAI->IsTank(bot)) + { + constexpr float ADD_TAUNT_RANGE = 30.0f; + constexpr float CHASE_STEP = 10.0f; + + Unit* strayAdd = nullptr; + float bestDist = FLT_MAX; + for (ObjectGuid const& guid : AI_VALUE(GuidVector, "possible targets")) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + if (unit->GetEntry() != NPC_GLUTTONOUS_ABOMINATION && unit->GetEntry() != NPC_ROT_WORM) + continue; + + Unit* victim = unit->GetVictim(); + Player* victimPlayer = victim ? victim->ToPlayer() : nullptr; + if (victimPlayer && botAI->IsTank(victimPlayer)) + continue; + + float const d = bot->GetExactDist2d(unit); + if (d < bestDist) + { + bestDist = d; + strayAdd = unit; + } + } + + if (strayAdd) + { + if (bestDist <= ADD_TAUNT_RANGE) + { + CastClassTaunt(bot, botAI, strayAdd); + } + else + { + float dx = strayAdd->GetPositionX() - bot->GetPositionX(); + float dy = strayAdd->GetPositionY() - bot->GetPositionY(); + float const len = std::sqrt(dx * dx + dy * dy); + if (len > 0.001f) + { + dx /= len; + dy /= len; + float const step = std::min(CHASE_STEP, bestDist - ADD_TAUNT_RANGE + 5.0f); + float const moveX = bot->GetPositionX() + dx * step; + float const moveY = bot->GetPositionY() + dy * step; + MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + return true; + } + } + } + } + + // Healers move toward the heal position when no portal is active + if (botAI->IsHeal(bot) && !portal && bot->GetExactDist2d(ICC_VDW_HEAL_POSITION) > 30.0f) + { + return MoveTo(bot->GetMapId(), ICC_VDW_HEAL_POSITION.GetPositionX(), ICC_VDW_HEAL_POSITION.GetPositionY(), + ICC_VDW_HEAL_POSITION.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_NORMAL); + } + + // Avoidance + if (manaVoid && bot->GetExactDist2d(manaVoid) < 10.0f && !botAI->GetAura("Twisted Nightmares", bot) && + !botAI->GetAura("Emerald Vigor", bot)) + { + botAI->Reset(); + FleePosition(manaVoid->GetPosition(), 11.0f, 250U); + } + + for (Unit* column : frostColumns) + { + if (column && bot->GetExactDist2d(column) < 7.0f) + { + botAI->Reset(); + FleePosition(column->GetPosition(), 8.0f, 250U); + } + } + + if (worm && worm->IsAlive() && worm->GetVictim() == bot && !botAI->IsTank(bot)) + { + botAI->Reset(); + FleePosition(worm->GetPosition(), 10.0f, 250U); + } + + // Zombie handling: only exploding-zombie flee here (any non-tank within 20f). + // Per-bot kiting when zombie victim handled by IccValithriaZombieKiteAction + // (higher priority trigger ACTION_EMERGENCY+9, blocks lower actions). + std::list allZombies; + bot->GetCreatureListWithEntryInGrid(allZombies, NPC_BLISTERING_ZOMBIE, 100.0f); + + Creature* explodingZombie = nullptr; + Creature* nearbyZombie = nullptr; + float nearbyDist = FLT_MAX; + + for (Creature* z : allZombies) + { + if (!z || !z->IsAlive()) + continue; + + bool const exploding = + z->HealthBelowPct(5) || z->FindCurrentSpellBySpellId(SPELL_ACID_BURST) || z->HasAura(SPELL_ACID_BURST); + float const d = bot->GetExactDist2d(z); + + if (exploding && d < 20.0f && (!explodingZombie || d < bot->GetExactDist2d(explodingZombie))) + explodingZombie = z; + + if (d < nearbyDist) + { + nearbyDist = d; + nearbyZombie = z; + } + } + + if (explodingZombie) + { + botAI->Reset(); + return FleePosition(explodingZombie->GetPosition(), 20.0f, 250U); + } + + // Non-tank melee must never sit inside 15f of any zombie: the zombie can + // flip victim at any time and at melee range the bot has no time to kite. + if (nearbyZombie && botAI->IsMelee(bot) && !botAI->IsTank(bot) && nearbyDist < 15.0f) + { + botAI->Reset(); + return FleePosition(nearbyZombie->GetPosition(), 15.0f, 250U); + } + + if (nearbyZombie && !botAI->IsMainTank(bot) && !botAI->IsHeal(bot) && nearbyZombie->GetVictim() != bot) + ApplyCrowdControl(nearbyZombie); + + // Leash: every role stays within 35f of the boss anchor unless doing an + // exclusive task (zombie kite, portal work, dream-state cloud collection). + // Zombie kite action runs at ACTION_EMERGENCY+9 so it preempts this action; + // extra skip-conditions below guard the remaining exclusive tasks. + constexpr float LEASH_RADIUS = 35.0f; + bool const inDreamState = bot->HasAura(SPELL_DREAM_STATE); + bool hasPortalClaim = false; + if (botAI->IsHeal(bot)) + { + auto instanceIt = VdwPortalClaim.find(bot->GetMap()->GetInstanceId()); + if (instanceIt != VdwPortalClaim.end() && + instanceIt->second.find(bot->GetGUID()) != instanceIt->second.end()) + hasPortalClaim = true; + } + bool const hasZombieThreat = nearbyZombie && nearbyZombie->GetVictim() == bot; + if (!inDreamState && !hasPortalClaim && !hasZombieThreat) + { + float const distToAnchor = bot->GetExactDist2d(ICC_VDW_HEAL_POSITION); + if (distToAnchor > LEASH_RADIUS) + { + constexpr float STEP = 8.0f; + float const dx = ICC_VDW_HEAL_POSITION.GetPositionX() - bot->GetPositionX(); + float const dy = ICC_VDW_HEAL_POSITION.GetPositionY() - bot->GetPositionY(); + float const dz = ICC_VDW_HEAL_POSITION.GetPositionZ() - bot->GetPositionZ(); + float const dist = std::hypot(dx, dy); + float moveX; + float moveY; + float moveZ; + if (dist > STEP) + { + moveX = bot->GetPositionX() + (dx / dist) * STEP; + moveY = bot->GetPositionY() + (dy / dist) * STEP; + moveZ = bot->GetPositionZ() + (dz / dist) * STEP; + } + else + { + moveX = ICC_VDW_HEAL_POSITION.GetPositionX(); + moveY = ICC_VDW_HEAL_POSITION.GetPositionY(); + moveZ = ICC_VDW_HEAL_POSITION.GetPositionZ(); + } + MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + return true; + } + } + + Difficulty const diff = bot->GetRaidDifficulty(); + Group* group = bot->GetGroup(); + + if (group && (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC)) + return Handle25ManGroupLogic(); + + return Handle10ManGroupLogic(); +} + +bool IccValithriaGroupAction::ApplyCrowdControl(Unit* zombie) +{ + switch (bot->getClass()) + { + case CLASS_MAGE: + if (!botAI->HasAura("Frost Nova", zombie)) + return botAI->CastSpell("Frost Nova", zombie); + break; + case CLASS_DRUID: + if (!botAI->HasAura("Entangling Roots", zombie)) + return botAI->CastSpell("Entangling Roots", zombie); + break; + case CLASS_PALADIN: + if (!botAI->HasAura("Hammer of Justice", zombie)) + return botAI->CastSpell("Hammer of Justice", zombie); + break; + case CLASS_WARRIOR: + if (!botAI->HasAura("Hamstring", zombie)) + return botAI->CastSpell("Hamstring", zombie); + break; + case CLASS_HUNTER: + if (!botAI->HasAura("Concussive Shot", zombie)) + return botAI->CastSpell("Concussive Shot", zombie); + break; + case CLASS_ROGUE: + if (!botAI->HasAura("Kidney Shot", zombie)) + return botAI->CastSpell("Kidney Shot", zombie); + break; + case CLASS_SHAMAN: + if (!botAI->HasAura("Frost Shock", zombie)) + return botAI->CastSpell("Frost Shock", zombie); + break; + case CLASS_DEATH_KNIGHT: + if (!botAI->HasAura("Chains of Ice", zombie)) + return botAI->CastSpell("Chains of Ice", zombie); + break; + case CLASS_PRIEST: + if (!botAI->HasAura("Psychic Scream", zombie)) + return botAI->CastSpell("Psychic Scream", zombie); + break; + case CLASS_WARLOCK: + if (!botAI->HasAura("Fear", zombie)) + return botAI->CastSpell("Fear", zombie); + break; + default: + break; + } + + return false; +} + +bool IccValithriaGroupAction::Handle25ManGroupLogic() +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + // Priority order follows Wowhead/Icy-Veins strategy: + // Blazing Skeleton (AoE Lay Waste) -> Suppresser (stacks -10% heal) -> + // Risen Archmage (Mana Void/Column of Frost) -> Blistering Zombie (ranged + // kites it, melee flees at 15f) -> Gluttonous Abomination (tank keeps it + // faced away) -> Rot Worm (cleanup from dead Abominations). + static constexpr std::array ADD_PRIORITY_CHECK = {NPC_BLAZING_SKELETON, NPC_SUPPRESSER, + NPC_RISEN_ARCHMAGE, NPC_BLISTERING_ZOMBIE, + NPC_GLUTTONOUS_ABOMINATION, NPC_ROT_WORM}; + constexpr float MARK_RADIUS = 45.0f; + + int priorityAddsAlive = 0; + GuidVector const targets = AI_VALUE(GuidVector, "possible targets"); + for (ObjectGuid const& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + uint32 const e = unit->GetEntry(); + bool isPriority = false; + for (uint32 pe : ADD_PRIORITY_CHECK) + { + if (pe == e) + { + isPriority = true; + break; + } + } + if (!isPriority) + continue; + float const d = + unit->GetExactDist2d(ICC_VDW_HEAL_POSITION.GetPositionX(), ICC_VDW_HEAL_POSITION.GetPositionY()); + if (d > MARK_RADIUS) + continue; + ++priorityAddsAlive; + if (priorityAddsAlive >= 2) + break; + } + + bool const singleMarkMode = priorityAddsAlive <= 1; + + std::vector eligible; + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (!memberAI || memberAI->IsRealPlayer()) + continue; + + if (memberAI->IsHeal(member) && member->HasAura(SPELL_DREAM_STATE)) + continue; + + if (!memberAI->IsTank(member) && !memberAI->IsDps(member) && !memberAI->IsHeal(member)) + continue; + + eligible.push_back(member); + } + + std::sort(eligible.begin(), eligible.end(), + [](Player const* a, Player const* b) { return a->GetGUID() < b->GetGUID(); }); + + bool inGroup1 = false; + bool inGroup2 = false; + + if (singleMarkMode) + { + inGroup1 = std::any_of(eligible.begin(), eligible.end(), [this](Player* p) { return p == bot; }); + } + else + { + // Bucket by role (tank / melee dps / ranged dps / healer) so each group + // gets roughly the same number of each role. Within a bucket alternate + // assignment; flip the starting side on odd-sized buckets so leftover + // slots don't all pile onto Group1. + std::vector tanks; + std::vector meleeDps; + std::vector rangedDps; + std::vector healers; + for (Player* p : eligible) + { + PlayerbotAI* pai = GET_PLAYERBOT_AI(p); + if (!pai) + continue; + if (pai->IsTank(p)) + tanks.push_back(p); + else if (pai->IsHeal(p)) + healers.push_back(p); + else if (pai->IsMelee(p)) + meleeDps.push_back(p); + else + rangedDps.push_back(p); + } + + std::vector group1; + std::vector group2; + int parity = 0; + auto splitBucket = [&](std::vector const& bucket) + { + for (size_t i = 0; i < bucket.size(); ++i) + { + if (((i + parity) & 1) == 0) + group1.push_back(bucket[i]); + else + group2.push_back(bucket[i]); + } + if (bucket.size() & 1) + parity ^= 1; + }; + splitBucket(tanks); + splitBucket(meleeDps); + splitBucket(rangedDps); + splitBucket(healers); + + inGroup1 = std::any_of(group1.begin(), group1.end(), [this](Player* p) { return p == bot; }); + inGroup2 = std::any_of(group2.begin(), group2.end(), [this](Player* p) { return p == bot; }); + } + + if (botAI->IsTank(bot) || botAI->IsDps(bot) || botAI->IsHeal(bot)) + HandleMarkingLogic(inGroup1, inGroup2, singleMarkMode); + + return false; +} + +bool IccValithriaGroupAction::HandleMarkingLogic(bool inGroup1, bool inGroup2, bool singleMarkMode) +{ + static constexpr uint8 SKULL_ICON = 7; + static constexpr uint8 CROSS_ICON = 6; + // Kill priority per Wowhead/Icy-Veins: Lay Waste skeletons first, then + // heal-debuff suppressers, then archmages, then zombies (ranged only - + // melee flee at 15f), then abominations (tank-faced), then rot worm cleanup. + static constexpr std::array ADD_PRIORITY = {NPC_BLAZING_SKELETON, NPC_SUPPRESSER, + NPC_RISEN_ARCHMAGE, NPC_BLISTERING_ZOMBIE, + NPC_GLUTTONOUS_ABOMINATION, NPC_ROT_WORM}; + + uint8 iconIndex; + std::string rtiValue; + + if (singleMarkMode) + { + if (!inGroup1) + return false; + iconIndex = SKULL_ICON; + rtiValue = "skull"; + } + else if (inGroup1) + { + iconIndex = SKULL_ICON; + rtiValue = "skull"; + } + else if (inGroup2) + { + iconIndex = CROSS_ICON; + rtiValue = "cross"; + } + else + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + constexpr float MARK_RADIUS = 45.0f; + + auto inMarkRange = [&](Unit* unit) -> bool + { + return unit->GetExactDist2d(ICC_VDW_HEAL_POSITION.GetPositionX(), ICC_VDW_HEAL_POSITION.GetPositionY()) <= + MARK_RADIUS; + }; + + context->GetValue("rti")->Set(rtiValue); + + bool const dispatchRti = !botAI->IsTank(bot) && !botAI->IsHeal(bot); + bool const isMeleeDps = botAI->IsMelee(bot) && !botAI->IsTank(bot); + + auto tryDispatchRti = [&](ObjectGuid rtiGuid) + { + if (!dispatchRti || rtiGuid.IsEmpty()) + return; + // Melee DPS must not attack zombies - they explode at melee range. + // Ranged DPS still handle the marked zombie via RTI. + Unit* rtiUnit = botAI->GetUnit(rtiGuid); + if (rtiUnit && rtiUnit->GetEntry() == NPC_BLISTERING_ZOMBIE && isMeleeDps) + return; + Unit* const victim = bot->GetVictim(); + if (victim && victim->GetGUID() == rtiGuid) + return; + botAI->DoSpecificAction("attack rti target"); + }; + + GuidVector const adds = AI_VALUE(GuidVector, "possible targets"); + + auto getPriorityRank = [&](uint32 entry) -> int + { + for (size_t i = 0; i < ADD_PRIORITY.size(); ++i) + if (ADD_PRIORITY[i] == entry) + return static_cast(i); + return -1; + }; + + auto isValidAdd = [&](Unit* unit) -> bool + { + if (!unit || !unit->IsAlive()) + return false; + if (getPriorityRank(unit->GetEntry()) < 0) + return false; + return inMarkRange(unit); + }; + + ObjectGuid otherIconGuid; + if (!singleMarkMode) + { + uint8 const otherIcon = (iconIndex == SKULL_ICON) ? CROSS_ICON : SKULL_ICON; + otherIconGuid = group->GetTargetIcon(otherIcon); + } + + Unit* currentIconUnit = botAI->GetUnit(group->GetTargetIcon(iconIndex)); + + // Split the room by a world-Y line through the heal anchor. Group1 (skull) + // owns one half, Group2 (cross) owns the other. A group only falls back to + // the other half when its own side has no marking candidate. + auto isOwnSide = [&](Unit* unit) -> bool + { + if (singleMarkMode) + return true; + bool const sideA = unit->GetPositionY() < ICC_VDW_HEAL_POSITION.GetPositionY(); + return inGroup1 ? sideA : !sideA; + }; + + // Pick the reference point for "closest add" selection so DPS travels the + // shortest distance when the mark flips: prefer the current icon's + // position (alive or corpse), then the other group's still-live mark, + // then the heal anchor. + Position refPos = ICC_VDW_HEAL_POSITION; + if (currentIconUnit) + refPos = currentIconUnit->GetPosition(); + else if (!singleMarkMode) + { + if (Unit* otherUnit = botAI->GetUnit(otherIconGuid)) + if (otherUnit->IsAlive()) + refPos = otherUnit->GetPosition(); + } + + Unit* bestTarget = nullptr; + int bestRank = static_cast(ADD_PRIORITY.size()); + bool bestTargetOwnSide = false; + + // Two-pass scan: own side first, then any side. Within each pass walk + // priorities top-down and within the first tier that has any candidate + // pick the one closest to refPos. + for (int pass = 0; pass < 2 && !bestTarget; ++pass) + { + bool const ownSideOnly = (pass == 0); + for (size_t rank = 0; rank < ADD_PRIORITY.size(); ++rank) + { + uint32 const entry = ADD_PRIORITY[rank]; + float bestDist = FLT_MAX; + for (ObjectGuid const& guid : adds) + { + if (!singleMarkMode && guid == otherIconGuid) + continue; + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || unit->GetEntry() != entry) + continue; + if (!inMarkRange(unit)) + continue; + if (ownSideOnly && !isOwnSide(unit)) + continue; + float const d = unit->GetExactDist2d(refPos.GetPositionX(), refPos.GetPositionY()); + if (d < bestDist) + { + bestDist = d; + bestTarget = unit; + bestRank = static_cast(rank); + bestTargetOwnSide = ownSideOnly; + } + } + if (bestTarget) + break; + } + } + + if (isValidAdd(currentIconUnit)) + { + int currentRank = -1; + if (currentIconUnit) + currentRank = getPriorityRank(currentIconUnit->GetEntry()); + + bool const currentOnOwnSide = isOwnSide(currentIconUnit); + + // Side takes precedence: if an own-side candidate exists and the current + // icon is on the other side, flip regardless of priority rank. + bool const sideFlip = bestTarget && bestTargetOwnSide && !currentOnOwnSide; + + if (!sideFlip && (!bestTarget || bestRank >= currentRank)) + { + if (currentIconUnit) + tryDispatchRti(currentIconUnit->GetGUID()); + return false; + } + } + + if (!bestTarget) + { + if (currentIconUnit && !group->GetTargetIcon(iconIndex).IsEmpty()) + group->SetTargetIcon(iconIndex, bot->GetGUID(), ObjectGuid::Empty); + return false; + } + + group->SetTargetIcon(iconIndex, bot->GetGUID(), bestTarget->GetGUID()); + + if (singleMarkMode) + { + uint8 const crossIcon = CROSS_ICON; + Unit* currentCrossUnit = botAI->GetUnit(group->GetTargetIcon(crossIcon)); + if (currentCrossUnit && !group->GetTargetIcon(crossIcon).IsEmpty()) + group->SetTargetIcon(crossIcon, bot->GetGUID(), ObjectGuid::Empty); + } + + tryDispatchRti(bestTarget->GetGUID()); + + return false; +} + +bool IccValithriaGroupAction::Handle10ManGroupLogic() +{ + static constexpr uint8 DEFAULT_ICON = 7; + // Kill priority per Wowhead/Icy-Veins: skeleton > suppresser > archmage > + // zombie (ranged) > abomination (tank) > rot worm (cleanup). + static constexpr std::array ADD_PRIORITY = {NPC_BLAZING_SKELETON, NPC_SUPPRESSER, + NPC_RISEN_ARCHMAGE, NPC_BLISTERING_ZOMBIE, + NPC_GLUTTONOUS_ABOMINATION, NPC_ROT_WORM}; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + PlayerbotAI* selfAI = GET_PLAYERBOT_AI(bot); + if (!selfAI || selfAI->IsRealPlayer()) + return false; + if (botAI->IsHeal(bot) && bot->HasAura(SPELL_DREAM_STATE)) + return false; + + constexpr float MARK_RADIUS = 45.0f; + + auto inMarkRange = [&](Unit* unit) -> bool + { + return unit->GetExactDist2d(ICC_VDW_HEAL_POSITION.GetPositionX(), ICC_VDW_HEAL_POSITION.GetPositionY()) <= + MARK_RADIUS; + }; + + context->GetValue("rti")->Set("skull"); + + bool const dispatchRti = !botAI->IsTank(bot) && !botAI->IsHeal(bot); + bool const isMeleeDps = botAI->IsMelee(bot) && !botAI->IsTank(bot); + + auto tryDispatchRti = [&](ObjectGuid rtiGuid) + { + if (!dispatchRti || rtiGuid.IsEmpty()) + return; + // Melee DPS must not attack zombies - ranged only. + Unit* rtiUnit = botAI->GetUnit(rtiGuid); + if (rtiUnit && rtiUnit->GetEntry() == NPC_BLISTERING_ZOMBIE && isMeleeDps) + return; + Unit* const victim = bot->GetVictim(); + if (victim && victim->GetGUID() == rtiGuid) + return; + botAI->DoSpecificAction("attack rti target"); + }; + + GuidVector const adds = AI_VALUE(GuidVector, "possible targets"); + + auto getPriorityRank = [&](uint32 entry) -> int + { + for (size_t i = 0; i < ADD_PRIORITY.size(); ++i) + if (ADD_PRIORITY[i] == entry) + return static_cast(i); + return -1; + }; + + auto isValidAdd = [&](Unit* unit) -> bool + { + if (!unit || !unit->IsAlive()) + return false; + if (getPriorityRank(unit->GetEntry()) < 0) + return false; + return inMarkRange(unit); + }; + + Unit* currentIconUnit = botAI->GetUnit(group->GetTargetIcon(DEFAULT_ICON)); + + // Use the current mark's position (alive or corpse) as the reference so + // DPS moves the least when the mark flips; fall back to the heal anchor. + Position refPos = ICC_VDW_HEAL_POSITION; + if (currentIconUnit) + refPos = currentIconUnit->GetPosition(); + + Unit* bestTarget = nullptr; + int bestRank = static_cast(ADD_PRIORITY.size()); + + for (size_t rank = 0; rank < ADD_PRIORITY.size(); ++rank) + { + uint32 const entry = ADD_PRIORITY[rank]; + float bestDist = FLT_MAX; + for (ObjectGuid const& guid : adds) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || unit->GetEntry() != entry) + continue; + if (!inMarkRange(unit)) + continue; + float const d = unit->GetExactDist2d(refPos.GetPositionX(), refPos.GetPositionY()); + if (d < bestDist) + { + bestDist = d; + bestTarget = unit; + bestRank = static_cast(rank); + } + } + if (bestTarget) + break; + } + if (isValidAdd(currentIconUnit)) + { + int currentRank; + if (currentIconUnit) + currentRank = getPriorityRank(currentIconUnit->GetEntry()); + else + { + // Handle the null case appropriately, e.g.: + currentRank = -1; // or another default/error value + } + + if (!bestTarget || bestRank >= currentRank) + { + if (currentIconUnit) + tryDispatchRti(currentIconUnit->GetGUID()); + return false; + } + } + + if (!bestTarget) + { + if (currentIconUnit && !group->GetTargetIcon(DEFAULT_ICON).IsEmpty()) + group->SetTargetIcon(DEFAULT_ICON, bot->GetGUID(), ObjectGuid::Empty); + return false; + } + + group->SetTargetIcon(DEFAULT_ICON, bot->GetGUID(), bestTarget->GetGUID()); + + tryDispatchRti(bestTarget->GetGUID()); + + return false; +} + +bool IccValithriaPortalAction::Execute(Event /*event*/) +{ + if (!botAI->IsHeal(bot) || bot->HasAura(SPELL_DREAM_STATE)) + return false; + + // Healer just dropped Dream State - if still airborne (portal exit leaves + // bots above the floor) snap to floor Z immediately so they don't fall and die. + constexpr float MAX_Z = 367.961f; + constexpr float TARGET_Z = 365.0f; + if (bot->GetPositionZ() > MAX_Z) + bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), TARGET_Z, bot->GetOrientation()); + + constexpr float SEARCH_RANGE = 200.0f; + + std::vector preEffectPortals = + GetCreaturesByEntries(bot, {NPC_DREAM_PORTAL_PRE_EFFECT, NPC_NIGHTMARE_PORTAL_PRE_EFFECT}, SEARCH_RANGE); + + std::vector realPortals = + GetCreaturesByEntries(bot, {NPC_DREAM_PORTAL, NPC_NIGHTMARE_PORTAL}, SEARCH_RANGE); + + // Evict stale claims BEFORE early-return so claims don't leak after fight. + // Scope eviction to this instance only - other ICC instances have their + // own portal GUID universes and we must not erase their claims. + uint32 const instanceId = bot->GetMap()->GetInstanceId(); + auto& claims = VdwPortalClaim[instanceId]; + + std::unordered_set livePortalGuids; + for (Creature* p : preEffectPortals) + if (p) + livePortalGuids.insert(p->GetGUID()); + for (Creature* p : realPortals) + if (p) + livePortalGuids.insert(p->GetGUID()); + + for (auto it = claims.begin(); it != claims.end();) + { + if (!livePortalGuids.count(it->second)) + it = claims.erase(it); + else + ++it; + } + + if (preEffectPortals.empty() && realPortals.empty()) + { + if (claims.empty()) + VdwPortalClaim.erase(instanceId); + return false; + } + + // Collect OTHER healers' claims so we never pick a portal already taken. + std::unordered_set reservedPortals; + for (auto const& kv : claims) + if (kv.first != bot->GetGUID()) + reservedPortals.insert(kv.second); + + auto pickClosestUnreserved = [&](std::vector const& portals) -> Creature* + { + Creature* best = nullptr; + float bestDist = FLT_MAX; + for (Creature* p : portals) + { + if (!p) + continue; + if (reservedPortals.count(p->GetGUID())) + continue; + float const d = bot->GetExactDist2d(p); + if (d < bestDist) + { + bestDist = d; + best = p; + } + } + return best; + }; + + // Prefer pre-effect (claim early so others see it). Fall through to real + // portal if no pre-effect available. + Creature* assigned = nullptr; + if (!preEffectPortals.empty()) + assigned = pickClosestUnreserved(preEffectPortals); + if (!assigned && !realPortals.empty()) + assigned = pickClosestUnreserved(realPortals); + + // If everything reserved (more healers than portals), fall back to nearest + // overall so this healer still does something useful. + if (!assigned) + { + std::vector const& pool = !preEffectPortals.empty() ? preEffectPortals : realPortals; + float bestDist = FLT_MAX; + for (Creature* p : pool) + { + if (!p) + continue; + float const d = bot->GetExactDist2d(p); + if (d < bestDist) + { + bestDist = d; + assigned = p; + } + } + } + + if (!assigned) + return false; + + claims[bot->GetGUID()] = assigned->GetGUID(); + + if (bot->GetDistance2d(assigned->GetPositionX(), assigned->GetPositionY()) > 0.5f) + { + MoveTo(assigned->GetMapId(), assigned->GetPositionX(), assigned->GetPositionY(), assigned->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + } + + botAI->RemoveShapeshift(); + + // Click the real portal once it spawns within reach. Only click our claimed + // portal (if it became real) or any real portal at our exact location. + Creature* clickTarget = nullptr; + float minDist = FLT_MAX; + for (Creature* portal : realPortals) + { + if (!portal) + continue; + float const d = bot->GetDistance2d(portal); + if (d < 3.0f && d < minDist) + { + clickTarget = portal; + minDist = d; + } + } + + if (clickTarget) + { + botAI->RemoveShapeshift(); + bot->GetMotionMaster()->Clear(); + bot->StopMoving(); + bot->SetFacingToObject(clickTarget); + clickTarget->HandleSpellClick(bot); + return true; + } + + return false; +} + +bool IccValithriaHealAction::Execute(Event /*event*/) +{ + if (!botAI->IsHeal(bot)) + return false; + + // Snap to floor Z first - healer may have just dropped Dream State and be + // airborne above MAX_Z. Run BEFORE the HP gate so it fires every tick. + constexpr float MAX_Z = 367.961f; + constexpr float TARGET_Z = 365.0f; + if (bot->GetPositionZ() > MAX_Z) + bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), TARGET_Z, bot->GetOrientation()); + + if (bot->GetHealthPct() < 50.0f) + return false; + + if (!bot->HasAura(SPELL_DREAM_STATE)) + { + constexpr float NORMAL_SPEED = 1.0f; + bot->SetSpeed(MOVE_RUN, NORMAL_SPEED, true); + bot->SetSpeed(MOVE_WALK, NORMAL_SPEED, true); + bot->SetSpeed(MOVE_FLIGHT, NORMAL_SPEED, true); + } + + Creature* valithria = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); + if (!valithria) + return false; + + switch (bot->getClass()) + { + case CLASS_DRUID: + { + constexpr uint32 SPELL_REJUVENATION = 48441; + constexpr uint32 SPELL_REGROWTH = 48443; + constexpr uint32 SPELL_LIFEBLOOM = 48451; + constexpr uint32 SPELL_WILD_GROWTH = 53251; + constexpr uint8 LIFEBLOOM_MAX = 3; + + if (!valithria->HasAura(SPELL_REJUVENATION, bot->GetGUID())) + return botAI->CastSpell(SPELL_REJUVENATION, valithria); + + if (!valithria->HasAura(SPELL_REGROWTH, bot->GetGUID())) + return botAI->CastSpell(SPELL_REGROWTH, valithria); + + Aura* lb = valithria->GetAura(SPELL_LIFEBLOOM, bot->GetGUID()); + if (!lb || lb->GetStackAmount() < LIFEBLOOM_MAX) + return botAI->CastSpell(SPELL_LIFEBLOOM, valithria); + + return botAI->CastSpell(SPELL_WILD_GROWTH, valithria); + } + case CLASS_SHAMAN: + { + constexpr uint32 SPELL_RIPTIDE = 61301; + constexpr uint32 SPELL_HEALING_WAVE = 49273; + return valithria->HasAura(SPELL_RIPTIDE, bot->GetGUID()) ? botAI->CastSpell(SPELL_HEALING_WAVE, valithria) + : botAI->CastSpell(SPELL_RIPTIDE, valithria); + } + case CLASS_PRIEST: + { + constexpr uint32 SPELL_RENEW = 48068; + constexpr uint32 SPELL_GREATER_HEAL = 48063; + return valithria->HasAura(SPELL_RENEW, bot->GetGUID()) ? botAI->CastSpell(SPELL_GREATER_HEAL, valithria) + : botAI->CastSpell(SPELL_RENEW, valithria); + } + case CLASS_PALADIN: + { + constexpr uint32 SPELL_BEACON = 53563; + constexpr uint32 SPELL_HOLY_LIGHT = 48782; + return valithria->HasAura(SPELL_BEACON, bot->GetGUID()) ? botAI->CastSpell(SPELL_HOLY_LIGHT, valithria) + : botAI->CastSpell(SPELL_BEACON, valithria); + } + default: + break; + } + return false; +} + +bool IccValithriaDreamCloudAction::Execute(Event /*event*/) +{ + if (!bot->HasAura(SPELL_DREAM_STATE)) + return false; + + bot->SetSpeed(MOVE_RUN, 2.0f, true); + bot->SetSpeed(MOVE_WALK, 2.0f, true); + bot->SetSpeed(MOVE_FLIGHT, 2.0f, true); + + std::vector allDream; + std::vector realDream; + + Map::PlayerList const& playerList = bot->GetMap()->GetPlayers(); + for (Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr) + { + Player* player = itr->GetSource(); + if (!player || !player->IsAlive() || !player->HasAura(SPELL_DREAM_STATE)) + continue; + + allDream.push_back(player); + + PlayerbotAI* playerBotAI = GET_PLAYERBOT_AI(player); + if (!playerBotAI || playerBotAI->IsRealPlayer()) + realDream.push_back(player); + } + + if (allDream.empty()) + return false; + + std::vector& leaderPool = realDream.empty() ? allDream : realDream; + + std::sort(leaderPool.begin(), leaderPool.end(), + [](Player const* a, Player const* b) { return a->GetGUID() < b->GetGUID(); }); + + Player* leader = leaderPool.front(); + + if (!realDream.empty()) + { + constexpr float STACK_DIST = 1.0f; + if (bot->GetDistance(leader) > STACK_DIST) + { + bot->TeleportTo(bot->GetMapId(), leader->GetPositionX(), leader->GetPositionY(), leader->GetPositionZ(), + bot->GetOrientation()); + } + return false; + } + + // No real players - synchronized bot cloud collection + uint32 const instanceId = bot->GetInstanceId(); + uint32 const nowMs = getMSTime(); + + ValithriaCloudSync& sync = VdwCloudSync[instanceId]; + + std::vector dreamClouds = CollectClouds(NPC_DREAM_CLOUD, leader); + std::vector nightmareClouds = CollectClouds(NPC_NIGHTMARE_CLOUD, leader); + + std::vector allClouds; + allClouds.insert(allClouds.end(), dreamClouds.begin(), dreamClouds.end()); + allClouds.insert(allClouds.end(), nightmareClouds.begin(), nightmareClouds.end()); + + if (allClouds.empty()) + { + VdwCloudSync.erase(instanceId); + return false; + } + + // Find whether the shared target cloud is still alive + Creature* target = nullptr; + if (!sync.targetCloudGuid.IsEmpty()) + { + for (Creature* c : allClouds) + { + if (c->GetGUID() == sync.targetCloudGuid) + { + target = c; + break; + } + } + } + + // Advance to a new cloud only when the current one is gone or the wait window has passed. + // The wait window gives every portal bot time to teleport in and collect the same cloud. + if (!target || nowMs >= sync.moveOnAfterMs) + { + Creature* closest = nullptr; + float minDist = FLT_MAX; + for (Creature* c : allClouds) + { + float const d = leader->GetExactDist(c); + if (d < minDist) + { + minDist = d; + closest = c; + } + } + + target = closest; + if (target) + sync.targetCloudGuid = target->GetGUID(); + else + sync.targetCloudGuid = ObjectGuid::Empty; + sync.moveOnAfterMs = nowMs + CLOUD_SYNC_WAIT_MS; + } + + // All bots teleport to the shared target so none miss the cloud + if (target) + bot->TeleportTo(target->GetMapId(), target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), + bot->GetOrientation()); + + return false; +} + +// Collect all live clouds sorted by distance from the given reference unit +std::vector IccValithriaDreamCloudAction::CollectClouds(uint32 entry, Unit* reference) +{ + constexpr float SEARCH_RANGE = 200.0f; + + std::list raw; + bot->GetCreatureListWithEntryInGrid(raw, entry, SEARCH_RANGE); + + std::vector result; + result.reserve(raw.size()); + for (Creature* c : raw) + if (c && c->IsAlive()) + result.push_back(c); + + // Sort by reference unit's distance so all bots get the same ordering + std::sort(result.begin(), result.end(), [reference](Creature const* a, Creature const* b) + { return reference->GetExactDist(a) < reference->GetExactDist(b); }); + + return result; +} + +bool IccValithriaZombieKiteAction::Execute(Event /*event*/) +{ + if (botAI->IsTank(bot)) + return false; + + Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); + if (!boss) + return false; + + std::list allZombies; + bot->GetCreatureListWithEntryInGrid(allZombies, NPC_BLISTERING_ZOMBIE, 100.0f); + + std::vector threatZombies; + Creature* nearestThreat = nullptr; + float nearestDist = FLT_MAX; + for (Creature* z : allZombies) + { + if (!z || !z->IsAlive()) + continue; + if (z->GetVictim() != bot) + continue; + threatZombies.push_back(z); + float const d = bot->GetExactDist2d(z); + if (d < nearestDist) + { + nearestDist = d; + nearestThreat = z; + } + } + + if (threatZombies.empty()) + return false; + + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + + // Stop spell-casting that would root the bot. DON'T call botAI->Reset() - + // it nukes the motion master mid-tick which restarts pathing every tick + // and lets zombies catch up. + if (bot->IsNonMeleeSpellCast(true)) + bot->InterruptNonMeleeSpells(true); + + constexpr float ANCHOR_RADIUS_LIMIT = 25.0f; // kite stays within this radius of heal anchor + constexpr float STEP = 18.0f; // how far we move per tick + constexpr float SAFE_FROM_ZOMBIE = 14.0f; // candidate must be this far from every zombie + constexpr float PATH_CLEARANCE = 8.0f; // path segment must miss every zombie by this much + constexpr int NUM_CANDIDATES = 36; + + float const anchorX = ICC_VDW_HEAL_POSITION.GetPositionX(); + float const anchorY = ICC_VDW_HEAL_POSITION.GetPositionY(); + + auto segmentDistToPoint = [](float x1, float y1, float x2, float y2, float px, float py) -> float + { + float const vx = x2 - x1; + float const vy = y2 - y1; + float const wx = px - x1; + float const wy = py - y1; + float const len2 = vx * vx + vy * vy; + float t = len2 > 0.0001f ? (vx * wx + vy * wy) / len2 : 0.0f; + if (t < 0.0f) + t = 0.0f; + else if (t > 1.0f) + t = 1.0f; + float const cx = x1 + t * vx; + float const cy = y1 + t * vy; + float const dx = px - cx; + float const dy = py - cy; + return std::sqrt(dx * dx + dy * dy); + }; + + // Compute weighted-away-from-all-zombies vector. Closer zombies pull harder. + float awayX = 0.0f; + float awayY = 0.0f; + for (Creature* z : threatZombies) + { + float const dx = bot->GetPositionX() - z->GetPositionX(); + float const dy = bot->GetPositionY() - z->GetPositionY(); + float const d = std::sqrt(dx * dx + dy * dy); + if (d < 0.1f) + continue; + float const w = 1.0f / std::max(d, 1.0f); + awayX += (dx / d) * w; + awayY += (dy / d) * w; + } + float const awayLen = std::sqrt(awayX * awayX + awayY * awayY); + float baseAngle = 0.0f; + if (awayLen > 0.001f) + baseAngle = std::atan2(awayY / awayLen, awayX / awayLen); + else if (nearestThreat) + baseAngle = std::atan2(bot->GetPositionY() - nearestThreat->GetPositionY(), + bot->GetPositionX() - nearestThreat->GetPositionX()); + + float const bx = bot->GetPositionX(); + float const by = bot->GetPositionY(); + + float bestX = bx; + float bestY = by; + float bestScore = -FLT_MAX; + + // Sample arc centered on away-direction, +/-150 degrees + for (int i = 0; i < NUM_CANDIDATES; ++i) + { + float const offset = (-150.0f + (300.0f * i) / (NUM_CANDIDATES - 1)) * static_cast(M_PI) / 180.0f; + float const angle = baseAngle + offset; + float const cx = bx + STEP * std::cos(angle); + float const cy = by + STEP * std::sin(angle); + + // Stay within the anchor radius so kite doesn't drag bot out of raid + float const dxAnchor = cx - anchorX; + float const dyAnchor = cy - anchorY; + if (std::sqrt(dxAnchor * dxAnchor + dyAnchor * dyAnchor) > ANCHOR_RADIUS_LIMIT) + continue; + + // Reject if endpoint too close to any zombie OR straight path passes near one + float minEndDist = FLT_MAX; + bool unsafe = false; + for (Creature* z : threatZombies) + { + float const zx = z->GetPositionX(); + float const zy = z->GetPositionY(); + float const dxe = cx - zx; + float const dye = cy - zy; + float const endDist = std::sqrt(dxe * dxe + dye * dye); + if (endDist < SAFE_FROM_ZOMBIE) + { + unsafe = true; + break; + } + float const pathDist = segmentDistToPoint(bx, by, cx, cy, zx, zy); + if (pathDist < PATH_CLEARANCE) + { + unsafe = true; + break; + } + if (endDist < minEndDist) + minEndDist = endDist; + } + if (unsafe) + continue; + + // Prefer points that maximize distance from the closest zombie. + // Mild bonus for being aligned with away-direction (smaller offset). + float const alignBonus = 1.5f * (1.0f - std::fabs(offset) / static_cast(M_PI)); + float const score = minEndDist + alignBonus; + if (score > bestScore) + { + bestScore = score; + bestX = cx; + bestY = cy; + } + } + + if (bestScore == -FLT_MAX) + { + // No safe arc point - sprint straight away from the weighted center, + // but clamp endpoint to the anchor radius so we don't flee out of raid. + float fx = bx + STEP * std::cos(baseAngle); + float fy = by + STEP * std::sin(baseAngle); + float const dxA = fx - anchorX; + float const dyA = fy - anchorY; + float const distA = std::sqrt(dxA * dxA + dyA * dyA); + if (distA > ANCHOR_RADIUS_LIMIT) + { + fx = anchorX + (dxA / distA) * ANCHOR_RADIUS_LIMIT; + fy = anchorY + (dyA / distA) * ANCHOR_RADIUS_LIMIT; + } + return MoveTo(bot->GetMapId(), fx, fy, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED); + } + + return MoveTo(bot->GetMapId(), bestX, bestY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_FORCED); +} diff --git a/src/Ai/Raid/Icecrown/RaidIccActionContext.h b/src/Ai/Raid/ICC/ICCActionContext.h similarity index 81% rename from src/Ai/Raid/Icecrown/RaidIccActionContext.h rename to src/Ai/Raid/ICC/ICCActionContext.h index e648b09b73a..2c1c4c9611f 100644 --- a/src/Ai/Raid/Icecrown/RaidIccActionContext.h +++ b/src/Ai/Raid/ICC/ICCActionContext.h @@ -1,9 +1,9 @@ -#ifndef _PLAYERBOT_RAIDICCACTIONCONTEXT_H -#define _PLAYERBOT_RAIDICCACTIONCONTEXT_H +#ifndef _PLAYERBOT_ICCACTIONCONTEXT_H +#define _PLAYERBOT_ICCACTIONCONTEXT_H #include "Action.h" #include "NamedObjectContext.h" -#include "RaidIccActions.h" +#include "ICCActions.h" class RaidIccActionContext : public NamedObjectContext { @@ -21,23 +21,29 @@ class RaidIccActionContext : public NamedObjectContext creators["icc rotting frost giant tank position"] = &RaidIccActionContext::icc_rotting_frost_giant_tank_position; creators["icc cannon fire"] = &RaidIccActionContext::icc_cannon_fire; creators["icc gunship enter cannon"] = &RaidIccActionContext::icc_gunship_enter_cannon; - creators["icc gunship teleport ally"] = &RaidIccActionContext::icc_gunship_teleport_ally; - creators["icc gunship teleport horde"] = &RaidIccActionContext::icc_gunship_teleport_horde; + creators["icc gunship rocket jump"] = &RaidIccActionContext::icc_gunship_rocket_jump; + creators["icc gunship rocket pack setup"] = &RaidIccActionContext::icc_gunship_rocket_pack_setup; creators["icc dbs tank position"] = &RaidIccActionContext::icc_dbs_tank_position; creators["icc adds dbs"] = &RaidIccActionContext::icc_adds_dbs; + creators["icc dogs tank position"] = &RaidIccActionContext::icc_dogs_tank_position; + creators["icc festergut group position"] = &RaidIccActionContext::icc_festergut_group_position; creators["icc festergut spore"] = &RaidIccActionContext::icc_festergut_spore; + creators["icc festergut avoid malleable goo"] = &RaidIccActionContext::icc_festergut_avoid_malleable_goo; creators["icc rotface tank position"] = &RaidIccActionContext::icc_rotface_tank_position; creators["icc rotface group position"] = &RaidIccActionContext::icc_rotface_group_position; creators["icc rotface move away from explosion"] = &RaidIccActionContext::icc_rotface_move_away_from_explosion; + creators["icc rotface avoid vile gas"] = &RaidIccActionContext::icc_rotface_avoid_vile_gas; + creators["icc putricide mutated plague"] = &RaidIccActionContext::icc_putricide_mutated_plague; creators["icc putricide volatile ooze"] = &RaidIccActionContext::icc_putricide_volatile_ooze; creators["icc putricide gas cloud"] = &RaidIccActionContext::icc_putricide_gas_cloud; creators["icc putricide growing ooze puddle"] = &RaidIccActionContext::icc_putricide_growing_ooze_puddle; creators["icc putricide avoid malleable goo"] = &RaidIccActionContext::icc_putricide_avoid_malleable_goo; + creators["icc putricide abomination"] = &RaidIccActionContext::icc_putricide_abomination; creators["icc bpc keleseth tank"] = &RaidIccActionContext::icc_bpc_keleseth_tank; creators["icc bpc main tank"] = &RaidIccActionContext::icc_bpc_main_tank; @@ -56,9 +62,11 @@ class RaidIccActionContext : public NamedObjectContext creators["icc valithria portal"] = &RaidIccActionContext::icc_valithria_portal; creators["icc valithria heal"] = &RaidIccActionContext::icc_valithria_heal; creators["icc valithria dream cloud"] = &RaidIccActionContext::icc_valithria_dream_cloud; + creators["icc valithria zombie kite"] = &RaidIccActionContext::icc_valithria_zombie_kite; creators["icc sindragosa group position"] = &RaidIccActionContext::icc_sindragosa_group_position; creators["icc sindragosa frost beacon"] = &RaidIccActionContext::icc_sindragosa_frost_beacon; + creators["icc sindragosa hot"] = &RaidIccActionContext::icc_sindragosa_hot; creators["icc sindragosa blistering cold"] = &RaidIccActionContext::icc_sindragosa_blistering_cold; creators["icc sindragosa unchained magic"] = &RaidIccActionContext::icc_sindragosa_unchained_magic; creators["icc sindragosa chilled to the bone"] = &RaidIccActionContext::icc_sindragosa_chilled_to_the_bone; @@ -70,6 +78,7 @@ class RaidIccActionContext : public NamedObjectContext creators["icc lich king necrotic plague"] = &RaidIccActionContext::icc_lich_king_necrotic_plague; creators["icc lich king winter"] = &RaidIccActionContext::icc_lich_king_winter; creators["icc lich king adds"] = &RaidIccActionContext::icc_lich_king_adds; + creators["icc lich king spirit bomb"] = &RaidIccActionContext::icc_lich_king_spirit_bomb; } private: @@ -84,23 +93,29 @@ class RaidIccActionContext : public NamedObjectContext static Action* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionAction(ai); } static Action* icc_cannon_fire(PlayerbotAI* ai) { return new IccCannonFireAction(ai); } static Action* icc_gunship_enter_cannon(PlayerbotAI* ai) { return new IccGunshipEnterCannonAction(ai); } - static Action* icc_gunship_teleport_ally(PlayerbotAI* ai) { return new IccGunshipTeleportAllyAction(ai); } - static Action* icc_gunship_teleport_horde(PlayerbotAI* ai) { return new IccGunshipTeleportHordeAction(ai); } + static Action* icc_gunship_rocket_jump(PlayerbotAI* ai) { return new IccGunshipRocketJumpAction(ai); } + static Action* icc_gunship_rocket_pack_setup(PlayerbotAI* ai) { return new IccGunshipRocketPackSetupAction(ai); } static Action* icc_dbs_tank_position(PlayerbotAI* ai) { return new IccDbsTankPositionAction(ai); } static Action* icc_adds_dbs(PlayerbotAI* ai) { return new IccAddsDbsAction(ai); } + static Action* icc_dogs_tank_position(PlayerbotAI* ai) { return new IccDogsTankPositionAction(ai); } + static Action* icc_festergut_group_position(PlayerbotAI* ai) { return new IccFestergutGroupPositionAction(ai); } static Action* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeAction(ai); } + static Action* icc_festergut_avoid_malleable_goo(PlayerbotAI* ai) { return new IccFestergutAvoidMalleableGooAction(ai); } static Action* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionAction(ai); } static Action* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionAction(ai); } static Action* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionAction(ai); } + static Action* icc_rotface_avoid_vile_gas(PlayerbotAI* ai) { return new IccRotfaceAvoidVileGasAction(ai); } + static Action* icc_putricide_mutated_plague(PlayerbotAI* ai) { return new IccPutricideMutatedPlagueAction(ai); } static Action* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeAction(ai); } static Action* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudAction(ai); } static Action* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleAction(ai); } static Action* icc_putricide_avoid_malleable_goo(PlayerbotAI* ai) { return new IccPutricideAvoidMalleableGooAction(ai); } + static Action* icc_putricide_abomination(PlayerbotAI* ai) { return new IccPutricideAbominationAction(ai); } static Action* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankAction(ai); } static Action* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankAction(ai); } @@ -119,9 +134,11 @@ class RaidIccActionContext : public NamedObjectContext static Action* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalAction(ai); } static Action* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealAction(ai); } static Action* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudAction(ai); } + static Action* icc_valithria_zombie_kite(PlayerbotAI* ai) { return new IccValithriaZombieKiteAction(ai); } static Action* icc_sindragosa_group_position(PlayerbotAI* ai) { return new IccSindragosaGroupPositionAction(ai); } static Action* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconAction(ai); } + static Action* icc_sindragosa_hot(PlayerbotAI* ai) { return new IccSindragosaHotAction(ai); } static Action* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdAction(ai); } static Action* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicAction(ai); } static Action* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneAction(ai); } @@ -133,6 +150,7 @@ class RaidIccActionContext : public NamedObjectContext static Action* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueAction(ai); } static Action* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterAction(ai); } static Action* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsAction(ai); } + static Action* icc_lich_king_spirit_bomb(PlayerbotAI* ai) { return new IccLichKingSpiritBombAction(ai); } }; diff --git a/src/Ai/Raid/ICC/ICCMultipliers.cpp b/src/Ai/Raid/ICC/ICCMultipliers.cpp new file mode 100644 index 00000000000..f7e7679921e --- /dev/null +++ b/src/Ai/Raid/ICC/ICCMultipliers.cpp @@ -0,0 +1,1208 @@ +#include "ICCMultipliers.h" + +#include "ChooseTargetActions.h" +#include "DKActions.h" +#include "DruidActions.h" +#include "DruidBearActions.h" +#include "FollowActions.h" +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "HunterActions.h" +#include "MageActions.h" +#include "MovementActions.h" +#include "PaladinActions.h" +#include "PriestActions.h" +#include "ICCActions.h" +#include "ReachTargetActions.h" +#include "RogueActions.h" +#include "ShamanActions.h" +#include "UseMeetingStoneAction.h" +#include "WarriorActions.h" +#include "PlayerbotAI.h" +#include "ICCTriggers.h" +#include "ICCScripts.h" + +// LK global variables +namespace +{ +std::map g_plagueTimes; +std::map g_allowCure; +std::mutex g_plagueMutex; // Lock before accessing shared variables +} + +// Lady Deathwhisper +float IccLadyDeathwhisperMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); + if (!boss) + return 1.0f; + + if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + static constexpr uint32 VENGEFUL_SHADE_ID = NPC_SHADE; + + // Get the nearest hostile NPCs + const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + // Allow the IccShadeLadyDeathwhisperAction to run + if (dynamic_cast(action)) + return 1.0f; + + for (auto const& npcGuid : npcs) + { + Unit* shade = botAI->GetUnit(npcGuid); + + if (!shade || shade->GetEntry() != VENGEFUL_SHADE_ID) + continue; + + if (!shade->GetVictim() || shade->GetVictim()->GetGUID() != bot->GetGUID()) + continue; + + return 0.0f; // Cancel all other actions when we need to handle Vengeful Shade + } + + return 1.0f; +} + +// dbs +float IccAddsDbsMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); + if (!boss) + return 1.0f; + + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + if (botAI->IsRanged(bot)) + if (dynamic_cast(action)) + return 0.0f; + + if (botAI->IsTank(bot)) + { + Aura* aura = botAI->GetAura("rune of blood", bot); + if (aura) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 1.0f; + + return 0.0f; + } + } + + return 1.0f; +} + +// Gunship +float IccGunshipMultiplier::GetValue(Action* action) +{ + // Detect gunship encounter via hostile enemy captain nearby + Unit* saurfang = bot->FindNearestCreature(NPC_HIGH_OVERLORD_SAURFANG, 200.0f); + Unit* muradin = bot->FindNearestCreature(NPC_MURADIN_BRONZEBEARD, 200.0f); + bool const inGunship = (saurfang && saurfang->IsAlive() && saurfang->IsHostileTo(bot)) || + (muradin && muradin->IsAlive() && muradin->IsHostileTo(bot)); + if (!inGunship) + return 1.0f; + + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + // Main tank is locked to captain via IccGunshipRocketJumpAction — block RTI targeting + if (botAI->IsMainTank(bot) && dynamic_cast(action)) + return 0.0f; + + // Bot in transit between ships: lock to rocket-jump action only so combat/movement + // actions don't interfere with jump packet timing. + bool const isHordeSide = muradin && muradin->IsAlive() && muradin->IsHostileTo(bot); + bool const isAllySide = saurfang && saurfang->IsAlive() && saurfang->IsHostileTo(bot); + Position const friendlyPoint = isHordeSide ? ICC_GUNSHIP_ROCKET_JUMP_HORDE_FRIENDLY_POINT + : ICC_GUNSHIP_ROCKET_JUMP_ALLY_FRIENDLY_POINT; + Position const middlePoint = isHordeSide ? ICC_GUNSHIP_ROCKET_JUMP_HORDE_MIDDLE_POINT + : ICC_GUNSHIP_ROCKET_JUMP_ALLY_MIDDLE_POINT; + Position const attackPos = isHordeSide ? ICC_GUNSHIP_ROCKET_JUMP_HORDE + : ICC_GUNSHIP_ROCKET_JUMP_ALLY; + static constexpr float JUMP_GATE = 30.0f; + bool const nearFriendly = bot->GetExactDist2d(friendlyPoint) <= JUMP_GATE; + bool const nearMiddle = bot->GetExactDist2d(middlePoint) <= JUMP_GATE; + bool const nearAttack = bot->GetExactDist2d(attackPos) <= JUMP_GATE; + if (!nearFriendly && !nearMiddle && !nearAttack) + { + if (!dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Dogs +float IccDogsMultiplier::GetValue(Action* action) +{ + if (!AI_VALUE2(Unit*, "find target", "stinky") && !AI_VALUE2(Unit*, "find target", "precious")) + return 1.0f; + + if (botAI->IsTank(bot)) + { + Aura* aura = botAI->GetAura("mortal wound", bot, false, true); + if (aura && aura->GetStackAmount() >= 8) + { + if (dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + + return 0.0f; + } + } + + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// Festergut +float IccFestergutMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); + if (!boss) + return 1.0f; + + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + if (botAI->IsTank(bot)) + { + Aura* aura = botAI->GetAura("gastric bloat", bot, false, true); + if (aura && aura->GetStackAmount() >= 6) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 1.0f; + + return 0.0f; + } + } + + if (dynamic_cast(action)) + return 1.0f; + + if (bot->HasAura(SPELL_GAS_SPORE)) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + // Hold position during the 8s malleable goo wait window so the bot can + // keep DPS/heal rotations running without drifting back into the impact. + // Avoid action itself is whitelisted (may still need to dodge new goos). + { + auto const& waitMap = IcecrownHelpers::festergutGooWaitUntil; + auto it = waitMap.find(bot->GetGUID()); + if (it != waitMap.end() && getMSTime() < it->second) + { + if (dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + } + } + + return 1.0f; +} + +// Rotface +float IccRotfaceMultiplier::GetValue(Action* action) +{ + Unit* boss1 = AI_VALUE2(Unit*, "find target", "rotface"); + if (!boss1) + return 1.0f; + + { + uint32 const now = getMSTime(); + auto const& waitMap = IcecrownHelpers::rotfaceVileGasWaitUntil; + auto it = waitMap.find(bot->GetGUID()); + bool const inWait = it != waitMap.end() && now < it->second; + auto vgIt = IcecrownHelpers::rotfaceVileGas.find(bot->GetMap()->GetInstanceId()); + bool const isVictim = + vgIt != IcecrownHelpers::rotfaceVileGas.end() && + vgIt->second.victimGuid == bot->GetGUID() && + getMSTimeDiff(vgIt->second.castTime, now) < 8000; + + if (isVictim || inWait || botAI->HasAura("Vile Gas", bot)) + { + if (dynamic_cast(action)) + return 1.0f; + if (dynamic_cast(action)) + return 0.0f; + } + } + + if (botAI->HasAura("Vile Gas", bot)) + return 0.0f; + + if (botAI->IsTank(bot) && dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) && !(bot->getClass() == CLASS_HUNTER)) + return 0.0f; + + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + if (botAI->IsAssistTank(bot) && + (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action))) + return 0.0f; + + if (botAI->IsAssistTank(bot) && boss1 && bot->GetVictim() == boss1) + { + bot->AttackStop(); + bot->SetTarget(ObjectGuid::Empty); + return 0.0f; + } + + // Never cure/dispel Mutated Infection — it must expire naturally to spawn a small ooze + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + { + Creature* bigOoze = bot->FindNearestCreature(NPC_BIG_OOZE, 100.0f); + bool castingNow = bigOoze && bigOoze->IsAlive() && + bigOoze->HasUnitState(UNIT_STATE_CASTING) && bigOoze->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION); + + if (castingNow && (dynamic_cast(action) || dynamic_cast(action)) && + !dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// pp +float IccAddsPutricideMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (!boss) + return 1.0f; + Unit* boss1 = AI_VALUE2(Unit*, "find target", "rotface"); + if (boss1) + return 1.0f; + Unit* boss2 = AI_VALUE2(Unit*, "find target", "festergut"); + if (boss2) + return 1.0f; + + if (Unit* vehBase = bot->GetVehicleBase()) + { + uint32 e = vehBase->GetEntry(); + if (e == NPC_MUTATED_ABOMINATION_10 || e == NPC_MUTATED_ABOMINATION_25) + { + if (dynamic_cast(action)) + return 1.0f; + return 0.0f; + } + } + + bool hasGaseousBloat = botAI->HasAura("Gaseous Bloat", bot); + bool hasUnboundPlague = botAI->HasAura("Unbound Plague", bot); + + if (botAI->IsTank(bot) && + bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) + { + if (dynamic_cast(action) || + dynamic_cast(action)) + return 1.0f; + return 0.0f; + } + + if (!(bot->getClass() == CLASS_HUNTER) && dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (botAI->IsTank(bot)) + { + auto GetPlagueStacks = [&](Unit* unit) -> uint32 + { + if (!unit) + return 0; + Aura* a = botAI->GetAura("mutated plague", unit, false, true); + return a ? a->GetStackAmount() : 0; + }; + + uint32 const myStacks = GetPlagueStacks(bot); + + // Another tank has fewer stacks — they should be tanking instead of us. + // Block generic taunts (so we don't fight for aggro), keep movement, and + // cancel the rest of the rotation (IccPutricideMutatedPlagueAction owns + // the AttackStop/taunt handoff). + bool anotherTankHasFewer = false; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || member == bot || !member->IsAlive() || !member->IsInWorld()) + continue; + if (!PlayerbotAI::IsTank(member)) + continue; + + if (GetPlagueStacks(member) < myStacks) + { + anotherTankHasFewer = true; + break; + } + } + } + + if (anotherTankHasFewer) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action)) + return 1.0f; + + return 0.0f; + } + } + + if (hasGaseousBloat) + { + if (dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action)) + return 1.0f; + + if (botAI->IsHeal(bot)) + return 1.0f; + else + return 0.0f; // Cancel all other actions when we need to handle Gaseous Bloat + } + + if (hasUnboundPlague && boss && !boss->HealthBelowPct(35)) + { + if (dynamic_cast(action)) + return 1.0f; + else + return 0.0f; // Cancel all other actions when we need to handle Unbound Plague + } + + if (dynamic_cast(action)) + { + if (dynamic_cast(action)) + return 1.0f; + if (dynamic_cast(action) && !botAI->IsMainTank(bot)) + return 0.0f; + } + + return 1.0f; +} + +// bpc +float IccBpcAssistMultiplier::GetValue(Action* action) +{ + Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth"); + if (!keleseth) + return 1.0f; + + if (keleseth && (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action))) + return 0.0f; + + Aura* aura = botAI->GetAura("Shadow Prison", bot, false, true); + + // Bomb assignment check (done early so it can override shadow prison stack limits) + static const std::array bombEntries = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2, NPC_KINETIC_BOMB3, + NPC_KINETIC_BOMB4}; + GuidVector const bombs = AI_VALUE(GuidVector, "possible targets no los"); + + std::vector kineticBombs; + for (auto const& guid : bombs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (std::find(bombEntries.begin(), bombEntries.end(), unit->GetEntry()) == bombEntries.end()) + continue; + + if (unit->GetPositionZ() - bot->GetPositionZ() > 35.0f) + continue; + + kineticBombs.push_back(unit); + } + + bool botAssignedToBomb = false; + if (!kineticBombs.empty() && botAI->IsRangedDps(bot) && + !(aura && aura->GetStackAmount() > 18)) + { + std::sort(kineticBombs.begin(), kineticBombs.end(), + [](Unit* a, Unit* b) { return a->GetPositionZ() < b->GetPositionZ(); }); + + std::vector rangedDps; + Group* group = bot->GetGroup(); + if (group) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && botAI->IsRangedDps(member)) + rangedDps.push_back(member); + } + } + + static float const MAX_ASSIGN_RANGE = 80.0f; + std::set assigned; + for (Unit* bomb : kineticBombs) + { + Player* nearest = nullptr; + float nearestDist = std::numeric_limits::max(); + + // Priority classes: hunter > druid > any + static constexpr std::array classPriority = {CLASS_HUNTER, CLASS_DRUID, 0}; + for (uint8 priorityClass : classPriority) + { + nearest = nullptr; + nearestDist = std::numeric_limits::max(); + + for (Player* dps : rangedDps) + { + if (assigned.count(dps)) + continue; + + if (priorityClass != 0 && dps->getClass() != priorityClass) + continue; + + float dist = dps->GetDistance(bomb); + if (dist < nearestDist && dist < MAX_ASSIGN_RANGE) + { + nearestDist = dist; + nearest = dps; + } + } + + if (nearest) + break; + } + + if (nearest) + { + assigned.insert(nearest); + if (nearest == bot) + botAssignedToBomb = true; + } + } + } + + // Bomb-assigned bot: block target switching and non-bomb BPC actions, allow combat rotation + if (botAssignedToBomb) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + // Shadow prison movement block (non-bomb bots use normal 12 stack limit) + if (aura) + { + if (aura->GetStackAmount() > 18 && botAI->IsTank(bot)) + { + if (dynamic_cast(action)) + return 0.0f; + } + + if (aura->GetStackAmount() > 12 && !botAI->IsTank(bot)) + { + if (dynamic_cast(action)) + return 0.0f; + } + } + + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + if (!valanar) + return 1.0f; + + if (valanar && valanar->HasUnitState(UNIT_STATE_CASTING) && + (valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX1) || + valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX2) || + valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX3) || + valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX4))) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 1.0f; + else + return 0.0f; + } + + Unit* flame1 = bot->FindNearestCreature(NPC_BALL_OF_FLAME, 100.0f); + Unit* flame2 = bot->FindNearestCreature(NPC_BALL_OF_INFERNO_FLAME, 100.0f); + bool ballOfFlame = flame1 && flame1->GetVictim() == bot; + bool infernoFlame = flame2 && flame2->GetVictim() == bot; + + if (flame2) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 1.0f; + } + + if (ballOfFlame || infernoFlame) + { + if (dynamic_cast(action)) + return 1.0f; + else + return 0.0f; + } + + // For assist tank during BPC fight + if (botAI->IsAssistTank(bot) && !(aura && aura->GetStackAmount() > 18)) + { + // Allow BPC-specific actions + if (dynamic_cast(action)) + return 1.0f; + + // Disable normal assist behavior (allow RTI targeting) + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +//BQL +float IccBqlMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); + if (!boss) + return 1.0f; + + Aura* aura2 = botAI->GetAura("Swarming Shadows", bot); + Aura* aura = botAI->GetAura("Frenzied Bloodthirst", bot); + + if (botAI->IsRanged(bot)) + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + // If bot has Pact of Darkfallen aura, return 0 for all other actions + if (bot->HasAura(SPELL_PACT_OF_THE_DARKFALLEN)) + { + if (dynamic_cast(action)) + return 1.0f; // Allow Pact of Darkfallen action + else + return 0.0f; // Cancel all other actions when we need to handle Pact of Darkfallen + } + + // Air phase: block movement/chase actions, allow combat rotation (attacks/heals) + if (((boss->GetPositionZ() - ICC_BQL_CENTER_POSITION.GetPositionZ()) > 5.0f) && !aura) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + + // If bot has frenzied bloodthirst, allow highest priority for bite action + if (aura) // If bot has frenzied bloodthirst + { + if (dynamic_cast(action)) + return 1.0f; + else + return 0.0f; + } + + if (aura2 && !aura) + { + if (dynamic_cast(action)) + return 1.0f; + else + return 0.0f; // Cancel all other actions when we need to handle Swarming Shadows + } + + if ((boss->GetExactDist2d(ICC_BQL_TANK_POSITION.GetPositionX(), ICC_BQL_TANK_POSITION.GetPositionY()) > 10.0f) && + botAI->IsRanged(bot) && !((boss->GetPositionZ() - bot->GetPositionZ()) > 5.0f)) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +//VDW +float IccValithriaDreamCloudMultiplier::GetValue(Action* action) +{ + Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); + + Aura* twistedNightmares = botAI->GetAura("Twisted Nightmares", bot); + Aura* emeraldVigor = botAI->GetAura("Emerald Vigor", bot); + + if (!boss && !bot->HasAura(SPELL_DREAM_STATE)) + return 1.0f; + + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + // Zombie victim: only the kite action runs. Blocks combat/movement so bot + // doesn't try to attack/cast/move toward marks while being chased. + if (boss && !botAI->IsTank(bot)) + { + Creature* attackingZombie = nullptr; + std::list zombies; + bot->GetCreatureListWithEntryInGrid(zombies, NPC_BLISTERING_ZOMBIE, 100.0f); + for (Creature* z : zombies) + { + if (z && z->IsAlive() && z->GetVictim() == bot) + { + attackingZombie = z; + break; + } + } + if (attackingZombie) + { + if (dynamic_cast(action)) + return 1.0f; + return 0.0f; + } + } + + if (botAI->IsTank(bot)) + { + if (dynamic_cast(action)) + return 0.0f; + } + else + { + // Non-tanks must strictly follow RTI marks. Block generic assist actions + // so bots never attack unmarked adds; AttackRtiTargetAction drives them to + // skull/cross targets set by HandleMarkingLogic. + if (dynamic_cast(action)) + return 0.0f; + + // Melee bots must not engage Blistering Zombies (one-shot melee swing). + // Only ranged DPS handle them. If RTI/current target is a zombie, block + // attack actions so melee falls through to other priorities. + if (!PlayerbotAI::IsRangedDps(bot) && !botAI->IsHeal(bot)) + { + Unit* victim = bot->GetVictim(); + bool victimIsZombie = victim && victim->GetEntry() == NPC_BLISTERING_ZOMBIE; + + bool rtiIsZombie = false; + if (Group* group = bot->GetGroup()) + { + ObjectGuid rtiGuid = group->GetTargetIcon(7); + if (!rtiGuid.IsEmpty()) + { + Unit* rtiUnit = ObjectAccessor::GetUnit(*bot, rtiGuid); + if (rtiUnit && rtiUnit->GetEntry() == NPC_BLISTERING_ZOMBIE) + rtiIsZombie = true; + } + } + + if (victimIsZombie || rtiIsZombie) + { + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + } + } + + if (botAI->IsHeal(bot) && (twistedNightmares || emeraldVigor)) + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + if (bot->HasAura(SPELL_DREAM_STATE) && !bot->HealthBelowPct(50)) + { + if (dynamic_cast(action)) + return 1.0f; // Allow Dream Cloud action + else + return 0.0f; // Cancel all other actions when we need to handle Dream Cloud + } + + return 1.0f; + +} + +//SINDRAGOSA + +float IccSindragosaMultiplier::GetValue(Action* action) +{ + Unit* boss = bot->FindNearestCreature(NPC_SINDRAGOSA, 200.0f); + if (!boss) + return 1.0f; + + // HoT support is an instant cast that never moves the bot. Always allow so + // beaconed targets stay topped up across air phase, blistering cold cast, + // phase 3 tank lockdown, and other "everything else 0.0f" branches below. + if (dynamic_cast(action)) + return 1.0f; + + Aura* aura = botAI->GetAura("Unchained Magic", bot, false, true); + + Difficulty diff = bot->GetRaidDifficulty(); + + if (boss->HealthBelowPct(95)) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + + if (aura && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC) && + !dynamic_cast(action)) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 1.0f; + else + return 0.0f; + } + + // Check if boss is casting blistering cold (using both normal and heroic spell IDs) + if (boss->HasUnitState(UNIT_STATE_CASTING) && + (boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD1) || boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD2) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD3) || boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD4))) + { + // If this is the blistering cold action, give it highest priority + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 1.0f; + + // Ranged / healer already beyond the blast radius: keep DPSing or + // healing, just block any movement so they don't wander back in. + bool const safe = bot->GetExactDist2d(boss) >= 33.0f; + if (safe && (botAI->IsRanged(bot) || botAI->IsHeal(bot))) + { + if (dynamic_cast(action)) + return 0.0f; + return 1.0f; + } + + // Disable all other actions while blistering cold is casting + return 0.0f; + } + + // Highest priority if we have beacon + if (bot->HasAura(SPELL_FROST_BEACON)) + { + if (dynamic_cast(action)) + return 1.0f; + else + return 0.0f; + } + + Group* group = bot->GetGroup(); + // Check if anyone in group has Frost Beacon (SPELL_FROST_BEACON) + bool anyoneHasFrostBeacon = false; + + if (group) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON)) + { + anyoneHasFrostBeacon = true; + break; + } + } + } + + if (anyoneHasFrostBeacon && boss && + boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), + ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f && + !boss->HealthBelowPct(25) && !boss->HealthAbovePct(99)) + { + if (dynamic_cast(action)) + return 1.0f; + else + return 0.0f; + } + + if (anyoneHasFrostBeacon && !botAI->IsMainTank(bot)) + { + if (dynamic_cast(action)) + return 0.0f; + } + + if (botAI->IsMainTank(bot)) + { + Aura* aura = botAI->GetAura("mystic buffet", bot, false, true); + if (aura && aura->GetStackAmount() >= 6) + { + if (dynamic_cast(action)) + return 1.0f; + else + return 0.0f; + } + } + + if (!botAI->IsTank(bot) && boss && boss->HealthBelowPct(35)) + { + if (dynamic_cast(action)) + return 0.0f; + } + + if (boss && botAI->IsTank(bot)) + { + if (boss->HealthBelowPct(35)) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 1.0f; + else + return 0.0f; + } + } + + if (boss && boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f && !boss->HealthBelowPct(25) && !boss->HealthAbovePct(99)) + { + if (dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +float IccLichKingAddsMultiplier::GetValue(Action* action) +{ + if (bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f)) + return 1.0f; + + Unit* terenas = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f); + if (terenas) + { + // Warlocks and melee stay functional (movement + adds action only) + if (botAI->IsMelee(bot) || bot->getClass() == CLASS_WARLOCK) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 1.0f; + return 0.0f; + } + + // Main tank near another tank: suppress movement jitter + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + if (!botAI->IsMainTank(bot) && mainTank && bot->GetExactDist2d(mainTank) < 2.0f && + dynamic_cast(action)) + return 0.0f; + + // Suppress all these regardless of role + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + + return 1.0f; + } + + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!boss) + return 1.0f; + + // Allow cure actions only after a brief delay so the plague can spread once + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + { + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!boss) + return 1.0f; + + Group* group = bot->GetGroup(); + if (!group) + return 1.0f; + + static constexpr float DELIVER_RANGE = 3.0f; + static constexpr std::array HorrorEntries = {NPC_SHAMBLING_HORROR1, NPC_SHAMBLING_HORROR2, + NPC_SHAMBLING_HORROR3, NPC_SHAMBLING_HORROR4}; + + // Check whether any Shambling Horror is alive anywhere in the encounter + auto const anyHorrorAlive = [&]() -> bool + { + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + uint32 const entry = unit->GetEntry(); + if (entry == NPC_SHAMBLING_HORROR1 || entry == NPC_SHAMBLING_HORROR2 || + entry == NPC_SHAMBLING_HORROR3 || entry == NPC_SHAMBLING_HORROR4) + return true; + } + return false; + }; + + bool anyPlagued = false; + bool allDelivered = true; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (!botAI->HasAura("Necrotic Plague", member)) + continue; + + anyPlagued = true; + + bool nearHorror = false; + for (uint32 const entry : HorrorEntries) + { + Creature* horror = member->FindNearestCreature(entry, DELIVER_RANGE); + if (horror && horror->IsAlive()) + { + nearHorror = true; + break; + } + } + + if (!nearHorror) + { + allDelivered = false; + break; + } + } + + if (!anyPlagued) + return 1.0f; + + // No Horror alive at all — allow immediate dispel to prevent + // uncontrolled spread wiping the raid + if (!anyHorrorAlive()) + return 1.0f; + + // Horrors exist but not everyone has delivered yet — suppress cures + return allDelivered ? 1.0f : 0.0f; + } + + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + // Hunters may flee (kite mechanics); everyone else stays put + if (dynamic_cast(action) && bot->getClass() != CLASS_HUNTER) + return 0.0f; + + if (boss->HealthAbovePct(71)) + { + // Assist tank targeting is fully managed by HandleAssistTankAddManagement — + // suppress generic target-switching actions so they don't override it. + if (botAI->IsAssistTank(bot) && + (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action))) + return 0.0f; + + if (!botAI->IsTank(bot) && dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + auto const hasWinterAura = [&]() -> bool + { + return boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || + boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4) || + boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || + boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8); + }; + + auto const isCastingWinter = [&]() -> bool + { + if (!boss->HasUnitState(UNIT_STATE_CASTING)) + return false; + + return boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8); + }; + + if (hasWinterAura() || isCastingWinter()) + { + // Winter action and facing take priority + if (dynamic_cast(action) || dynamic_cast(action)) + return 1.0f; + + // Staging window: while boss is casting Winter, non-tanks must commit + // to the staging move. Only heals are allowed; everything else blocked. + if (isCastingWinter() && !botAI->IsTank(bot)) + { + if (dynamic_cast(action) || + dynamic_cast(action)) + return 1.0f; + return 0.0f; + } + + // Adds action is suppressed during winter + if (dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + + // Assist tank should not pick up adds independently during winter + if (botAI->IsAssistTank(bot) && dynamic_cast(action)) + return 0.0f; + + // Suppress movement/attack toward the boss if we are far away + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + if (currentTarget && currentTarget == boss && bot->GetDistance2d(boss) > 50.0f) + { + if (dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + + // Suppress movement toward boss/sphere — but allow target-switching actions + // (DpsAssistAction, AttackRtiTargetAction) so bots can pick up skull-marked adds. + if (currentTarget && + (currentTarget == boss || currentTarget->GetEntry() == NPC_ICE_SPHERE1 || + currentTarget->GetEntry() == NPC_ICE_SPHERE2 || + currentTarget->GetEntry() == NPC_ICE_SPHERE3 || currentTarget->GetEntry() == NPC_ICE_SPHERE4)) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + } + + if (botAI->IsRanged(bot) && !botAI->GetAura("Harvest Soul", bot, false, false)) + { + GuidVector const& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + bool defilePresent = false; + for (ObjectGuid const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == DEFILE_NPC_ID) + { + defilePresent = true; + break; + } + } + + if (defilePresent && (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action))) + return 0.0f; + } + + if (botAI->IsAssistTank(bot) && boss->HealthAbovePct(71)) + { + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + if (currentTarget && currentTarget == boss && dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +float IccLichKingSpiritBombMultiplier::GetValue(Action* action) +{ + if (!IccLichKingSpiritBombAction::IsBombThreatActive(botAI, bot)) + return 1.0f; + + // Allowlist: only the avoidance move and facing run during a bomb threat. + // Everything else is suppressed so the avoidance move sticks. + if (dynamic_cast(action) || + dynamic_cast(action)) + return 1.0f; + + return 0.0f; +} diff --git a/src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.h b/src/Ai/Raid/ICC/ICCMultipliers.h similarity index 84% rename from src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.h rename to src/Ai/Raid/ICC/ICCMultipliers.h index 69d1ac51bac..245b4d1321b 100644 --- a/src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.h +++ b/src/Ai/Raid/ICC/ICCMultipliers.h @@ -1,5 +1,5 @@ -#ifndef _PLAYERBOT_RAIDICCMULTIPLIERS_H -#define _PLAYERBOT_RAIDICCMULTIPLIERS_H +#ifndef _PLAYERBOT_ICCM_H +#define _PLAYERBOT_ICCM_H #include "Multiplier.h" @@ -99,4 +99,19 @@ class IccLichKingAddsMultiplier : public Multiplier virtual float GetValue(Action* action); }; +class IccLichKingSpiritBombMultiplier : public Multiplier +{ +public: + IccLichKingSpiritBombMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc lich king spirit bomb") {} + virtual float GetValue(Action* action); +}; + +//GUNSHIP +class IccGunshipMultiplier : public Multiplier +{ +public: + IccGunshipMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc gunship") {} + virtual float GetValue(Action* action); +}; + #endif diff --git a/src/Ai/Raid/ICC/ICCScripts.cpp b/src/Ai/Raid/ICC/ICCScripts.cpp new file mode 100644 index 00000000000..81c42c00590 --- /dev/null +++ b/src/Ai/Raid/ICC/ICCScripts.cpp @@ -0,0 +1,123 @@ +#include "ICCScripts.h" +#include "Player.h" +#include "ICCTriggers.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellInfo.h" +#include "Timer.h" +#include + +namespace IcecrownHelpers +{ + std::unordered_map> malleableGooImpacts; + std::map festergutGooWaitUntil; + std::unordered_map defileCast; + std::unordered_map rotfaceVileGas; + std::map rotfaceVileGasWaitUntil; +} + +class IccPutricideListenerScript : public AllSpellScript +{ +public: + IccPutricideListenerScript() : AllSpellScript("IccPutricideListenerScript") { } + + void OnSpellCast(Spell* spell, Unit* caster, SpellInfo const* spellInfo, bool /*skipCheck*/) override + { + if (!caster || !spellInfo) + return; + + if (spellInfo->Id != SPELL_MALLEABLE_GOO_10N && + spellInfo->Id != SPELL_MALLEABLE_GOO_25N && + spellInfo->Id != SPELL_MALLEABLE_GOO_10H && + spellInfo->Id != SPELL_MALLEABLE_GOO_25H && + spellInfo->Id != SPELL_MALLEABLE_GOO_BALCONY) + return; + + // Malleable Goo is cast triggered, so m_UniqueTargetInfo is not yet + // populated at this point; read the explicit unit target directly. + Unit* target = spell->m_targets.GetUnitTarget(); + if (!target || !target->IsPlayer()) + return; + + uint32 now = getMSTime(); + + IcecrownHelpers::MalleableGooImpact impact; + impact.position = target->GetPosition(); + impact.castTime = now; + + auto& impacts = IcecrownHelpers::malleableGooImpacts[caster->GetMap()->GetInstanceId()]; + impacts.push_back(impact); + + // Evict stale entries to keep the list bounded. Retention covers the + // longest consumer window (Festergut avoid: 8s) + slack. + impacts.erase( + std::remove_if(impacts.begin(), impacts.end(), + [now](IcecrownHelpers::MalleableGooImpact const& i) + { return getMSTimeDiff(i.castTime, now) > 9000; }), + impacts.end()); + } +}; + +class IccRotfaceListenerScript : public AllSpellScript +{ +public: + IccRotfaceListenerScript() : AllSpellScript("IccRotfaceListenerScript") { } + + void OnSpellCast(Spell* spell, Unit* caster, SpellInfo const* spellInfo, bool /*skipCheck*/) override + { + if (!caster || !spellInfo) + return; + + if (spellInfo->Id != SPELL_VILE_GAS_H) + return; + + // Professor Putricide casts vile gas from the balcony during Rotface + // heroic. Filtering on caster entry keeps this hook scoped to the + // Rotface encounter only (Festergut also uses 'vile gas' as the gas + // spore aura name but a different spell ID). + if (caster->GetEntry() != NPC_PROFESSOR_PUTRICIDE) + return; + + Unit* target = spell->m_targets.GetUnitTarget(); + if (!target || !target->IsPlayer()) + return; + + IcecrownHelpers::VileGasVictim& entry = IcecrownHelpers::rotfaceVileGas[caster->GetMap()->GetInstanceId()]; + entry.victimGuid = target->GetGUID(); + entry.castTime = getMSTime(); + } +}; + +class IccLichKingListenerScript : public AllSpellScript +{ +public: + IccLichKingListenerScript() : AllSpellScript("IccLichKingListenerScript") { } + + // OnSpellPrepare fires at cast START (Spell::prepare). OnSpellCast fires + // at cast END, which for Defile (2s cast time) is too late - the puddle + // is already spawning and bots have no time to move out. + void OnSpellPrepare(Spell* spell, Unit* caster, SpellInfo const* spellInfo) override + { + if (!caster || !spellInfo) + return; + + if (spellInfo->Id != DEFILE_CAST_ID) + return; + + Unit* target = spell->m_targets.GetUnitTarget(); + if (!target || !target->IsPlayer()) + return; + + IcecrownHelpers::DefileCastInfo& entry = + IcecrownHelpers::defileCast[caster->GetMap()->GetInstanceId()]; + entry.targetGuid = target->GetGUID(); + entry.castTime = getMSTime(); + } +}; + +void AddSC_IcecrownBotScripts() +{ + new IccPutricideListenerScript(); + new IccRotfaceListenerScript(); + new IccLichKingListenerScript(); +} diff --git a/src/Ai/Raid/ICC/ICCScripts.h b/src/Ai/Raid/ICC/ICCScripts.h new file mode 100644 index 00000000000..28e02b00209 --- /dev/null +++ b/src/Ai/Raid/ICC/ICCScripts.h @@ -0,0 +1,68 @@ +#ifndef _PLAYERBOT_ICCSCRIPTS_H +#define _PLAYERBOT_ICCSCRIPTS_H + +#include +#include +#include +#include "ObjectGuid.h" +#include "Position.h" + +namespace IcecrownHelpers +{ + // Putricide - Malleable Goo + // Each entry records the impact position (target's location at cast time) + // and the ms timestamp of the cast. IccPutricideAvoidMalleableGooAction + // reads this list on every tick and makes every bot flee any active + // impact points, since the core casts the spell triggered (no cast bar) + // and it is neither a DynamicObject, trap, nor trigger NPC. + struct MalleableGooImpact + { + Position position; + uint32 castTime; + }; + extern std::unordered_map> malleableGooImpacts; + + // Festergut avoid-malleable-goo wait state. When a bot dodges goo we stamp + // a wait-until timestamp here so the trigger stays active and movement is + // held for the full 8s impact window - otherwise the group-position action + // pulls the bot back the very next tick, producing jitter. + extern std::map festergutGooWaitUntil; + + // Lich King - Defile (SPELL_DEFILE = 72762). Stamped at OnSpellCast time + // because the boss script casts via CastSpell(target, ...) and reading the + // target later via current-spell APIs is unreliable. Readers treat entries + // older than ~3s as expired (cast time is 2s). + struct DefileCastInfo + { + ObjectGuid targetGuid; + uint32 castTime; + }; + extern std::unordered_map defileCast; + + // Rotface - Vile Gas. Stamped at OnSpellCast time so the targeted bot can + // react before the aura applies. Readers treat entries older than ~5s as + // expired (covers the dodge window plus the 3s post-arrival hold). + struct VileGasVictim + { + ObjectGuid victimGuid; + uint32 castTime; + }; + extern std::unordered_map rotfaceVileGas; + + // Rotface vile gas hold-at-safe-spot state. When the victim bot reaches + // its safe spot we stamp now+3000ms so the multiplier blocks any other + // movement action that would yank the bot back into the raid stack. + extern std::map rotfaceVileGasWaitUntil; +} + +// Putricide - Mutated Abomination vehicle +constexpr uint32 GO_PUTRICIDE_DRINK_ME = 201584; +constexpr uint32 NPC_MUTATED_ABOMINATION_10 = 37672; +constexpr uint32 NPC_MUTATED_ABOMINATION_25 = 38285; +constexpr uint32 SPELL_MUTATED_TRANSFORMATION = 70311; +constexpr uint32 SPELL_ABO_EAT_OOZE = 70346; +constexpr uint32 SPELL_ABO_REGURGITATED_OOZE = 70539; + +void AddSC_IcecrownBotScripts(); + +#endif diff --git a/src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.cpp b/src/Ai/Raid/ICC/ICCStrategy.cpp similarity index 81% rename from src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.cpp rename to src/Ai/Raid/ICC/ICCStrategy.cpp index e0dbe82306e..8e651354036 100644 --- a/src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.cpp +++ b/src/Ai/Raid/ICC/ICCStrategy.cpp @@ -1,6 +1,6 @@ -#include "RaidIccStrategy.h" +#include "ICCStrategy.h" -#include "RaidIccMultipliers.h" +#include "ICCMultipliers.h" void RaidIccStrategy::InitTriggers(std::vector& triggers) { @@ -28,34 +28,32 @@ void RaidIccStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("icc in cannon", { NextAction("icc cannon fire", ACTION_RAID+5) })); - triggers.push_back(new TriggerNode("icc gunship teleport ally", - { NextAction("icc gunship teleport ally", ACTION_RAID + 4) })); + triggers.push_back(new TriggerNode("icc gunship rocket jump", + { NextAction("icc gunship rocket jump", ACTION_RAID + 4)})); - triggers.push_back(new TriggerNode("icc gunship teleport horde", - { NextAction("icc gunship teleport horde", ACTION_RAID + 4) })); + triggers.push_back(new TriggerNode("icc gunship rocket pack setup", + { NextAction("icc gunship rocket pack setup", ACTION_RAID + 2)})); //DBS triggers.push_back(new TriggerNode("icc dbs", { NextAction("icc dbs tank position", ACTION_RAID + 3), NextAction("icc adds dbs", ACTION_RAID + 5) })); - triggers.push_back(new TriggerNode("icc dbs main tank rune of blood", - { NextAction("taunt spell", ACTION_EMERGENCY + 4) })); + // Boss taunt on Rune of Blood is handled inside icc dbs tank position action - //DOGS - triggers.push_back(new TriggerNode("icc stinky precious main tank mortal wound", - { NextAction("taunt spell", ACTION_EMERGENCY + 4) })); + triggers.push_back(new TriggerNode("icc dogs", + { NextAction("icc dogs tank position", ACTION_RAID + 3) })); //FESTERGUT triggers.push_back(new TriggerNode("icc festergut group position", { NextAction("icc festergut group position", ACTION_MOVE + 4) })); - triggers.push_back(new TriggerNode("icc festergut main tank gastric bloat", - { NextAction("taunt spell", ACTION_EMERGENCY + 6) })); - triggers.push_back(new TriggerNode("icc festergut spore", { NextAction("icc festergut spore", ACTION_MOVE + 5) })); + triggers.push_back(new TriggerNode("icc festergut avoid malleable goo", + { NextAction("icc festergut avoid malleable goo", ACTION_RAID + 7) })); + //ROTFACE triggers.push_back(new TriggerNode("icc rotface tank position", { NextAction("icc rotface tank position", ACTION_RAID + 5) })); @@ -66,6 +64,9 @@ void RaidIccStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("icc rotface move away from explosion", { NextAction("icc rotface move away from explosion", ACTION_RAID +7) })); + triggers.push_back(new TriggerNode("icc rotface avoid vile gas", + { NextAction("icc rotface avoid vile gas", ACTION_RAID + 8) })); + //PP triggers.push_back(new TriggerNode("icc putricide volatile ooze", { NextAction("icc putricide volatile ooze", ACTION_RAID + 4) })); @@ -76,11 +77,14 @@ void RaidIccStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("icc putricide growing ooze puddle", { NextAction("icc putricide growing ooze puddle", ACTION_RAID + 3) })); - triggers.push_back(new TriggerNode("icc putricide main tank mutated plague", - { NextAction("taunt spell", ACTION_RAID + 10) })); + triggers.push_back(new TriggerNode("icc putricide mutated plague", + { NextAction("icc putricide mutated plague", ACTION_RAID + 3) })); triggers.push_back(new TriggerNode("icc putricide malleable goo", - { NextAction("icc putricide avoid malleable goo", ACTION_RAID + 2) })); + { NextAction("icc putricide avoid malleable goo", ACTION_RAID + 6) })); + + triggers.push_back(new TriggerNode("icc putricide abomination", + { NextAction("icc putricide abomination", ACTION_RAID + 7) })); //BPC triggers.push_back(new TriggerNode("icc bpc keleseth tank", @@ -119,6 +123,9 @@ void RaidIccStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("icc valithria group", { NextAction("icc valithria group", ACTION_RAID + 1) })); + triggers.push_back(new TriggerNode("icc valithria zombie kite", + { NextAction("icc valithria zombie kite", ACTION_EMERGENCY + 9) })); + triggers.push_back(new TriggerNode("icc valithria portal", { NextAction("icc valithria portal", ACTION_RAID + 5) })); @@ -135,6 +142,9 @@ void RaidIccStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("icc sindragosa frost beacon", { NextAction("icc sindragosa frost beacon", ACTION_RAID + 5) })); + triggers.push_back(new TriggerNode("icc sindragosa hot", + { NextAction("icc sindragosa hot", ACTION_RAID + 6) })); + triggers.push_back(new TriggerNode("icc sindragosa blistering cold", { NextAction("icc sindragosa blistering cold", ACTION_EMERGENCY + 4) })); @@ -168,6 +178,9 @@ void RaidIccStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("icc lich king adds", { NextAction("icc lich king adds", ACTION_RAID +2) })); + + triggers.push_back(new TriggerNode("icc lich king spirit bomb", + { NextAction("icc lich king spirit bomb", ACTION_RAID +7) })); } void RaidIccStrategy::InitMultipliers(std::vector& multipliers) @@ -183,4 +196,6 @@ void RaidIccStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new IccValithriaDreamCloudMultiplier(botAI)); multipliers.push_back(new IccSindragosaMultiplier(botAI)); multipliers.push_back(new IccLichKingAddsMultiplier(botAI)); + multipliers.push_back(new IccLichKingSpiritBombMultiplier(botAI)); + multipliers.push_back(new IccGunshipMultiplier(botAI)); } diff --git a/src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.h b/src/Ai/Raid/ICC/ICCStrategy.h similarity index 83% rename from src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.h rename to src/Ai/Raid/ICC/ICCStrategy.h index fbd54cc6482..8f331eba383 100644 --- a/src/Ai/Raid/Icecrown/Strategy/RaidIccStrategy.h +++ b/src/Ai/Raid/ICC/ICCStrategy.h @@ -1,5 +1,5 @@ -#ifndef _PLAYERBOT_RAIDICCSTRATEGY_H -#define _PLAYERBOT_RAIDICCSTRATEGY_H +#ifndef _PLAYERBOT_ICCS_H +#define _PLAYERBOT_ICCS_H #include "Strategy.h" diff --git a/src/Ai/Raid/Icecrown/RaidIccTriggerContext.h b/src/Ai/Raid/ICC/ICCTriggerContext.h similarity index 81% rename from src/Ai/Raid/Icecrown/RaidIccTriggerContext.h rename to src/Ai/Raid/ICC/ICCTriggerContext.h index 83f3004668e..ca6e8f7da01 100644 --- a/src/Ai/Raid/Icecrown/RaidIccTriggerContext.h +++ b/src/Ai/Raid/ICC/ICCTriggerContext.h @@ -1,8 +1,8 @@ -#ifndef _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H -#define _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H +#ifndef _PLAYERBOT_ICCTRIGGERCONTEXT_H +#define _PLAYERBOT_ICCTRIGGERCONTEXT_H #include "NamedObjectContext.h" -#include "RaidIccTriggers.h" +#include "ICCTriggers.h" class RaidIccTriggerContext : public NamedObjectContext { @@ -17,27 +17,29 @@ class RaidIccTriggerContext : public NamedObjectContext creators["icc rotting frost giant tank position"] = &RaidIccTriggerContext::icc_rotting_frost_giant_tank_position; creators["icc in cannon"] = &RaidIccTriggerContext::icc_in_cannon; creators["icc gunship cannon near"] = &RaidIccTriggerContext::icc_gunship_cannon_near; - creators["icc gunship teleport ally"] = &RaidIccTriggerContext::icc_gunship_teleport_ally; - creators["icc gunship teleport horde"] = &RaidIccTriggerContext::icc_gunship_teleport_horde; + creators["icc gunship rocket jump"] = &RaidIccTriggerContext::icc_gunship_rocket_jump; + creators["icc gunship rocket pack setup"] = &RaidIccTriggerContext::icc_gunship_rocket_pack_setup; creators["icc dbs"] = &RaidIccTriggerContext::icc_dbs; creators["icc dbs main tank rune of blood"] = &RaidIccTriggerContext::icc_dbs_main_tank_rune_of_blood; - creators["icc stinky precious main tank mortal wound"] = &RaidIccTriggerContext::icc_stinky_precious_main_tank_mortal_wound; + creators["icc dogs"] = &RaidIccTriggerContext::icc_dogs; creators["icc festergut group position"] = &RaidIccTriggerContext::icc_festergut_group_position; - creators["icc festergut main tank gastric bloat"] = &RaidIccTriggerContext::icc_festergut_main_tank_gastric_bloat; creators["icc festergut spore"] = &RaidIccTriggerContext::icc_festergut_spore; + creators["icc festergut avoid malleable goo"] = &RaidIccTriggerContext::icc_festergut_avoid_malleable_goo; creators["icc rotface tank position"] = &RaidIccTriggerContext::icc_rotface_tank_position; creators["icc rotface group position"] = &RaidIccTriggerContext::icc_rotface_group_position; creators["icc rotface move away from explosion"] = &RaidIccTriggerContext::icc_rotface_move_away_from_explosion; + creators["icc rotface avoid vile gas"] = &RaidIccTriggerContext::icc_rotface_avoid_vile_gas; creators["icc putricide volatile ooze"] = &RaidIccTriggerContext::icc_putricide_volatile_ooze; creators["icc putricide gas cloud"] = &RaidIccTriggerContext::icc_putricide_gas_cloud; creators["icc putricide growing ooze puddle"] = &RaidIccTriggerContext::icc_putricide_growing_ooze_puddle; - creators["icc putricide main tank mutated plague"] = &RaidIccTriggerContext::icc_putricide_main_tank_mutated_plague; + creators["icc putricide mutated plague"] = &RaidIccTriggerContext::icc_putricide_mutated_plague; creators["icc putricide malleable goo"] = &RaidIccTriggerContext::icc_putricide_malleable_goo; + creators["icc putricide abomination"] = &RaidIccTriggerContext::icc_putricide_abomination; creators["icc bpc keleseth tank"] = &RaidIccTriggerContext::icc_bpc_keleseth_tank; creators["icc bpc main tank"] = &RaidIccTriggerContext::icc_bpc_main_tank; @@ -56,9 +58,11 @@ class RaidIccTriggerContext : public NamedObjectContext creators["icc valithria portal"] = &RaidIccTriggerContext::icc_valithria_portal; creators["icc valithria heal"] = &RaidIccTriggerContext::icc_valithria_heal; creators["icc valithria dream cloud"] = &RaidIccTriggerContext::icc_valithria_dream_cloud; + creators["icc valithria zombie kite"] = &RaidIccTriggerContext::icc_valithria_zombie_kite; creators["icc sindragosa group position"] = &RaidIccTriggerContext::icc_sindragosa_group_position; creators["icc sindragosa frost beacon"] = &RaidIccTriggerContext::icc_sindragosa_frost_beacon; + creators["icc sindragosa hot"] = &RaidIccTriggerContext::icc_sindragosa_hot; creators["icc sindragosa blistering cold"] = &RaidIccTriggerContext::icc_sindragosa_blistering_cold; creators["icc sindragosa unchained magic"] = &RaidIccTriggerContext::icc_sindragosa_unchained_magic; creators["icc sindragosa chilled to the bone"] = &RaidIccTriggerContext::icc_sindragosa_chilled_to_the_bone; @@ -71,6 +75,7 @@ class RaidIccTriggerContext : public NamedObjectContext creators["icc lich king necrotic plague"] = &RaidIccTriggerContext::icc_lich_king_necrotic_plague; creators["icc lich king winter"] = &RaidIccTriggerContext::icc_lich_king_winter; creators["icc lich king adds"] = &RaidIccTriggerContext::icc_lich_king_adds; + creators["icc lich king spirit bomb"] = &RaidIccTriggerContext::icc_lich_king_spirit_bomb; } private: @@ -82,27 +87,29 @@ class RaidIccTriggerContext : public NamedObjectContext static Trigger* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionTrigger(ai); } static Trigger* icc_in_cannon(PlayerbotAI* ai) { return new IccInCannonTrigger(ai); } static Trigger* icc_gunship_cannon_near(PlayerbotAI* ai) { return new IccGunshipCannonNearTrigger(ai); } - static Trigger* icc_gunship_teleport_ally(PlayerbotAI* ai) { return new IccGunshipTeleportAllyTrigger(ai); } - static Trigger* icc_gunship_teleport_horde(PlayerbotAI* ai) { return new IccGunshipTeleportHordeTrigger(ai); } + static Trigger* icc_gunship_rocket_jump(PlayerbotAI* ai) { return new IccGunshipRocketJumpTrigger(ai); } + static Trigger* icc_gunship_rocket_pack_setup(PlayerbotAI* ai) { return new IccGunshipRocketPackSetupTrigger(ai); } static Trigger* icc_dbs(PlayerbotAI* ai) { return new IccDbsTrigger(ai); } static Trigger* icc_dbs_main_tank_rune_of_blood(PlayerbotAI* ai) { return new IccDbsMainTankRuneOfBloodTrigger(ai); } - static Trigger* icc_stinky_precious_main_tank_mortal_wound(PlayerbotAI* ai) { return new IccStinkyPreciousMainTankMortalWoundTrigger(ai); } + static Trigger* icc_dogs(PlayerbotAI* ai) { return new IccDogsTrigger(ai); } static Trigger* icc_festergut_group_position(PlayerbotAI* ai) { return new IccFestergutGroupPositionTrigger(ai); } - static Trigger* icc_festergut_main_tank_gastric_bloat(PlayerbotAI* ai) { return new IccFestergutMainTankGastricBloatTrigger(ai); } static Trigger* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeTrigger(ai); } + static Trigger* icc_festergut_avoid_malleable_goo(PlayerbotAI* ai) { return new IccFestergutAvoidMalleableGooTrigger(ai); } static Trigger* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionTrigger(ai); } static Trigger* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionTrigger(ai); } static Trigger* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionTrigger(ai); } + static Trigger* icc_rotface_avoid_vile_gas(PlayerbotAI* ai) { return new IccRotfaceAvoidVileGasTrigger(ai); } static Trigger* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeTrigger(ai); } static Trigger* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudTrigger(ai); } static Trigger* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleTrigger(ai); } - static Trigger* icc_putricide_main_tank_mutated_plague(PlayerbotAI* ai) { return new IccPutricideMainTankMutatedPlagueTrigger(ai); } + static Trigger* icc_putricide_mutated_plague(PlayerbotAI* ai) { return new IccPutricideMutatedPlagueTrigger(ai); } static Trigger* icc_putricide_malleable_goo(PlayerbotAI* ai) { return new IccPutricideMalleableGooTrigger(ai); } + static Trigger* icc_putricide_abomination(PlayerbotAI* ai) { return new IccPutricideAbominationTrigger(ai); } static Trigger* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankTrigger(ai); } static Trigger* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankTrigger(ai); } @@ -120,10 +127,12 @@ class RaidIccTriggerContext : public NamedObjectContext static Trigger* icc_valithria_group(PlayerbotAI* ai) { return new IccValithriaGroupTrigger(ai); } static Trigger* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalTrigger(ai); } static Trigger* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealTrigger(ai); } + static Trigger* icc_valithria_zombie_kite(PlayerbotAI* ai) { return new IccValithriaZombieKiteTrigger(ai); } static Trigger* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudTrigger(ai); } static Trigger* icc_sindragosa_group_position(PlayerbotAI* ai) { return new IccSindragosaGroupPositionTrigger(ai); } static Trigger* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconTrigger(ai); } + static Trigger* icc_sindragosa_hot(PlayerbotAI* ai) { return new IccSindragosaHotTrigger(ai); } static Trigger* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdTrigger(ai); } static Trigger* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicTrigger(ai); } static Trigger* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneTrigger(ai); } @@ -136,6 +145,7 @@ class RaidIccTriggerContext : public NamedObjectContext static Trigger* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueTrigger(ai); } static Trigger* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterTrigger(ai); } static Trigger* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsTrigger(ai); } + static Trigger* icc_lich_king_spirit_bomb(PlayerbotAI* ai) { return new IccLichKingSpiritBombTrigger(ai); } }; diff --git a/src/Ai/Raid/Icecrown/Trigger/RaidIccTriggers.cpp b/src/Ai/Raid/ICC/ICCTriggers.cpp similarity index 64% rename from src/Ai/Raid/Icecrown/Trigger/RaidIccTriggers.cpp rename to src/Ai/Raid/ICC/ICCTriggers.cpp index bc304ec0c61..c3a28a2a576 100644 --- a/src/Ai/Raid/Icecrown/Trigger/RaidIccTriggers.cpp +++ b/src/Ai/Raid/ICC/ICCTriggers.cpp @@ -1,5 +1,5 @@ -#include "RaidIccTriggers.h" -#include "RaidIccActions.h" +#include "ICCTriggers.h" +#include "ICCActions.h" #include "NearestNpcsValue.h" #include "PlayerbotAIConfig.h" #include "ObjectAccessor.h" @@ -8,6 +8,7 @@ #include "Trigger.h" #include "GridNotifiers.h" #include "Vehicle.h" +#include "ICCScripts.h" //Lord Marrogwar bool IccLmTrigger::IsActive() @@ -37,9 +38,6 @@ bool IccLadyDeathwhisperTrigger::IsActive() if (!boss) return false; - if (bot->HasAura(SPELL_EXPERIENCED)) - bot->RemoveAura(SPELL_EXPERIENCED); - return true; } @@ -73,52 +71,60 @@ bool IccGunshipCannonNearTrigger::IsActive() return false; Unit* mount1 = bot->FindNearestCreature(NPC_CANNONA, 100.0f); - Unit* mount2 = bot->FindNearestCreature(NPC_CANNONH, 100.0f); if (!mount1 && !mount2) return false; - if (!botAI->IsDps(bot)) + // If cannons have Below Zero aura, don't try to enter them + Unit* friendlyCannon = nullptr; + if (mount1 && mount1->IsFriendlyTo(bot)) + friendlyCannon = mount1; + else if (mount2 && mount2->IsFriendlyTo(bot)) + friendlyCannon = mount2; + + if (friendlyCannon && friendlyCannon->HasAura(SPELL_BELOW_ZERO)) return false; - // Player* master = botAI->GetMaster(); - // if (!master) - // return false; - // if (!master->GetVehicle()) - // return false; + if (!botAI->IsDps(bot)) + return false; return true; } -bool IccGunshipTeleportAllyTrigger::IsActive() +bool IccGunshipRocketJumpTrigger::IsActive() { - Unit* boss = bot->FindNearestCreature(NPC_HIGH_OVERLORD_SAURFANG, 100.0f); - if (!boss || !boss->IsInWorld() || boss->IsDuringRemoveFromWorld()) - return false; - - if (!boss->IsAlive()) - return false; + // The rocket jump mechanic is only needed when the gunship battle is active. + // We detect which ship we are on by checking which enemy boss is present: + // - Saurfang hostile => we are on the Alliance ship + // - Muradin hostile => we are on the Horde ship + // Using the hostile boss (not cannon friendliness) avoids conflicting with + // the cannon-near trigger that fires on the same condition. + Unit* saurfang = bot->FindNearestCreature(NPC_HIGH_OVERLORD_SAURFANG, 100.0f); + if (saurfang && saurfang->IsAlive() && saurfang->IsHostileTo(bot)) + return true; - if (!boss->IsHostileTo(bot)) - return false; + Unit* muradin = bot->FindNearestCreature(NPC_MURADIN_BRONZEBEARD, 100.0f); + if (muradin && muradin->IsAlive() && muradin->IsHostileTo(bot)) + return true; - return true; + return false; } -bool IccGunshipTeleportHordeTrigger::IsActive() +bool IccGunshipRocketPackSetupTrigger::IsActive() { - Unit* boss = bot->FindNearestCreature(NPC_MURADIN_BRONZEBEARD, 100.0f); - if (!boss || !boss->IsInWorld() || boss->IsDuringRemoveFromWorld()) - return false; - - if (!boss->IsAlive()) - return false; + // Fires any time a bot is standing on a friendly gunship deck, regardless of + // combat state. Lets bots walk to Zafod and equip the rocket pack before the + // encounter starts (and keep it ready if they acquire it mid-fight). + Unit* cannonA = bot->FindNearestCreature(NPC_CANNONA, 100.0f); + if (cannonA && cannonA->IsFriendlyTo(bot)) + return true; - if (!boss->IsHostileTo(bot)) - return false; + Unit* cannonH = bot->FindNearestCreature(NPC_CANNONH, 100.0f); + if (cannonH && cannonH->IsFriendlyTo(bot)) + return true; - return true; + return false; } //DBS @@ -155,28 +161,12 @@ bool IccDbsMainTankRuneOfBloodTrigger::IsActive() return true; } -//DOGS -bool IccStinkyPreciousMainTankMortalWoundTrigger::IsActive() +bool IccDogsTrigger::IsActive() { - bool bossPresent = false; if (AI_VALUE2(Unit*, "find target", "stinky") || AI_VALUE2(Unit*, "find target", "precious")) - bossPresent = true; - - if (!bossPresent) - return false; - - if (!botAI->IsAssistTankOfIndex(bot, 0)) - return false; - - Unit* mt = AI_VALUE(Unit*, "main tank"); - if (!mt) - return false; - - Aura* aura = botAI->GetAura("mortal wound", mt, false, true); - if (!aura || aura->GetStackAmount() < 8) - return false; + return true; - return true; + return false; } //FESTERGUT @@ -192,30 +182,6 @@ bool IccFestergutGroupPositionTrigger::IsActive() return true; } -bool IccFestergutMainTankGastricBloatTrigger::IsActive() -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); - if (!boss) - { - return false; - } - if (!botAI->IsAssistTankOfIndex(bot, 0)) - { - return false; - } - Unit* mt = AI_VALUE(Unit*, "main tank"); - if (!mt) - { - return false; - } - Aura* aura = botAI->GetAura("Gastric Bloat", mt, false, true); - if (!aura || aura->GetStackAmount() < 6) - { - return false; - } - return true; -} - bool IccFestergutSporeTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); @@ -240,6 +206,71 @@ bool IccFestergutSporeTrigger::IsActive() return false; } +bool IccFestergutAvoidMalleableGooTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); + if (!boss) + return false; + + // Tanks hold the boss at the fixed tank spot; goo can land on tanks but + // moving would lose threat and let goo land on melee stack anyway. + if (botAI->IsTank(bot)) + return false; + + // During spore phase, position switching handles goo avoidance — free-dodge + // would pull bots out of their assigned spore spots. + Group* group = bot->GetGroup(); + if (group) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->HasAura(SPELL_GAS_SPORE)) + return false; + } + } + + constexpr uint32 impactLifetimeMs = 8000; + constexpr float gooDangerRadius = 12.0f; + + uint32 now = getMSTime(); + float botX = bot->GetPositionX(); + float botY = bot->GetPositionY(); + ObjectGuid botGuid = bot->GetGUID(); + + auto impactIt = IcecrownHelpers::malleableGooImpacts.find(bot->GetMap()->GetInstanceId()); + if (impactIt != IcecrownHelpers::malleableGooImpacts.end()) + { + for (auto const& impact : impactIt->second) + { + if (getMSTimeDiff(impact.castTime, now) > impactLifetimeMs) + continue; + float dx = botX - impact.position.GetPositionX(); + float dy = botY - impact.position.GetPositionY(); + if (dx * dx + dy * dy < gooDangerRadius * gooDangerRadius) + { + // Lock bot into wait mode until this impact expires - prevents + // group-position from yanking it back into the danger zone. + uint32 waitUntil = impact.castTime + impactLifetimeMs; + auto& slot = IcecrownHelpers::festergutGooWaitUntil[botGuid]; + if (waitUntil > slot) + slot = waitUntil; + return true; + } + } + } + + auto it = IcecrownHelpers::festergutGooWaitUntil.find(botGuid); + if (it != IcecrownHelpers::festergutGooWaitUntil.end()) + { + if (now < it->second) + return true; + IcecrownHelpers::festergutGooWaitUntil.erase(it); + } + + return false; +} + //ROTFACE bool IccRotfaceTankPositionTrigger::IsActive() { @@ -264,11 +295,58 @@ bool IccRotfaceGroupPositionTrigger::IsActive() bool IccRotfaceMoveAwayFromExplosionTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "big ooze"); + Creature* boss = bot->FindNearestCreature(NPC_BIG_OOZE, 100.0f); + bool castingNow = boss && boss->IsAlive() && + boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION); + + if (castingNow) + { + _wasCasting = true; + _castEndTime = 0; + return true; + } + + // Cast just ended — record the time + if (_wasCasting) + { + _wasCasting = false; + if (_castEndTime == 0) + _castEndTime = time(nullptr); + } + + // Stay active for 6 seconds after cast ended (2s wait + return movement) + if (_castEndTime > 0 && time(nullptr) - _castEndTime < 6) + return true; + + _castEndTime = 0; + return false; +} + +bool IccRotfaceAvoidVileGasTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "rotface"); if (!boss) return false; - return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION); + uint32 const now = getMSTime(); + + auto vgIt = IcecrownHelpers::rotfaceVileGas.find(bot->GetMap()->GetInstanceId()); + bool const isVictim = + vgIt != IcecrownHelpers::rotfaceVileGas.end() && + vgIt->second.victimGuid == bot->GetGUID() && + getMSTimeDiff(vgIt->second.castTime, now) < 8000; + if (isVictim) + return true; + + if (botAI->HasAura("Vile Gas", bot)) + return true; + + auto const& waitMap = IcecrownHelpers::rotfaceVileGasWaitUntil; + auto it = waitMap.find(bot->GetGUID()); + if (it != waitMap.end() && now < it->second) + return true; + + return false; } //PP @@ -280,25 +358,6 @@ bool IccPutricideGrowingOozePuddleTrigger::IsActive() if (!boss) return false; - Difficulty diff = bot->GetRaidDifficulty(); - - if (sPlayerbotAIConfig.EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - { - //-------CHEAT------- - if (!bot->HasAura(SPELL_EXPERIENCED)) - bot->AddAura(SPELL_EXPERIENCED, bot); - - if (!bot->HasAura(SPELL_AGEIS_OF_DALARAN)) - bot->AddAura(SPELL_AGEIS_OF_DALARAN, bot); - - if (!bot->HasAura(SPELL_NO_THREAT) && botAI->HasAggro(boss) && !botAI->IsTank(bot)) - bot->AddAura(SPELL_NO_THREAT, bot); - - if (botAI->IsMainTank(bot) && !bot->HasAura(SPELL_SPITEFULL_FURY) && boss->GetVictim() != bot) - bot->AddAura(SPELL_SPITEFULL_FURY, bot); - //-------CHEAT------- - } - const GuidVector& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); for (auto const& npc : npcs) { @@ -344,50 +403,98 @@ bool IccPutricideGasCloudTrigger::IsActive() return true; } -bool IccPutricideMainTankMutatedPlagueTrigger::IsActive() +bool IccPutricideMutatedPlagueTrigger::IsActive() { - bool bossPresent = false; - if (AI_VALUE2(Unit*, "find target", "professor putricide")) - bossPresent = true; + return AI_VALUE2(Unit*, "find target", "professor putricide") != nullptr; +} - if (!bossPresent) +bool IccPutricideMalleableGooTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (!boss) return false; - if (!botAI->IsAssistTankOfIndex(bot, 0)) - { - return false; - } - Unit* mt = AI_VALUE(Unit*, "main tank"); - if (!mt) - { - return false; - } - Aura* aura = botAI->GetAura("Mutated Plague", mt, false, true); - if (!aura || aura->GetStackAmount() < 4) + Difficulty const diff = bot->GetRaidDifficulty(); + + // Heroic cheat buffs — apply to all group members (bots + real players) + if (boss && sPlayerbotAIConfig.EnableICCBuffs && + (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) { - return false; + if (Group* buffGroup = bot->GetGroup()) + { + for (GroupReference* itr = buffGroup->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || !member->IsInWorld()) + continue; + + if (!member->HasAura(SPELL_EXPERIENCED)) + member->AddAura(SPELL_EXPERIENCED, member); + + if (!member->HasAura(SPELL_AGEIS_OF_DALARAN)) + member->AddAura(SPELL_AGEIS_OF_DALARAN, member); + + if (!PlayerbotAI::IsTank(member) && !member->HasAura(SPELL_NO_THREAT)) + member->AddAura(SPELL_NO_THREAT, member); + + if (PlayerbotAI::IsTank(member) && !member->HasAura(SPELL_SPITEFULL_FURY) && + boss->GetVictim() != member) + member->AddAura(SPELL_SPITEFULL_FURY, member); + } + } } + return true; } -bool IccPutricideMalleableGooTrigger::IsActive() +bool IccPutricideAbominationTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); if (!boss) return false; - if (botAI->IsTank(bot)) - return true; - - Unit* boss1 = AI_VALUE2(Unit*, "find target", "volatile ooze"); - if (boss1) + if (!botAI->IsAssistTank(bot)) return false; - Unit* boss2 = AI_VALUE2(Unit*, "find target", "gas cloud"); - if (boss2) + // Already piloting - keep action firing until vehicle drops. + if (Unit* veh = bot->GetVehicleBase()) + { + uint32 e = veh->GetEntry(); + if (e == NPC_MUTATED_ABOMINATION_10 || e == NPC_MUTATED_ABOMINATION_25) + return true; + } + + // Phase 3: boss takes toy back. No transformation. + if (boss->HealthBelowPct(35)) return false; - return true; + // Someone else already piloting - do not drink. + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* m = itr->GetSource(); + if (!m || m == bot || !m->IsAlive()) + continue; + if (Unit* vb = m->GetVehicleBase()) + { + uint32 e = vb->GetEntry(); + if (e == NPC_MUTATED_ABOMINATION_10 || e == NPC_MUTATED_ABOMINATION_25) + return false; + } + } + } + + // Require at least one Growing Ooze Puddle nearby. + GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto const& g : npcs) + { + if (Unit* u = botAI->GetUnit(g)) + if (u->GetEntry() == NPC_GROWING_OOZE_PUDDLE) + return true; + } + + return false; } //BPC @@ -463,10 +570,10 @@ bool IccBpcKineticBombTrigger::IsActive() if (!botAI->IsRanged(bot) || botAI->IsHeal(bot)) return false; - // Early exit condition - if Shadow Prison has too many stacks + // Allow up to 18 stacks for bomb-assigned bots (multiplier handles assignment) if (Aura* aura = botAI->GetAura("Shadow Prison", bot, false, true)) { - if (aura->GetStackAmount() > 12) + if (aura->GetStackAmount() > 18) return false; } @@ -485,7 +592,7 @@ bool IccBpcKineticBombTrigger::IsActive() if (unit->GetEntry() == entry) { // Check if bomb is within valid Z-axis range - if (unit->GetPositionZ() - bot->GetPositionZ() < 25.0f) + if (unit->GetPositionZ() - bot->GetPositionZ() < 35.0f) { bombFound = true; break; @@ -513,16 +620,23 @@ bool IccBpcBallOfFlameTrigger::IsActive() if (!auraTaldaram) return false; - return true; + return true; } -//BQL +// BQL bool IccBqlGroupPositionTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); if (!boss) return false; + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram"); + Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth"); + + if (valanar || taldaram || keleseth) + return false; + if (bot->HasAura(SPELL_EXPERIENCED)) bot->RemoveAura(SPELL_EXPERIENCED); @@ -535,6 +649,13 @@ bool IccBqlPactOfDarkfallenTrigger::IsActive() if (!boss) return false; + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram"); + Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth"); + + if (valanar || taldaram || keleseth) + return false; + Aura* aura = botAI->GetAura("Pact of the Darkfallen", bot); if (!aura) return false; @@ -548,6 +669,13 @@ bool IccBqlVampiricBiteTrigger::IsActive() if (!boss) return false; + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram"); + Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth"); + + if (valanar || taldaram || keleseth) + return false; + Aura* aura = botAI->GetAura("Frenzied Bloodthirst", bot); if (!aura) return false; @@ -590,6 +718,26 @@ bool IccValithriaGroupTrigger::IsActive() return true; } +bool IccValithriaZombieKiteTrigger::IsActive() +{ + Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); + if (!boss) + return false; + + if (botAI->IsTank(bot)) + return false; + + std::list zombies; + bot->GetCreatureListWithEntryInGrid(zombies, NPC_BLISTERING_ZOMBIE, 100.0f); + for (Creature* z : zombies) + { + if (z && z->IsAlive() && z->GetVictim() == bot) + return true; + } + + return false; +} + bool IccValithriaPortalTrigger::IsActive() { Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); @@ -809,15 +957,10 @@ bool IccValithriaHealTrigger::IsActive() bool IccValithriaDreamCloudTrigger::IsActive() { - // Only active if we're in dream state if (!bot->HasAura(SPELL_DREAM_STATE) || bot->HealthBelowPct(50)) return false; - // Find nearest cloud of either type - Creature* dreamCloud = bot->FindNearestCreature(NPC_DREAM_CLOUD, 100.0f); - Creature* nightmareCloud = bot->FindNearestCreature(NPC_NIGHTMARE_CLOUD, 100.0f); - - return (dreamCloud || nightmareCloud); + return true; } //SINDRAGOSA @@ -832,21 +975,47 @@ bool IccSindragosaGroupPositionTrigger::IsActive() if (sPlayerbotAIConfig.EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) { //-------CHEAT------- - if (!bot->HasAura(SPELL_EXPERIENCED)) - bot->AddAura(SPELL_EXPERIENCED, bot); + // Apply to every alive group member so real players benefit too, + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || !member->IsInWorld()) + continue; + + if (!member->HasAura(SPELL_EXPERIENCED)) + member->AddAura(SPELL_EXPERIENCED, member); - if (!bot->HasAura(SPELL_AGEIS_OF_DALARAN)) - bot->AddAura(SPELL_AGEIS_OF_DALARAN, bot); + if (!member->HasAura(SPELL_AGEIS_OF_DALARAN)) + member->AddAura(SPELL_AGEIS_OF_DALARAN, member); - if (!bot->HasAura(SPELL_NO_THREAT) && botAI->HasAggro(boss) && !botAI->IsTank(bot)) - bot->AddAura(SPELL_NO_THREAT, bot); + if (!botAI->IsTank(member) && !member->HasAura(SPELL_NO_THREAT)) + member->AddAura(SPELL_NO_THREAT, member); - if (botAI->IsMainTank(bot) && !bot->HasAura(SPELL_SPITEFULL_FURY) && boss->GetVictim() != bot) - bot->AddAura(SPELL_SPITEFULL_FURY, bot); + if (botAI->IsMainTank(member) && boss->GetVictim() != member && + !member->HasAura(SPELL_SPITEFULL_FURY)) + member->AddAura(SPELL_SPITEFULL_FURY, member); + } + } //-------CHEAT------- } - if (!boss || bot->HasAura(SPELL_FROST_BEACON) /*|| bot->HasAura(69762)*/ || boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 50.0f) + // Air phase: give all tanks nitro boosts so they can quickly reposition to tombs + if (boss->IsInCombat() && botAI->IsTank(bot) && + boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 50.0f) + { + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + } + + // Last phase: tanks must keep tanking, never run to a tomb spot. Strip + // Frost Beacon so the tomb-positioning logic doesn't apply to them. + if (botAI->IsTank(bot) && bot->HasAura(SPELL_FROST_BEACON) && boss->HealthBelowPct(35) && + boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) >= 30.0f) + bot->RemoveAura(SPELL_FROST_BEACON); + + if (!boss || bot->HasAura(SPELL_FROST_BEACON) || boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 50.0f) return false; return true; @@ -854,7 +1023,7 @@ bool IccSindragosaGroupPositionTrigger::IsActive() bool IccSindragosaFrostBeaconTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + Unit* boss = bot->FindNearestCreature(NPC_SINDRAGOSA, 200.0f); if (!boss) return false; @@ -879,6 +1048,32 @@ bool IccSindragosaFrostBeaconTrigger::IsActive() return false; } +bool IccSindragosaHotTrigger::IsActive() +{ + if (!botAI->IsHeal(bot)) + return false; + + if (bot->HasAura(SPELL_FROST_BEACON)) + return false; + + Unit* boss = bot->FindNearestCreature(NPC_SINDRAGOSA, 200.0f); + if (!boss) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON)) + return true; + } + + return false; +} + bool IccSindragosaBlisteringColdTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); @@ -974,6 +1169,15 @@ bool IccSindragosaMysticBuffetTrigger::IsActive() if (bot->HasAura(SPELL_FROST_BEACON)) return false; + // Blistering Cold takes priority over tomb-hiding in the last phase: + // skip hiding so the bot can run to the safe spot instead. + if (boss->HasUnitState(UNIT_STATE_CASTING) && + (boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD1) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD2) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD3) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD4))) + return false; + if (aura->GetStackAmount() >= 1) return true; @@ -1023,6 +1227,7 @@ bool IccSindragosaMainTankMysticBuffetTrigger::IsActive() return true; } +// TODO never triggers since mystic buffet is bypassed in action bool IccSindragosaTankSwapPositionTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); @@ -1072,9 +1277,12 @@ bool IccSindragosaFrostBombTrigger::IsActive() if (!boss) return false; - if (!bot->IsAlive() || bot->HasAura(SPELL_ICE_TOMB)) // Skip if dead or in Ice Tomb + if (!bot->IsAlive()) // Skip if dead return false; + // Tombed bots intentionally pass through: the action pins their group to + // the current tomb's zone so when freed they don't migrate to the wrong + // zone. The action returns false for tombed bots without moving them. if (boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 50.0f && !boss->HealthBelowPct(25) && !boss->HealthAbovePct(99)) return true; @@ -1085,6 +1293,10 @@ bool IccSindragosaFrostBombTrigger::IsActive() bool IccLichKingShadowTrapTrigger::IsActive() { + Unit* vdw = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); + if (vdw) + return false; + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); if (!boss) return false; @@ -1118,71 +1330,76 @@ bool IccLichKingShadowTrapTrigger::IsActive() bool IccLichKingNecroticPlagueTrigger::IsActive() { - bool hasPlague = botAI->HasAura("Necrotic Plague", bot); + Unit* vdw = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); + if (vdw) + return false; + + if (!AI_VALUE2(Unit*, "find target", "the lich king")) + return false; - return hasPlague; + return botAI->HasAura("Necrotic Plague", bot); } bool IccLichKingWinterTrigger::IsActive() { + Unit* vdw = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); + if (vdw) + return false; + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); if (!boss) return false; - // Check for either Remorseless Winter - bool hasWinterAura = false; - if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || - boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4))) - hasWinterAura = true; - - bool hasWinter2Aura = false; - if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || - boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8))) - hasWinter2Aura = true; - - bool isCasting = false; - if (boss && boss->HasUnitState(UNIT_STATE_CASTING)) - isCasting = true; - - bool isWinter = false; - if (boss && boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8)) - isWinter = true; - - if (hasWinterAura || hasWinter2Aura) - return true; + auto const hasWinterAura = [&]() -> bool + { + return boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || + boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4) || + boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || + boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8); + }; - if (isCasting && isWinter) - return true; + auto const isCastingWinter = [&]() -> bool + { + if (!boss->HasUnitState(UNIT_STATE_CASTING)) + return false; - return false; + return boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8); + }; + + return hasWinterAura() || isCastingWinter(); } bool IccLichKingAddsTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - - bool hasPlague = botAI->HasAura("Necrotic Plague", bot); - if (hasPlague) + Unit* vdw = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); + if (vdw) return false; - Unit* terenasMenethilHC = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f); - Unit* terenasMenethil = bot->FindNearestCreature(NPC_TERENAS_MENETHIL, 55.0f); + if (bot->HasAura(SPELL_HARVEST_SOUL_VALKYR)) + return false; - if (terenasMenethilHC) - return true; + if (botAI->HasAura("Necrotic Plague", bot)) + return false; - if (terenasMenethil) + if (bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f) || + bot->FindNearestCreature(NPC_TERENAS_MENETHIL, 55.0f)) return true; - if (!boss) + Unit* lk = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!lk) return false; return true; } + +bool IccLichKingSpiritBombTrigger::IsActive() +{ + return IccLichKingSpiritBombAction::IsBombThreatActive(botAI, bot); +} diff --git a/src/Ai/Raid/Icecrown/Trigger/RaidIccTriggers.h b/src/Ai/Raid/ICC/ICCTriggers.h similarity index 74% rename from src/Ai/Raid/Icecrown/Trigger/RaidIccTriggers.h rename to src/Ai/Raid/ICC/ICCTriggers.h index cd332e00467..96b8af5a487 100644 --- a/src/Ai/Raid/Icecrown/Trigger/RaidIccTriggers.h +++ b/src/Ai/Raid/ICC/ICCTriggers.h @@ -1,5 +1,5 @@ -#ifndef _PLAYERBOT_RAIDICCTRIGGERS_H -#define _PLAYERBOT_RAIDICCTRIGGERS_H +#ifndef _PLAYERBOT_ICCT_H +#define _PLAYERBOT_ICCT_H #include "PlayerbotAI.h" #include "Playerbots.h" @@ -9,11 +9,23 @@ enum CreatureIdsICC { // Lord Marrowgar - NPC_SPIKE1 = 36619, - NPC_SPIKE2 = 38711, - NPC_SPIKE3 = 38712, + NPC_SPIKE1 = 36619, // 10N base + NPC_SPIKE1_10H = 38233, // 10H + NPC_SPIKE1_25N = 38459, // 25N + NPC_SPIKE1_25H = 38460, // 25H + NPC_SPIKE2 = 38711, // 25N base + NPC_SPIKE2_10H = 38970, + NPC_SPIKE2_25N = 38971, + NPC_SPIKE2_25H = 38972, + NPC_SPIKE3 = 38712, // 25N base + NPC_SPIKE3_10H = 38973, + NPC_SPIKE3_25N = 38974, + NPC_SPIKE3_25H = 38975, + NPC_COLDFLAME = 36672, // Lady Deathwhisper + NPC_DARNAVAN_10 = 38472, + NPC_DARNAVAN_25 = 38485, NPC_SHADE = 38222, // Gunship Battle @@ -29,6 +41,8 @@ enum CreatureIdsICC NPC_CANNONH = 36839, NPC_MURADIN_BRONZEBEARD = 36948, NPC_HIGH_OVERLORD_SAURFANG = 36939, + NPC_ZAFOD_BOOMBOX = 37184, + ITEM_GOBLIN_ROCKET_PACK = 49278, // Deathbringer Saurfang NPC_BLOOD_BEAST1 = 38508, @@ -38,9 +52,11 @@ enum CreatureIdsICC // Rotface NPC_PUDDLE = 37013, + NPC_SMALL_OOZE = 36897, NPC_BIG_OOZE = 36899, // Putricide + NPC_PROFESSOR_PUTRICIDE = 36678, NPC_MALLEABLE_OOZE_STALKER = 38556, NPC_GROWING_OOZE_PUDDLE = 37690, NPC_CHOKING_GAS_BOMB = 38159, @@ -56,6 +72,7 @@ enum CreatureIdsICC NPC_KINETIC_BOMB4 = 38777, NPC_BALL_OF_FLAME = 38332, NPC_BALL_OF_INFERNO_FLAME = 38451, + NPC_SHOCK_VORTEX = 38422, // Blood Queen Lana'thel NPC_SWARMING_SHADOWS = 38163, @@ -133,26 +150,47 @@ enum SpellIdsICC SPELL_NO_THREAT = 70115, //reduce threat SPELL_SPITEFULL_FURY = 36886, //500% more threat SPELL_NITRO_BOOSTS = 54861, //Speed + SPELL_FROST_TRAP1 = 13809, //Hunter slow trap SPELL_PAIN_SUPPRESION = 69910, //40% dmg reduction SPELL_AGEIS_OF_DALARAN = 71638, //268 all ress SPELL_CYCLONE = 33786, SPELL_HAMMER_OF_JUSTICE = 10308, //stun + // Taunt spells (used to reset cooldowns for assist tank) + SPELL_TAUNT_WARRIOR = 355, + SPELL_TAUNT_PALADIN = 62124, // Hand of Reckoning + SPELL_TAUNT_DK = 56222, // Dark Command + SPELL_TAUNT_DRUID = 6795, // Growl + SPELL_VIPER_STING = 3034, + + // Lord Marrowgar + SPELL_LM_IMPALED = 69065, // Lady Deathwhisper SPELL_DARK_RECKONING = 69483, + SPELL_TOUCH_OF_INSIGNIFICANCE = 71204, // Gunship Battle SPELL_DEATH_PLAGUE = 72865, + SPELL_FROZEN_CANNON = 69704, SPELL_BELOW_ZERO = 69705, + SPELL_ROCKET_PACK_USE = 68645, + SPELL_ROCKET_PACK_USEABLE = 70348, + SPELL_BATTLE_FURY1 = 69637, + SPELL_BATTLE_FURY2 = 69638, + SPELL_BATTLE_FURY3 = 72306, + SPELL_BATTLE_FURY4 = 72307, + SPELL_BATTLE_FURY5 = 72308, // Festergut SPELL_GAS_SPORE = 69279, - // Rotface SPELL_SLIME_SPRAY = 69508, SPELL_OOZE_FLOOD = 71215, SPELL_UNSTABLE_OOZE_EXPLOSION = 69839, SPELL_OOZE_FLOOD_VISUAL = 69785, + // Cast by Professor Putricide from balcony during Rotface heroic. + // Single ID (no difficulty variants in spelldifficulty_dbc). + SPELL_VILE_GAS_H = 69240, // Putricide SPELL_MALLEABLE_GOO = 70852, @@ -166,6 +204,7 @@ enum SpellIdsICC // Blood Queen Lana'thel SPELL_PACT_OF_THE_DARKFALLEN = 71340, + SPELL_BLOODBOLT_WHIRL = 71772, // Sister Svalna SPELL_AETHER_SHIELD = 71463, @@ -173,6 +212,7 @@ enum SpellIdsICC // Valithria Dreamwalker SPELL_DREAM_STATE = 70766, SPELL_EMERALD_VIGOR = 70873, + SPELL_ACID_BURST = 70744, // Sindragosa SPELL_FROST_BEACON = 70126, @@ -182,6 +222,9 @@ enum SpellIdsICC SPELL_BLISTERING_COLD2 = 71047, SPELL_BLISTERING_COLD3 = 71048, SPELL_BLISTERING_COLD4 = 71049, + SPELL_HAND_OF_FREEDOM = 1044, + ITEM_RED_SMOKE_FLARE = 23769, + ITEM_BLUE_SMOKE_FLARE = 23770, // The Lich King SPELL_HARVEST_SOUL_VALKYR = 68985, @@ -194,12 +237,26 @@ enum SpellIdsICC SPELL_REMORSELESS_WINTER6 = 74270, SPELL_REMORSELESS_WINTER7 = 74271, SPELL_REMORSELESS_WINTER8 = 74272, -}; - -const uint32 DEFILE_AURAS[] = {72756, 74162, 74163, 74164}; -const uint32 DEFILE_CAST_ID = 72762; -const uint32 DEFILE_NPC_ID = 38757; -const size_t DEFILE_AURA_COUNT = 4; + SPELL_VALKYR_CARRY = 30440, + SPELL_HARVEST_SOUL_LK = 68980, + SPELL_HARVEST_SOULS_LK_25 = 73654, + SPELL_HARVEST_SOULS_LK_H1 = 74295, + SPELL_HARVEST_SOULS_LK_H2 = 74296, + SPELL_HARVEST_SOULS_LK_H3 = 74297, +}; + +inline constexpr uint32 DEFILE_AURAS[] = {72756, 74162, 74163, 74164}; +inline constexpr uint32 DEFILE_CAST_ID = 72762; +inline constexpr uint32 DEFILE_NPC_ID = 38757; +inline constexpr size_t DEFILE_AURA_COUNT = 4; + +// Malleable Goo (Putricide / Festergut-heroic). Multiple variants because of +// SpellDifficulty remapping and the balcony stalker variant. +inline constexpr uint32 SPELL_MALLEABLE_GOO_10N = 70852; +inline constexpr uint32 SPELL_MALLEABLE_GOO_25N = 72297; +inline constexpr uint32 SPELL_MALLEABLE_GOO_10H = 74280; +inline constexpr uint32 SPELL_MALLEABLE_GOO_25H = 74281; +inline constexpr uint32 SPELL_MALLEABLE_GOO_BALCONY = 72296; // All fanatics and adherents entry ids Lady Deathwhisper static const std::array addEntriesLady = { @@ -212,8 +269,8 @@ const std::vector spellEntriesFlood = { 69799, 69801, 69802, 69795}; const std::vector availableTargetsGS = { - NPC_KOR_KRON_AXETHROWER, NPC_KOR_KRON_ROCKETEER, NPC_KOR_KRON_BATTLE_MAGE, NPC_IGB_HIGH_OVERLORD_SAURFANG, - NPC_SKYBREAKER_RIFLEMAN, NPC_SKYBREAKER_MORTAR_SOLDIER, NPC_SKYBREAKER_SORCERER, NPC_IGB_MURADIN_BRONZEBEARD}; + NPC_KOR_KRON_ROCKETEER, NPC_KOR_KRON_AXETHROWER, NPC_KOR_KRON_BATTLE_MAGE, NPC_IGB_HIGH_OVERLORD_SAURFANG, + NPC_SKYBREAKER_MORTAR_SOLDIER, NPC_SKYBREAKER_RIFLEMAN, NPC_SKYBREAKER_SORCERER, NPC_IGB_MURADIN_BRONZEBEARD}; static std::vector sporeOrder; @@ -262,17 +319,17 @@ class IccGunshipCannonNearTrigger : public Trigger bool IsActive() override; }; -class IccGunshipTeleportAllyTrigger : public Trigger +class IccGunshipRocketJumpTrigger : public Trigger { public: - IccGunshipTeleportAllyTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship teleport ally") {} + IccGunshipRocketJumpTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship rocket jump") {} bool IsActive() override; }; -class IccGunshipTeleportHordeTrigger : public Trigger +class IccGunshipRocketPackSetupTrigger : public Trigger { public: - IccGunshipTeleportHordeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship teleport horde") {} + IccGunshipRocketPackSetupTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship rocket pack setup") {} bool IsActive() override; }; @@ -291,11 +348,10 @@ class IccDbsMainTankRuneOfBloodTrigger : public Trigger bool IsActive() override; }; -//DOGS -class IccStinkyPreciousMainTankMortalWoundTrigger : public Trigger +class IccDogsTrigger : public Trigger { public: - IccStinkyPreciousMainTankMortalWoundTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc stinky precious main tank mortal wound") {} + IccDogsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc dogs") {} bool IsActive() override; }; @@ -321,6 +377,14 @@ class IccFestergutSporeTrigger : public Trigger bool IsActive() override; }; +class IccFestergutAvoidMalleableGooTrigger : public Trigger +{ +public: + IccFestergutAvoidMalleableGooTrigger(PlayerbotAI* botAI) + : Trigger(botAI, "icc festergut avoid malleable goo") {} + bool IsActive() override; +}; + //ROTFACE class IccRotfaceTankPositionTrigger : public Trigger { @@ -339,7 +403,20 @@ class IccRotfaceGroupPositionTrigger : public Trigger class IccRotfaceMoveAwayFromExplosionTrigger : public Trigger { public: - IccRotfaceMoveAwayFromExplosionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotface move away from explosion") {} + IccRotfaceMoveAwayFromExplosionTrigger(PlayerbotAI* botAI) + : Trigger(botAI, "icc rotface move away from explosion"), _castEndTime(0), _wasCasting(false) {} + bool IsActive() override; + +private: + time_t _castEndTime; + bool _wasCasting; +}; + +class IccRotfaceAvoidVileGasTrigger : public Trigger +{ +public: + IccRotfaceAvoidVileGasTrigger(PlayerbotAI* botAI) + : Trigger(botAI, "icc rotface avoid vile gas") {} bool IsActive() override; }; @@ -365,10 +442,10 @@ class IccPutricideGrowingOozePuddleTrigger : public Trigger bool IsActive() override; }; -class IccPutricideMainTankMutatedPlagueTrigger : public Trigger +class IccPutricideMutatedPlagueTrigger : public Trigger { public: - IccPutricideMainTankMutatedPlagueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide main tank mutated plague") {} + IccPutricideMutatedPlagueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide mutated plague") {} bool IsActive() override; }; @@ -379,6 +456,13 @@ class IccPutricideMalleableGooTrigger : public Trigger bool IsActive() override; }; +class IccPutricideAbominationTrigger : public Trigger +{ +public: + IccPutricideAbominationTrigger(PlayerbotAI* ai) : Trigger(ai, "icc putricide abomination") {} + bool IsActive() override; +}; + //BPC class IccBpcKelesethTankTrigger : public Trigger { @@ -461,6 +545,13 @@ class IccValithriaGroupTrigger : public Trigger bool IsActive() override; }; +class IccValithriaZombieKiteTrigger : public Trigger +{ +public: + IccValithriaZombieKiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valithria zombie kite") {} + bool IsActive() override; +}; + class IccValithriaPortalTrigger : public Trigger { public: @@ -497,6 +588,13 @@ class IccSindragosaFrostBeaconTrigger : public Trigger bool IsActive() override; }; +class IccSindragosaHotTrigger : public Trigger +{ +public: + IccSindragosaHotTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa hot") {} + bool IsActive() override; +}; + class IccSindragosaBlisteringColdTrigger : public Trigger { public: @@ -575,4 +673,11 @@ class IccLichKingAddsTrigger : public Trigger bool IsActive() override; }; +class IccLichKingSpiritBombTrigger : public Trigger +{ +public: + IccLichKingSpiritBombTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lich king spirit bomb") {} + bool IsActive() override; +}; + #endif diff --git a/src/Ai/Raid/Icecrown/Action/RaidIccActions.cpp b/src/Ai/Raid/Icecrown/Action/RaidIccActions.cpp deleted file mode 100644 index 0ae27ffabbb..00000000000 --- a/src/Ai/Raid/Icecrown/Action/RaidIccActions.cpp +++ /dev/null @@ -1,9258 +0,0 @@ -#include "RaidIccActions.h" -#include "NearestNpcsValue.h" -#include "ObjectAccessor.h" -#include "Playerbots.h" -#include "Vehicle.h" -#include "RtiValue.h" -#include "GenericSpellActions.h" -#include "GenericActions.h" -#include "RaidIccTriggers.h" -#include "Multiplier.h" - -// Lord Marrowgwar -bool IccLmTankPositionAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar"); - if (!boss) - return false; - - if (!botAI->IsTank(bot)) - return false; - - const bool isBossInBoneStorm = botAI->GetAura("Bone Storm", boss) != nullptr; - - if (isBossInBoneStorm) - return false; - - if (botAI->HasAggro(boss) && botAI->IsMainTank(bot) && boss->GetVictim() == bot) - { - const float maxDistanceThreshold = 3.0f; - const float distance = bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY()); - - if (distance > maxDistanceThreshold) - return MoveTowardPosition(ICC_LM_TANK_POSITION, maxDistanceThreshold); - } - - if (botAI->IsAssistTank(bot)) - { - const float maxDistanceThreshold = 3.0f; - const float distance = bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY()); - - if (distance > maxDistanceThreshold) - return MoveTowardPosition(ICC_LM_TANK_POSITION, maxDistanceThreshold); - - if (distance < maxDistanceThreshold) - { - bot->SetFacingToObject(boss); - return true; - } - } - - return false; -} - -bool IccLmTankPositionAction::MoveTowardPosition(const Position& position, float incrementSize) -{ - // Calculate direction vector - const float dirX = position.GetPositionX() - bot->GetPositionX(); - const float dirY = position.GetPositionY() - bot->GetPositionY(); - const float length = std::sqrt(dirX * dirX + dirY * dirY); - - // Normalize direction vector - const float normalizedDirX = dirX / length; - const float normalizedDirY = dirY / length; - - // Calculate new position with increment - const float moveX = bot->GetPositionX() + normalizedDirX * incrementSize; - const float moveY = bot->GetPositionY() + normalizedDirY * incrementSize; - - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); -} - -bool IccSpikeAction::Execute(Event /*event*/) -{ - // If we're impaled, we can't do anything - if (botAI->GetAura("Impaled", bot)) - return false; - - // Find the boss - Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar"); - if (!boss) - return false; - - const bool isBossInBoneStorm = botAI->GetAura("Bone Storm", boss) != nullptr; - const bool shouldMoveToSafePosition = boss->isInFront(bot) && !botAI->IsTank(bot) && !isBossInBoneStorm; - - if (shouldMoveToSafePosition) - { - const Position safePosition{-390.6757f, 2230.5283f, 0.0f}; // Z value to be overridden by actual bot Z - const float distance = bot->GetExactDist2d(safePosition.GetPositionX(), safePosition.GetPositionY()); - const float maxDistanceThreshold = 3.0f; - - if (distance > maxDistanceThreshold) - return MoveTowardPosition(safePosition, maxDistanceThreshold); - - return false; - } - - if (!botAI->IsTank(bot)) - return false; - - return HandleSpikeTargeting(boss); -} - -bool IccSpikeAction::HandleSpikeTargeting(Unit* boss) -{ - static const std::array spikeEntries = {NPC_SPIKE1, NPC_SPIKE2, NPC_SPIKE3}; - const GuidVector spikes = AI_VALUE(GuidVector, "possible targets no los"); - - Unit* priorityTarget = nullptr; - bool anySpikesExist = false; - - // First check for alive spikes - for (const auto entry : spikeEntries) - { - for (auto const& guid : spikes) - { - if (Unit* unit = botAI->GetUnit(guid)) - { - if (unit->GetEntry() == entry) - { - anySpikesExist = true; // At least one spike exists - - if (unit->IsAlive()) - { // Only consider alive ones for targeting - priorityTarget = unit; - break; - } - } - } - } - if (priorityTarget) - break; - } - - // Only fallback to boss if NO spikes exist at all (alive or dead) - if (!anySpikesExist && boss->IsAlive()) - priorityTarget = boss; - - // Update raid target icon if needed - if (priorityTarget) - UpdateRaidTargetIcon(priorityTarget); - - return false; -} - -bool IccSpikeAction::MoveTowardPosition(const Position& position, float incrementSize) -{ - // Calculate direction vector - const float dirX = position.GetPositionX() - bot->GetPositionX(); - const float dirY = position.GetPositionY() - bot->GetPositionY(); - const float length = std::sqrt(dirX * dirX + dirY * dirY); - - // Normalize direction vector - const float normalizedDirX = dirX / length; - const float normalizedDirY = dirY / length; - - // Calculate new position with increment - const float moveX = bot->GetPositionX() + normalizedDirX * incrementSize; - const float moveY = bot->GetPositionY() + normalizedDirY * incrementSize; - - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); -} - -void IccSpikeAction::UpdateRaidTargetIcon(Unit* target) -{ - static constexpr uint8_t SKULL_ICON_INDEX = 7; - - if (Group* group = bot->GetGroup()) - { - const ObjectGuid currentSkull = group->GetTargetIcon(SKULL_ICON_INDEX); - Unit* currentSkullUnit = botAI->GetUnit(currentSkull); - - const bool needsUpdate = !currentSkullUnit || !currentSkullUnit->IsAlive() || currentSkullUnit != target; - - if (needsUpdate) - group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), target->GetGUID()); - } -} - -// Lady Deathwhisper -bool IccDarkReckoningAction::Execute(Event /*event*/) -{ - constexpr float SAFE_DISTANCE_THRESHOLD = 2.0f; - - // Check if the bot needs to move to the safe position - if (bot->HasAura(SPELL_DARK_RECKONING) && - bot->GetExactDist2d(ICC_DARK_RECKONING_SAFE_POSITION) > SAFE_DISTANCE_THRESHOLD) - { - // Move to the safe position with the same parameters as before - return MoveTo(bot->GetMapId(), ICC_DARK_RECKONING_SAFE_POSITION.GetPositionX(), - ICC_DARK_RECKONING_SAFE_POSITION.GetPositionY(), - ICC_DARK_RECKONING_SAFE_POSITION.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_NORMAL); - } - - return false; -} - -bool IccRangedPositionLadyDeathwhisperAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); - if (!boss) - return false; - - const float currentDistance = bot->GetDistance2d(boss); - const float minDistance = 7.0f; - const float maxDistance = 30.0f; - - if (currentDistance < minDistance || currentDistance > maxDistance) - return false; - - if (!botAI->IsRanged(bot) && !botAI->IsHeal(bot)) - return false; - - return MaintainRangedSpacing(); -} - -bool IccRangedPositionLadyDeathwhisperAction::MaintainRangedSpacing() -{ - const float safeSpacingRadius = 3.0f; - const float moveIncrement = 2.0f; - const float maxMoveDistance = 5.0f; // Limit maximum movement distance - const bool isRanged = botAI->IsRanged(bot) || botAI->IsHeal(bot); - - if (!isRanged) - return false; - - // Ranged: spread from other members - const GuidVector members = AI_VALUE(GuidVector, "group members"); - - // Calculate a combined vector representing all nearby members' positions - float totalX = 0.0f; - float totalY = 0.0f; - int nearbyCount = 0; - - for (auto const& memberGuid : members) - { - Unit* member = botAI->GetUnit(memberGuid); - if (!member || !member->IsAlive() || member == bot) - { - continue; - } - - const float distance = bot->GetExactDist2d(member); - if (distance < safeSpacingRadius) - { - // Calculate vector from member to bot - float dx = bot->GetPositionX() - member->GetPositionX(); - float dy = bot->GetPositionY() - member->GetPositionY(); - - // Weight by inverse distance (closer members have more influence) - float weight = (safeSpacingRadius - distance) / safeSpacingRadius; - totalX += dx * weight; - totalY += dy * weight; - nearbyCount++; - } - } - - // If we have nearby members, move away in the combined direction - if (nearbyCount > 0) - { - // Normalize the combined vector - float magnitude = std::sqrt(totalX * totalX + totalY * totalY); - if (magnitude > 0.001f) // Avoid division by zero - { - totalX /= magnitude; - totalY /= magnitude; - - // Calculate move distance based on nearest member - float moveDistance = std::min(moveIncrement, maxMoveDistance); - - // Create target position in the combined direction - float targetX = bot->GetPositionX() + totalX * moveDistance; - float targetY = bot->GetPositionY() + totalY * moveDistance; - float targetZ = bot->GetPositionZ(); // Maintain current Z - - // Check if the target position is valid and move there - if (bot->IsWithinLOS(targetX, targetY, targetZ)) - { - Position targetPos(targetX, targetY, targetZ); - MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_NORMAL); - } - else - { - // If los check fails, try shorter distance - targetX = bot->GetPositionX() + totalX * (moveDistance * 0.5f); - targetY = bot->GetPositionY() + totalY * (moveDistance * 0.5f); - Position targetPos(targetX, targetY, targetZ); - MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_NORMAL); - } - } - } - - return false; // Everyone is properly spaced -} - -bool IccAddsLadyDeathwhisperAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); - if (!boss) - return false; - - if (botAI->HasAura("Dominate Mind", bot, false, false) && !bot->HasAura(SPELL_CYCLONE)) - bot->AddAura(SPELL_CYCLONE, bot); - else if (bot->HasAura(SPELL_CYCLONE) && !botAI->HasAura("Dominate Mind", bot, false, false)) - bot->RemoveAura(SPELL_CYCLONE); - - const uint32 shadeEntryId = NPC_SHADE; - - if (botAI->IsTank(bot) && boss && boss->HealthBelowPct(95) && boss->GetVictim() == bot) - { - // Check if the bot is not the victim of a shade - if (IsTargetedByShade(shadeEntryId)) - return false; - - const float maxDistanceToTankPosition = 20.0f; - const float moveIncrement = 3.0f; - - const float distance = bot->GetExactDist2d(ICC_LDW_TANK_POSTION.GetPositionX(), ICC_LDW_TANK_POSTION.GetPositionY()); - - if (distance > maxDistanceToTankPosition) - { - return MoveTowardPosition(ICC_LDW_TANK_POSTION, moveIncrement); - } - } - - if (!botAI->IsTank(bot)) - return false; - - return HandleAddTargeting(boss); -} - -bool IccAddsLadyDeathwhisperAction::IsTargetedByShade(uint32 shadeEntry) -{ - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto const& npcGuid : npcs) - { - Unit* unit = botAI->GetUnit(npcGuid); - if (unit && unit->GetEntry() == shadeEntry && unit->GetVictim() == bot) - return true; - } - return false; -} - -bool IccAddsLadyDeathwhisperAction::MoveTowardPosition(const Position& position, float incrementSize) -{ - // Calculate direction vector - const float dirX = position.GetPositionX() - bot->GetPositionX(); - const float dirY = position.GetPositionY() - bot->GetPositionY(); - const float length = std::sqrt(dirX * dirX + dirY * dirY); - - // Normalize direction vector - const float normalizedDirX = dirX / length; - const float normalizedDirY = dirY / length; - - // Calculate new position with increment - const float moveX = bot->GetPositionX() + normalizedDirX * incrementSize; - const float moveY = bot->GetPositionY() + normalizedDirY * incrementSize; - - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); -} - -bool IccAddsLadyDeathwhisperAction::HandleAddTargeting(Unit* boss) -{ - const GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); - - Unit* priorityTarget = nullptr; - bool hasValidAdds = false; - - // First check for alive adds - for (auto const& entry : addEntriesLady) - { - for (auto const& guid : targets) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == entry) - { - priorityTarget = unit; - hasValidAdds = true; - break; - } - } - if (priorityTarget) - break; - } - - // Only fallback to boss if NO adds exist - if (!hasValidAdds && boss->IsAlive()) - priorityTarget = boss; - - // Update skull icon if needed - if (priorityTarget) - UpdateRaidTargetIcon(priorityTarget); - - return false; -} - -void IccAddsLadyDeathwhisperAction::UpdateRaidTargetIcon(Unit* target) -{ - static constexpr uint8_t SKULL_ICON_INDEX = 7; - - if (Group* group = bot->GetGroup()) - { - const ObjectGuid currentSkull = group->GetTargetIcon(SKULL_ICON_INDEX); - Unit* currentSkullUnit = botAI->GetUnit(currentSkull); - - const bool needsUpdate = !currentSkullUnit || !currentSkullUnit->IsAlive() || currentSkullUnit != target; - - if (needsUpdate) - group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), target->GetGUID()); - } -} - -bool IccShadeLadyDeathwhisperAction::Execute(Event /*event*/) -{ - static constexpr uint32 VENGEFUL_SHADE_ID = NPC_SHADE; - static constexpr float SAFE_DISTANCE = 12.0f; - - // Get the nearest hostile NPCs - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - - for (auto const& npcGuid : npcs) - { - Unit* shade = botAI->GetUnit(npcGuid); - - // Skip if not a vengeful shade - if (!shade || shade->GetEntry() != VENGEFUL_SHADE_ID) - continue; - - // Only run away if the shade is targeting us - // Check by GUID comparison to ensure we're accurately identifying the specific shade - // This is especially important in 25HC where multiple shades can spawn - if (!shade->GetVictim() || shade->GetVictim()->GetGUID() != bot->GetGUID()) - continue; - - const float currentDistance = bot->GetDistance2d(shade); - - // Move away from the Vengeful Shade if the bot is too close - if (currentDistance < SAFE_DISTANCE) - { - // Calculate direction away from shade - float dx = bot->GetPositionX() - shade->GetPositionX(); - float dy = bot->GetPositionY() - shade->GetPositionY(); - float dist = std::sqrt(dx * dx + dy * dy); - - if (dist < 0.001f) - continue; - - dx /= dist; - dy /= dist; - - float moveDistance = SAFE_DISTANCE - currentDistance; - float targetX = bot->GetPositionX() + dx * moveDistance; - float targetY = bot->GetPositionY() + dy * moveDistance; - float targetZ = bot->GetPositionZ(); - - if (bot->IsWithinLOS(targetX, targetY, targetZ)) - { - botAI->Reset(); - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT); - } - } - } - - return false; -} - -bool IccRottingFrostGiantTankPositionAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "rotting frost giant"); - if (!boss) - return false; - - Aura* aura = botAI->GetAura("death plague", bot, false, false); - if (aura) - bot->RemoveAura(aura->GetId()); - -/* TODO: code works for handling plague, but atm script is bugged and one bot can have 2 plagues at the same time or when cured, which should not happen, and it is immpossible to handle plague atm the legit way. - const bool hasCure = botAI->GetAura("recently infected", bot) != nullptr; - - // Tank behavior - unchanged - if (botAI->IsTank(bot) && botAI->HasAggro(boss) && !isInfected) - if (bot->GetExactDist2d(ICC_ROTTING_FROST_GIANT_TANK_POSITION) > 5.0f) - return MoveTo(bot->GetMapId(), ICC_ROTTING_FROST_GIANT_TANK_POSITION.GetPositionX(), - ICC_ROTTING_FROST_GIANT_TANK_POSITION.GetPositionY(), - ICC_ROTTING_FROST_GIANT_TANK_POSITION.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - - if (botAI->IsTank(bot)) - return false; - - // Handle infected bot behavior - move near a non-infected, non-cured bot - if (isInfected) - { - const GuidVector members = AI_VALUE(GuidVector, "group members"); - - // Count how many bots are targeting each potential target - std::map targetCounts; - - // First, identify all infected bots and their current targets (approximate) - for (auto const& memberGuid : members) - { - Unit* member = botAI->GetUnit(memberGuid); - if (!member || !member->IsAlive() || member == bot) - continue; - - const bool memberIsInfected = botAI->GetAura("death plague", member) != nullptr; - - if (memberIsInfected) - { - // Find the nearest non-infected bot to this infected bot (as a guess of its target) - float minDist = 5.0f; // Only count if they're close enough to likely be targeting - Unit* likelyTarget = nullptr; - - for (auto const& targetGuid : members) - { - Unit* potentialTarget = botAI->GetUnit(targetGuid); - if (!potentialTarget || !potentialTarget->IsAlive() || potentialTarget == member) - continue; - - const bool targetIsInfected = botAI->GetAura("death plague", potentialTarget) != nullptr; - const bool targetHasCure = botAI->GetAura("recently infected", potentialTarget) != nullptr; - - if (!targetIsInfected && !targetHasCure) - { - float dist = member->GetExactDist2d(potentialTarget); - if (dist < minDist) - { - minDist = dist; - likelyTarget = potentialTarget; - } - } - } - - if (likelyTarget) - { - targetCounts[likelyTarget->GetGUID()]++; - } - } - } - - // Find viable targets and score them based on various factors - std::vector> viableTargets; - - // First try to find ranged, non-infected, non-cured bots - for (auto const& memberGuid : members) - { - Unit* member = botAI->GetUnit(memberGuid); - if (!member || !member->IsAlive() || member == bot) - continue; - - const bool memberHasCure = botAI->GetAura("recently infected", member) != nullptr; - const bool memberIsInfected = botAI->GetAura("death plague", member) != nullptr; - - if (!memberIsInfected && !memberHasCure) - { - // Base score is distance (lower is better) - float score = bot->GetExactDist2d(member); - - // Prefer ranged targets - if (botAI->IsRanged(bot)) - { - score *= 0.7f; // Bonus for ranged targets - } - - // Apply penalty based on how many other infected bots are targeting this one - int targetingCount = targetCounts[member->GetGUID()]; - score *= (1.0f + targetingCount * 0.5f); // Increase score (worse) for heavily targeted bots - - viableTargets.push_back(std::make_pair(member, score)); - } - } - - // Sort targets by score (lowest/best first) - std::sort(viableTargets.begin(), viableTargets.end(), - [](const std::pair& a, const std::pair& b) - { return a.second < b.second; }); - - // Choose the best target - Unit* targetBot = nullptr; - if (!viableTargets.empty()) - { - targetBot = viableTargets[0].first; - } - - // Move to target bot if found - if (targetBot) - { - // If we're already close enough (1 yard), no need to move - if (bot->GetExactDist2d(targetBot) > 1.0f) - { - // Calculate a unique angle based on bot's GUID to ensure different approach angles - // This helps spread infected bots around the target - uint32 guidLow = bot->GetGUID().GetCounter(); - float angleOffset = float(guidLow % 628) / 100.0f; // Random angle between 0 and 2π - - // Calculate position 1 yard away from target at our unique angle - float angle = targetBot->GetOrientation() + angleOffset; - float targetX = targetBot->GetPositionX() + cos(angle) * 1.0f; - float targetY = targetBot->GetPositionY() + sin(angle) * 1.0f; - float targetZ = targetBot->GetPositionZ(); - - return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); - } - return true; // Already in position - } - // No suitable target found, continue with normal behavior - } - - // For ranged bots, only spread from non-infected bots - if (botAI->IsRanged(bot)) - { - const float safeSpacingRadius = 11.0f; - const float moveIncrement = 2.0f; - const float maxMoveDistance = 15.0f; - - const GuidVector members = AI_VALUE(GuidVector, "group members"); - - // Calculate a combined vector representing all nearby NON-INFECTED members' positions - float totalX = 0.0f; - float totalY = 0.0f; - int nearbyCount = 0; - - for (auto const& memberGuid : members) - { - Unit* member = botAI->GetUnit(memberGuid); - if (!member || !member->IsAlive() || member == bot) - continue; - - // Only spread from non-infected bots (can stay near infected or cured bots) - const bool memberIsInfected = botAI->GetAura("death plague", member) != nullptr; - if (memberIsInfected) - continue; - - const float distance = bot->GetExactDist2d(member); - if (distance < safeSpacingRadius) - { - // Calculate vector from member to bot - float dx = bot->GetPositionX() - member->GetPositionX(); - float dy = bot->GetPositionY() - member->GetPositionY(); - - // Weight by inverse distance (closer members have more influence) - float weight = (safeSpacingRadius - distance) / safeSpacingRadius; - totalX += dx * weight; - totalY += dy * weight; - nearbyCount++; - } - } - - // If we have nearby non-infected members, move away in the combined direction - if (nearbyCount > 0) - { - // Normalize the combined vector - float magnitude = std::sqrt(totalX * totalX + totalY * totalY); - if (magnitude > 0.001f) // Avoid division by zero - { - totalX /= magnitude; - totalY /= magnitude; - - // Calculate move distance based on nearest member - float moveDistance = std::min(moveIncrement, maxMoveDistance); - - // Create target position in the combined direction - float targetX = bot->GetPositionX() + totalX * moveDistance; - float targetY = bot->GetPositionY() + totalY * moveDistance; - float targetZ = bot->GetPositionZ(); // Maintain current Z - - // Check if the target position is valid and move there - if (bot->IsWithinLOS(targetX, targetY, targetZ)) - { - return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - else - { - // If los check fails, try shorter distance - targetX = bot->GetPositionX() + totalX * (moveDistance * 0.5f); - targetY = bot->GetPositionY() + totalY * (moveDistance * 0.5f); - return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - } - } - } -*/ - return false; // No movement needed -} - -//Gunship -bool IccCannonFireAction::Execute(Event /*event*/) -{ - Unit* vehicleBase = bot->GetVehicleBase(); - Vehicle* vehicle = bot->GetVehicle(); - - if (!vehicleBase || !vehicle) - return false; - - Unit* target = FindValidCannonTarget(); - if (!target) - return false; - - // Try to cast Incinerating Blast if we have enough energy - const float energyThreshold = 90.0f; - if (vehicleBase->GetPower(POWER_ENERGY) >= energyThreshold) - { - const uint32 blastSpellId = AI_VALUE2(uint32, "vehicle spell id", "incinerating blast"); - if (TryCastCannonSpell(blastSpellId, target, vehicleBase)) - return true; - } - - // Otherwise just use regular Cannon Blast - const uint32 cannonSpellId = AI_VALUE2(uint32, "vehicle spell id", "cannon blast"); - return TryCastCannonSpell(cannonSpellId, target, vehicleBase); -} - -Unit* IccCannonFireAction::FindValidCannonTarget() -{ - const GuidVector attackers = AI_VALUE(GuidVector, "possible targets no los"); - - for (auto const& attackerGuid : attackers) - { - Unit* unit = botAI->GetUnit(attackerGuid); - if (!unit) - continue; - - for (const uint32 entry : availableTargetsGS) - { - if (unit->GetEntry() == entry) - return unit; - } - } - - return nullptr; -} - -bool IccCannonFireAction::TryCastCannonSpell(uint32 spellId, Unit* target, Unit* vehicleBase) -{ - static constexpr uint32 cooldownMs = 1000; - - if (botAI->CanCastVehicleSpell(spellId, target) && botAI->CastVehicleSpell(spellId, target)) - { - vehicleBase->AddSpellCooldown(spellId, 0, cooldownMs); - return true; - } - - return false; -} - -bool IccGunshipEnterCannonAction::Execute(Event /*event*/) -{ - // Do not switch vehicles if already in one - if (bot->GetVehicle()) - return false; - - Unit* bestVehicle = FindBestAvailableCannon(); - if (!bestVehicle) - return false; - - return EnterVehicle(bestVehicle, true); -} - -Unit* IccGunshipEnterCannonAction::FindBestAvailableCannon() -{ - const uint32 validCannonEntries[] = {NPC_CANNONA, NPC_CANNONH}; - Unit* bestVehicle = nullptr; - - const GuidVector npcs = AI_VALUE(GuidVector, "nearest vehicles"); - for (auto const& npcGuid : npcs) - { - Unit* vehicleBase = botAI->GetUnit(npcGuid); - if (!IsValidCannon(vehicleBase, validCannonEntries)) - continue; - - // Choose the closest valid cannon - if (!bestVehicle || bot->GetExactDist(vehicleBase) < bot->GetExactDist(bestVehicle)) - bestVehicle = vehicleBase; - } - - return bestVehicle; -} - -bool IccGunshipEnterCannonAction::IsValidCannon(Unit* vehicle, const uint32 validEntries[]) -{ - if (!vehicle) - return false; - - // Must be selectable - if (vehicle->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) - return false; - - // Must be friendly - if (!vehicle->IsFriendlyTo(bot)) - return false; - - // Must have available seats - if (!vehicle->GetVehicleKit() || !vehicle->GetVehicleKit()->GetAvailableSeatCount()) - return false; - - // Must be one of the cannon entries - const uint32 entry = vehicle->GetEntry(); - bool isValidEntry = false; - for (size_t i = 0; i < 2; ++i) - { // 2 is the size of validEntries - if (entry == validEntries[i]) - { - isValidEntry = true; - break; - } - } - - if (!isValidEntry) - return false; - - // Must not have these auras (frozen or disabled) - if (vehicle->HasAura(69704) || vehicle->HasAura(69705)) - return false; - - return true; -} - -bool IccGunshipEnterCannonAction::EnterVehicle(Unit* vehicleBase, bool moveIfFar) -{ - const float dist = bot->GetDistance(vehicleBase); - - if (dist > INTERACTION_DISTANCE && !moveIfFar) - return false; - - if (dist > INTERACTION_DISTANCE) - return MoveTo(vehicleBase); - - // Prepare for entering vehicle - botAI->RemoveShapeshift(); - bot->GetMotionMaster()->Clear(); - bot->StopMoving(); - - // Enter the vehicle - vehicleBase->HandleSpellClick(bot); - - if (!bot->IsOnVehicle(vehicleBase)) - return false; - - // Dismount because bots can enter vehicle while mounted - WorldPacket emptyPacket; - bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); - - return true; -} - -bool IccGunshipTeleportAllyAction::Execute(Event /*event*/) -{ - static constexpr float MAX_WAITING_DISTANCE = 45.0f; - static constexpr float MAX_ATTACK_DISTANCE = 15.0f; - static constexpr uint8_t SKULL_ICON_INDEX = 7; - - // Find the Battle-Mage boss - Unit* boss = AI_VALUE2(Unit*, "find target", "kor'kron battle-mage"); - - // Check if we need to remove skull icon when boss is dead - CleanupSkullIcon(SKULL_ICON_INDEX); - - // If no boss found or boss is dead or not casting, check waiting position - if (!boss || !boss->IsAlive() || !boss->HasUnitState(UNIT_STATE_CASTING)) - { - // If we're too far from waiting position, go there - if (bot->GetExactDist2d(ICC_GUNSHIP_TELEPORT_ALLY2) > MAX_WAITING_DISTANCE) - return TeleportTo(ICC_GUNSHIP_TELEPORT_ALLY2); - } - else if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_BELOW_ZERO) && - boss->IsAlive()) - { - // Mark the boss with skull icon - UpdateBossSkullIcon(boss, SKULL_ICON_INDEX); - - // Teleport non-tank bots to attack position if not already there - if (!botAI->IsAssistTank(bot) && bot->GetExactDist2d(ICC_GUNSHIP_TELEPORT_ALLY) > MAX_ATTACK_DISTANCE) - return TeleportTo(ICC_GUNSHIP_TELEPORT_ALLY); - } - - return false; -} - -bool IccGunshipTeleportAllyAction::TeleportTo(const Position& position) -{ - return bot->TeleportTo(bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), - bot->GetOrientation()); -} - -void IccGunshipTeleportAllyAction::CleanupSkullIcon(uint8_t SKULL_ICON_INDEX) -{ - if (Group* group = bot->GetGroup()) - { - const ObjectGuid currentSkullTarget = group->GetTargetIcon(SKULL_ICON_INDEX); - - if (!currentSkullTarget.IsEmpty()) - { - Unit* skullTarget = ObjectAccessor::GetUnit(*bot, currentSkullTarget); - - if (!skullTarget || !skullTarget->IsAlive()) - { - // Target is dead or doesn't exist, remove icon - group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), ObjectGuid::Empty); - } - } - } -} - -void IccGunshipTeleportAllyAction::UpdateBossSkullIcon(Unit* boss, uint8_t SKULL_ICON_INDEX) -{ - if (Group* group = bot->GetGroup()) - { - if (group->GetTargetIcon(SKULL_ICON_INDEX) != boss->GetGUID()) - group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), boss->GetGUID()); - } -} - -bool IccGunshipTeleportHordeAction::Execute(Event /*event*/) -{ - static constexpr float MAX_WAITING_DISTANCE = 45.0f; - static constexpr float MAX_ATTACK_DISTANCE = 15.0f; - static constexpr uint8_t SKULL_ICON_INDEX = 7; - - // Find the Sorcerer boss - Unit* boss = AI_VALUE2(Unit*, "find target", "skybreaker sorcerer"); - - // Check if we need to remove skull icon when boss is dead - CleanupSkullIcon(SKULL_ICON_INDEX); - - // If no boss found or boss is dead or not casting, check waiting position - if (!boss || !boss->IsAlive() || !boss->HasUnitState(UNIT_STATE_CASTING)) - { - // If we're too far from waiting position, go there - if (bot->GetExactDist2d(ICC_GUNSHIP_TELEPORT_HORDE2) > MAX_WAITING_DISTANCE) - return TeleportTo(ICC_GUNSHIP_TELEPORT_HORDE2); - } - else if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_BELOW_ZERO) && - boss->IsAlive()) - { - // Mark the boss with skull icon - UpdateBossSkullIcon(boss, SKULL_ICON_INDEX); - - // Teleport non-tank bots to attack position if not already there - if (!botAI->IsAssistTank(bot) && bot->GetExactDist2d(ICC_GUNSHIP_TELEPORT_HORDE) > MAX_ATTACK_DISTANCE) - return TeleportTo(ICC_GUNSHIP_TELEPORT_HORDE); - } - - return false; -} - -bool IccGunshipTeleportHordeAction::TeleportTo(const Position& position) -{ - return bot->TeleportTo(bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), - bot->GetOrientation()); -} - -void IccGunshipTeleportHordeAction::CleanupSkullIcon(uint8_t SKULL_ICON_INDEX) -{ - if (Group* group = bot->GetGroup()) - { - const ObjectGuid currentSkullTarget = group->GetTargetIcon(SKULL_ICON_INDEX); - - if (!currentSkullTarget.IsEmpty()) - { - Unit* skullTarget = ObjectAccessor::GetUnit(*bot, currentSkullTarget); - - if (!skullTarget || !skullTarget->IsAlive()) - { - // Target is dead or doesn't exist, remove icon - group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), ObjectGuid::Empty); - } - } - } -} - -void IccGunshipTeleportHordeAction::UpdateBossSkullIcon(Unit* boss, uint8_t SKULL_ICON_INDEX) -{ - if (Group* group = bot->GetGroup()) - { - if (group->GetTargetIcon(SKULL_ICON_INDEX) != boss->GetGUID()) - group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), boss->GetGUID()); - } -} - -//DBS -bool IccDbsTankPositionAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); - if (!boss) - return false; - - Unit* beast = AI_VALUE2(Unit*, "find target", "blood beast"); - - // Handle tank positioning - if (botAI->IsTank(bot) && !beast) - { - if (bot->GetExactDist2d(ICC_DBS_TANK_POSITION) > 5.0f) - return MoveTo(bot->GetMapId(), ICC_DBS_TANK_POSITION.GetPositionX(), ICC_DBS_TANK_POSITION.GetPositionY(), - ICC_DBS_TANK_POSITION.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - - // Early return if this tank has Rune of Blood - if (botAI->GetAura("Rune of Blood", bot)) - return true; - } - - if (!botAI->IsTank(bot)) - { - if (CrowdControlBloodBeasts()) - return true; - } - - // Handle ranged and healer positioning - if (botAI->IsRanged(bot) || botAI->IsHeal(bot)) - { - // Handle evasion from blood beasts - if (EvadeBloodBeasts()) - return true; - - // Position in formation - return PositionInRangedFormation(); - } - - return false; -} - -bool IccDbsTankPositionAction::CrowdControlBloodBeasts() -{ - const std::array bloodBeastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3, - NPC_BLOOD_BEAST4}; - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - - for (auto const& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || !unit->IsAlive()) - continue; - - // Check if this is a blood beast - const bool isBloodBeast = - std::find(bloodBeastEntries.begin(), bloodBeastEntries.end(), unit->GetEntry()) != bloodBeastEntries.end(); - - if (!isBloodBeast) - continue; - - // Apply class-specific CC - switch (bot->getClass()) - { - case CLASS_MAGE: - if (!botAI->HasAura("Frost Nova", unit)) - botAI->CastSpell("Frost Nova", unit); - break; - case CLASS_DRUID: - if (!botAI->HasAura("Entangling Roots", unit)) - botAI->CastSpell("Entangling Roots", unit); - break; - case CLASS_PALADIN: - if (!botAI->HasAura("Hammer of Justice", unit)) - botAI->CastSpell("Hammer of Justice", unit); - break; - case CLASS_WARRIOR: - if (!botAI->HasAura("Hamstring", unit)) - botAI->CastSpell("Hamstring", unit); - break; - case CLASS_HUNTER: - if (!botAI->HasAura("Concussive Shot", unit)) - botAI->CastSpell("Concussive Shot", unit); - break; - case CLASS_ROGUE: - if (!botAI->HasAura("Kidney Shot", unit)) - botAI->CastSpell("Kidney Shot", unit); - break; - case CLASS_SHAMAN: - if (!botAI->HasAura("Frost Shock", unit)) - botAI->CastSpell("Frost Shock", unit); - break; - case CLASS_DEATH_KNIGHT: - if (!botAI->HasAura("Chains of Ice", unit)) - botAI->CastSpell("Chains of Ice", unit); - break; - case CLASS_PRIEST: - if (!botAI->HasAura("Psychic Scream", unit)) - botAI->CastSpell("Psychic Scream", unit); - break; - case CLASS_WARLOCK: - if (!botAI->HasAura("Fear", unit)) - botAI->CastSpell("Fear", unit); - break; - default: - break; - } - } - - return false; -} - -bool IccDbsTankPositionAction::EvadeBloodBeasts() -{ - const float evasionDistance = 12.0f; - const std::array bloodBeastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3, NPC_BLOOD_BEAST4}; - - // Get the nearest hostile NPCs - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - - for (auto const& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit) - continue; - - // Check if this is a blood beast - const bool isBloodBeast = std::find(bloodBeastEntries.begin(), bloodBeastEntries.end(), unit->GetEntry()) != bloodBeastEntries.end(); - - // Only evade if it's a blood beast targeting us - if (isBloodBeast && unit->GetVictim() == bot) - { - float currentDistance = bot->GetDistance2d(unit); - - // Move away if too close - if (currentDistance < evasionDistance) - return MoveAway(unit, evasionDistance - currentDistance); - } - } - - return false; -} - -bool IccDbsTankPositionAction::PositionInRangedFormation() -{ - // Get group - Group* group = bot->GetGroup(); - if (!group) - return false; - - // Find this bot's position among ranged/healers in the group - int rangedIndex = -1; - int currentIndex = 0; - - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive()) - continue; - - if ((botAI->IsRanged(member) || botAI->IsHeal(member)) && !botAI->IsTank(member)) - { - if (member == bot) - { - rangedIndex = currentIndex; - break; - } - currentIndex++; - } - } - - if (rangedIndex == -1) - return false; - - // Fixed positions calculation - constexpr float tankToBossAngle = 3.14f; - constexpr float minBossDistance = 11.0f; - constexpr float spreadDistance = 10.0f; - constexpr int columnsPerRow = 5; - - // Calculate position in a fixed grid (3 rows x 5 columns) - int row = rangedIndex / columnsPerRow; - int col = rangedIndex % columnsPerRow; - - // Calculate base position - float xOffset = (col - 2) * spreadDistance; // Center around tank position - float yOffset = minBossDistance + (row * spreadDistance); // Each row further back - - // Add zigzag offset for odd rows - if (row % 2 == 1) - xOffset += spreadDistance / 2; - - // Rotate position based on tank-to-boss angle - float finalX = - ICC_DBS_TANK_POSITION.GetPositionX() + (cos(tankToBossAngle) * yOffset - sin(tankToBossAngle) * xOffset); - float finalY = - ICC_DBS_TANK_POSITION.GetPositionY() + (sin(tankToBossAngle) * yOffset + cos(tankToBossAngle) * xOffset); - float finalZ = ICC_DBS_TANK_POSITION.GetPositionZ(); - - // Update Z coordinate - bot->UpdateAllowedPositionZ(finalX, finalY, finalZ); - - // Move if not in position - if (bot->GetExactDist2d(finalX, finalY) > 3.0f) - return MoveTo(bot->GetMapId(), finalX, finalY, finalZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); - - return false; -} - -bool IccAddsDbsAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); - if (!boss) - return false; - - // This action is only for melee - if (!botAI->IsMelee(bot)) - return false; - - Unit* priorityTarget = FindPriorityTarget(boss); - - // Update raid target icons if needed - UpdateSkullMarker(priorityTarget); - - return false; -} - -Unit* IccAddsDbsAction::FindPriorityTarget(Unit* boss) -{ - const GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); - - // Blood beast entry IDs - constexpr std::array addEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3, NPC_BLOOD_BEAST4}; - - // First check for alive adds - for (uint32_t entry : addEntries) - { - for (const ObjectGuid& guid : targets) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == entry) - return unit; - } - } - - // Only fallback to boss if it's alive - return boss->IsAlive() ? const_cast(boss) : nullptr; -} - -void IccAddsDbsAction::UpdateSkullMarker(Unit* priorityTarget) -{ - if (!priorityTarget) - return; - - Group* group = bot->GetGroup(); - if (!group) - return; - - constexpr uint8_t skullIconId = 7; - - // Get current skull target - ObjectGuid currentSkull = group->GetTargetIcon(skullIconId); - Unit* currentSkullUnit = botAI->GetUnit(currentSkull); - - // Determine if skull marker needs updating - bool needsUpdate = !currentSkullUnit || !currentSkullUnit->IsAlive() || currentSkullUnit != priorityTarget; - - // Update if needed - if (needsUpdate) - group->SetTargetIcon(skullIconId, bot->GetGUID(), priorityTarget->GetGUID()); -} - -// Festergut -bool IccFestergutGroupPositionAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); - if (!boss) - return false; - - bot->SetTarget(boss->GetGUID()); - - // Handle tank positioning - if ((botAI->HasAggro(boss) && botAI->IsMainTank(bot)) || botAI->IsAssistTank(bot)) - { - if (bot->GetExactDist2d(ICC_FESTERGUT_TANK_POSITION) > 5.0f) - return MoveTo(bot->GetMapId(), ICC_FESTERGUT_TANK_POSITION.GetPositionX(), - ICC_FESTERGUT_TANK_POSITION.GetPositionY(), ICC_FESTERGUT_TANK_POSITION.GetPositionZ(), false, - false, false, true, MovementPriority::MOVEMENT_NORMAL); - } - - // Check for spores in the group - if (HasSporesInGroup()) - return false; - - // Position non-tank ranged and healers - return PositionNonTankMembers(); -} - -bool IccFestergutGroupPositionAction::HasSporesInGroup() -{ - const GuidVector members = AI_VALUE(GuidVector, "group members"); - - for (auto const& memberGuid : members) - { - Unit* unit = botAI->GetUnit(memberGuid); - if (unit && unit->HasAura(SPELL_GAS_SPORE)) - return true; - } - - return false; -} - -bool IccFestergutGroupPositionAction::PositionNonTankMembers() -{ - // Only position ranged and healers without spores - if (!(botAI->IsRanged(bot) || botAI->IsHeal(bot))) - return false; - - Group* group = bot->GetGroup(); - if (!group) - return false; - - int positionIndex = CalculatePositionIndex(group); - if (positionIndex == -1) - return false; - - // Position calculation parameters - constexpr float tankToBossAngle = 4.58f; - constexpr float minBossDistance = 15.0f; - constexpr float spreadDistance = 10.0f; - constexpr int columnsPerRow = 6; - - // Calculate grid position - int row = positionIndex / columnsPerRow; - int col = positionIndex % columnsPerRow; - - // Calculate base position - float xOffset = (col - 2) * spreadDistance; // Center around tank position - float yOffset = minBossDistance + (row * spreadDistance); // Each row further back - - // Add zigzag offset for odd rows - if (row % 2 == 1) - xOffset += spreadDistance / 2; - - // Rotate position based on tank-to-boss angle - float finalX = - ICC_FESTERGUT_TANK_POSITION.GetPositionX() + (cos(tankToBossAngle) * yOffset - sin(tankToBossAngle) * xOffset); - float finalY = - ICC_FESTERGUT_TANK_POSITION.GetPositionY() + (sin(tankToBossAngle) * yOffset + cos(tankToBossAngle) * xOffset); - float finalZ = ICC_FESTERGUT_TANK_POSITION.GetPositionZ(); - - // Update Z coordinate - bot->UpdateAllowedPositionZ(finalX, finalY, finalZ); - - // Move if not in position - if (bot->GetExactDist2d(finalX, finalY) > 3.0f) - return MoveTo(bot->GetMapId(), finalX, finalY, finalZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); - - return false; -} - -int IccFestergutGroupPositionAction::CalculatePositionIndex(Group* group) -{ - std::vector healerGuids; - std::vector rangedDpsGuids; - std::vector hunterGuids; - - // Collect all eligible members with their GUIDs - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || botAI->IsTank(member)) - continue; - - ObjectGuid memberGuid = member->GetGUID(); - - if (botAI->IsHeal(member)) - { - healerGuids.push_back(memberGuid); - } - else if (botAI->IsRanged(member)) - { - if (member->getClass() == CLASS_HUNTER) - { - hunterGuids.push_back(memberGuid); - } - else - { - rangedDpsGuids.push_back(memberGuid); - } - } - } - - // Sort GUIDs for consistent ordering - std::sort(healerGuids.begin(), healerGuids.end()); - std::sort(rangedDpsGuids.begin(), rangedDpsGuids.end()); - std::sort(hunterGuids.begin(), hunterGuids.end()); - - ObjectGuid botGuid = bot->GetGUID(); - - // Find bot's position among healers - auto healerIt = std::find(healerGuids.begin(), healerGuids.end(), botGuid); - if (healerIt != healerGuids.end()) - { - int healerIndex = std::distance(healerGuids.begin(), healerIt); - int totalHealers = healerGuids.size(); - - // Healers in first two rows, distributed evenly - int healersPerRow = (totalHealers + 1) / 2; // Round up for first row - - if (healerIndex < healersPerRow) - { - // First row of healers (positions 0-5) - return healerIndex; - } - else - { - // Second row of healers (positions 6-11) - return healerIndex - healersPerRow + 6; - } - } - - // Find bot's position among non-hunter ranged DPS - auto rangedIt = std::find(rangedDpsGuids.begin(), rangedDpsGuids.end(), botGuid); - if (rangedIt != rangedDpsGuids.end()) - { - int rangedIndex = std::distance(rangedDpsGuids.begin(), rangedIt); - int totalHealers = healerGuids.size(); - - // Non-hunter ranged DPS fill remaining spots in healer rows first - int healerSpotsUsed = totalHealers; - int remainingSpotsInHealerRows = 12 - healerSpotsUsed; // 2 rows of 6 spots each - - if (rangedIndex < remainingSpotsInHealerRows) - { - // Fill remaining spots in healer rows - if (healerSpotsUsed < 6) - { - // Fill remaining spots in first row - return healerSpotsUsed + rangedIndex; - } - else - { - // Fill remaining spots in second row - int spotsInSecondRow = healerSpotsUsed - 6; - int remainingInSecondRow = 6 - spotsInSecondRow; - - if (rangedIndex < remainingInSecondRow) - { - return 6 + spotsInSecondRow + rangedIndex; - } - else - { - // Move to third row - return 12 + (rangedIndex - remainingInSecondRow); - } - } - } - else - { - // Start new rows for remaining ranged DPS - return 12 + (rangedIndex - remainingSpotsInHealerRows); - } - } - - // Find bot's position among hunters - auto hunterIt = std::find(hunterGuids.begin(), hunterGuids.end(), botGuid); - if (hunterIt != hunterGuids.end()) - { - int hunterIndex = std::distance(hunterGuids.begin(), hunterIt); - int totalHealers = healerGuids.size(); - int totalRangedDps = rangedDpsGuids.size(); - - // Hunters start after healers and non-hunter ranged DPS - // But ensure they're at least in the second row (position 6+) - int basePosition = totalHealers + totalRangedDps + hunterIndex; - - // If position would be in first row (0-5), move to second row minimum - if (basePosition < 6) - basePosition = 6 + hunterIndex; - - return basePosition; - } - - return -1; -} - -bool IccFestergutSporeAction::Execute(Event /*event*/) -{ - constexpr float POSITION_TOLERANCE = 4.0f; - - // Check if bot has spore - bool hasSpore = bot->HasAura(SPELL_GAS_SPORE); // gas spore - - // Stop attacking if spored - if (hasSpore) - botAI->Reset(); - - // Calculate unique spread position for ranged - Position spreadRangedPos = CalculateSpreadPosition(); - - // Find spored players - SporeInfo sporeInfo = FindSporedPlayers(); - - // Determine target position - Position targetPos = DetermineTargetPosition(hasSpore, sporeInfo, spreadRangedPos); - - // Move to position if not already there - if (bot->GetExactDist2d(targetPos) > POSITION_TOLERANCE) - { - botAI->Reset(); - return MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(), - true, false, false, true, MovementPriority::MOVEMENT_FORCED); - } - - return hasSpore; -} - -Position IccFestergutSporeAction::CalculateSpreadPosition() -{ - constexpr float SPREAD_RADIUS = 2.0f; - - // Unique angle based on bot's GUID - float angle = (bot->GetGUID().GetCounter() % 16) * (M_PI / 8); - - Position spreadRangedPos = ICC_FESTERGUT_RANGED_SPORE; - spreadRangedPos.Relocate(spreadRangedPos.GetPositionX() + cos(angle) * SPREAD_RADIUS, - spreadRangedPos.GetPositionY() + sin(angle) * SPREAD_RADIUS, - spreadRangedPos.GetPositionZ(), - spreadRangedPos.GetOrientation()); - - return spreadRangedPos; -} - -IccFestergutSporeAction::SporeInfo IccFestergutSporeAction::FindSporedPlayers() -{ - SporeInfo info; - const GuidVector members = AI_VALUE(GuidVector, "group members"); - - for (auto const& memberGuid : members) - { - Unit* unit = botAI->GetUnit(memberGuid); - if (!unit) - continue; - - if (unit->HasAura(SPELL_GAS_SPORE)) - { - info.sporedPlayers.push_back(unit); - - if (!info.hasLowestGuid || unit->GetGUID() < info.lowestGuid) - { - info.lowestGuid = unit->GetGUID(); - info.hasLowestGuid = true; - } - } - } - - return info; -} - -Position IccFestergutSporeAction::DetermineTargetPosition(bool hasSpore, const SporeInfo& sporeInfo, const Position& spreadRangedPos) -{ - // No spores at all - if (sporeInfo.sporedPlayers.empty()) - return botAI->IsMelee(bot) ? ICC_FESTERGUT_MELEE_SPORE : spreadRangedPos; - - // Bot has no spore, go to standard position - if (!hasSpore) - return botAI->IsMelee(bot) ? ICC_FESTERGUT_MELEE_SPORE : spreadRangedPos; - - // Check if main tank has spore - bool mainTankHasSpore = CheckMainTankSpore(); - - // Determine position based on spore logic - if (botAI->IsMainTank(bot)) - return ICC_FESTERGUT_MELEE_SPORE; - - if (bot->GetGUID() == sporeInfo.lowestGuid && !botAI->IsTank(bot) && !mainTankHasSpore) - return ICC_FESTERGUT_MELEE_SPORE; - - return spreadRangedPos; -} - -bool IccFestergutSporeAction::CheckMainTankSpore() -{ - const GuidVector members = AI_VALUE(GuidVector, "group members"); - - for (auto const& memberGuid : members) - { - Unit* unit = botAI->GetUnit(memberGuid); - if (!unit) - continue; - - if (botAI->IsMainTank(unit->ToPlayer()) && unit->HasAura(SPELL_GAS_SPORE)) - return true; - } - - return false; -} - -// Rotface -bool IccRotfaceTankPositionAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "rotface"); - if (!boss) - return false; - - Unit* smallOoze = AI_VALUE2(Unit*, "find target", "little ooze"); - bool victimOfSmallOoze = smallOoze && smallOoze->GetVictim() == bot; - // Mark Rotface with skull - MarkBossWithSkull(boss); - - // Main tank positioning and melee positioning - if ((botAI->IsMainTank(bot) || botAI->IsMelee(bot)) && !botAI->IsAssistTank(bot) && !victimOfSmallOoze) - return PositionMainTankAndMelee(boss); - - // Assist tank positioning for big ooze - if (botAI->IsAssistTank(bot)) - return HandleAssistTankPositioning(boss); - - return false; -} - -void IccRotfaceTankPositionAction::MarkBossWithSkull(Unit* boss) -{ - Group* group = bot->GetGroup(); - if (!group) - return; - - constexpr uint8_t skullIconId = 7; - ObjectGuid skullGuid = group->GetTargetIcon(skullIconId); - if (skullGuid != boss->GetGUID()) - group->SetTargetIcon(skullIconId, bot->GetGUID(), boss->GetGUID()); -} - -bool IccRotfaceTankPositionAction::PositionMainTankAndMelee(Unit* boss) -{ - bool isBossCasting = false; - if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY)) - isBossCasting = true; - - if (bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION) > 7.0f && botAI->HasAggro(boss) && botAI->IsMainTank(bot)) - MoveTo(bot->GetMapId(), ICC_ROTFACE_CENTER_POSITION.GetPositionX(), - ICC_ROTFACE_CENTER_POSITION.GetPositionY(), ICC_ROTFACE_CENTER_POSITION.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_COMBAT); - - if (boss && isBossCasting && !botAI->IsTank(bot)) - { - float x = boss->GetPositionX(); - float y = boss->GetPositionY(); - float z = boss->GetPositionZ(); - - // If not already close to the boss's position, move there - if (bot->GetExactDist2d(x, y) > 0.5f) - { - MoveTo(bot->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, - false); - } - // Otherwise, already at the correct position - return false; - } - - if (!isBossCasting && (bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION) < 2.0f || bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION) > 7.0f) && !botAI->IsTank(bot)) - { - MoveTo(bot->GetMapId(), ICC_ROTFACE_CENTER_POSITION.GetPositionX(), ICC_ROTFACE_CENTER_POSITION.GetPositionY(), - bot->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); - } - - return false; -} - -bool IccRotfaceTankPositionAction::HandleAssistTankPositioning(Unit* boss) -{ - // Handle big ooze positioning - return HandleBigOozePositioning(boss); -} - -bool IccRotfaceTankPositionAction::HandleBigOozePositioning(Unit*) -{ - // Find all big oozes - GuidVector bigOozes = AI_VALUE(GuidVector, "nearest hostile npcs"); - std::vector activeBigOozes; - - for (auto const& guid : bigOozes) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_BIG_OOZE && unit->IsVisible()) - activeBigOozes.push_back(unit); - } - - if (activeBigOozes.empty()) - return false; - - // Iterate through all big oozes and handle them - for (Unit* bigOoze : activeBigOozes) - { - // Taunt if not targeting us - if (bigOoze->GetVictim() != bot && bigOoze->IsAlive() && bigOoze->IsVisible()) - { - if (botAI->CastSpell("taunt", bigOoze)) - return true; - bot->SetTarget(bigOoze->GetGUID()); - bot->SetFacingToObject(bigOoze); - return Attack(bigOoze); - } - - // Calculate distances - float oozeDistance = bot->GetExactDist2d(bigOoze); - - // Stop moving if ooze is far enough - if (oozeDistance > 12.0f) - { - bot->SetTarget(bigOoze->GetGUID()); - bot->SetFacingToObject(bigOoze); - return true; - } - - // If we have the ooze's aggro, kite it in a larger circular pattern between 20f and 30f from the center - if (bigOoze->GetVictim() == bot) - { - const float minRadius = 24.0f; - const float maxRadius = 34.0f; - const float safeDistanceFromOoze = 13.0f; - const float puddleSafeDistance = 30.0f; - const Position centerPosition = ICC_ROTFACE_CENTER_POSITION; - - float currentDistance = bot->GetExactDist2d(centerPosition); - - // If too close or too far, adjust position - if (currentDistance < minRadius || currentDistance > maxRadius) - { - // Calculate direction vector from bot to center - float dirX = bot->GetPositionX() - centerPosition.GetPositionX(); - float dirY = bot->GetPositionY() - centerPosition.GetPositionY(); - float length = std::sqrt(dirX * dirX + dirY * dirY); - - // Normalize direction vector - dirX /= length; - dirY /= length; - - // Adjust position to stay within the desired radius - float targetX = centerPosition.GetPositionX() + dirX * maxRadius; - float targetY = centerPosition.GetPositionY() + dirY * maxRadius; - - // Ensure the position is at least 10f away from the ooze - if (bigOoze->GetExactDist2d(targetX, targetY) >= safeDistanceFromOoze) - { - return MoveTo(bot->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); - } - } - - // If within the desired radius, continue kiting in a circular pattern - float currentAngle = atan2(bot->GetPositionY() - centerPosition.GetPositionY(), - bot->GetPositionX() - centerPosition.GetPositionX()); - - // Adjust rotation direction to find a safe position - for (int i = 0; i < 16; ++i) // Try 16 directions around the circle - { - float angleOffset = (i % 2 == 0 ? 1 : -1) * (M_PI / 16.0f) * (i / static_cast(2)); - float newAngle = currentAngle + angleOffset; - - // Calculate new position along the circle - float newX = centerPosition.GetPositionX() + maxRadius * cos(newAngle); - float newY = centerPosition.GetPositionY() + maxRadius * sin(newAngle); - - // Ensure the position is at least 10f away from the ooze - if (bigOoze->GetExactDist2d(newX, newY) >= safeDistanceFromOoze) - { - // Check if the position is at least 30f away from any puddle - GuidVector puddles = AI_VALUE(GuidVector, "nearest hostile npcs"); - bool isSafeFromPuddles = true; - - for (auto const& puddleGuid : puddles) - { - Unit* puddle = botAI->GetUnit(puddleGuid); - if (puddle && botAI->GetAura("Ooze Flood", puddle)) - { - float puddleDistance = puddle->GetDistance2d(newX, newY); - if (puddleDistance < puddleSafeDistance) - { - isSafeFromPuddles = false; - break; - } - } - } - - if (isSafeFromPuddles) - { - return MoveTo(bot->GetMapId(), newX, newY, bot->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); - } - } - } - } - } - - return false; -} - -bool IccRotfaceGroupPositionAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "rotface"); - if (!boss) - return false; - - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - - bool hasOozeFlood = botAI->HasAura("Ooze Flood", bot); - Unit* smallOoze = AI_VALUE2(Unit*, "find target", "little ooze"); - bool hasMutatedInfection = botAI->HasAura("Mutated Infection", bot); - - // Handle puddle avoidance - if (!botAI->IsTank(bot) && HandlePuddleAvoidance(boss)) - return true; - - // Handle little ooze or mutated infection - if (HandleOozeTargeting()) - return true; - - // Position ranged and healers - if (/*!floodPresent && */ !((smallOoze && smallOoze->GetVictim() == bot) || hasMutatedInfection) && !hasOozeFlood && PositionRangedAndHealers(boss, smallOoze)) - return true; - - //if (!hasOozeFlood && bigOoze && bigOoze->IsAlive() && MoveAwayFromBigOoze(bigOoze)) - //return true; - - return false; -} - -bool IccRotfaceGroupPositionAction::HandlePuddleAvoidance(Unit* boss) -{ - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - - for (auto const& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || !botAI->HasAura("Ooze Flood", unit)) - continue; - - float puddleDistance = bot->GetExactDist2d(unit); - float bossDistance = bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION); - - if (bossDistance < 15.0f) // Reduced center distance threshold - return false; - - if (puddleDistance < 30.0f) - return MoveAwayFromPuddle(boss, unit, puddleDistance); - } - - return false; -} - -bool IccRotfaceGroupPositionAction::MoveAwayFromPuddle(Unit* boss, Unit* puddle, float) -{ - if (!boss || !puddle) - return false; - - // Calculate angle and move direction - float dx = puddle->GetPositionX() - bot->GetPositionX(); - float dy = puddle->GetPositionY() - bot->GetPositionY(); - float angle = atan2(dy, dx); - - // Try to find a valid position in 8 directions - const float increment = 5.0f; - const float minPuddleDistance = 30.0f; - const float minCenterDistance = 15.0f; // Reduced center distance threshold - const float maxCenterDistance = 25.0f; // New maximum center distance threshold - const int directions = 8; // Number of directions to check - float bestX = bot->GetPositionX(); - float bestY = bot->GetPositionY(); - float bestZ = bot->GetPositionZ(); - float maxSafetyScore = 0.0f; - - for (int i = 0; i < directions; ++i) - { - float testAngle = angle + (i * M_PI / 4); // 8 directions (45-degree increments) - for (float distance = increment; distance <= 35.0f; distance += increment) - { - float moveX = bot->GetPositionX() - distance * cos(testAngle); - float moveY = bot->GetPositionY() - distance * sin(testAngle); - float moveZ = bot->GetPositionZ(); - - // Check distances and line of sight - float newPuddleDistance = puddle->GetDistance2d(moveX, moveY); - float newCenterDistance = sqrt(pow(moveX - ICC_ROTFACE_CENTER_POSITION.GetPositionX(), 2) + - pow(moveY - ICC_ROTFACE_CENTER_POSITION.GetPositionY(), 2)); - - if (newPuddleDistance >= minPuddleDistance && newCenterDistance >= minCenterDistance && - newCenterDistance <= maxCenterDistance && bot->IsWithinLOS(moveX, moveY, moveZ)) - { - // Calculate safety score (favor positions farther from puddle and center) - float safetyScore = newPuddleDistance + newCenterDistance; - if (safetyScore > maxSafetyScore) - { - maxSafetyScore = safetyScore; - bestX = moveX; - bestY = moveY; - bestZ = moveZ; - } - } - } - - // If we are already in a valid position, stop moving - if (maxSafetyScore > 0.0f && bot->GetExactDist2d(bestX, bestY) <= increment) - return false; - } - - // 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 IccRotfaceGroupPositionAction::HandleOozeTargeting() -{ - Unit* smallOoze = AI_VALUE2(Unit*, "find target", "little ooze"); - bool hasMutatedInfection = botAI->HasAura("Mutated Infection", bot); - - if ((smallOoze && smallOoze->GetVictim() == bot) || hasMutatedInfection) - return HandleOozeMemberPositioning(); - - return false; -} - -bool IccRotfaceGroupPositionAction::HandleOozeMemberPositioning() -{ - Unit* bigOoze = AI_VALUE2(Unit*, "find target", "big ooze"); - - // First case: No big ooze exists or is not alive, move to designated position - if (!bigOoze || !bigOoze->IsAlive() || !bigOoze->IsVisible()) - { - if (bot->GetExactDist2d(ICC_ROTFACE_BIG_OOZE_POSITION) > 3.0f) - { - return MoveTo(bot->GetMapId(), ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionX(), - ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionY(), ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_COMBAT); - } - } - // Second case: Big ooze exists and is alive, move to it for merging - else if (bot->GetExactDist2d(bigOoze) > 2.0f && bigOoze->IsAlive() && bigOoze->IsVisible()) - { - // Move to big ooze for merge in increments of 5 - float dx = bigOoze->GetPositionX() - bot->GetPositionX(); - float dy = bigOoze->GetPositionY() - bot->GetPositionY(); - float dz = bigOoze->GetPositionZ() - bot->GetPositionZ(); - float dist = std::sqrt(dx * dx + dy * dy); - if (dist > 5.0f) - { - dx /= dist; - dy /= dist; - float moveX = bot->GetPositionX() + dx * 5.0f; - float moveY = bot->GetPositionY() + dy * 5.0f; - float moveZ = bot->GetPositionZ() + (dz / dist) * 5.0f; - return MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); - } - return MoveTo(bot->GetMapId(), bigOoze->GetPositionX(), bigOoze->GetPositionY(), bigOoze->GetPositionZ(), false, - false, false, true, MovementPriority::MOVEMENT_COMBAT); - - } - - return false; // Stay at position -} - -bool IccRotfaceGroupPositionAction::PositionRangedAndHealers(Unit* boss,Unit *smallOoze) -{ - // Only for ranged and healers - if (!(botAI->IsRanged(bot) || botAI->IsHeal(bot))) - return false; - - Difficulty diff = bot->GetRaidDifficulty(); - bool isBossCasting = false; - if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY)) - isBossCasting = true; - - bool isHeroic = (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC); - - // Move to the exact same position as the boss during slime spray - if (boss && isBossCasting && !isHeroic) - { - float x = boss->GetPositionX(); - float y = boss->GetPositionY(); - float z = boss->GetPositionZ(); - - // If not already close to the boss's position, move there - if (bot->GetExactDist2d(x, y) > 0.5f) - { - MoveTo(bot->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, - false); - } - // Otherwise, already at the correct position - return false; - } - - if (!isHeroic && !isBossCasting && boss && !(bot->getClass() == CLASS_HUNTER) && - (bot->GetExactDist2d(boss->GetPositionX(), boss->GetPositionY()) < 2.0f || - bot->GetExactDist2d(boss->GetPositionX(), boss->GetPositionY()) > 5.0f)) - { - MoveTo(bot->GetMapId(), boss->GetPositionX(), boss->GetPositionY(), - bot->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); - } - - if (!isHeroic) - return false; - - return FindAndMoveFromClosestMember(smallOoze); -} - -bool IccRotfaceGroupPositionAction::FindAndMoveFromClosestMember(Unit* smallOoze) -{ - - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - Unit* puddle = nullptr; - - for (auto const& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || !botAI->HasAura("Ooze Flood", unit)) - continue; - - puddle = unit; - break; - } - - const float safeSpacingRadius = 10.0f; - const float moveIncrement = 2.0f; - const float maxMoveDistance = 12.0f; // Limit maximum movement distance - const float puddleSafeDistance = 30.0f; // Minimum distance to stay away from puddle - const float minCenterDistance = 20.0f; // Minimum distance from center position - - // Ranged: spread from other members - const GuidVector members = AI_VALUE(GuidVector, "group members"); - - // Calculate a combined vector representing all nearby members' positions - float totalX = 0.0f; - float totalY = 0.0f; - int nearbyCount = 0; - - for (auto const& memberGuid : members) - { - Unit* member = botAI->GetUnit(memberGuid); - if (!member || !member->IsAlive() || member == bot || (smallOoze && smallOoze->GetVictim() == member) || - (member->IsPlayer() && botAI->IsAssistTank(static_cast(member)))) - continue; - - const float distance = bot->GetExactDist2d(member); - if (distance < safeSpacingRadius) - { - // Calculate vector from member to bot - float dx = bot->GetPositionX() - member->GetPositionX(); - float dy = bot->GetPositionY() - member->GetPositionY(); - - // Weight by inverse distance (closer members have more influence) - float weight = (safeSpacingRadius - distance) / safeSpacingRadius; - totalX += dx * weight; - totalY += dy * weight; - nearbyCount++; - } - } - - // If we have nearby members, move away in the combined direction - if (nearbyCount > 0) - { - // Normalize the combined vector - float magnitude = std::sqrt(totalX * totalX + totalY * totalY); - if (magnitude > 0.001f) // Avoid division by zero - { - totalX /= magnitude; - totalY /= magnitude; - - // Calculate move distance based on nearest member - float moveDistance = std::min(moveIncrement, maxMoveDistance); - - // Create target position in the combined direction - float targetX = bot->GetPositionX() + totalX * moveDistance; - float targetY = bot->GetPositionY() + totalY * moveDistance; - float targetZ = bot->GetPositionZ(); // Maintain current Z - - // Ensure the target position is at least 30 yards away from the puddle - if (puddle) - { - float puddleDistance = puddle->GetDistance2d(targetX, targetY); - if (puddleDistance < puddleSafeDistance) - { - // Adjust the target position to move further away from the puddle - float dx = targetX - puddle->GetPositionX(); - float dy = targetY - puddle->GetPositionY(); - float adjustmentFactor = (puddleSafeDistance - puddleDistance) / puddleDistance; - targetX += dx * adjustmentFactor; - targetY += dy * adjustmentFactor; - } - } - - // Ensure the target position is at least 20 yards away from the center position - const float centerX = ICC_ROTFACE_CENTER_POSITION.GetPositionX(); - const float centerY = ICC_ROTFACE_CENTER_POSITION.GetPositionY(); - float centerDistance = std::sqrt(std::pow(targetX - centerX, 2) + std::pow(targetY - centerY, 2)); - if (centerDistance < minCenterDistance) - { - // Adjust the target position to move further away from the center - float dx = targetX - centerX; - float dy = targetY - centerY; - float adjustmentFactor = (minCenterDistance - centerDistance) / centerDistance; - targetX += dx * adjustmentFactor; - targetY += dy * adjustmentFactor; - } - - // Check if the target position is valid and move there - if (bot->IsWithinLOS(targetX, targetY, targetZ)) - { - Position targetPos(targetX, targetY, targetZ); - MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_NORMAL); - } - else - { - // If los check fails, try shorter distance - targetX = bot->GetPositionX() + totalX * (moveDistance * 0.5f); - targetY = bot->GetPositionY() + totalY * (moveDistance * 0.5f); - Position targetPos(targetX, targetY, targetZ); - MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_NORMAL); - } - } - } - - return false; // Everyone is properly spaced -} - -bool IccRotfaceMoveAwayFromExplosionAction::Execute(Event /*event*/) -{ - // Skip if main tank or ooze flood - if (botAI->IsMainTank(bot)) - return false; - - botAI->Reset(); - - return MoveToRandomSafeLocation(); -} - -bool IccRotfaceMoveAwayFromExplosionAction::MoveToRandomSafeLocation() -{ - // Generate random angle - float angle = frand(0, 2 * M_PI); - - // Calculate initial move position - float moveX = bot->GetPositionX() + 5.0f * cos(angle); - float moveY = bot->GetPositionY() + 5.0f * sin(angle); - float moveZ = bot->GetPositionZ(); - - // Ensure the position is at least 30 yards away from any puddle - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto const& npc : npcs) - { - Unit* puddle = botAI->GetUnit(npc); - if (!puddle || !botAI->HasAura("Ooze Flood", puddle)) - continue; - - float puddleDistance = puddle->GetDistance2d(moveX, moveY); - if (puddleDistance < 30.0f) - { - // Adjust the position to move further away from the puddle - float dx = moveX - puddle->GetPositionX(); - float dy = moveY - puddle->GetPositionY(); - float adjustmentFactor = (30.0f - puddleDistance) / puddleDistance; - moveX += dx * adjustmentFactor; - moveY += dy * adjustmentFactor; - } - } - - // Ensure the position is at least 30 yards away from the center position - const Position centerPosition = ICC_ROTFACE_CENTER_POSITION; - float centerDistance = std::sqrt(std::pow(moveX - centerPosition.GetPositionX(), 2) + - std::pow(moveY - centerPosition.GetPositionY(), 2)); - if (centerDistance < 40.0f) - { - // Adjust the position to move further away from the center - float dx = moveX - centerPosition.GetPositionX(); - float dy = moveY - centerPosition.GetPositionY(); - float adjustmentFactor = (40.0f - centerDistance) / centerDistance; - moveX += dx * adjustmentFactor; - moveY += dy * adjustmentFactor; - } - - // Check line of sight - if (!bot->IsWithinLOS(moveX, moveY, moveZ)) - return false; - - // Move in increments of 5.0f towards the calculated position - float currentX = bot->GetPositionX(); - float currentY = bot->GetPositionY(); - - float directionX = moveX - currentX; - float directionY = moveY - currentY; - float distance = std::sqrt(directionX * directionX + directionY * directionY); - - if (distance > 5.0f) - { - directionX /= distance; - directionY /= distance; - - moveX = currentX + directionX * 5.0f; - moveY = currentY + directionY * 5.0f; - } - - // Move to the position - return MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, false, MovementPriority::MOVEMENT_FORCED); -} - -// Proffesor Putricide -bool IccPutricideGrowingOozePuddleAction::Execute(Event /*event*/) -{ - Unit* closestPuddle = FindClosestThreateningPuddle(); - if (!closestPuddle) - return false; - - Position movePosition = CalculateSafeMovePosition(closestPuddle); - return MoveTo(bot->GetMapId(), movePosition.GetPositionX(), movePosition.GetPositionY(), - movePosition.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); -} - -Unit* IccPutricideGrowingOozePuddleAction::FindClosestThreateningPuddle() -{ - static const float BASE_RADIUS = 2.0f; - static const float STACK_MULTIPLIER = 0.6f; - static const float MIN_DISTANCE = 0.1f; - - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - if (npcs.empty()) - return nullptr; - - Unit* closestPuddle = nullptr; - float closestDistance = FLT_MAX; - - for (auto const& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || unit->GetEntry() != NPC_GROWING_OOZE_PUDDLE) - continue; - - float currentDistance = std::max(MIN_DISTANCE, bot->GetExactDist(unit)); - float safeDistance = BASE_RADIUS; - - if (Aura* grow = unit->GetAura(SPELL_GROW_AURA)) - safeDistance += (grow->GetStackAmount() * STACK_MULTIPLIER); - - if (currentDistance < safeDistance && currentDistance < closestDistance) - { - closestDistance = currentDistance; - closestPuddle = unit; - } - } - - return closestPuddle; -} - -Position IccPutricideGrowingOozePuddleAction::CalculateSafeMovePosition(Unit* closestPuddle) -{ - static const float BASE_RADIUS = 2.0f; - static const float STACK_MULTIPLIER = 0.6f; - static const float BUFFER_DISTANCE = 2.0f; - static const float MIN_DISTANCE = 0.1f; - static const int NUM_ANGLES_TO_TEST = 8; - - float botX = bot->GetPositionX(); - float botY = bot->GetPositionY(); - float botZ = bot->GetPositionZ(); - - // Calculate current distance and safe distance - float currentDistance = std::max(MIN_DISTANCE, bot->GetExactDist(closestPuddle)); - float safeDistance = BASE_RADIUS; - if (Aura* grow = closestPuddle->GetAura(SPELL_GROW_AURA)) - safeDistance += (grow->GetStackAmount() * STACK_MULTIPLIER); - - // Calculate direction vector - float dx = botX - closestPuddle->GetPositionX(); - float dy = botY - closestPuddle->GetPositionY(); - float dist = std::max(MIN_DISTANCE, sqrt(dx * dx + dy * dy)); - - if (dist < MIN_DISTANCE * 2) - { - float randomAngle = float(rand()) / float(RAND_MAX) * 2 * M_PI; - dx = cos(randomAngle); - dy = sin(randomAngle); - } - else - { - dx /= dist; - dy /= dist; - } - - float moveDistance = safeDistance - currentDistance + BUFFER_DISTANCE; - - // Try different angles to find safe position - for (int i = 0; i < NUM_ANGLES_TO_TEST; i++) - { - float angle = (2 * M_PI * i) / NUM_ANGLES_TO_TEST; - float rotatedDx = dx * cos(angle) - dy * sin(angle); - float rotatedDy = dx * sin(angle) + dy * cos(angle); - - float testX = botX + rotatedDx * moveDistance; - float testY = botY + rotatedDy * moveDistance; - - if (!IsPositionTooCloseToOtherPuddles(testX, testY, closestPuddle) && bot->IsWithinLOS(testX, testY, botZ)) - { - // If main tank, add 6f to calculated position in the direction away from the puddle - if (botAI->IsTank(bot)) - { - float awayDx = testX - closestPuddle->GetPositionX(); - float awayDy = testY - closestPuddle->GetPositionY(); - float awayDist = std::sqrt(awayDx * awayDx + awayDy * awayDy); - if (awayDist > 0.001f) - { - awayDx /= awayDist; - awayDy /= awayDist; - testX += awayDx * 6.0f; - testY += awayDy * 6.0f; - } - } - return Position(testX, testY, botZ); - } - } - - // Fallback position if no safe position found - float fallbackX = botX + dx * moveDistance; - float fallbackY = botY + dy * moveDistance; - if (botAI->IsTank(bot)) - { - float awayDx = fallbackX - closestPuddle->GetPositionX(); - float awayDy = fallbackY - closestPuddle->GetPositionY(); - float awayDist = std::sqrt(awayDx * awayDx + awayDy * awayDy); - if (awayDist > 0.001f) - { - awayDx /= awayDist; - awayDy /= awayDist; - fallbackX += awayDx * 6.0f; - fallbackY += awayDy * 6.0f; - } - } - return Position(fallbackX, fallbackY, botZ); -} - -bool IccPutricideGrowingOozePuddleAction::IsPositionTooCloseToOtherPuddles(float x, float y, Unit* ignorePuddle) -{ - static const float BASE_RADIUS = 2.0f; - static const float STACK_MULTIPLIER = 0.6f; - - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto const& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || unit == ignorePuddle || unit->GetEntry() != NPC_GROWING_OOZE_PUDDLE) - continue; - - float safeDistance = BASE_RADIUS; - if (Aura* grow = unit->GetAura(SPELL_GROW_AURA)) - safeDistance += (grow->GetStackAmount() * STACK_MULTIPLIER); - - float dist = unit->GetDistance2d(x, y); - if (dist < safeDistance) - return true; - } - - return false; -} - -bool IccPutricideVolatileOozeAction::Execute(Event /*event*/) -{ - static const float STACK_DISTANCE = 7.0f; - - Unit* ooze = AI_VALUE2(Unit*, "find target", "volatile ooze"); - if (!ooze) - return false; - - Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); - if (!boss) - return false; - - // Main tank handling - if (botAI->IsMainTank(bot) && bot->GetExactDist2d(ICC_PUTRICIDE_TANK_POSITION) > 20.0f && - !boss->HealthBelowPct(36) && boss->GetVictim() == bot) - return MoveTo(bot->GetMapId(), ICC_PUTRICIDE_TANK_POSITION.GetPositionX(), ICC_PUTRICIDE_TANK_POSITION.GetPositionY(), - ICC_PUTRICIDE_TANK_POSITION.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); - - // Skip if we have forbidden auras - if (botAI->HasAura("Gaseous Bloat", bot) || botAI->HasAura("Unbound Plague", bot)) - return false; - - // Find all alive oozes - std::vector aliveOozes; - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto const& guid : npcs) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == ooze->GetEntry()) - aliveOozes.push_back(unit); - } - - // If more than one ooze is alive, kill all but one - if (aliveOozes.size() > 1) - { - for (size_t i = 0; i < aliveOozes.size() - 1; ++i) - { - bot->Kill(bot, aliveOozes[i]); - } - } - - // Mark ooze with skull - MarkOozeWithSkull(ooze); - - // Melee handling (non-tanks) - if (botAI->IsMelee(bot) && !botAI->IsMainTank(bot)) - { - bot->SetTarget(ooze->GetGUID()); - bot->SetFacingToObject(ooze); - if (bot->IsWithinMeleeRange(ooze)) - return Attack(ooze); - } - - // Ranged/healer handling - if (botAI->IsRanged(bot) || botAI->IsHeal(bot)) - { - Unit* stackTarget = FindAuraTarget(); - if (stackTarget && bot->GetDistance2d(stackTarget) > STACK_DISTANCE) - { - return MoveTo(bot->GetMapId(), stackTarget->GetPositionX(), stackTarget->GetPositionY(), - stackTarget->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); - } - - if (ooze && !botAI->IsHeal(bot) && stackTarget && bot->GetDistance2d(stackTarget) <= STACK_DISTANCE) - { - bot->SetTarget(ooze->GetGUID()); - bot->SetFacingToObject(ooze); - if (bot->IsWithinRange(ooze, 25.0f)) - return Attack(ooze); - } - } - - return false; -} - -void IccPutricideVolatileOozeAction::MarkOozeWithSkull(Unit* ooze) -{ - Group* group = bot->GetGroup(); - if (!group) - return; - - constexpr uint8_t skullIconId = 7; - ObjectGuid skullGuid = group->GetTargetIcon(skullIconId); - Unit* markedUnit = botAI->GetUnit(skullGuid); - - // Clear dead marks or marks that are not on ooze - if (markedUnit && (!markedUnit->IsAlive() || (ooze && markedUnit != ooze))) - group->SetTargetIcon(skullIconId, bot->GetGUID(), ObjectGuid::Empty); - - // Mark alive ooze if needed - if (ooze && ooze->IsAlive() && (!skullGuid || !markedUnit)) - group->SetTargetIcon(skullIconId, bot->GetGUID(), ooze->GetGUID()); -} - -Unit* IccPutricideVolatileOozeAction::FindAuraTarget() -{ - Group* group = bot->GetGroup(); - if (!group) - return nullptr; - - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || member == bot) - continue; - - if (botAI->HasAura("Volatile Ooze Adhesive", member)) - return member; - } - - return nullptr; -} - -bool IccPutricideGasCloudAction::Execute(Event /*event*/) -{ - Unit* gasCloud = AI_VALUE2(Unit*, "find target", "gas cloud"); - if (!gasCloud) - return false; - - Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); - if (!boss) - return false; - - // Tank positioning logic - if (botAI->IsTank(bot) && bot->GetExactDist2d(ICC_PUTRICIDE_TANK_POSITION) > 20.0f && !boss->HealthBelowPct(36) && - boss->GetVictim() == bot) - return MoveTo(bot->GetMapId(), ICC_PUTRICIDE_TANK_POSITION.GetPositionX(), - ICC_PUTRICIDE_TANK_POSITION.GetPositionY(), ICC_PUTRICIDE_TANK_POSITION.GetPositionZ(), false, - false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); - - if (botAI->IsMainTank(bot)) - return false; - - bool hasGaseousBloat = botAI->HasAura("Gaseous Bloat", bot); - Unit* volatileOoze = AI_VALUE2(Unit*, "find target", "volatile ooze"); - - // Find all alive gasCloud - std::vector aliveGasCloud; - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto const& guid : npcs) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == gasCloud->GetEntry()) - aliveGasCloud.push_back(unit); - } - - // If more than one GasCloud is alive, kill all but one - if (aliveGasCloud.size() > 1) - { - for (size_t i = 0; i < aliveGasCloud.size() - 1; ++i) - { - bot->Kill(bot, aliveGasCloud[i]); - } - } - - // Skip if we have no aura but ooze exists - if (!hasGaseousBloat && volatileOoze) - return false; - - if (hasGaseousBloat) - return HandleGaseousBloatMovement(gasCloud); - - return HandleGroupAuraSituation(gasCloud); -} - -bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) -{ - bool hasGaseousBloat = botAI->HasAura("Gaseous Bloat", bot); - - if (!hasGaseousBloat) - return false; - - if (hasGaseousBloat && !bot->HasAura(SPELL_NITRO_BOOSTS)) - bot->AddAura(SPELL_NITRO_BOOSTS, - bot); // to make it a bit easier when abo fails to slow or bots take forever to kill oozes - - static const int NUM_ANGLES = 32; // Increased from 16 for better corner escape - static const float MIN_SAFE_DISTANCE = 35.0f; - static const float EMERGENCY_DISTANCE = 15.0f; - static const float GAS_BOMB_SAFE_DIST = 6.0f; - static const float MOVEMENT_INCREMENT = 5.0f; // Fixed movement increment - - Position botPos = bot->GetPosition(); - Position cloudPos = gasCloud->GetPosition(); - float cloudDist = gasCloud->GetExactDist2d(botPos); - - if (cloudDist >= MIN_SAFE_DISTANCE) - return false; - - // Gather all choking gas bombs - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - std::vector gasBombs; - for (auto const& guid : npcs) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_CHOKING_GAS_BOMB) - gasBombs.push_back(unit); - } - - // Calculate direction away from cloud - float dx = botPos.GetPositionX() - cloudPos.GetPositionX(); - float dy = botPos.GetPositionY() - cloudPos.GetPositionY(); - float dist = std::max(0.1f, sqrt(dx * dx + dy * dy)); - - if (dist <= 0) - return false; - - dx /= dist; - dy /= dist; - - // Try to find safe movement position with strict corner avoidance - Position bestPos; - bool foundPath = false; - float bestScore = 0.0f; - - for (int i = 0; i < NUM_ANGLES; i++) - { - float angle = (2 * M_PI * i) / NUM_ANGLES; - float rotatedDx = dx * cos(angle) - dy * sin(angle); - float rotatedDy = dx * sin(angle) + dy * cos(angle); - - // Only test positions at fixed increments of 5.0f - for (float testDist = MOVEMENT_INCREMENT; testDist <= 20.0f; testDist += MOVEMENT_INCREMENT) - { - float testX = botPos.GetPositionX() + rotatedDx * testDist; - float testY = botPos.GetPositionY() + rotatedDy * testDist; - float testZ = botPos.GetPositionZ(); - Position testPos(testX, testY, testZ); - - float newCloudDist = cloudPos.GetExactDist2d(testPos); - - // Check gas bomb distance - float minGasBombDist = FLT_MAX; - for (Unit* bomb : gasBombs) - { - float bombDist = bomb->GetDistance2d(testX, testY); - if (bombDist < minGasBombDist) - minGasBombDist = bombDist; - } - - if (newCloudDist > cloudDist && minGasBombDist >= GAS_BOMB_SAFE_DIST && - bot->IsWithinLOS(testX, testY, testZ)) - { - // Strict corner avoidance - test movement freedom thoroughly - int freeDirections = 0; - static const int CHECK_DIRS = 16; // More directions for better detection - static const float CHECK_DIST = 8.0f; // Longer distance to detect walls/corners early - - for (int j = 0; j < CHECK_DIRS; j++) - { - float checkAngle = (2 * M_PI * j) / CHECK_DIRS; - float checkX = testX + cos(checkAngle) * CHECK_DIST; - float checkY = testY + sin(checkAngle) * CHECK_DIST; - if (bot->IsWithinLOS(checkX, checkY, testZ)) - freeDirections++; - } - - float freedomScore = (float)freeDirections / CHECK_DIRS; - - // STRICT: Reject any position that looks like a corner or restricted area - // Require at least 80% of directions to be free (13 out of 16) - if (freedomScore < 0.8f) - continue; // Skip this position entirely - - // Also check for "dead ends" - positions that might lead to corners - // Test if we can move further in the same direction - bool canContinueMoving = true; - float continueX = testX + rotatedDx * MOVEMENT_INCREMENT; - float continueY = testY + rotatedDy * MOVEMENT_INCREMENT; - if (!bot->IsWithinLOS(continueX, continueY, testZ)) - canContinueMoving = false; - - // Bonus for positions that allow continued movement in the same direction - float continuityBonus = canContinueMoving ? 5.0f : 0.0f; - - float combinedScore = newCloudDist + (freedomScore * 15.0f) + minGasBombDist + continuityBonus; - - if (!foundPath || combinedScore > bestScore) - { - bestPos = testPos; - bestScore = combinedScore; - foundPath = true; - } - } - } - } - - if (foundPath) - { - botAI->Reset(); - return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, - false, false, false, MovementPriority::MOVEMENT_COMBAT); - } - // Emergency movement - only when very close to cloud - if (cloudDist < EMERGENCY_DISTANCE) - { - Position emergencyPos = CalculateEmergencyPosition(botPos, dx, dy); - - // Even for emergency, avoid corners - float minEmergencyGasBombDist = FLT_MAX; - for (Unit* bomb : gasBombs) - { - float bombDist = bomb->GetDistance2d(emergencyPos.GetPositionX(), emergencyPos.GetPositionY()); - if (bombDist < minEmergencyGasBombDist) - minEmergencyGasBombDist = bombDist; - } - - if (minEmergencyGasBombDist >= GAS_BOMB_SAFE_DIST && - bot->IsWithinLOS(emergencyPos.GetPositionX(), emergencyPos.GetPositionY(), emergencyPos.GetPositionZ())) - { - botAI->Reset(); - return MoveTo(bot->GetMapId(), emergencyPos.GetPositionX(), emergencyPos.GetPositionY(), - emergencyPos.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); - } - } - - return false; -} - -bool IccPutricideGasCloudAction::FindSafeMovementPosition(const Position& botPos, const Position& cloudPos, float dx, - float dy, int numAngles, Position& resultPos) -{ - float bestScore = 0.0f; - bool foundPath = false; - resultPos = botPos; - static const float MOVEMENT_INCREMENT = 5.0f; // Fixed movement increment - - for (int i = 0; i < numAngles; i++) - { - float angle = (2 * M_PI * i) / numAngles; - float rotatedDx = dx * cos(angle) - dy * sin(angle); - float rotatedDy = dx * sin(angle) + dy * cos(angle); - - for (float testDist = MOVEMENT_INCREMENT; testDist <= 20.0f; testDist += MOVEMENT_INCREMENT) - { - Position testPos(botPos.GetPositionX() + rotatedDx * testDist, botPos.GetPositionY() + rotatedDy * testDist, - botPos.GetPositionZ()); - - float newCloudDist = cloudPos.GetExactDist2d(testPos); - if (newCloudDist > cloudPos.GetExactDist2d(botPos) && - bot->IsWithinLOS(testPos.GetPositionX(), testPos.GetPositionY(), testPos.GetPositionZ())) - { - // Strict corner prevention - test movement freedom thoroughly - int freeDirections = 0; - static const int CHECK_DIRECTIONS = 16; - static const float CHECK_DISTANCE = 8.0f; - - for (int j = 0; j < CHECK_DIRECTIONS; j++) - { - float checkAngle = (2 * M_PI * j) / CHECK_DIRECTIONS; - float checkX = testPos.GetPositionX() + cos(checkAngle) * CHECK_DISTANCE; - float checkY = testPos.GetPositionY() + sin(checkAngle) * CHECK_DISTANCE; - if (bot->IsWithinLOS(checkX, checkY, testPos.GetPositionZ())) - freeDirections++; - } - - float freedomScore = (float)freeDirections / CHECK_DIRECTIONS; - - // REJECT positions that could lead to corners - require high freedom - if (freedomScore < 0.8f) // 80% of directions must be free - continue; - - // Check if we can continue moving in the same direction (avoid dead ends) - float continueX = testPos.GetPositionX() + rotatedDx * MOVEMENT_INCREMENT; - float continueY = testPos.GetPositionY() + rotatedDy * MOVEMENT_INCREMENT; - bool canContinue = bot->IsWithinLOS(continueX, continueY, testPos.GetPositionZ()); - - float continuityBonus = canContinue ? 3.0f : 0.0f; - float combinedScore = newCloudDist + (freedomScore * 12.0f) + continuityBonus; - - if (!foundPath || combinedScore > bestScore) - { - resultPos = testPos; - bestScore = combinedScore; - foundPath = true; - } - } - } - } - - return foundPath; -} - -Position IccPutricideGasCloudAction::CalculateEmergencyPosition(const Position& botPos, float dx, float dy) -{ - // For emergency, still try to avoid corners but prioritize getting away from immediate danger - Position bestPos = - Position(botPos.GetPositionX() + dx * 15.0f, botPos.GetPositionY() + dy * 15.0f, botPos.GetPositionZ()); - float bestFreedom = 0.0f; - - // Try fewer directions for emergency but still avoid corners - for (int i = 0; i < 8; i++) - { - float angle = (2 * M_PI * i) / 8; - float rotatedDx = dx * cos(angle) - dy * sin(angle); - float rotatedDy = dx * sin(angle) + dy * cos(angle); - - Position testPos(botPos.GetPositionX() + rotatedDx * 15.0f, botPos.GetPositionY() + rotatedDy * 15.0f, - botPos.GetPositionZ()); - - if (bot->IsWithinLOS(testPos.GetPositionX(), testPos.GetPositionY(), testPos.GetPositionZ())) - { - // Quick freedom check for emergency - int freeDirections = 0; - static const int EMERGENCY_CHECK_DIRS = 8; - static const float EMERGENCY_CHECK_DIST = 6.0f; - - for (int j = 0; j < EMERGENCY_CHECK_DIRS; j++) - { - float checkAngle = (2 * M_PI * j) / EMERGENCY_CHECK_DIRS; - float checkX = testPos.GetPositionX() + cos(checkAngle) * EMERGENCY_CHECK_DIST; - float checkY = testPos.GetPositionY() + sin(checkAngle) * EMERGENCY_CHECK_DIST; - if (bot->IsWithinLOS(checkX, checkY, testPos.GetPositionZ())) - freeDirections++; - } - - float freedom = (float)freeDirections / EMERGENCY_CHECK_DIRS; - - // For emergency, accept positions with at least 60% freedom - if (freedom >= 0.6f && freedom > bestFreedom) - { - bestPos = testPos; - bestFreedom = freedom; - } - } - } - - return bestPos; -} - -bool IccPutricideGasCloudAction::HandleGroupAuraSituation(Unit* gasCloud) -{ - Group* group = bot->GetGroup(); - if (!group || botAI->IsHeal(bot)) - return false; - - // Mark gas cloud with skull if no volatile ooze is present or alive - Unit* volatileOoze = AI_VALUE2(Unit*, "find target", "volatile ooze"); - if ((!volatileOoze || !volatileOoze->IsAlive()) && gasCloud && gasCloud->IsAlive()) - { - Group* group = bot->GetGroup(); - if (group) - { - constexpr uint8_t skullIconId = 7; - ObjectGuid currentSkull = group->GetTargetIcon(skullIconId); - Unit* markedUnit = botAI->GetUnit(currentSkull); - if (!markedUnit || !markedUnit->IsAlive() || markedUnit != gasCloud) - group->SetTargetIcon(skullIconId, bot->GetGUID(), gasCloud->GetGUID()); - } - } - - float currentDist = gasCloud ? bot->GetDistance(gasCloud) : 0.0f; - const float MIN_SAFE_DISTANCE = 15.0f; - - // Always maintain minimum distance when group doesn't have bloat - if (!GroupHasGaseousBloat(group) && gasCloud && currentDist < MIN_SAFE_DISTANCE) - { - // Move away from gas cloud - float angle = bot->GetAngle(gasCloud); - float x = bot->GetPositionX() + cos(angle) * -MIN_SAFE_DISTANCE; - float y = bot->GetPositionY() + sin(angle) * -MIN_SAFE_DISTANCE; - return MoveTo(bot->GetMapId(), x, y, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); - } - - if (GroupHasGaseousBloat(group) && gasCloud) - { - bot->SetTarget(gasCloud->GetGUID()); - bot->SetFacingToObject(gasCloud); - - // Attack logic for group with Gaseous Bloat - if (botAI->IsRanged(bot)) - { - // For ranged attackers, maintain optimal distance (15-25 yards) - if (currentDist > 25.0f) - { - // Move closer if too far - return MoveTo(bot->GetMapId(), gasCloud->GetPositionX(), gasCloud->GetPositionY(), - gasCloud->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); - } - else if (currentDist < MIN_SAFE_DISTANCE) - { - // Move away if too close (but stay closer than normal safe distance since we need to attack) - float angle = bot->GetAngle(gasCloud); - float x = bot->GetPositionX() + cos(angle) * -12.0f; - float y = bot->GetPositionY() + sin(angle) * -12.0f; - return MoveTo(bot->GetMapId(), x, y, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); - } - else - { - // Attack if at good range - return Attack(gasCloud); - } - } - else if (botAI->IsMelee(bot) && !botAI->IsTank(bot)) - { - // For melee attackers, move to attack range (0-5 yards) - if (currentDist > 5.0f) - { - return MoveTo(bot->GetMapId(), gasCloud->GetPositionX(), gasCloud->GetPositionY(), - gasCloud->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); - } - else - return Attack(gasCloud); - } - } - - return false; -} - -bool IccPutricideGasCloudAction::GroupHasGaseousBloat(Group* group) -{ - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (member && botAI->HasAura("Gaseous Bloat", member)) - return true; - } - return false; -} - -bool IccPutricideAvoidMalleableGooAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); - if (!boss) - return false; - - // Tank handling for choking gas bomb - if (HandleTankPositioning(boss)) - return true; - - // Skip if volatile ooze or gas cloud exists - if (AI_VALUE2(Unit*, "find target", "volatile ooze") || AI_VALUE2(Unit*, "find target", "gas cloud")) - return false; - - // Handle unbound plague movement - if (HandleUnboundPlague(boss)) - return true; - - // Handle ranged/melee positioning - return HandleBossPositioning(boss); -} - -bool IccPutricideAvoidMalleableGooAction::HandleTankPositioning(Unit*) -{ - if (!botAI->IsTank(bot)) - return false; - - Unit* bomb = bot->FindNearestCreature(NPC_CHOKING_GAS_BOMB, 100.0f); - if (!bomb) - return false; - - const float safeDistance = 15.0f; - float currentDistance = bot->GetDistance2d(bomb); - - if (currentDistance < safeDistance) - return MoveAway(bomb, safeDistance - currentDistance); - - return false; -} - -bool IccPutricideAvoidMalleableGooAction::HandleUnboundPlague(Unit* boss) -{ - if (boss && boss->HealthBelowPct(35)) - return false; - - if (!botAI->HasAura("Unbound Plague", bot)) - return false; - - Group* group = bot->GetGroup(); - if (!group) - return false; - - const float UNBOUND_PLAGUE_DISTANCE = 20.0f; - float closestDistance = UNBOUND_PLAGUE_DISTANCE; - Unit* closestPlayer = nullptr; - - // Find closest player - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || member == bot) - continue; - - float dist = bot->GetDistance2d(member); - if (dist < closestDistance) - { - closestDistance = dist; - closestPlayer = member; - } - } - - if (!closestPlayer || closestDistance >= UNBOUND_PLAGUE_DISTANCE) - { - bot->Kill(bot, bot); - return true; - } - - // Calculate move away position - float dx = bot->GetPositionX() - closestPlayer->GetPositionX(); - float dy = bot->GetPositionY() - closestPlayer->GetPositionY(); - float dist = sqrt(dx * dx + dy * dy); - - if (dist <= 0) - return false; - - dx /= dist; - dy /= dist; - float moveDistance = UNBOUND_PLAGUE_DISTANCE - closestDistance + 2.0f; - - float moveX = bot->GetPositionX() + dx * moveDistance; - float moveY = bot->GetPositionY() + dy * moveDistance; - - if (bot->IsWithinLOS(moveX, moveY, bot->GetPositionZ())) - { - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); - } - - return false; -} - -bool IccPutricideAvoidMalleableGooAction::HandleBossPositioning(Unit* boss) -{ - if (botAI->IsTank(bot)) - return false; - - // If boss is close to putricide_bad_position, all non-tank bots should be 1f in front of boss - const float BAD_POS_THRESHOLD = 10.0f; - const float IN_FRONT_DISTANCE = 1.0f; - float bossToBadPos = boss->GetExactDist2d(ICC_PUTRICIDE_BAD_POSITION.GetPositionX(), ICC_PUTRICIDE_BAD_POSITION.GetPositionY()); - - if (bossToBadPos <= BAD_POS_THRESHOLD) - { - // Move to 1f in front of boss - float bossOrientation = boss->GetOrientation(); - float targetX = boss->GetPositionX() + cos(bossOrientation) * IN_FRONT_DISTANCE; - float targetY = boss->GetPositionY() + sin(bossOrientation) * IN_FRONT_DISTANCE; - float targetZ = boss->GetPositionZ(); - - // Only move if not already close enough - if (bot->GetExactDist2d(targetX, targetY) > 0.5f) - { - bot->SetFacingToObject(boss); - return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, botAI->IsRanged(bot), - MovementPriority::MOVEMENT_COMBAT); - } - return false; - } - - float distToBoss = bot->GetExactDist2d(boss); - bool isRanged = botAI->IsRanged(bot); - - // Calculate desired position in front of boss - float desiredDistance = - isRanged ? ((bot->getClass() == CLASS_HUNTER) ? 14.0f : 6.0f) : (distToBoss < 2.0f ? 3.0f : 5.0f); - - // Check if we need to move - if ((std::abs(distToBoss - desiredDistance) > 0.5f || !boss->isInFront(bot)) && - (!isRanged || (isRanged && !botAI->IsTank(bot)))) - { - Position targetPos = CalculateBossPosition(boss, desiredDistance); - - // Check for obstacles - if (HasObstacleBetween(bot->GetPosition(), targetPos)) - { - // Use arc movement to navigate around obstacles - Position arcPoint = CalculateArcPoint(bot->GetPosition(), targetPos, boss->GetPosition()); - - if (bot->GetExactDist2d(arcPoint) > 1.0f) - { - bot->SetFacingToObject(boss); - return MoveTo(bot->GetMapId(), arcPoint.GetPositionX(), arcPoint.GetPositionY(), - arcPoint.GetPositionZ(), false, false, false, isRanged, - MovementPriority::MOVEMENT_COMBAT); - } - } - else - { - // No obstacles, move in increments directly toward target - Position adjustedTarget = CalculateIncrementalMove(bot->GetPosition(), targetPos, 2.0f); - bot->SetFacingToObject(boss); - return MoveTo(bot->GetMapId(), adjustedTarget.GetPositionX(), adjustedTarget.GetPositionY(), - adjustedTarget.GetPositionZ(), false, false, false, isRanged, - MovementPriority::MOVEMENT_COMBAT); - } - } - - return false; -} - -Position IccPutricideAvoidMalleableGooAction::CalculateBossPosition(Unit* boss, float distance) -{ - float bossOrientation = boss->GetOrientation(); - return Position(boss->GetPositionX() + cos(bossOrientation) * distance, - boss->GetPositionY() + sin(bossOrientation) * distance, boss->GetPositionZ()); -} - -bool IccPutricideAvoidMalleableGooAction::HasObstacleBetween(const Position& from, const Position& to) -{ - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto const& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || !unit->IsAlive()) - continue; - - if (unit->GetEntry() == NPC_GROWING_OOZE_PUDDLE || unit->GetEntry() == NPC_CHOKING_GAS_BOMB) - { - if (IsOnPath(from, to, unit->GetPosition(), 3.0f)) - return true; - } - } - return false; -} - -bool IccPutricideAvoidMalleableGooAction::IsOnPath(const Position& from, const Position& to, const Position& point, - float threshold) -{ - float pathX = to.GetPositionX() - from.GetPositionX(); - float pathY = to.GetPositionY() - from.GetPositionY(); - float pathLen = std::sqrt(pathX * pathX + pathY * pathY); - - if (pathLen < 0.1f) - return false; - - float normX = pathX / pathLen; - float normY = pathY / pathLen; - - float toPointX = point.GetPositionX() - from.GetPositionX(); - float toPointY = point.GetPositionY() - from.GetPositionY(); - float proj = toPointX * normX + toPointY * normY; - - if (proj < 0 || proj > pathLen) - return false; - - float closestX = from.GetPositionX() + normX * proj; - float closestY = from.GetPositionY() + normY * proj; - float distToPath = std::sqrt((point.GetPositionX() - closestX) * (point.GetPositionX() - closestX) + - (point.GetPositionY() - closestY) * (point.GetPositionY() - closestY)); - - return distToPath < threshold; -} - -Position IccPutricideAvoidMalleableGooAction::CalculateArcPoint(const Position& current, const Position& target, const Position& center) -{ - // Calculate vectors from center to current position and target - float currentX = current.GetPositionX() - center.GetPositionX(); - float currentY = current.GetPositionY() - center.GetPositionY(); - float targetX = target.GetPositionX() - center.GetPositionX(); - float targetY = target.GetPositionY() - center.GetPositionY(); - - // Calculate distances - float currentDist = std::sqrt(currentX * currentX + currentY * currentY); - float targetDist = std::sqrt(targetX * targetX + targetY * targetY); - - // Normalize vectors - currentX /= currentDist; - currentY /= currentDist; - targetX /= targetDist; - targetY /= targetDist; - - // Calculate dot product to find the angle between vectors - float dotProduct = currentX * targetX + currentY * targetY; - dotProduct = std::max(-1.0f, std::min(1.0f, dotProduct)); // Clamp to [-1, 1] - float angle = std::acos(dotProduct); - - // Determine rotation direction (clockwise or counterclockwise) - float crossProduct = currentX * targetY - currentY * targetX; - float stepAngle = angle * 0.25f; // Move 25% along the arc - - if (crossProduct < 0) - stepAngle = -stepAngle; // Clockwise - - // Calculate rotation matrix components - float cos_a = std::cos(stepAngle); - float sin_a = std::sin(stepAngle); - - // Rotate current vector - float rotatedX = currentX * cos_a - currentY * sin_a; - float rotatedY = currentX * sin_a + currentY * cos_a; - - // Scale to match the target distance for smoother approach - float desiredDist = currentDist * 0.9f + targetDist * 0.1f; - - // Calculate the new position - return Position(center.GetPositionX() + rotatedX * desiredDist, center.GetPositionY() + rotatedY * desiredDist, - current.GetPositionZ()); -} - -Position IccPutricideAvoidMalleableGooAction::CalculateIncrementalMove(const Position& current, const Position& target, - float maxDistance) -{ - float dx = target.GetPositionX() - current.GetPositionX(); - float dy = target.GetPositionY() - current.GetPositionY(); - float distance = std::sqrt(dx * dx + dy * dy); - - if (distance <= maxDistance) - return target; - - dx /= distance; - dy /= distance; - - return Position(current.GetPositionX() + dx * maxDistance, current.GetPositionY() + dy * maxDistance, - target.GetPositionZ()); -} - -// BPC -bool IccBpcKelesethTankAction::Execute(Event /*event*/) -{ - if (!botAI->IsAssistTank(bot)) - return false; - - // Handle boss positioning - Unit* boss = AI_VALUE2(Unit*, "find target", "prince keleseth"); - if (!boss) - return false; - - bool isBossVictim = false; - if (boss && boss->GetVictim() == bot) - isBossVictim = true; - - // If not actively tanking, attack the boss - if (boss->GetVictim() != bot) - { - bot->SetTarget(boss->GetGUID()); - bot->SetFacingToObject(boss); - Attack(boss); - } - - // If tanking boss, check for Dark Nucleus logic - collect any nucleus not targeting us - if (boss->GetVictim() == bot) - { - GuidVector targets = AI_VALUE(GuidVector, "possible targets"); - for (auto const& targetGuid : targets) - { - Unit* nucleus = botAI->GetUnit(targetGuid); - if (nucleus && nucleus->IsAlive() && nucleus->GetEntry() == NPC_DARK_NUCLEUS) - { - // Attack nucleus that are NOT targeting us (to collect them) - if (nucleus->GetVictim() != bot) - { - bot->SetTarget(nucleus->GetGUID()); - bot->SetFacingToObject(nucleus); - Attack(nucleus); - // Return early to focus on nucleus collection first - return false; - } - } - } - } - - // Positioning logic - only execute if no nucleus needs collecting - if (botAI->HasAura("Invocation of Blood", boss) && bot->GetExactDist2d(ICC_BPC_MT_POSITION) > 15.0f && isBossVictim) - { - // Calculate direction vector - float dirX = ICC_BPC_MT_POSITION.GetPositionX() - bot->GetPositionX(); - float dirY = ICC_BPC_MT_POSITION.GetPositionY() - bot->GetPositionY(); - - // Calculate distance and normalize - float length = std::sqrt(dirX * dirX + dirY * dirY); - if (length > 0.001f) - { - dirX /= length; - dirY /= length; - - // Calculate movement distance (max 3 yards at a time) - float moveDist = std::min(3.0f, length); - - // Calculate target position - float moveX = bot->GetPositionX() + dirX * moveDist; - float moveY = bot->GetPositionY() + dirY * moveDist; - float moveZ = bot->GetPositionZ(); - - MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT); - } - } - - // Always attack boss when tanking (if no nucleus was prioritized) - if (boss->GetVictim() == bot) - { - bot->SetTarget(boss->GetGUID()); - bot->SetFacingToObject(boss); - Attack(boss); - } - - return false; -} - -bool IccBpcMainTankAction::Execute(Event /*event*/) -{ - // Main tank specific behavior (higher priority) - if (botAI->IsMainTank(bot)) - { - // Get target princes - auto* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); - auto* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram"); - - // Check if we're the target of both princes - bool isVictimOfValanar = false; - if (valanar && valanar->GetVictim() == bot) - isVictimOfValanar = true; - - bool isVictimOfTaldaram = false; - if (taldaram && taldaram->GetVictim() == bot) - isVictimOfTaldaram = true; - - // Move to MT position if targeted by both princes and not already close - if (isVictimOfValanar && isVictimOfTaldaram && bot->GetExactDist2d(ICC_BPC_MT_POSITION) > 15.0f) - { - // Calculate direction vector - float dirX = ICC_BPC_MT_POSITION.GetPositionX() - bot->GetPositionX(); - float dirY = ICC_BPC_MT_POSITION.GetPositionY() - bot->GetPositionY(); - - // Calculate distance and normalize - float length = std::sqrt(dirX * dirX + dirY * dirY); - if (length > 0.001f) - { - dirX /= length; - dirY /= length; - - // Calculate movement distance (max 3 yards at a time) - float moveDist = std::min(3.0f, length); - - // Calculate target position - float moveX = bot->GetPositionX() + dirX * moveDist; - float moveY = bot->GetPositionY() + dirY * moveDist; - float moveZ = bot->GetPositionZ(); - - MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); - } - } - - // Attack prince that's not targeting us - if (valanar && !isVictimOfValanar) - { - bot->SetTarget(valanar->GetGUID()); - bot->SetFacingToObject(valanar); - Attack(valanar); - } - - if (taldaram && !isVictimOfTaldaram) - { - bot->SetTarget(taldaram->GetGUID()); - bot->SetFacingToObject(taldaram); - Attack(taldaram); - } - } - - // Target marking for all tanks, called after main tank priority actions - if (botAI->IsTank(bot)) - MarkEmpoweredPrince(); - - return false; -} - -void IccBpcMainTankAction::MarkEmpoweredPrince() -{ - static constexpr uint8_t SKULL_RAID_ICON = 7; - - // Find empowered prince (Invocation of Blood) - Unit* empoweredPrince = nullptr; - const GuidVector& targets = AI_VALUE(GuidVector, "possible targets"); - - for (auto const& targetGuid : targets) - { - Unit* unit = botAI->GetUnit(targetGuid); - if (!unit || !unit->IsAlive()) - continue; - - if (botAI->HasAura("Invocation of Blood", unit)) - { - const uint32 entry = unit->GetEntry(); - if (entry == NPC_PRINCE_KELESETH || entry == NPC_PRINCE_VALANAR || entry == NPC_PRINCE_TALDARAM) - { - empoweredPrince = unit; - break; - } - } - } - - // Handle marking if we found an empowered prince - if (empoweredPrince && empoweredPrince->IsAlive()) - { - Group* group = bot->GetGroup(); - if (group) - { - const ObjectGuid currentSkullGuid = group->GetTargetIcon(SKULL_RAID_ICON); - Unit* markedUnit = botAI->GetUnit(currentSkullGuid); - - // Clear dead marks or marks that are not on empowered prince - if (markedUnit && (!markedUnit->IsAlive() || markedUnit != empoweredPrince)) - { - group->SetTargetIcon(SKULL_RAID_ICON, bot->GetGUID(), ObjectGuid::Empty); - } - - // Mark alive empowered prince if needed - if (!currentSkullGuid || !markedUnit) - { - group->SetTargetIcon(SKULL_RAID_ICON, bot->GetGUID(), empoweredPrince->GetGUID()); - } - } - } -} - -bool IccBpcEmpoweredVortexAction::Execute(Event /*event*/) -{ - Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); - if (!valanar) - return false; - - // Check if boss is casting empowered vortex - bool isCastingVortex = false; - if (valanar && valanar->HasUnitState(UNIT_STATE_CASTING) && - (valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX1) || - valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX2) || - valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX3) || - valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX4))) - isCastingVortex = true; - - if (isCastingVortex) - { - // Use complex positioning system for empowered vortex - return HandleEmpoweredVortexSpread(); - } - else - { - // Use simple ranged spreading for non-vortex situations - return MaintainRangedSpacing(); - } -} - -bool IccBpcEmpoweredVortexAction::MaintainRangedSpacing() -{ - const float safeSpacingRadius = 7.0f; - const float moveIncrement = 2.0f; - const float maxMoveDistance = 5.0f; - const bool isRanged = botAI->IsRanged(bot) || botAI->IsHeal(bot); - - if (!isRanged) - return false; - - // Get group members - const GuidVector members = AI_VALUE(GuidVector, "group members"); - - // Calculate a combined vector representing all nearby members' positions - float totalX = 0.0f; - float totalY = 0.0f; - int nearbyCount = 0; - - for (auto const& memberGuid : members) - { - Unit* member = botAI->GetUnit(memberGuid); - if (!member || !member->IsAlive() || member == bot) - { - continue; - } - - const float distance = bot->GetExactDist2d(member); - if (distance < safeSpacingRadius) - { - // Calculate vector from member to bot - float dx = bot->GetPositionX() - member->GetPositionX(); - float dy = bot->GetPositionY() - member->GetPositionY(); - - // Weight by inverse distance (closer members have more influence) - float weight = (safeSpacingRadius - distance) / safeSpacingRadius; - totalX += dx * weight; - totalY += dy * weight; - nearbyCount++; - } - } - - // If we have nearby members, move away in the combined direction - if (nearbyCount > 0) - { - // Normalize the combined vector - float magnitude = std::sqrt(totalX * totalX + totalY * totalY); - if (magnitude > 0.001f) // Avoid division by zero - { - totalX /= magnitude; - totalY /= magnitude; - - // Calculate move distance - float moveDistance = std::min(moveIncrement, maxMoveDistance); - - // Create target position in the combined direction - float targetX = bot->GetPositionX() + totalX * moveDistance; - float targetY = bot->GetPositionY() + totalY * moveDistance; - float targetZ = bot->GetPositionZ(); - - // Check if the target position is valid and move there - if (bot->IsWithinLOS(targetX, targetY, targetZ)) - { - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - else - { - // If LOS check fails, try shorter distance - targetX = bot->GetPositionX() + totalX * (moveDistance * 0.5f); - targetY = bot->GetPositionY() + totalY * (moveDistance * 0.5f); - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - } - } - - return false; // Everyone is properly spaced -} - -bool IccBpcEmpoweredVortexAction::HandleEmpoweredVortexSpread() -{ - const float safeSpacingRadius = 13.0f; - const float moveIncrement = 2.0f; - const float maxMoveDistance = 5.0f; - const bool isTank = botAI->IsTank(bot); - - if (isTank) - return false; - - // Get group members - const GuidVector members = AI_VALUE(GuidVector, "group members"); - - // Calculate a combined vector representing all nearby members' positions - float totalX = 0.0f; - float totalY = 0.0f; - int nearbyCount = 0; - - for (auto const& memberGuid : members) - { - Unit* member = botAI->GetUnit(memberGuid); - if (!member || !member->IsAlive() || member == bot) - { - continue; - } - - const float distance = bot->GetExactDist2d(member); - if (distance < safeSpacingRadius) - { - // Calculate vector from member to bot - float dx = bot->GetPositionX() - member->GetPositionX(); - float dy = bot->GetPositionY() - member->GetPositionY(); - - // Weight by inverse distance (closer members have more influence) - float weight = (safeSpacingRadius - distance) / safeSpacingRadius; - totalX += dx * weight; - totalY += dy * weight; - nearbyCount++; - } - } - - // If we have nearby members, move away in the combined direction - if (nearbyCount > 0) - { - // Normalize the combined vector - float magnitude = std::sqrt(totalX * totalX + totalY * totalY); - if (magnitude > 0.001f) // Avoid division by zero - { - totalX /= magnitude; - totalY /= magnitude; - - // Calculate move distance - float moveDistance = std::min(moveIncrement, maxMoveDistance); - - // Create target position in the combined direction - float targetX = bot->GetPositionX() + totalX * moveDistance; - float targetY = bot->GetPositionY() + totalY * moveDistance; - float targetZ = bot->GetPositionZ(); - - // Check if the target position is valid and move there - if (bot->IsWithinLOS(targetX, targetY, targetZ)) - { - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - else - { - // If LOS check fails, try shorter distance - targetX = bot->GetPositionX() + totalX * (moveDistance * 0.5f); - targetY = bot->GetPositionY() + totalY * (moveDistance * 0.5f); - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - } - } - - return false; // Everyone is properly spaced -} - -bool IccBpcKineticBombAction::Execute(Event /*event*/) -{ - // Early exit if not ranged DPS - if (!botAI->IsRangedDps(bot)) - return false; - - // Static constants - static constexpr float MAX_HEIGHT_DIFF = 20.0f; - static constexpr float SAFE_HEIGHT = 371.16473f; - static constexpr float TELEPORT_HEIGHT = 366.16473f; - static constexpr std::array KINETIC_BOMB_ENTRIES = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2, - NPC_KINETIC_BOMB3, NPC_KINETIC_BOMB4}; - - // Handle edge case where bot is too high - if (bot->GetPositionZ() > SAFE_HEIGHT) - { - bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), TELEPORT_HEIGHT, - bot->GetOrientation()); - } - - // Check current target if valid - if (Unit* currentTarget = AI_VALUE(Unit*, "current target")) - { - if (currentTarget->IsAlive() && std::find(KINETIC_BOMB_ENTRIES.begin(), KINETIC_BOMB_ENTRIES.end(), - currentTarget->GetEntry()) != KINETIC_BOMB_ENTRIES.end()) - { - const float heightDiff = currentTarget->GetPositionZ() - 361.18222f; - if (heightDiff < MAX_HEIGHT_DIFF) - return false; // Continue current attack - } - } - - // Find the best kinetic bomb to attack - if (Unit* bestBomb = FindOptimalKineticBomb()) - { - bot->SetTarget(bestBomb->GetGUID()); - bot->SetFacingToObject(bestBomb); - Attack(bestBomb); - } - - return false; -} - -Unit* IccBpcKineticBombAction::FindOptimalKineticBomb() -{ - static constexpr std::array KINETIC_BOMB_ENTRIES = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2, - NPC_KINETIC_BOMB3, NPC_KINETIC_BOMB4}; - - const GuidVector targets = AI_VALUE(GuidVector, "possible targets"); - if (targets.empty()) - return nullptr; - - const float botZ = 361.18222f; - Group* group = bot->GetGroup(); - - // Gather all valid kinetic bombs - std::vector kineticBombs; - for (auto const& guid : targets) - { - Unit* unit = botAI->GetUnit(guid); - if (!unit || !unit->IsAlive()) - continue; - if (std::find(KINETIC_BOMB_ENTRIES.begin(), KINETIC_BOMB_ENTRIES.end(), unit->GetEntry()) == - KINETIC_BOMB_ENTRIES.end()) - continue; - kineticBombs.push_back(unit); - } - - if (kineticBombs.empty()) - return nullptr; - - // Sort bombs by Z ascending (lowest first), then by heightDiff ascending (closest to ground) - std::sort(kineticBombs.begin(), kineticBombs.end(), - [botZ](Unit* a, Unit* b) - { - if (a->GetPositionZ() != b->GetPositionZ()) - return a->GetPositionZ() < b->GetPositionZ(); - return std::abs(a->GetPositionZ() - botZ) < std::abs(b->GetPositionZ() - botZ); - }); - - // Assign each ranged DPS to a unique bomb (lowest Z first) - std::vector rangedDps; - if (group) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (member && member->IsAlive() && botAI->IsRangedDps(member)) - rangedDps.push_back(member); - } - // Sort by GUID for deterministic assignment - std::sort(rangedDps.begin(), rangedDps.end(), [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); - } - else - { - rangedDps.push_back(bot); - } - - // Find this bot's index among ranged DPS - auto it = std::find(rangedDps.begin(), rangedDps.end(), bot); - if (it == rangedDps.end()) - return nullptr; - size_t botIndex = std::distance(rangedDps.begin(), it); - - // Assign bombs in order, skip bombs already handled by other ranged DPS - size_t bombCount = kineticBombs.size(); - for (size_t i = 0, assigned = 0; i < bombCount; ++i) - { - Unit* bomb = kineticBombs[i]; - // Check if bomb is already handled by another ranged DPS closer than this bot - if (IsBombAlreadyHandled(bomb, group)) - continue; - if (assigned == botIndex) - return bomb; - ++assigned; - } - - // Fallback: pick the lowest bomb not already handled - for (Unit* bomb : kineticBombs) - { - if (!IsBombAlreadyHandled(bomb, group)) - return bomb; - } - - return nullptr; -} - -bool IccBpcKineticBombAction::IsBombAlreadyHandled(Unit* bomb, Group* group) -{ - if (!group) - return false; - - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || member == bot || !member->IsAlive() || !botAI->IsRangedDps(member)) - continue; - - if (member->GetTarget() == bomb->GetGUID() && member->GetDistance(bomb) < bot->GetDistance(bomb)) - return true; - } - - return false; -} - -bool IccBpcBallOfFlameAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "prince taldaram"); - if (!boss) - return false; - - Unit* flame1 = bot->FindNearestCreature(NPC_BALL_OF_FLAME, 100.0f); - Unit* flame2 = bot->FindNearestCreature(NPC_BALL_OF_INFERNO_FLAME, 100.0f); - - bool ballOfFlame = flame1 && (flame1->GetVictim() == bot); - bool infernoFlame = flame2 && (flame2->GetVictim() == bot); - - if (flame2 && (flame2->GetDistance2d(boss) > 2.0f) && !(flame2->GetDistance2d(boss) > 10.0f) && !infernoFlame && - bot->getClass() != CLASS_HUNTER) - { - if (!botAI->IsTank(bot) && !(flame2->GetVictim() == bot)) - { - float targetX = flame2->GetPositionX(); - float targetY = flame2->GetPositionY(); - float currentX = bot->GetPositionX(); - float currentY = bot->GetPositionY(); - - // Calculate direction vector - float dx = targetX - currentX; - float dy = targetY - currentY; - float distance = sqrt(dx * dx + dy * dy); - - // Normalize and scale to 5 units (or remaining distance if less than 5) - float step = std::min(5.0f, distance); - if (distance > 0.1) - { - dx = dx / distance * step; - dy = dy / distance * step; - } - - // Calculate intermediate position - float newX = currentX + dx; - float newY = currentY + dy; - - MoveTo(flame2->GetMapId(), newX, newY, bot->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); - } - } - - // If victim of ball of flame, keep at least 15f from other party members - if (ballOfFlame || infernoFlame) - { - const float SAFE_DIST = 15.0f; - GuidVector members = AI_VALUE(GuidVector, "group members"); - for (auto const& memberGuid : members) - { - Unit* member = botAI->GetUnit(memberGuid); - if (!member || !member->IsAlive() || member == bot) - continue; - float dist = bot->GetExactDist2d(member); - if (dist < SAFE_DIST) - { - // Move away from this member - float dx = bot->GetPositionX() - member->GetPositionX(); - float dy = bot->GetPositionY() - member->GetPositionY(); - float len = std::sqrt(dx * dx + dy * dy); - if (len < 0.01f) - continue; - dx /= len; - dy /= len; - float moveX = bot->GetPositionX() + dx * (SAFE_DIST - dist + 1.0f); - float moveY = bot->GetPositionY() + dy * (SAFE_DIST - dist + 1.0f); - float moveZ = bot->GetPositionZ(); - if (bot->IsWithinLOS(moveX, moveY, moveZ)) - { - MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, - MovementPriority::MOVEMENT_FORCED); - } - } - } - } - - return false; -} - -// Blood Queen Lana'thel -bool IccBqlGroupPositionAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); - if (!boss) - return false; - - Aura* frenzyAura = botAI->GetAura("Frenzied Bloodthirst", bot); - Aura* shadowAura = botAI->GetAura("Swarming Shadows", bot); - bool isTank = botAI->IsTank(bot); - // Handle tank positioning - if (isTank && HandleTankPosition(boss, frenzyAura, shadowAura)) - return true; - - // Handle swarming shadows movement - if (shadowAura && HandleShadowsMovement()) - return true; - - // Handle group positioning - if (!frenzyAura && !shadowAura && HandleGroupPosition(boss, frenzyAura, shadowAura)) - return true; - - return false; -} - -bool IccBqlGroupPositionAction::HandleTankPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura) -{ - if (frenzyAura || shadowAura) - return false; - - // Main tank positioning - if (botAI->IsMainTank(bot) && botAI->HasAggro(boss)) - { - if (bot->GetExactDist2d(ICC_BQL_TANK_POSITION) > 3.0f) - { - MoveTo(bot->GetMapId(), ICC_BQL_TANK_POSITION.GetPositionX(), ICC_BQL_TANK_POSITION.GetPositionY(), - ICC_BQL_TANK_POSITION.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); - } - } - - // Assist tank positioning - if (botAI->IsAssistTank(bot) && !botAI->GetAura("Blood Mirror", bot)) - { - if (Unit* mainTank = AI_VALUE(Unit*, "main tank")) - { - MoveTo(bot->GetMapId(), mainTank->GetPositionX(), mainTank->GetPositionY(), mainTank->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_COMBAT); - } - } - - return false; -} - -bool IccBqlGroupPositionAction::HandleShadowsMovement() -{ - const float SAFE_SHADOW_DIST = 4.0f; - const float ARC_STEP = 0.05f; - const float CURVE_SPACING = 15.0f; - const int MAX_CURVES = 3; - const float maxClosestDist = botAI->IsMelee(bot) ? 25.0f : 20.0f; - const Position& center = ICC_BQL_CENTER_POSITION; - const float OUTER_CURVE_PREFERENCE = 200.0f; // Strong preference for outer curves - const float CURVE_SWITCH_PENALTY = 50.0f; // Penalty for switching curves - const float DISTANCE_PENALTY_FACTOR = 100.0f; // Penalty per yard moved from current position - const float MAX_CURVE_JUMP_DIST = 5.0f; // Maximum distance for jumping between curves - - // Track current curve to avoid unnecessary switching - static std::map botCurrentCurve; - int currentCurve = botCurrentCurve.count(bot->GetGUID()) ? botCurrentCurve[bot->GetGUID()] : 0; - - // Find closest wall path - Position lwall[4] = {ICC_BQL_LWALL1_POSITION, AdjustControlPoint(ICC_BQL_LWALL2_POSITION, center, 1.30f), - AdjustControlPoint(ICC_BQL_LWALL3_POSITION, center, 1.30f), ICC_BQL_LRWALL4_POSITION}; - Position rwall[4] = {ICC_BQL_RWALL1_POSITION, AdjustControlPoint(ICC_BQL_RWALL2_POSITION, center, 1.30f), - AdjustControlPoint(ICC_BQL_RWALL3_POSITION, center, 1.30f), ICC_BQL_LRWALL4_POSITION}; - Position* basePath = (bot->GetExactDist2d(lwall[0]) < bot->GetExactDist2d(rwall[0])) ? lwall : rwall; - - // Find all swarming shadows - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - Unit* shadows[100]{}; // Reasonable max estimate - int shadowCount = 0; - for (int i = 0; i < npcs.size() && shadowCount < 100; i++) - { - Unit* unit = botAI->GetUnit(npcs[i]); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SWARMING_SHADOWS) - shadows[shadowCount++] = unit; - } - - // Helper lambda to check if a position is inside a shadow - auto IsPositionInShadow = [&](const Position& pos) -> bool - { - for (int i = 0; i < shadowCount; ++i) - { - if (pos.GetExactDist2d(shadows[i]) < SAFE_SHADOW_DIST) - return true; - } - return false; - }; - - // If bot is at the 4th position (end of the wall), move towards 3rd position or center to avoid getting stuck - float distToL4 = bot->GetExactDist2d(lwall[3]); - float distToR4 = bot->GetExactDist2d(rwall[3]); - const float STUCK_DIST = 2.0f; // within 2 yards is considered stuck at the end - - if (distToL4 < STUCK_DIST || distToR4 < STUCK_DIST) - { - // Move towards 3rd position of the same wall, or towards center if blocked - Position target; - if (distToL4 < distToR4) - { - target = lwall[2]; - } - else - { - target = rwall[2]; - } - - float tx = target.GetPositionX(); - float ty = target.GetPositionY(); - float tz = target.GetPositionZ(); - bot->UpdateAllowedPositionZ(tx, ty, tz); - if (!bot->IsWithinLOS(tx, ty, tz) || IsPositionInShadow(Position(tx, ty, tz))) - { - tx = center.GetPositionX(); - ty = center.GetPositionY(); - tz = center.GetPositionZ(); - } - - if (bot->GetExactDist2d(tx, ty) > 1.0f) - { - MoveTo(bot->GetMapId(), tx, ty, tz, false, false, false, true, MovementPriority::MOVEMENT_FORCED, - true, false); - } - return false; - } - - CurveInfo bestCurve; - bestCurve.foundSafe = false; - bestCurve.score = FLT_MAX; - bool foundCurve = false; - - // Keep track of information about all curves for possible fallback - CurveInfo curveInfos[MAX_CURVES]; - for (int i = 0; i < MAX_CURVES; i++) - { - curveInfos[i].foundSafe = false; - curveInfos[i].score = FLT_MAX; - } - - // Evaluate all curves starting from outermost (lowest index) - for (int curveIdx = 0; curveIdx < MAX_CURVES; curveIdx++) - { - float curveShrink = float(curveIdx) * CURVE_SPACING; - float shrinkFactor = 1.30f - (curveShrink / 30.0f); - if (shrinkFactor < 1.0f) - shrinkFactor = 1.0f; - - Position path[4] = {basePath[0], AdjustControlPoint(basePath[1], center, shrinkFactor / 1.30f), - AdjustControlPoint(basePath[2], center, shrinkFactor / 1.30f), basePath[3]}; - - // Find closest point on curve - float minDist = 9999.0f; - float t_closest = 0.0f; - Position closestPoint = path[0]; - - for (float t = 0.0f; t <= 1.0f; t += ARC_STEP) - { - Position pt = CalculateBezierPoint(t, path); - float dist = bot->GetExactDist2d(pt); - if (dist < minDist) - { - minDist = dist; - t_closest = t; - closestPoint = pt; - } - } - - // Check if the closest point is safe - bool closestIsSafe = !IsPositionInShadow(closestPoint); - - // Find closest safe point by searching in both directions from closest point - Position safeMoveTarget = closestPoint; - bool foundSafe = closestIsSafe; - - // Only search for safe spots if the closest point isn't already safe - if (!closestIsSafe) - { - // Find the nearest safe point along the curve, not by direct distance - // but by distance along the curve from the closest point - - // Search forward on curve from closest point - float forwardT = -1.0f; - Position forwardPt; - for (float t = t_closest + ARC_STEP; t <= 1.0f; t += ARC_STEP) - { - Position pt = CalculateBezierPoint(t, path); - if (!IsPositionInShadow(pt)) - { - forwardT = t; - forwardPt = pt; - break; - } - } - - // Search backward on curve from closest point - float backwardT = -1.0f; - Position backwardPt; - for (float t = t_closest - ARC_STEP; t >= 0.0f; t -= ARC_STEP) - { - Position pt = CalculateBezierPoint(t, path); - if (!IsPositionInShadow(pt)) - { - backwardT = t; - backwardPt = pt; - break; - } - } - - // Choose the closest safe point based on curve distance, not direct distance - if (forwardT >= 0 && backwardT >= 0) - { - // Both directions have safe points, choose the closer one by curve distance - if (std::abs(forwardT - t_closest) < std::abs(backwardT - t_closest)) - { - safeMoveTarget = forwardPt; - foundSafe = true; - } - else - { - safeMoveTarget = backwardPt; - foundSafe = true; - } - } - else if (forwardT >= 0) - { - safeMoveTarget = forwardPt; - foundSafe = true; - } - else if (backwardT >= 0) - { - safeMoveTarget = backwardPt; - foundSafe = true; - } - } - - // Score this curve - float distancePenalty = 0.0f; - float score = 0.0f; - - if (foundSafe) - { - // If we found a safe point, penalize based on travel distance along the curve to reach it - float safeDist = bot->GetExactDist2d(safeMoveTarget); - - // Add distance penalty based on how far we need to move along the curve - distancePenalty = safeDist * (1.0f / DISTANCE_PENALTY_FACTOR); - score = safeDist + distancePenalty; - } - else - { - // No safe point found, assign a high score - distancePenalty = minDist * (1.0f / DISTANCE_PENALTY_FACTOR); - score = minDist + distancePenalty + 1000.0f; // Penalty for unsafe position - } - - // Apply strong penalty for curves that are too far - if (minDist > maxClosestDist) - score += 500.0f; - - // Apply penalty for unsafe curves - if (!foundSafe) - score += 1000.0f; - - // Apply curve index preference (strongly prefer outer curves) - score += curveIdx * OUTER_CURVE_PREFERENCE; - - // Apply curve switching penalty - if (curveIdx != currentCurve && currentCurve != 0) - score += CURVE_SWITCH_PENALTY; - - // MORE IMPORTANT: Apply additional curve switching penalty if the bot is far away - // from the target curve (prevent jumping between curves when far away) - if (curveIdx != currentCurve && minDist > MAX_CURVE_JUMP_DIST) - score += 2000.0f; // Strong penalty to prevent jumping between curves - - // Store this curve's info - curveInfos[curveIdx].moveTarget = foundSafe ? safeMoveTarget : closestPoint; - curveInfos[curveIdx].foundSafe = foundSafe; - curveInfos[curveIdx].minDist = minDist; - curveInfos[curveIdx].curveIdx = curveIdx; - curveInfos[curveIdx].score = score; - curveInfos[curveIdx].closestPoint = closestPoint; - curveInfos[curveIdx].t_closest = t_closest; - - // Only update if this curve is better than our current best - if (!foundCurve || score < bestCurve.score) - { - bestCurve = curveInfos[curveIdx]; - foundCurve = true; - } - } - - // Fallback: If we're trying to switch to a far curve and we're not near any curve, - // find and use the closest curve instead of making a direct beeline - if (foundCurve && bestCurve.minDist > MAX_CURVE_JUMP_DIST && bestCurve.curveIdx != currentCurve) - { - // Look for the closest curve first - float closestDist = FLT_MAX; - int closestCurveIdx = -1; - - for (int i = 0; i < MAX_CURVES; i++) - { - if (curveInfos[i].minDist < closestDist) - { - closestDist = curveInfos[i].minDist; - closestCurveIdx = i; - } - } - - // If we found a closer curve, use that instead - if (closestCurveIdx >= 0 && closestCurveIdx != bestCurve.curveIdx) - { - bestCurve = curveInfos[closestCurveIdx]; - } - } - - // Remember the selected curve for next time - if (foundCurve) - { - botCurrentCurve[bot->GetGUID()] = bestCurve.curveIdx; - } - - // Create a move plan to guide the bot along the curve if necessary - if (foundCurve && bot->GetExactDist2d(bestCurve.moveTarget) > 1.0f) - { - // Final check: ensure we're not moving into a shadow - if (!IsPositionInShadow(bestCurve.moveTarget)) - { - // Get the curve - float curveShrink = float(bestCurve.curveIdx) * CURVE_SPACING; - float shrinkFactor = 1.30f - (curveShrink / 30.0f); - if (shrinkFactor < 1.0f) - shrinkFactor = 1.0f; - - Position path[4] = {basePath[0], AdjustControlPoint(basePath[1], center, shrinkFactor / 1.30f), - AdjustControlPoint(basePath[2], center, shrinkFactor / 1.30f), basePath[3]}; - - // CRITICAL CHANGE: First check if we need to move to the curve - float distToClosestPoint = bot->GetExactDist2d(bestCurve.closestPoint); - - // If we're not on the curve yet, first move to the closest point on the curve - if (distToClosestPoint > 2.0f) - { - botAI->Reset(); - return MoveTo(bot->GetMapId(), bestCurve.closestPoint.GetPositionX(), - bestCurve.closestPoint.GetPositionY(), bestCurve.closestPoint.GetPositionZ(), false, - false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); - } - - // Now we know we're on or very close to the curve, so we'll follow it properly - - // Find target point on curve (t_target parameter) - float t_target = 0.0f; - float targetMinDist = 9999.0f; - - for (float t = 0.0f; t <= 1.0f; t += ARC_STEP) - { - Position pt = CalculateBezierPoint(t, path); - float dist = bestCurve.moveTarget.GetExactDist2d(pt); - if (dist < targetMinDist) - { - targetMinDist = dist; - t_target = t; - } - } - - // Find an intermediate point along the curve between closest and target - float t_step = (t_target > bestCurve.t_closest) ? ARC_STEP : -ARC_STEP; - float t_intermediate = bestCurve.t_closest + t_step; - Position intermediateTarget; - bool foundValidIntermediate = false; - - // Limit the distance we move along the curve in one step - const float MAX_CURVE_MOVEMENT = 7.0f; // Max yards to move along curve - float curveDistanceMoved = 0.0f; - Position lastPos = bestCurve.closestPoint; - - while ((t_step > 0 && t_intermediate <= t_target) || (t_step < 0 && t_intermediate >= t_target)) - { - Position pt = CalculateBezierPoint(t_intermediate, path); - - // Check if this point is safe - if (!IsPositionInShadow(pt)) - { - // Calculate distance moved along curve so far - curveDistanceMoved += lastPos.GetExactDist2d(pt); - lastPos = pt; - - // If we've moved the maximum allowed distance, use this position - if (curveDistanceMoved >= MAX_CURVE_MOVEMENT) - { - intermediateTarget = pt; - foundValidIntermediate = true; - break; - } - - // Otherwise, continue moving along the curve - intermediateTarget = pt; - foundValidIntermediate = true; - } - else - { - // We've hit a shadow, stop here - break; - } - - t_intermediate += t_step; - } - - // If we found a valid intermediate point, use it - if (foundValidIntermediate) - { - botAI->Reset(); - MoveTo(bot->GetMapId(), intermediateTarget.GetPositionX(), intermediateTarget.GetPositionY(), - intermediateTarget.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_FORCED, true, false); - } - - botAI->Reset(); - // Fallback to direct movement to the target point on the curve - MoveTo(bot->GetMapId(), bestCurve.moveTarget.GetPositionX(), bestCurve.moveTarget.GetPositionY(), - bestCurve.moveTarget.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_FORCED, true, false); - } - } - - return false; -} - -Position IccBqlGroupPositionAction::AdjustControlPoint(const Position& wall, const Position& center, float factor) -{ - float dx = wall.GetPositionX() - center.GetPositionX(); - float dy = wall.GetPositionY() - center.GetPositionY(); - float dz = wall.GetPositionZ() - center.GetPositionZ(); - return Position(center.GetPositionX() + dx * factor, center.GetPositionY() + dy * factor, - center.GetPositionZ() + dz * factor); -} - -Position IccBqlGroupPositionAction::CalculateBezierPoint(float t, const Position path[4]) -{ - float omt = 1 - t; - float omt2 = omt * omt; - float omt3 = omt2 * omt; - float t2 = t * t; - float t3 = t2 * t; - - float x = omt3 * path[0].GetPositionX() + 3 * omt2 * t * path[1].GetPositionX() + - 3 * omt * t2 * path[2].GetPositionX() + t3 * path[3].GetPositionX(); - - float y = omt3 * path[0].GetPositionY() + 3 * omt2 * t * path[1].GetPositionY() + - 3 * omt * t2 * path[2].GetPositionY() + t3 * path[3].GetPositionY(); - - float z = omt3 * path[0].GetPositionZ() + 3 * omt2 * t * path[1].GetPositionZ() + - 3 * omt * t2 * path[2].GetPositionZ() + t3 * path[3].GetPositionZ(); - - return Position(x, y, z); -} - -bool IccBqlGroupPositionAction::HandleGroupPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura) -{ - if (frenzyAura || shadowAura) - return false; - - GuidVector members = AI_VALUE(GuidVector, "group members"); - bool isRanged = botAI->IsRanged(bot); - bool isMelee = botAI->IsMelee(bot); - - if (isRanged && bot->GetExactDist2d(boss->GetPositionX(), boss->GetPositionY()) > 35.0f) - MoveTo(boss, 5.0f, MovementPriority::MOVEMENT_FORCED); - - if ((boss->GetExactDist2d(ICC_BQL_TANK_POSITION.GetPositionX(), ICC_BQL_TANK_POSITION.GetPositionY()) > 10.0f) && - isRanged && !((boss->GetPositionZ() - ICC_BQL_CENTER_POSITION.GetPositionZ()) > 5.0f) && - (bot->GetExactDist2d(ICC_BQL_CENTER_POSITION.GetPositionX(), ICC_BQL_CENTER_POSITION.GetPositionY()) > 10.0f)) - MoveTo(bot->GetMapId(), ICC_BQL_CENTER_POSITION.GetPositionX(), ICC_BQL_CENTER_POSITION.GetPositionY(), - ICC_BQL_CENTER_POSITION.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - - // --- Ranged bots wall assignment logic --- - if (isRanged) - { - // Gather all ranged and healers, sort by GUID for deterministic assignment - std::vector rangedBots; - std::vector healers; - for (auto const& guid : members) - { - Unit* member = botAI->GetUnit(guid); - if (!member || !member->IsAlive()) - continue; - Player* player = member->ToPlayer(); - if (!player) - continue; - if (botAI->IsRanged(player)) - rangedBots.push_back(player); - if (botAI->IsHeal(player)) - healers.push_back(player); - } - // Remove duplicates (healer can be ranged) - std::sort(rangedBots.begin(), rangedBots.end(), - [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); - std::sort(healers.begin(), healers.end(), [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); - - // Assign at least one healer to each side, then balance the rest - std::vector leftSide, rightSide; - Position leftPos = ICC_BQL_LWALL2_POSITION; - Position rightPos = ICC_BQL_RWALL2_POSITION; - - // Assign healers first - if (!healers.empty()) - { - leftSide.push_back(healers[0]); - if (healers.size() > 1) - rightSide.push_back(healers[1]); - } - // If only one healer, assign to left, right will be filled by ranged DPS - - // Remove assigned healers from rangedBots - for (Player* h : leftSide) - rangedBots.erase(std::remove(rangedBots.begin(), rangedBots.end(), h), rangedBots.end()); - for (Player* h : rightSide) - rangedBots.erase(std::remove(rangedBots.begin(), rangedBots.end(), h), rangedBots.end()); - - // Distribute remaining ranged evenly - size_t leftCount = leftSide.size(); - size_t rightCount = rightSide.size(); - for (Player* p : rangedBots) - { - if (leftCount <= rightCount) - { - leftSide.push_back(p); - leftCount++; - } - else - { - rightSide.push_back(p); - rightCount++; - } - } - - // Determine which side this bot is assigned to - bool isLeft = std::find(leftSide.begin(), leftSide.end(), bot) != leftSide.end(); - bool isRight = std::find(rightSide.begin(), rightSide.end(), bot) != rightSide.end(); - - // Move to assigned wall position if not already close - const float MAX_WALL_DIST = 30.0f; - const float MOVE_INCREMENT = 2.0f; - const float MAX_MOVE_DISTANCE = 7.0f; - const float SAFE_SPACING_RADIUS = 7.0f; - const float MIN_CENTER_DISTANCE = 10.0f; - - Position targetWall = isLeft ? leftPos : (isRight ? rightPos : Position()); - if (isLeft || isRight) - { - float distToWall = bot->GetExactDist2d(targetWall.GetPositionX(), targetWall.GetPositionY()); - if (distToWall > MAX_WALL_DIST) - { - // Move in increments toward wall - float dx = targetWall.GetPositionX() - bot->GetPositionX(); - float dy = targetWall.GetPositionY() - bot->GetPositionY(); - float len = std::sqrt(dx * dx + dy * dy); - if (len > 0.001f) - { - dx /= len; - dy /= len; - float moveDist = std::min(MOVE_INCREMENT, distToWall); - float targetX = bot->GetPositionX() + dx * moveDist; - float targetY = bot->GetPositionY() + dy * moveDist; - float targetZ = bot->GetPositionZ(); - if (!bot->IsWithinLOS(targetX, targetY, targetZ)) - { - targetX = bot->GetPositionX() + dx * (moveDist * 0.5f); - targetY = bot->GetPositionY() + dy * (moveDist * 0.5f); - } - botAI->Reset(); - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } - } - // Spread from other assigned members on the same side and from swarming shadows - float totalX = 0.0f, totalY = 0.0f; - int nearbyCount = 0; - const std::vector& mySide = isLeft ? leftSide : rightSide; - for (Player* member : mySide) - { - if (!member || !member->IsAlive() || member == bot) - continue; - float distance = bot->GetExactDist2d(member); - if (distance < SAFE_SPACING_RADIUS) - { - float dx = bot->GetPositionX() - member->GetPositionX(); - float dy = bot->GetPositionY() - member->GetPositionY(); - float weight = (SAFE_SPACING_RADIUS - distance) / SAFE_SPACING_RADIUS; - totalX += dx * weight; - totalY += dy * weight; - nearbyCount++; - } - } - // Also spread from swarming shadows - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto const& npcGuid : npcs) - { - Unit* unit = botAI->GetUnit(npcGuid); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SWARMING_SHADOWS) - { - float distance = bot->GetExactDist2d(unit); - if (distance < SAFE_SPACING_RADIUS) - { - float dx = bot->GetPositionX() - unit->GetPositionX(); - float dy = bot->GetPositionY() - unit->GetPositionY(); - float weight = (SAFE_SPACING_RADIUS - distance) / SAFE_SPACING_RADIUS; - totalX += dx * weight; - totalY += dy * weight; - nearbyCount++; - } - } - } - if (nearbyCount > 0) - { - float magnitude = sqrt(totalX * totalX + totalY * totalY); - if (magnitude > 0.001f) - { - totalX /= magnitude; - totalY /= magnitude; - float moveDistance = std::min(MOVE_INCREMENT, MAX_MOVE_DISTANCE); - float targetX = bot->GetPositionX() + totalX * moveDistance; - float targetY = bot->GetPositionY() + totalY * moveDistance; - float targetZ = bot->GetPositionZ(); - if (!bot->IsWithinLOS(targetX, targetY, targetZ)) - { - targetX = bot->GetPositionX() + totalX * (moveDistance * 0.5f); - targetY = bot->GetPositionY() + totalY * (moveDistance * 0.5f); - } - botAI->Reset(); - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } - } - // Maintain minimum distance from center position (if too close to center, move out) - float centerX = ICC_BQL_CENTER_POSITION.GetPositionX(); - float centerY = ICC_BQL_CENTER_POSITION.GetPositionY(); - float centerDist = bot->GetDistance2d(centerX, centerY); - if (centerDist < MIN_CENTER_DISTANCE && !((boss->GetPositionZ() - bot->GetPositionZ()) > 5.0f)) - { - float dx = bot->GetPositionX() - centerX; - float dy = bot->GetPositionY() - centerY; - float dist = std::sqrt(dx * dx + dy * dy); - if (dist > 0.001f) - { - dx /= dist; - dy /= dist; - float moveDistance = std::min(MIN_CENTER_DISTANCE - centerDist + 1.0f, MAX_MOVE_DISTANCE); - float targetX = bot->GetPositionX() + dx * moveDistance; - float targetY = bot->GetPositionY() + dy * moveDistance; - float targetZ = bot->GetPositionZ(); - if (!bot->IsWithinLOS(targetX, targetY, targetZ)) - { - targetX = bot->GetPositionX() + dx * (moveDistance * 0.5f); - targetY = bot->GetPositionY() + dy * (moveDistance * 0.5f); - } - botAI->Reset(); - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } - } - } - } - - if (isMelee && ((boss->GetPositionZ() - ICC_BQL_CENTER_POSITION.GetPositionZ()) > 5.0f)) - { - const float SAFE_SPACING_RADIUS = 7.0f; - const float MOVE_INCREMENT = 2.0f; - const float MAX_MOVE_DISTANCE = 7.0f; - - float totalX = 0.0f; - float totalY = 0.0f; - int nearbyCount = 0; - - // Find all swarming shadows - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - std::vector swarmingShadows; - for (int i = 0; i < npcs.size(); ++i) - { - Unit* unit = botAI->GetUnit(npcs[i]); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SWARMING_SHADOWS) - swarmingShadows.push_back(unit); - } - - for (int i = 0; i < members.size(); i++) - { - Unit* member = botAI->GetUnit(members[i]); - if (!member || !member->IsAlive() || member == bot || botAI->GetAura("Frenzied Bloodthirst", member) || - botAI->GetAura("Uncontrollable Frenzy", member)) - continue; - - float distance = bot->GetExactDist2d(member); - if (distance < SAFE_SPACING_RADIUS) - { - float dx = bot->GetPositionX() - member->GetPositionX(); - float dy = bot->GetPositionY() - member->GetPositionY(); - float weight = (SAFE_SPACING_RADIUS - distance) / SAFE_SPACING_RADIUS; - totalX += dx * weight; - totalY += dy * weight; - nearbyCount++; - } - } - - // Also spread from swarming shadows - for (Unit* shadow : swarmingShadows) - { - float distance = bot->GetExactDist2d(shadow); - if (distance < SAFE_SPACING_RADIUS) - { - float dx = bot->GetPositionX() - shadow->GetPositionX(); - float dy = bot->GetPositionY() - shadow->GetPositionY(); - float weight = (SAFE_SPACING_RADIUS - distance) / SAFE_SPACING_RADIUS; - totalX += dx * weight; - totalY += dy * weight; - nearbyCount++; - } - } - - if (nearbyCount > 0) - { - float magnitude = sqrt(totalX * totalX + totalY * totalY); - if (magnitude > 0.001f) - { - totalX /= magnitude; - totalY /= magnitude; - float moveDistance = MOVE_INCREMENT < MAX_MOVE_DISTANCE ? MOVE_INCREMENT : MAX_MOVE_DISTANCE; - float targetX = bot->GetPositionX() + totalX * moveDistance; - float targetY = bot->GetPositionY() + totalY * moveDistance; - float targetZ = bot->GetPositionZ(); - - if (!bot->IsWithinLOS(targetX, targetY, targetZ)) - { - targetX = bot->GetPositionX() + totalX * (moveDistance * 0.5f); - targetY = bot->GetPositionY() + totalY * (moveDistance * 0.5f); - } - - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } - } - return true; - } - - return false; -} - -bool IccBqlPactOfDarkfallenAction::Execute(Event /*event*/) -{ - // Check if bot has Pact of the Darkfallen - if (!botAI->GetAura("Pact of the Darkfallen", bot)) - return false; - Group* group = bot->GetGroup(); - if (!group) - return false; - - // Find other players with Pact of the Darkfallen - Player* tankWithAura = nullptr; - std::vector playersWithAura; - - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || member == bot) - continue; - if (botAI->GetAura("Pact of the Darkfallen", member)) - { - playersWithAura.push_back(member); - if (botAI->IsTank(member)) - tankWithAura = member; - } - } - - if (playersWithAura.empty()) - return false; - - // Determine target position - Position targetPos; - if (tankWithAura) - { - // If there's a tank with aura, everyone moves to the tank (including the tank itself for center positioning) - if (botAI->IsTank(bot)) - { - // If current bot is the tank, stay put or move slightly for better positioning - targetPos.Relocate(bot); - } - else - { - // Non-tank bots move to the tank - targetPos.Relocate(tankWithAura); - } - } - else if (playersWithAura.size() >= 2) - { - // Calculate center position of all players with aura (including bot) - CalculateCenterPosition(targetPos, playersWithAura); - } - else if (playersWithAura.size() == 1) - { - // Move to the single other player with aura - targetPos.Relocate(playersWithAura[0]); - } - else - { - // No valid movement case found - return true; - } - - // Move to target position if needed - return MoveToTargetPosition(targetPos, playersWithAura.size() + 1); // +1 to include the bot itself -} - -void IccBqlPactOfDarkfallenAction::CalculateCenterPosition(Position& targetPos, - const std::vector& playersWithAura) -{ - float sumX = bot->GetPositionX(); - float sumY = bot->GetPositionY(); - float sumZ = bot->GetPositionZ(); - - // Add positions of all other players with aura - for (Player* player : playersWithAura) - { - sumX += player->GetPositionX(); - sumY += player->GetPositionY(); - sumZ += player->GetPositionZ(); - } - - // Calculate average position (center) - int totalPlayers = playersWithAura.size() + 1; // +1 for the bot itself - targetPos.Relocate(sumX / totalPlayers, sumY / totalPlayers, sumZ / totalPlayers); -} - -bool IccBqlPactOfDarkfallenAction::MoveToTargetPosition(const Position& targetPos, int auraCount) -{ - const float POSITION_TOLERANCE = 0.1f; - float distance = bot->GetDistance(targetPos); - if (distance <= POSITION_TOLERANCE) - return true; - - // Calculate movement increment - float dx = targetPos.GetPositionX() - bot->GetPositionX(); - float dy = targetPos.GetPositionY() - bot->GetPositionY(); - float dz = targetPos.GetPositionZ() - bot->GetPositionZ(); - float len = sqrt(dx * dx + dy * dy); - - float moveX, moveY, moveZ; - if (len > 5.0f && auraCount <= 2) - { - dx /= len; - dy /= len; - moveX = bot->GetPositionX() + dx * 5.0f; - moveY = bot->GetPositionY() + dy * 5.0f; - moveZ = bot->GetPositionZ() + (dz / distance) * 5.0f; - } - else - { - moveX = targetPos.GetPositionX(); - moveY = targetPos.GetPositionY(); - moveZ = targetPos.GetPositionZ(); - } - - botAI->Reset(); - MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); - return false; -} - -bool IccBqlVampiricBiteAction::Execute(Event /*event*/) -{ - // Only act when bot has Frenzied Bloodthirst - if (!botAI->GetAura("Frenzied Bloodthirst", bot)) - return false; - - const float BITE_RANGE = 2.0f; - Group* group = bot->GetGroup(); - if (!group) - return false; - - // Find best target - Player* target = FindBestBiteTarget(group); - if (!target) - return false; - - // Handle movement or casting - float x = target->GetPositionX(); - float y = target->GetPositionY(); - float z = target->GetPositionZ(); - - if (bot->GetExactDist2d(target) > BITE_RANGE) - { - return MoveTowardsTarget(target); - } - else if (bot->IsWithinLOS(x, y, z)) - { - return CastVampiricBite(target); - } - - return false; -} - -Player* IccBqlVampiricBiteAction::FindBestBiteTarget(Group* group) -{ - std::set currentlyTargetedPlayers; - std::vector> dpsTargets; - std::vector> healTargets; - - // Get currently targeted players - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || member == bot) - continue; - - if (botAI->GetAura("Frenzied Bloodthirst", member) && member->GetTarget()) - { - currentlyTargetedPlayers.insert(member->GetTarget()); - } - } - - // Evaluate potential targets - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || member == bot || !member->IsAlive()) - continue; - - if (IsInvalidTarget(member) || currentlyTargetedPlayers.count(member->GetGUID())) - continue; - - float distance = bot->GetDistance(member); - if (botAI->IsDps(member)) - dpsTargets.push_back({member, distance}); - else if (botAI->IsHeal(member)) - healTargets.push_back({member, distance}); - } - - // Sort by distance - auto sortByDistance = [](auto const& a, auto const& b) { return a.second < b.second; }; - std::sort(dpsTargets.begin(), dpsTargets.end(), sortByDistance); - std::sort(healTargets.begin(), healTargets.end(), sortByDistance); - - // Return closest valid target - if (!dpsTargets.empty()) - return dpsTargets[0].first; - if (!healTargets.empty()) - return healTargets[0].first; - return nullptr; -} - -bool IccBqlVampiricBiteAction::IsInvalidTarget(Player* player) -{ - return botAI->GetAura("Frenzied Bloodthirst", player) || botAI->GetAura("Essence of the Blood Queen", player) || - botAI->GetAura("Uncontrollable Frenzy", player) || botAI->GetAura("Swarming Shadows", player) || - botAI->IsTank(player); -} - -bool IccBqlVampiricBiteAction::MoveTowardsTarget(Player* target) -{ - if (IsInvalidTarget(target) || !target->IsAlive()) - return false; - - float x = target->GetPositionX(); - float y = target->GetPositionY(); - float z = target->GetPositionZ(); - - if (!bot->IsWithinLOS(x, y, z)) - return false; - - float dx = x - bot->GetPositionX(); - float dy = y - bot->GetPositionY(); - float dz = z - bot->GetPositionZ(); - float len = sqrt(dx * dx + dy * dy); - - float moveX, moveY, moveZ; - if (len > 5.0f) - { - dx /= len; - dy /= len; - moveX = bot->GetPositionX() + dx * 5.0f; - moveY = bot->GetPositionY() + dy * 5.0f; - moveZ = bot->GetPositionZ() + (dz / len) * 5.0f; - } - else - { - moveX = x; - moveY = y; - moveZ = z; - } - - MoveTo(target->GetMapId(), moveX, moveY, moveZ, false, false, false, true, - MovementPriority::MOVEMENT_FORCED); - - return false; -} - -bool IccBqlVampiricBiteAction::CastVampiricBite(Player* target) -{ - if (IsInvalidTarget(target) || !target->IsAlive()) - return false; - - return botAI->CanCastSpell("Vampiric Bite", target) && botAI->CastSpell("Vampiric Bite", target); -} - -// Sister Svalna -bool IccValkyreSpearAction::Execute(Event /*event*/) -{ - // Find the nearest spear - Creature* spear = bot->FindNearestCreature(NPC_SPEAR, 100.0f); - if (!spear) - return false; - - // Move to the spear if not in range - if (!spear->IsWithinDistInMap(bot, INTERACTION_DISTANCE)) - return MoveTo(spear, INTERACTION_DISTANCE); - - // Remove shapeshift forms - botAI->RemoveShapeshift(); - - // Stop movement and click the spear - bot->GetMotionMaster()->Clear(); - bot->StopMoving(); - spear->HandleSpellClick(bot); - - // Dismount if mounted - WorldPacket emptyPacket; - bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); - - return false; -} - -bool IccSisterSvalnaAction::Execute(Event /*event*/) -{ - Unit* svalna = AI_VALUE2(Unit*, "find target", "sister svalna"); - if (!svalna || !svalna->HasAura(SPELL_AETHER_SHIELD)) // Check for Aether Shield aura - return false; - - // Check if bot has the spear item - if (!botAI->HasItemInInventory(ITEM_SPEAR)) - return false; - - // Get all items from inventory - std::vector items = botAI->GetInventoryItems(); - for (Item* item : items) - { - if (item->GetEntry() == ITEM_SPEAR) // Spear ID - { - botAI->ImbueItem(item, svalna); // Use spear on Svalna - return false; - } - } - - return false; -} - -// VDW -bool IccValithriaGroupAction::Execute(Event /*event*/) -{ - // Helper lambda to find nearest creature of given entries - auto findNearestCreature = [this](std::initializer_list entries, float range) -> Creature* - { - for (uint32 entry : entries) - { - if (Creature* creature = bot->FindNearestCreature(entry, range)) - { - return creature; - } - } - return nullptr; - }; - - // Find portals and enemies - Creature* portal = findNearestCreature({NPC_DREAM_PORTAL, NPC_DREAM_PORTAL_PRE_EFFECT, NPC_NIGHTMARE_PORTAL, NPC_NIGHTMARE_PORTAL_PRE_EFFECT}, 100.0f); - - Creature* worm = bot->FindNearestCreature(NPC_ROT_WORM, 100.0f); - Creature* zombie = bot->FindNearestCreature(NPC_BLISTERING_ZOMBIE, 100.0f); - Creature* manaVoid = bot->FindNearestCreature(NPC_MANA_VOID, 100.0f); - - // Find column of frost units - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - std::vector columnOfFrost; - for (ObjectGuid guid : npcs) - { - if (Unit* unit = botAI->GetUnit(guid)) - { - if (unit->IsAlive() && unit->GetEntry() == NPC_COLUMN_OF_FROST) - { - columnOfFrost.push_back(unit); - } - } - } - - // Tank behavior - if (botAI->IsTank(bot)) - { - for (auto const& targetGuid : AI_VALUE(GuidVector, "possible targets")) - { - if (Unit* unit = botAI->GetUnit(targetGuid)) - { - if (unit->IsAlive() && - (unit->GetEntry() == NPC_GLUTTONOUS_ABOMINATION || unit->GetEntry() == NPC_ROT_WORM)) - { - // Skip if unit is already attacking any tank - if (Unit* victim = unit->GetVictim()) - { - if (victim->IsPlayer() && botAI->IsTank(static_cast(victim))) - { - continue; - } - } - - // Only attack if not already targeting us - if (unit->GetVictim() != bot) - { - bot->SetTarget(unit->GetGUID()); - bot->SetFacingToObject(unit); - Attack(unit); - } - } - } - } - } - - // Healer movement logic - if (botAI->IsHeal(bot) && bot->GetExactDist2d(ICC_VDW_HEAL_POSITION) > 30.0f && !portal) - return MoveTo(bot->GetMapId(), ICC_VDW_HEAL_POSITION.GetPositionX(), ICC_VDW_HEAL_POSITION.GetPositionY(), - ICC_VDW_HEAL_POSITION.GetPositionZ(), - false, false, false, false, MovementPriority::MOVEMENT_NORMAL); - - // Avoidance behaviors - if (manaVoid && bot->GetExactDist2d(manaVoid) < 10.0f && - !(botAI->GetAura("Twisted Nightmares", bot) || botAI->GetAura("Emerald Vigor", bot))) - { - botAI->Reset(); - FleePosition(manaVoid->GetPosition(), 11.0f, 250U); - } - - for (Unit* column : columnOfFrost) - { - if (column && bot->GetExactDist2d(column) < 7.0f) - { - botAI->Reset(); - FleePosition(column->GetPosition(), 8.0f, 250U); - } - } - - if (worm && worm->IsAlive() && worm->GetVictim() == bot && !botAI->IsTank(bot)) - { - botAI->Reset(); - FleePosition(worm->GetPosition(), 10.0f, 250U); - } - - if (zombie && zombie->IsAlive() && zombie->GetVictim() == bot && !botAI->IsTank(bot) && - bot->GetExactDist2d(zombie) < 20.0f) - { - botAI->Reset(); - FleePosition(zombie->GetPosition(), 21.0f, 250U); - } - - // Crowd control logic - if (zombie && !botAI->IsMainTank(bot) && !botAI->IsHeal(bot) && zombie->GetVictim() != bot) - { - switch (bot->getClass()) - { - case CLASS_MAGE: - if (!botAI->HasAura("Frost Nova", zombie)) - botAI->CastSpell("Frost Nova", zombie); - break; - case CLASS_DRUID: - if (!botAI->HasAura("Entangling Roots", zombie)) - botAI->CastSpell("Entangling Roots", zombie); - break; - case CLASS_PALADIN: - if (!botAI->HasAura("Hammer of Justice", zombie)) - botAI->CastSpell("Hammer of Justice", zombie); - break; - case CLASS_WARRIOR: - if (!botAI->HasAura("Hamstring", zombie)) - botAI->CastSpell("Hamstring", zombie); - break; - case CLASS_HUNTER: - if (!botAI->HasAura("Concussive Shot", zombie)) - botAI->CastSpell("Concussive Shot", zombie); - break; - case CLASS_ROGUE: - if (!botAI->HasAura("Kidney Shot", zombie)) - botAI->CastSpell("Kidney Shot", zombie); - break; - case CLASS_SHAMAN: - if (!botAI->HasAura("Frost Shock", zombie)) - botAI->CastSpell("Frost Shock", zombie); - break; - case CLASS_DEATH_KNIGHT: - if (!botAI->HasAura("Chains of Ice", zombie)) - botAI->CastSpell("Chains of Ice", zombie); - break; - default: - break; - } - } - - // Group assignment and movement logic - Difficulty diff = bot->GetRaidDifficulty(); - Group* group = bot->GetGroup(); - - if (group && (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - return Handle25ManGroupLogic(); - else - return Handle10ManGroupLogic(); -} - -bool IccValithriaGroupAction::MoveTowardsPosition(const Position& pos, float increment) -{ - float dx = pos.GetPositionX() - bot->GetPositionX(); - float dy = pos.GetPositionY() - bot->GetPositionY(); - float dz = pos.GetPositionZ() - bot->GetPositionZ(); - float dist = std::hypot(dx, dy); - - float moveX, moveY, moveZ; - if (dist > increment) - { - dx /= dist; - dy /= dist; - moveX = bot->GetPositionX() + dx * increment; - moveY = bot->GetPositionY() + dy * increment; - moveZ = bot->GetPositionZ() + (dz / dist) * increment; - } - else - { - moveX = pos.GetPositionX(); - moveY = pos.GetPositionY(); - moveZ = pos.GetPositionZ(); - } - - MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT, - true, false); - - return false; -} - -bool IccValithriaGroupAction::Handle25ManGroupLogic() -{ - const Position group1Pos = ICC_VDW_GROUP1_POSITION; - const Position group2Pos = ICC_VDW_GROUP2_POSITION; - - Group* group = bot->GetGroup(); - if (!group) - return false; - - // Collect group members - std::vector tanks, dps; - std::vector> nonHeals; - - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - if (Player* member = itr->GetSource()) - { - if (member->IsAlive() && !botAI->IsHeal(member)) - { - if (botAI->IsTank(member)) - { - tanks.push_back(member); - } - else - { - dps.push_back(member); - } - nonHeals.emplace_back(member->GetGUID(), member); - } - } - } - - // Sort by GUID for consistent ordering - std::sort(nonHeals.begin(), nonHeals.end(), [](auto const& a, auto const& b) { return a.first < b.first; }); - - // Assign to groups - std::vector group1, group2; - if (!tanks.empty()) - group1.push_back(tanks[0]); - - if (tanks.size() > 1) - group2.push_back(tanks[1]); - else if (tanks.size() == 1 && !dps.empty()) - group2.push_back(dps[0]); - - // Assign remaining DPS - std::set assigned; - for (Player* p : group1) - assigned.insert(p->GetGUID()); - for (Player* p : group2) - assigned.insert(p->GetGUID()); - - for (Player* p : dps) - { - if (assigned.find(p->GetGUID()) == assigned.end()) - { - (group1.size() <= group2.size() ? group1 : group2).push_back(p); - } - } - - // Check which group the bot is in - bool inGroup1 = std::any_of(group1.begin(), group1.end(), [this](Player* p) { return p == bot; }); - bool inGroup2 = std::any_of(group2.begin(), group2.end(), [this](Player* p) { return p == bot; }); - - // Marking logic for tanks and DPS - if (botAI->IsTank(bot) || botAI->IsDps(bot)) - HandleMarkingLogic(inGroup1, inGroup2, group1Pos, group2Pos); - - // Movement logic for non-healers - if (!botAI->IsHeal(bot)) - { - if (inGroup1) - { - float distance = bot->GetDistance(group1Pos); - if (distance > 25.0f) - { - // If far away, move directly to position - MoveTowardsPosition(group1Pos, 5.0f); - } - } - else if (inGroup2) - { - float distance = bot->GetDistance(group2Pos); - if (distance > 25.0f) - { - MoveTowardsPosition(group2Pos, 5.0f); - } - } - } - - return false; -} - -bool IccValithriaGroupAction::HandleMarkingLogic(bool inGroup1, bool inGroup2, const Position& group1Pos, - const Position& group2Pos) -{ - static constexpr uint8_t SKULL_ICON_INDEX = 7; - static constexpr uint8_t CROSS_ICON_INDEX = 6; - static const std::array addPriority = {NPC_BLAZING_SKELETON, NPC_SUPPRESSER, - NPC_RISEN_ARCHMAGE, NPC_BLISTERING_ZOMBIE, - NPC_GLUTTONOUS_ABOMINATION, NPC_ROT_WORM}; - - const Position* groupPos = nullptr; - uint8_t iconIndex = 0; - std::string rtiValue; - - if (inGroup1) - { - iconIndex = SKULL_ICON_INDEX; - groupPos = &group1Pos; - rtiValue = "skull"; - } - else if (inGroup2) - { - iconIndex = CROSS_ICON_INDEX; - groupPos = &group2Pos; - rtiValue = "cross"; - } - else - return false; - - context->GetValue("rti")->Set(rtiValue); - - // Find priority target - const GuidVector adds = AI_VALUE(GuidVector, "possible targets"); - Unit* priorityTarget = nullptr; - - for (uint32 entry : addPriority) - { - for (auto const& guid : adds) - { - if (Unit* unit = botAI->GetUnit(guid)) - { - if (unit->IsAlive() && unit->GetEntry() == entry && - unit->GetExactDist2d(groupPos->GetPositionX(), groupPos->GetPositionY()) <= 40.0f) - { - priorityTarget = unit; - break; - } - } - } - if (priorityTarget) - break; - } - - // Update target icon if needed - if (priorityTarget && bot->GetGroup()) - { - Group* group = bot->GetGroup(); - ObjectGuid currentIcon = group->GetTargetIcon(iconIndex); - Unit* currentIconUnit = botAI->GetUnit(currentIcon); - - // Check if the target already has any raid icon - bool hasOtherIcon = false; - for (uint8 i = 0; i < 8; ++i) - { - if (i == iconIndex) - continue; // Skip our own icon index - if (group->GetTargetIcon(i) == priorityTarget->GetGUID()) - { - hasOtherIcon = true; - break; - } - } - - if (!hasOtherIcon && (!currentIconUnit || !currentIconUnit->IsAlive() || currentIconUnit != priorityTarget)) - { - group->SetTargetIcon(iconIndex, bot->GetGUID(), priorityTarget->GetGUID()); - } - } - - return false; -} - -bool IccValithriaGroupAction::Handle10ManGroupLogic() -{ - static constexpr uint8_t DEFAULT_ICON_INDEX = 7; - static const std::array addPriority = {NPC_BLAZING_SKELETON, NPC_SUPPRESSER, - NPC_RISEN_ARCHMAGE, NPC_BLISTERING_ZOMBIE, - NPC_GLUTTONOUS_ABOMINATION, NPC_ROT_WORM}; - - // Marking logic - Group* group = bot->GetGroup(); - if (group) - { - const GuidVector adds = AI_VALUE(GuidVector, "possible targets"); - Unit* priorityTarget = nullptr; - - for (uint32 entry : addPriority) - { - for (auto const& guid : adds) - { - if (Unit* unit = botAI->GetUnit(guid)) - { - if (unit->IsAlive() && unit->GetEntry() == entry && - unit->GetExactDist2d(ICC_VDW_HEAL_POSITION.GetPositionX(), - ICC_VDW_HEAL_POSITION.GetPositionY()) <= 50.0f) - { - priorityTarget = unit; - break; - } - } - } - if (priorityTarget) - break; - } - - if (priorityTarget) - { - ObjectGuid currentIcon = group->GetTargetIcon(DEFAULT_ICON_INDEX); - Unit* currentIconUnit = botAI->GetUnit(currentIcon); - - if (!currentIconUnit || !currentIconUnit->IsAlive() || currentIconUnit != priorityTarget) - { - group->SetTargetIcon(DEFAULT_ICON_INDEX, bot->GetGUID(), priorityTarget->GetGUID()); - } - } - } - - // Movement logic - if (bot->GetExactDist2d(ICC_VDW_HEAL_POSITION.GetPositionX(), ICC_VDW_HEAL_POSITION.GetPositionY()) > 25.0f) - MoveTowardsPosition(ICC_VDW_HEAL_POSITION, 5.0f); - - return false; -} - -bool IccValithriaPortalAction::Execute(Event /*event*/) -{ - // Only healers should take portals, and not if already inside - if (!botAI->IsHeal(bot) || bot->HasAura(SPELL_DREAM_STATE)) - return false; - - // Gather all portals (pre-effect and real) using nearest npcs - GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs"); - std::vector preEffectPortals; - std::vector realPortals; - for (auto const& guid : npcs) - { - Creature* c = dynamic_cast(botAI->GetUnit(guid)); - if (!c) - continue; - uint32 entry = c->GetEntry(); - if (entry == NPC_DREAM_PORTAL_PRE_EFFECT || entry == NPC_NIGHTMARE_PORTAL_PRE_EFFECT) - preEffectPortals.push_back(c); - else if (entry == NPC_DREAM_PORTAL || entry == NPC_NIGHTMARE_PORTAL) - realPortals.push_back(c); - } - - if (preEffectPortals.empty() && realPortals.empty()) - return false; - - // Remove duplicates (in case of overlap) - auto sortByGuid = [](Creature* a, Creature* b) { return a->GetGUID() < b->GetGUID(); }; - std::sort(preEffectPortals.begin(), preEffectPortals.end(), sortByGuid); - preEffectPortals.erase(std::unique(preEffectPortals.begin(), preEffectPortals.end()), preEffectPortals.end()); - std::sort(realPortals.begin(), realPortals.end(), sortByGuid); - realPortals.erase(std::unique(realPortals.begin(), realPortals.end()), realPortals.end()); - - // Gather all healers in group, sort by GUID for deterministic assignment - Group* group = bot->GetGroup(); - std::vector healers; - if (group) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (member && member->IsAlive() && botAI->IsHeal(member)) - healers.push_back(member); - } - std::sort(healers.begin(), healers.end(), [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); - } - else - healers.push_back(bot); - - // Find this bot's index among healers - auto it = std::find(healers.begin(), healers.end(), bot); - if (it == healers.end()) - return false; - size_t healerIndex = std::distance(healers.begin(), it); - - // Assign each healer to a pre-effect portal by index (wrap if more healers than portals) - Creature* assignedPreEffect = nullptr; - if (!preEffectPortals.empty()) - assignedPreEffect = preEffectPortals[healerIndex % preEffectPortals.size()]; - - // Move to assigned pre-effect portal, stand at portal - if (assignedPreEffect) - { - float portalX = assignedPreEffect->GetPositionX(); - float portalY = assignedPreEffect->GetPositionY(); - float portalZ = assignedPreEffect->GetPositionZ(); - float dist = bot->GetDistance2d(portalX, portalY); - - if (dist > 0.5f) - { - // Move directly to the pre-effect portal position - MoveTo(assignedPreEffect->GetMapId(), portalX, portalY, portalZ, false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - // Remove shapeshift forms - botAI->RemoveShapeshift(); - - // Try to click the real portal if it is close enough - Creature* nearestRealPortal = nullptr; - float minDist = 9999.0f; - for (Creature* portal : realPortals) - { - float d = bot->GetDistance2d(portal); - if (d < 3.0f && d < minDist) - { - nearestRealPortal = portal; - minDist = d; - } - } - - if (nearestRealPortal) - { - botAI->RemoveShapeshift(); - bot->GetMotionMaster()->Clear(); - bot->StopMoving(); - bot->SetFacingToObject(nearestRealPortal); - nearestRealPortal->HandleSpellClick(bot); - return true; - } - - // If no real portal is close, wait at the position - return false; - } - - // If no pre-effect portals, try to find a real portal within 3f - Creature* nearestRealPortal = nullptr; - float minDist = 9999.0f; - for (Creature* portal : realPortals) - { - float d = bot->GetDistance2d(portal); - if (d < 3.0f && d < minDist) - { - nearestRealPortal = portal; - minDist = d; - } - } - - if (nearestRealPortal && minDist > 2.0f) - MoveTo(bot->GetMapId(), nearestRealPortal->GetPositionX(), nearestRealPortal->GetPositionY(), - nearestRealPortal->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL); - - if (nearestRealPortal) - { - botAI->RemoveShapeshift(); - bot->GetMotionMaster()->Clear(); - bot->StopMoving(); - bot->SetFacingToObject(nearestRealPortal); - nearestRealPortal->HandleSpellClick(bot); - return true; - } - - return false; -} - -bool IccValithriaHealAction::Execute(Event /*event*/) -{ - // Early validation checks - if (!botAI->IsHeal(bot) || bot->GetHealthPct() < 50.0f) - return false; - - // Handle movement speed when not in dream state - if (!bot->HasAura(SPELL_DREAM_STATE)) - { - constexpr float NORMAL_SPEED = 1.0f; - bot->SetSpeed(MOVE_RUN, NORMAL_SPEED, true); - bot->SetSpeed(MOVE_WALK, NORMAL_SPEED, true); - bot->SetSpeed(MOVE_FLIGHT, NORMAL_SPEED, true); - } - - // Enforce Z-position limit - constexpr float MAX_Z_POSITION = 367.961f; - constexpr float TARGET_Z_POSITION = 365.0f; - if (bot->GetPositionZ() > MAX_Z_POSITION) - bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), TARGET_Z_POSITION, - bot->GetOrientation()); - - // Find Valithria within range - Creature* valithria = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); - if (!valithria) - return false; - - // Execute class-specific healing logic - switch (bot->getClass()) - { - case CLASS_DRUID: - { - // Druid healing spell constants - constexpr uint32 SPELL_REJUVENATION = 48441; - constexpr uint32 SPELL_REGROWTH = 48443; - constexpr uint32 SPELL_LIFEBLOOM = 48451; - constexpr uint32 SPELL_WILD_GROWTH = 53251; - constexpr uint8 LIFEBLOOM_MAX_STACKS = 3; - - // Apply Rejuvenation if missing - if (!valithria->HasAura(SPELL_REJUVENATION, bot->GetGUID())) - return botAI->CastSpell(SPELL_REJUVENATION, valithria); - - // Apply Regrowth if missing - if (!valithria->HasAura(SPELL_REGROWTH, bot->GetGUID())) - return botAI->CastSpell(SPELL_REGROWTH, valithria); - - // Stack Lifebloom to maximum stacks - Aura* lifebloom = valithria->GetAura(SPELL_LIFEBLOOM, bot->GetGUID()); - if (!lifebloom || lifebloom->GetStackAmount() < LIFEBLOOM_MAX_STACKS) - return botAI->CastSpell(SPELL_LIFEBLOOM, valithria); - - // All HoTs active with full stacks - cast Wild Growth - return botAI->CastSpell(SPELL_WILD_GROWTH, valithria); - } - case CLASS_SHAMAN: - { - constexpr uint32 SPELL_RIPTIDE = 61301; - constexpr uint32 SPELL_HEALING_WAVE = 49273; - - // Cast Healing Wave if Riptide is active, otherwise apply Riptide - return valithria->HasAura(SPELL_RIPTIDE, bot->GetGUID()) ? botAI->CastSpell(SPELL_HEALING_WAVE, valithria) - : botAI->CastSpell(SPELL_RIPTIDE, valithria); - } - case CLASS_PRIEST: - { - constexpr uint32 SPELL_RENEW = 48068; - constexpr uint32 SPELL_GREATER_HEAL = 48063; - - // Cast Greater Heal if Renew is active, otherwise apply Renew - return valithria->HasAura(SPELL_RENEW, bot->GetGUID()) ? botAI->CastSpell(SPELL_GREATER_HEAL, valithria) - : botAI->CastSpell(SPELL_RENEW, valithria); - } - case CLASS_PALADIN: - { - constexpr uint32 SPELL_BEACON_OF_LIGHT = 53563; - constexpr uint32 SPELL_HOLY_LIGHT = 48782; - - // Cast Holy Light if Beacon is active, otherwise apply Beacon of Light - return valithria->HasAura(SPELL_BEACON_OF_LIGHT, bot->GetGUID()) - ? botAI->CastSpell(SPELL_HOLY_LIGHT, valithria) - : botAI->CastSpell(SPELL_BEACON_OF_LIGHT, valithria); - } - default: - return false; - } - - return false; -} - -bool IccValithriaDreamCloudAction::Execute(Event /*event*/) -{ - // Only execute if we're in dream state - if (!bot->HasAura(SPELL_DREAM_STATE)) - return false; - - // Set speed to match players in dream state - bot->SetSpeed(MOVE_RUN, 2.0f, true); - bot->SetSpeed(MOVE_WALK, 2.0f, true); - bot->SetSpeed(MOVE_FLIGHT, 2.0f, true); - - // Gather all group members with dream state - const GuidVector members = AI_VALUE(GuidVector, "group members"); - std::vector dreamBots; - for (auto const& guid : members) - { - Unit* member = botAI->GetUnit(guid); - if (member && member->IsAlive() && member->HasAura(SPELL_DREAM_STATE)) - dreamBots.push_back(member); - } - - if (dreamBots.empty()) - return false; - - // Sort dreamBots by GUID (lowest first) - std::sort(dreamBots.begin(), dreamBots.end(), [](Unit* a, Unit* b) { return a->GetGUID() < b->GetGUID(); }); - - // Find this bot's index in the sorted list - auto it = std::find(dreamBots.begin(), dreamBots.end(), bot); - if (it == dreamBots.end()) - return false; - - // Check if all dream bots are stacked within 3f of the current leader (lowest guid) - constexpr float STACK_RADIUS = 2.0f; - Unit* leader = dreamBots.front(); - bool allStacked = true; - for (Unit* member : dreamBots) - { - // Only require stacking for bots, not real players - Player* player = member->ToPlayer(); - if (player && !player->GetSession()) // is a bot - { - if (member->GetDistance(leader) > STACK_RADIUS) - { - allStacked = false; - break; - } - } - } - - // If not all stacked, everyone moves to the leader's position (clouds' position) - constexpr float PORTALSTART_TOLERANCE = 1.0f; - if (!allStacked) - { - if (bot != leader) - { - if (bot->GetDistance(leader) > PORTALSTART_TOLERANCE) - { - bot->TeleportTo(bot->GetMapId(), leader->GetPositionX(), leader->GetPositionY(), leader->GetPositionZ(), - bot->GetOrientation()); - } - } - } - - // All stacked: leader (lowest guid) moves to next cloud, others follow and stack at leader's new position - // Find all dream and nightmare clouds - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - std::vector dreamClouds; - std::vector nightmareClouds; - - for (int i = 0; i < npcs.size(); ++i) - { - Unit* unit = botAI->GetUnit(npcs[i]); - if (unit && unit->IsAlive()) - { - if (Creature* creature = unit->ToCreature()) - { - if (creature->GetEntry() == NPC_DREAM_CLOUD) - dreamClouds.push_back(creature); - else if (creature->GetEntry() == NPC_NIGHTMARE_CLOUD) - nightmareClouds.push_back(creature); - } - } - } - - // Sort clouds by distance - std::sort(dreamClouds.begin(), dreamClouds.end(), - [this](Creature* a, Creature* b) { return bot->GetExactDist2d(a) < bot->GetExactDist2d(b); }); - - std::sort(nightmareClouds.begin(), nightmareClouds.end(), - [this](Creature* a, Creature* b) { return bot->GetExactDist2d(a) < bot->GetExactDist2d(b); }); - - // Only the leader moves to the next cloud - if (bot == leader) - { - // Use GUID to determine which cloud type to prefer - bool preferDream = (bot->GetGUID().GetCounter() % 2 == 0); - - // Check if we're close to any cloud - bool atDreamCloud = false; - bool atNightmareCloud = false; - - for (Creature* cloud : dreamClouds) - { - if (bot->GetExactDist2d(cloud) <= 2.0f) - { - atDreamCloud = true; - break; - } - } - - for (Creature* cloud : nightmareClouds) - { - if (bot->GetExactDist2d(cloud) <= 2.0f) - { - atNightmareCloud = true; - break; - } - } - - // If we have emerald vigor, prioritize dream clouds - if (bot->HasAura(SPELL_EMERALD_VIGOR)) - { - // If at dream cloud, move to 2nd closest dream cloud or closest nightmare cloud - if (atDreamCloud) - { - Creature* targetCloud = nullptr; - // Try 2nd closest dream cloud first - if (dreamClouds.size() >= 2 && bot->GetExactDist2d(dreamClouds[1]) > 2.0f) - targetCloud = dreamClouds[1]; - // Otherwise move to closest nightmare cloud - else if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) - targetCloud = nightmareClouds[0]; - - if (targetCloud) - MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), - targetCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL); - } - // If at nightmare cloud, move to closest dream cloud or 2nd closest nightmare cloud - else if (atNightmareCloud) - { - Creature* targetCloud = nullptr; - // Try closest dream cloud first - if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) - targetCloud = dreamClouds[0]; - // Otherwise move to 2nd closest nightmare cloud - else if (nightmareClouds.size() >= 2 && bot->GetExactDist2d(nightmareClouds[1]) > 2.0f) - targetCloud = nightmareClouds[1]; - - if (targetCloud) - MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), - targetCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL); - } - // If not at any cloud, move to closest dream cloud or nightmare cloud - else - { - if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) - MoveTo(dreamClouds[0]->GetMapId(), dreamClouds[0]->GetPositionX(), dreamClouds[0]->GetPositionY(), - dreamClouds[0]->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - else if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) - MoveTo(nightmareClouds[0]->GetMapId(), nightmareClouds[0]->GetPositionX(), - nightmareClouds[0]->GetPositionY(), nightmareClouds[0]->GetPositionZ(), false, false, false, - true, MovementPriority::MOVEMENT_NORMAL); - } - } - // Otherwise use GUID-based preference - else - { - // If prefer dream clouds based on GUID - if (preferDream) - { - // If at dream cloud, move to 2nd closest dream cloud or closest nightmare cloud - if (atDreamCloud) - { - Creature* targetCloud = nullptr; - // Try 2nd closest dream cloud first - if (dreamClouds.size() >= 2 && bot->GetExactDist2d(dreamClouds[1]) > 2.0f) - targetCloud = dreamClouds[1]; - // Otherwise move to closest nightmare cloud - else if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) - targetCloud = nightmareClouds[0]; - - if (targetCloud) - MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), - targetCloud->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - // If at nightmare cloud, move to closest dream cloud or 2nd closest nightmare cloud - else if (atNightmareCloud) - { - Creature* targetCloud = nullptr; - // Try closest dream cloud first - if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) - targetCloud = dreamClouds[0]; - // Otherwise move to 2nd closest nightmare cloud - else if (nightmareClouds.size() >= 2 && bot->GetExactDist2d(nightmareClouds[1]) > 2.0f) - targetCloud = nightmareClouds[1]; - - if (targetCloud) - MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), - targetCloud->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - // If not at any cloud, move to closest dream cloud or nightmare cloud based on preference - else - { - if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) - MoveTo(dreamClouds[0]->GetMapId(), dreamClouds[0]->GetPositionX(), - dreamClouds[0]->GetPositionY(), dreamClouds[0]->GetPositionZ(), false, false, false, - true, MovementPriority::MOVEMENT_NORMAL); - else if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) - MoveTo(nightmareClouds[0]->GetMapId(), nightmareClouds[0]->GetPositionX(), - nightmareClouds[0]->GetPositionY(), nightmareClouds[0]->GetPositionZ(), false, false, - false, true, MovementPriority::MOVEMENT_NORMAL); - } - } - // If prefer nightmare clouds based on GUID - else - { - // If at nightmare cloud, move to 2nd closest nightmare cloud or closest dream cloud - if (atNightmareCloud) - { - Creature* targetCloud = nullptr; - // Try 2nd closest nightmare cloud first - if (nightmareClouds.size() >= 2 && bot->GetExactDist2d(nightmareClouds[1]) > 2.0f) - targetCloud = nightmareClouds[1]; - // Otherwise move to closest dream cloud - else if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) - targetCloud = dreamClouds[0]; - - if (targetCloud) - MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), - targetCloud->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - // If at dream cloud, move to closest nightmare cloud or 2nd closest dream cloud - else if (atDreamCloud) - { - Creature* targetCloud = nullptr; - // Try closest nightmare cloud first - if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) - targetCloud = nightmareClouds[0]; - // Otherwise move to 2nd closest dream cloud - else if (dreamClouds.size() >= 2 && bot->GetExactDist2d(dreamClouds[1]) > 2.0f) - targetCloud = dreamClouds[1]; - - if (targetCloud) - MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), - targetCloud->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); - } - // If not at any cloud, move to closest nightmare cloud or dream cloud based on preference - else - { - if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) - MoveTo(nightmareClouds[0]->GetMapId(), nightmareClouds[0]->GetPositionX(), - nightmareClouds[0]->GetPositionY(), nightmareClouds[0]->GetPositionZ(), false, false, - false, true, MovementPriority::MOVEMENT_NORMAL); - else if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) - MoveTo(dreamClouds[0]->GetMapId(), dreamClouds[0]->GetPositionX(), - dreamClouds[0]->GetPositionY(), dreamClouds[0]->GetPositionZ(), false, false, false, - true, MovementPriority::MOVEMENT_NORMAL); - } - } - } - } - else - { - // Non-leader bots follow and stack at leader's position - if (bot->GetDistance(leader) > PORTALSTART_TOLERANCE) - { - botAI->Reset(); - bot->TeleportTo(bot->GetMapId(), leader->GetPositionX(), leader->GetPositionY(), leader->GetPositionZ(), - bot->GetOrientation()); - } - } - - return false; -} - -// Sindragosa -bool IccSindragosaGroupPositionAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); - if (!boss || boss->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY)) - return false; - - Aura* aura = botAI->GetAura("mystic buffet", bot, false, true); - - if (aura && aura->GetStackAmount() >= 6 && botAI->IsMainTank(bot)) - return false; - - if (botAI->IsTank(bot) && boss->GetVictim() == bot) - return HandleTankPositioning(boss); - - if (boss && boss->GetVictim() != bot) - return HandleNonTankPositioning(); - - return false; -} - -bool IccSindragosaGroupPositionAction::HandleTankPositioning(Unit* boss) -{ - float distBossToCenter = boss->GetExactDist2d(ICC_SINDRAGOSA_CENTER_POSITION); - float distToTankPos = bot->GetExactDist2d(ICC_SINDRAGOSA_TANK_POSITION); - float targetOrientation = M_PI / 2; // We want boss to face east - float currentOrientation = boss->GetOrientation(); - - // Normalize both orientations to 0-2π range - currentOrientation = fmod(currentOrientation + 2 * M_PI, 2 * M_PI); - targetOrientation = fmod(targetOrientation + 2 * M_PI, 2 * M_PI); - - float orientationDiff = currentOrientation - targetOrientation; - - // Normalize the difference to be between -PI and PI - while (orientationDiff > M_PI) - orientationDiff -= 2 * M_PI; - while (orientationDiff < -M_PI) - orientationDiff += 2 * M_PI; - - // Stage 1: Move boss to center if too far - if (boss && boss->GetVictim() == bot && distBossToCenter > 16.0f && distToTankPos <= 20.0f) - { - // Calculate direction vector from boss to center - float dirX = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionX() - boss->GetPositionX(); - float dirY = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionY() - boss->GetPositionY(); - - // Move 10 yards beyond center in the same direction - float moveX = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionX() + (dirX / distBossToCenter) * 4.0f; - float moveY = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionY() + (dirY / distBossToCenter) * 4.0f; - - return MoveTo(bot->GetMapId(), moveX, moveY, boss->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_FORCED, true, false); - } - - // Stage 2: Move to tank position if too far - if (boss && boss->GetVictim() == bot && distToTankPos > 10.0f) - { - Position botPos = bot->GetPosition(); - Position tankPos = ICC_SINDRAGOSA_TANK_POSITION; - - float dx = tankPos.GetPositionX() - botPos.GetPositionX(); - float dy = tankPos.GetPositionY() - botPos.GetPositionY(); - - float distance = std::sqrt(dx * dx + dy * dy); - float step = 1.0f; - - // Normalize and scale direction vector - float scale = step / distance; - - float targetX = botPos.GetPositionX() + dx * scale; - float targetY = botPos.GetPositionY() + dy * scale; - - return MoveTo(bot->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } - - // Stage 3: Adjust orientation when in position - if (boss && boss->GetVictim() == bot && std::abs(orientationDiff) > 0.15f) - { - // Move in an arc (circle) north or south around the boss until the orientation matches - float currentX = bot->GetPositionX(); - float currentY = bot->GetPositionY(); - float centerX = boss->GetPositionX(); - float centerY = boss->GetPositionY(); - float radius = std::max(2.0f, bot->GetExactDist2d(centerX, centerY)); // keep at least 2 yards from boss - - // Calculate current angle from boss to bot - float angle = atan2(currentY - centerY, currentX - centerX); - - // Determine direction: negative diff = move counterclockwise (north), positive = clockwise (south) - float arcStep = 0.125f; // radians per move, adjust for smoothness - if (orientationDiff < 0) - angle += arcStep; // move north (counterclockwise) - else - angle -= arcStep; // move south (clockwise) - - // Calculate new position on the arc - float moveX = centerX + radius * cos(angle); - float moveY = centerY + radius * sin(angle); - - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_FORCED, true, false); - } - - // Stage 4: Adjust Y-axis position if too far from tank position - float yDiff = std::abs(bot->GetPositionY() - ICC_SINDRAGOSA_TANK_POSITION.GetPositionY()); - if (boss && boss->GetVictim() == bot && yDiff > 2.0f) - { - Position botPos = bot->GetPosition(); - Position tankPos = ICC_SINDRAGOSA_TANK_POSITION; - - // Only adjust Y position, keep X and Z the same - float newY = botPos.GetPositionY() + (tankPos.GetPositionY() > botPos.GetPositionY() ? 1.0f : -1.0f); - - return MoveTo(bot->GetMapId(), botPos.GetPositionX(), newY, botPos.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_FORCED, true, false); - } - - return false; -} - -bool IccSindragosaGroupPositionAction::HandleNonTankPositioning() -{ - Group* group = bot->GetGroup(); - if (!group) - return false; - - // Collect all alive raid members - std::vector raidMembers; - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive()) - continue; - raidMembers.push_back(member); - } - - // Count members without aura 1111 - size_t membersWithoutAura = 0; - for (Player* member : raidMembers) - { - if (!botAI->GetAura("mystic buffet", member)) - membersWithoutAura++; - } - - // Calculate percentage without aura - size_t totalMembers = raidMembers.size(); - if (totalMembers == 0) - return false; - - double percentageWithoutAura = static_cast(membersWithoutAura) / totalMembers; - bool raidClear = (percentageWithoutAura >= 0.6); // 60% or more don't have aura 1111 - - if (raidClear && botAI->IsTank(bot)) - { - static const std::array tombEntries = {NPC_TOMB1, NPC_TOMB2, NPC_TOMB3, NPC_TOMB4}; - const GuidVector tombGuids = AI_VALUE(GuidVector, "possible targets no los"); - - Unit* nearestTomb = nullptr; - float minDist = 150.0f; - - for (const auto entry : tombEntries) - { - for (auto const& guid : tombGuids) - { - if (Unit* unit = botAI->GetUnit(guid)) - { - if (unit->GetEntry() == entry && unit->IsAlive()) - { - float dist = bot->GetDistance(unit); - if (dist < minDist) - { - minDist = dist; - nearestTomb = unit; - } - } - } - } - } - - static constexpr uint8_t SKULL_ICON_INDEX = 7; - - Group* group = bot->GetGroup(); - if (!group) - return false; // Cannot assign icon without group - - Unit* targetToMark = nearestTomb; - - // Fallback: mark boss if no tomb is found - if (!targetToMark) - { - Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); - if (boss && boss->IsAlive()) - targetToMark = boss; - } - - if (targetToMark) - { - const ObjectGuid currentSkull = group->GetTargetIcon(SKULL_ICON_INDEX); - Unit* currentSkullUnit = botAI->GetUnit(currentSkull); - - const bool needsUpdate = - !currentSkullUnit || !currentSkullUnit->IsAlive() || currentSkullUnit != targetToMark; - - if (needsUpdate) - group->SetTargetIcon(SKULL_ICON_INDEX, bot->GetGUID(), targetToMark->GetGUID()); - } - } - - context->GetValue("rti")->Set("skull"); - if (botAI->IsRanged(bot)) - { - const float TOLERANCE = 9.0f; - const float MAX_STEP = 5.0f; - - float distToTarget = bot->GetExactDist2d(ICC_SINDRAGOSA_RANGED_POSITION); - - // Only move if outside tolerance - if (distToTarget > TOLERANCE) - return MoveIncrementallyToPosition(ICC_SINDRAGOSA_RANGED_POSITION, MAX_STEP); - - return false; - } - else - { - const float TOLERANCE = 10.0f; - const float MAX_STEP = 5.0f; - - float distToTarget = bot->GetExactDist2d(ICC_SINDRAGOSA_MELEE_POSITION); - - // Only move if outside tolerance - if (distToTarget > TOLERANCE) - return MoveIncrementallyToPosition(ICC_SINDRAGOSA_MELEE_POSITION, MAX_STEP); - - return false; - } -} - -bool IccSindragosaGroupPositionAction::MoveIncrementallyToPosition(const Position& targetPos, float maxStep) -{ - // Calculate direction vector to target - float dirX = targetPos.GetPositionX() - bot->GetPositionX(); - float dirY = targetPos.GetPositionY() - bot->GetPositionY(); - - // Normalize direction vector - float length = sqrt(dirX * dirX + dirY * dirY); - dirX /= length; - dirY /= length; - - // Calculate intermediate point - float distToTarget = bot->GetExactDist2d(targetPos); - float stepSize = std::min(maxStep, distToTarget); - float moveX = bot->GetPositionX() + dirX * stepSize; - float moveY = bot->GetPositionY() + dirY * stepSize; - - return MoveTo(bot->GetMapId(), moveX, moveY, targetPos.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); -} - -bool IccSindragosaTankSwapPositionAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); - if (!boss) - return false; - - // Only for assist tank - if (!botAI->IsAssistTank(bot)) - return false; - - float distToTankPos = bot->GetExactDist2d(ICC_SINDRAGOSA_TANK_POSITION); - - // Move to tank position - if (distToTankPos > 3.0f) // Tighter tolerance for tank swap - { - return MoveTo(bot->GetMapId(), ICC_SINDRAGOSA_TANK_POSITION.GetPositionX(), - ICC_SINDRAGOSA_TANK_POSITION.GetPositionY(), - ICC_SINDRAGOSA_TANK_POSITION.GetPositionZ(), - false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); - } - - return false; -} - -bool IccSindragosaFrostBeaconAction::Execute(Event /*event*/) -{ - const Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); - if (!boss) - return false; - - HandleSupportActions(); - - if (bot->HasAura(FROST_BEACON_AURA_ID)) - { - return HandleBeaconedPlayer(boss); - } - - return HandleNonBeaconedPlayer(boss); -} - -void IccSindragosaFrostBeaconAction::HandleSupportActions() -{ - Group* group = bot->GetGroup(); - - // Tank support - Paladin Hand of Freedom - if (group && bot->getClass() == CLASS_PALADIN) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || !botAI->IsTank(member)) - { - continue; - } - - if (botAI->GetAura("Frost Breath", member) && !member->HasAura(HAND_OF_FREEDOM_SPELL_ID)) - { - botAI->CastSpell(HAND_OF_FREEDOM_SPELL_ID, member); - break; - } - } - } - - // Healer support - Apply HoTs to beaconed players - if (botAI->IsHeal(bot) && !bot->HasAura(FROST_BEACON_AURA_ID)) - { - const auto members = AI_VALUE(GuidVector, "group members"); - for (auto const& memberGuid : members) - { - Unit* member = botAI->GetUnit(memberGuid); - if (!member || !member->IsAlive() || !member->HasAura(FROST_BEACON_AURA_ID)) - { - continue; - } - - // Apply class-specific HoT spells - uint32 spellId = 0; - switch (bot->getClass()) - { - case CLASS_PRIEST: - spellId = 48068; - break; // Renew - case CLASS_SHAMAN: - spellId = 61301; - break; // Riptide - case CLASS_DRUID: - spellId = 48441; - break; // Rejuvenation - default: - continue; - } - - if (!member->HasAura(spellId)) - { - botAI->CastSpell(spellId, member); - } - } - } -} - -bool IccSindragosaFrostBeaconAction::HandleBeaconedPlayer(const Unit* boss) -{ - // Phase 3 positioning (below 35% health, not flying) - if (boss->HealthBelowPct(35) && !IsBossFlying(boss)) - { - if (!bot->HasAura(SPELL_NITRO_BOOSTS)) - bot->AddAura(SPELL_NITRO_BOOSTS, bot); - botAI->Reset(); - return MoveToPositionIfNeeded(ICC_SINDRAGOSA_THOMBMB2_POSITION, POSITION_TOLERANCE); - } - - // Regular beacon positioning using tomb spots - Group* group = bot->GetGroup(); - if (!group) - { - return false; - } - - // Collect and sort beaconed players by GUID for deterministic assignment - std::vector beaconedPlayers; - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (member && member->IsAlive() && member->HasAura(FROST_BEACON_AURA_ID)) - { - beaconedPlayers.push_back(member); - } - } - - std::sort(beaconedPlayers.begin(), beaconedPlayers.end(), - [](const Player* a, const Player* b) { return a->GetGUID() < b->GetGUID(); }); - - // Find this bot's index - const auto it = std::find(beaconedPlayers.begin(), beaconedPlayers.end(), bot); - if (it == beaconedPlayers.end()) - { - return false; - } - - const size_t myIndex = std::distance(beaconedPlayers.begin(), it); - const size_t beaconCount = beaconedPlayers.size(); - - // Calculate tomb spot based on beacon count - size_t spot = 0; - switch (beaconCount) - { - case 2: - spot = (myIndex == 0) ? 0 : 2; - break; - case 5: - spot = (myIndex < 2) ? 0 : ((myIndex == 2) ? 1 : 2); - break; - case 6: - spot = myIndex / 2; - break; - default: - spot = myIndex % 3; - break; - } - - // Get tomb position and move if needed - static constexpr std::array tombPositions = { - &ICC_SINDRAGOSA_THOMB1_POSITION, &ICC_SINDRAGOSA_THOMB2_POSITION, &ICC_SINDRAGOSA_THOMB3_POSITION}; - - const Position& tombPosition = *tombPositions[std::min(spot, tombPositions.size() - 1)]; - return MoveToPositionIfNeeded(tombPosition, TOMB_POSITION_TOLERANCE); -} - -bool IccSindragosaFrostBeaconAction::HandleNonBeaconedPlayer(const Unit* boss) -{ - // Collect beaconed players - std::vector beaconedPlayers; - const auto members = AI_VALUE(GuidVector, "group members"); - for (auto const& memberGuid : members) - { - Unit* player = botAI->GetUnit(memberGuid); - if (player && player->GetGUID() != bot->GetGUID() && player->HasAura(FROST_BEACON_AURA_ID)) - { - beaconedPlayers.push_back(player); - } - } - - if (beaconedPlayers.empty()) - { - return false; - } - - // Air phase positioning - if (IsBossFlying(boss)) - { - if (!bot->HasAura(FROST_BEACON_AURA_ID)) - { - const Difficulty diff = bot->GetRaidDifficulty(); - bool is25Man = false; - if (diff && (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - is25Man = true; - - const Position& safePosition = is25Man ? ICC_SINDRAGOSA_FBOMB_POSITION : ICC_SINDRAGOSA_FBOMB10_POSITION; - - const float dist = bot->GetExactDist2d(safePosition.GetPositionX(), safePosition.GetPositionY()); - if (dist > MOVE_TOLERANCE) - { - return MoveToPosition(safePosition); - } - } - return botAI->IsHeal(bot); // Continue for healers, wait for others - } - - // Ground phase - position based on role and avoid beaconed players - const bool isRanged = botAI->IsRanged(bot) || (bot->GetExactDist2d(ICC_SINDRAGOSA_RANGED_POSITION.GetPositionX(),ICC_SINDRAGOSA_RANGED_POSITION.GetPositionY()) < - bot->GetExactDist2d(ICC_SINDRAGOSA_MELEE_POSITION.GetPositionX(),ICC_SINDRAGOSA_MELEE_POSITION.GetPositionY())); - - const Position& targetPosition = isRanged ? ICC_SINDRAGOSA_RANGED_POSITION : ICC_SINDRAGOSA_MELEE_POSITION; - - const float deltaX = std::abs(targetPosition.GetPositionX() - bot->GetPositionX()); - const float deltaY = std::abs(targetPosition.GetPositionY() - bot->GetPositionY()); - if (boss && boss->GetVictim() != bot) - { - if ((deltaX > MOVE_TOLERANCE) || (deltaY > MOVE_TOLERANCE)) - { - if (bot->HasUnitState(UNIT_STATE_CASTING)) - { - botAI->Reset(); - } - return MoveToPosition(targetPosition); - } - } - return false; -} - -bool IccSindragosaFrostBeaconAction::MoveToPositionIfNeeded(const Position& position, float tolerance) -{ - const float distance = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distance > tolerance) - { - return MoveToPosition(position); - } - return distance <= tolerance; -} - -bool IccSindragosaFrostBeaconAction::MoveToPosition(const Position& position) -{ - float posX = position.GetPositionX(); - float posY = position.GetPositionY(); - float posZ = position.GetPositionZ(); - - bot->UpdateAllowedPositionZ(posX, posY, posZ); - - return MoveTo(bot->GetMapId(), posX, posY, posZ, false, false, false, false, MovementPriority::MOVEMENT_FORCED, - true, false); -} - -bool IccSindragosaFrostBeaconAction::IsBossFlying(const Unit* boss) -{ - return boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), - ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f; -} - -bool IccSindragosaBlisteringColdAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); - if (!boss) - return false; - - // Only non-tanks should move out - if (botAI->IsMainTank(bot)) - return false; - - float dist = bot->GetExactDist2d(boss->GetPositionX(), boss->GetPositionY()); - - if (dist >= 33.0f) - return false; - - Position const& targetPos = ICC_SINDRAGOSA_BLISTERING_COLD_POSITION; - - // Only move if we're too close to the boss (< 30 yards) - if (dist < 33.0f) - { - - float const STEP_SIZE = 15.0f; - float distToTarget = bot->GetDistance2d(targetPos.GetPositionX(), targetPos.GetPositionY()); - - if (distToTarget > 0.1f) // Avoid division by zero - { - if (!bot->HasAura(SPELL_NITRO_BOOSTS)) - bot->AddAura(SPELL_NITRO_BOOSTS, bot); - // Calculate direction vector - float dirX = targetPos.GetPositionX() - bot->GetPositionX(); - float dirY = targetPos.GetPositionY() - bot->GetPositionY(); - - // Normalize direction vector - float length = sqrt(dirX * dirX + dirY * dirY); - dirX /= length; - dirY /= length; - - // Move STEP_SIZE yards in that direction - float moveX = bot->GetPositionX() + dirX * STEP_SIZE; - float moveY = bot->GetPositionY() + dirY * STEP_SIZE; - - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); - } - } - return false; -} - -bool IccSindragosaUnchainedMagicAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); - if (!boss) - return false; - - Aura* aura = botAI->GetAura("Unchained Magic", bot, false, true); - if (!aura) - return false; - - Aura* aura1 = botAI->GetAura("Instability", bot, false, true); - - Difficulty diff = bot->GetRaidDifficulty(); - if (aura && (diff == RAID_DIFFICULTY_10MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_NORMAL)) - { - if (aura1 && aura1->GetStackAmount() >= 6) - return true; // Stop casting spells - } - - return false; -} - -bool IccSindragosaChilledToTheBoneAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); - if (!boss) - return false; - - Aura* aura = botAI->GetAura("Chilled to the Bone", bot, false, true); - if (!aura) - return false; - - if (aura) // Chilled to the Bone - { - if (aura->GetStackAmount() >= 6) - { - botAI->Reset(); - bot->AttackStop(); - return true; - } - } - - return false; -} - -bool IccSindragosaMysticBuffetAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); - if (!boss || !bot || !bot->IsAlive()) - return false; - - // Check if we have Mystic Buffet - Aura* aura = botAI->GetAura("mystic buffet", bot, false, true); - if (!aura) - return false; - - if (boss->GetVictim() == bot) - return false; - - // Skip if we have Frost Beacon - if (bot->HasAura(SPELL_FROST_BEACON)) - return false; - - Group* group = bot->GetGroup(); - if (!group) - return false; - - static const std::array tombEntries = {NPC_TOMB1, NPC_TOMB2, NPC_TOMB3, NPC_TOMB4}; - const GuidVector tombGuids = AI_VALUE(GuidVector, "possible targets no los"); - - Unit* nearestTomb = nullptr; - float minDist = 150.0f; - - for (const auto entry : tombEntries) - { - for (auto const& guid : tombGuids) - { - if (Unit* unit = botAI->GetUnit(guid)) - { - if (unit->GetEntry() == entry && unit->IsAlive()) - { - float dist = bot->GetDistance(unit); - if (dist < minDist) - { - minDist = dist; - nearestTomb = unit; - } - } - } - } - } - - // Check if anyone in group has Frost Beacon (SPELL_FROST_BEACON) - bool anyoneHasFrostBeacon = false; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON)) - { - anyoneHasFrostBeacon = true; - break; - } - } - - bool tombPresent = nearestTomb != nullptr; - bool atLOS2 = bot->GetExactDist2d(ICC_SINDRAGOSA_LOS2_POSITION.GetPositionX(), - ICC_SINDRAGOSA_LOS2_POSITION.GetPositionY()) <= 2.0f; - - // Move to LOS2 position if: tomb is present and no one has Frost Beacon - bool shouldMoveLOS2 = tombPresent && !anyoneHasFrostBeacon; - - if (shouldMoveLOS2) - { - // If already at LOS2 and have 3+ stacks, stay still - if (atLOS2 && aura && !botAI->IsHeal(bot)) - { - return true; - } - - botAI->Reset(); - // Move to LOS2 position - return MoveTo(bot->GetMapId(), ICC_SINDRAGOSA_LOS2_POSITION.GetPositionX(), - ICC_SINDRAGOSA_LOS2_POSITION.GetPositionY(), ICC_SINDRAGOSA_LOS2_POSITION.GetPositionZ(), false, - false, false, true, MovementPriority::MOVEMENT_FORCED); - } - return false; -} - -bool IccSindragosaFrostBombAction::Execute(Event /*event*/) -{ - if (!bot || !bot->IsAlive() || bot->HasAura(SPELL_ICE_TOMB)) // Skip if dead or in Ice Tomb - return false; - - Group* group = bot->GetGroup(); - if (!group) - return false; - - // Find frost bomb marker and tombs - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - const uint32 tombEntries[] = {NPC_TOMB1, NPC_TOMB2, NPC_TOMB3, NPC_TOMB4}; // tomb id's - Unit* marker = nullptr; - std::vector tombs; - std::vector tombGuids; - - // Manually search for units with frost bomb aura (SPELL_FROST_BOMB_VISUAL) using NearestHostileNpcsValue logic - std::list units; - float range = 200.0f; - Acore::AnyUnitInObjectRangeCheck u_check(bot, range); - Acore::UnitListSearcher searcher(bot, units, u_check); - Cell::VisitObjects(bot, searcher, range); - - for (Unit* unit : units) - { - if (!unit || !unit->IsAlive()) - continue; - - if (unit->HasAura(SPELL_FROST_BOMB_VISUAL)) // Frost bomb visual - marker = unit; - - // Check if unit is a tomb - for (uint32 entry : tombEntries) - { - if (unit->GetEntry() == entry) - { - tombs.push_back(unit); - tombGuids.push_back(unit->GetGUID()); - break; - } - } - } - - if (!marker || tombs.empty()) - { - bot->AttackStop(); - return true; - } - - // Get persistent group assignment - use a static map to store assignments - static std::map persistentGroupAssignments; - static std::vector allGroupGuids; // All guids that have ever been in the raid - - // Gather all group members (alive and dead, including those with ice tomb) - std::vector currentGuids; - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (member) - currentGuids.push_back(member->GetGUID()); - } - - // Add any new GUIDs to our persistent list - for (const ObjectGuid& guid : currentGuids) - { - if (std::find(allGroupGuids.begin(), allGroupGuids.end(), guid) == allGroupGuids.end()) - { - allGroupGuids.push_back(guid); - } - } - - // Sort the complete guid list for consistency - std::sort(allGroupGuids.begin(), allGroupGuids.end()); - - Difficulty diff = bot->GetRaidDifficulty(); - // Determine group count (2 for 10m, 3 for 25m) - int groupCount = (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC) ? 3 : 2; - - // Assign group indices to GUIDs that don't have assignments yet - for (size_t i = 0; i < allGroupGuids.size(); ++i) - { - const ObjectGuid& guid = allGroupGuids[i]; - if (persistentGroupAssignments.find(guid) == persistentGroupAssignments.end()) - { - // Assign to group based on their position in the sorted list - persistentGroupAssignments[guid] = int(i) % groupCount; - } - } - - // Get this bot's group assignment - auto it = persistentGroupAssignments.find(bot->GetGUID()); - if (it == persistentGroupAssignments.end()) - return false; - - int myGroupIndex = it->second; - - // Build group positions based on available tombs - std::vector groupPositions; - for (int i = 0; i < groupCount; ++i) - { - if (i < int(tombs.size())) - { - groupPositions.push_back(tombs[i]->GetPosition()); - } - else - { - groupPositions.push_back(marker->GetPosition()); - } - } - - // PRIORITY 1: Check if there are any tombs near our current position (within 8 yards) - std::vector nearbyTombs; - for (Unit* tomb : tombs) - { - if (tomb->GetExactDist2d(bot) <= 8.0f) - { - nearbyTombs.push_back(tomb); - } - } - - // PRIORITY 2: If no tombs nearby, find tombs near our assigned group position - std::vector groupPositionTombs; - if (nearbyTombs.empty()) - { - for (Unit* tomb : tombs) - { - if (tomb->GetExactDist2d(groupPositions[myGroupIndex]) <= 8.0f) - { - groupPositionTombs.push_back(tomb); - } - } - } - - // Select which tombs to use based on priority - std::vector myTombs; - std::vector myTombGuids; - - if (!nearbyTombs.empty()) - { - // Use tombs near current position (highest priority) - myTombs = nearbyTombs; - for (Unit* tomb : nearbyTombs) - { - myTombGuids.push_back(tomb->GetGUID()); - } - } - else if (!groupPositionTombs.empty()) - { - // Use tombs near group position (medium priority) - myTombs = groupPositionTombs; - for (Unit* tomb : groupPositionTombs) - { - myTombGuids.push_back(tomb->GetGUID()); - } - } - else - { - // Fallback: use closest available tomb (lowest priority) - Unit* closestTomb = nullptr; - float closestDist = 999.0f; - for (Unit* tomb : tombs) - { - float dist = tomb->GetExactDist2d(bot); - if (dist < closestDist) - { - closestDist = dist; - closestTomb = tomb; - } - } - if (closestTomb) - { - myTombs.push_back(closestTomb); - myTombGuids.push_back(closestTomb->GetGUID()); - } - } - - if (myTombs.empty()) - return false; - - // Pick the tomb with highest HP in our selection - size_t bestIdx = 0; - float bestHp = 0.0f; - for (size_t i = 0; i < myTombs.size(); ++i) - { - float hp = myTombs[i]->GetHealthPct(); - if (i == 0 || hp > bestHp) - { - bestHp = hp; - bestIdx = i; - } - } - Unit* losTomb = myTombs[bestIdx]; - - // Calculate position for LOS (stand at least 6.5f behind the tomb from the bomb) - float angle = marker->GetAngle(losTomb); - float posX = losTomb->GetPositionX() + cos(angle) * 6.5f; - float posY = losTomb->GetPositionY() + sin(angle) * 6.5f; - float posZ = losTomb->GetPositionZ(); - - // Always move to exact LOS position for safety - float distToLosPos = bot->GetDistance2d(posX, posY); - if (distToLosPos > 0.01f) - { - botAI->Reset(); - bot->AttackStop(); - return MoveTo(bot->GetMapId(), posX, posY, posZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); - } - - // Check if we are in LOS of the bomb (must be very close to calculated position) - bool inLOS = (distToLosPos <= 0.01f); - - // RTI marker constants - static constexpr uint8_t SKULL_ICON_INDEX = 7; - static constexpr uint8_t CROSS_ICON_INDEX = 6; - static constexpr uint8_t STAR_ICON_INDEX = 0; - - // If in LOS, handle RTI marking for group's tombs - if (inLOS) - { - // Determine RTI marker for this group - uint8_t iconIndex = 0; - std::string rtiValue; - if (myGroupIndex == 0) - { - iconIndex = SKULL_ICON_INDEX; - rtiValue = "skull"; - } - else if (myGroupIndex == 1) - { - iconIndex = CROSS_ICON_INDEX; - rtiValue = "cross"; - } - else if (myGroupIndex == 2) - { - iconIndex = STAR_ICON_INDEX; - rtiValue = "star"; - } - else - return false; - - context->GetValue("rti")->Set(rtiValue); - - // Find a tomb in our group with 45% or more HP to mark - Unit* tombToMark = nullptr; - for (size_t i = 0; i < myTombs.size(); ++i) - { - Unit* tomb = myTombs[i]; - if (tomb->IsAlive() && tomb->HealthAbovePct(45)) - { - tombToMark = tomb; - break; - } - } - - if (tombToMark) - { - // Check if this tomb is already marked with our group's icon - ObjectGuid currentIcon = group->GetTargetIcon(iconIndex); - Unit* currentIconUnit = botAI->GetUnit(currentIcon); - if (!currentIconUnit || !currentIconUnit->IsAlive() || currentIconUnit != tombToMark) - { - // Mark the tomb with our group's target icon - group->SetTargetIcon(iconIndex, bot->GetGUID(), tombToMark->GetGUID()); - } - } - else - { - // No tombs above 45% HP, remove marker if one exists - ObjectGuid currentIcon = group->GetTargetIcon(iconIndex); - if (!currentIcon.IsEmpty()) - { - // Clear the marker for our group's icon - group->SetTargetIcon(iconIndex, bot->GetGUID(), ObjectGuid::Empty); - } - bot->AttackStop(); - return true; - } - } - - return false; -} - -// The Lich King -bool IccLichKingShadowTrapAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - if (!boss || !botAI->IsTank(bot)) - return false; - - Difficulty diff = bot->GetRaidDifficulty(); - - if (sPlayerbotAIConfig.EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - { - //-------CHEAT------- - if (!bot->HasAura(SPELL_EXPERIENCED)) - bot->AddAura(SPELL_EXPERIENCED, bot); - - if (!bot->HasAura(SPELL_AGEIS_OF_DALARAN)) - bot->AddAura(SPELL_AGEIS_OF_DALARAN, bot); - - if (!bot->HasAura(SPELL_NO_THREAT) && !botAI->IsTank(bot)) - bot->AddAura(SPELL_NO_THREAT, bot); - - if (!bot->HasAura(SPELL_PAIN_SUPPRESION)) - bot->AddAura(SPELL_PAIN_SUPPRESION, bot); - //-------CHEAT------- - } - - // Define ICC_LICH_POSITION and circle parameters - const float X = ICC_LICH_KING_ASSISTHC_POSITION.GetPositionX(); - const float Y = ICC_LICH_KING_ASSISTHC_POSITION.GetPositionY(); - const float Z = ICC_LICH_KING_ASSISTHC_POSITION.GetPositionZ(); - const float CIRCLE_RADIUS = 20.0f; - const float SAFE_DISTANCE = 12.0f; - const int TEST_POSITIONS = 16; - const float ANGLE_STEP = 2 * M_PI / TEST_POSITIONS; - - // Find all nearby shadow traps - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - std::vector trapGuids; - for (auto& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || !unit->IsAlive() || unit->GetEntry() != NPC_SHADOW_TRAP) - continue; - if (bot->GetDistance(unit) < SAFE_DISTANCE + 5.0f) - { - trapGuids.push_back(npc); - } - } - - if (trapGuids.empty()) - return false; - - // Check if current position is already safe - bool currentPositionSafe = true; - for (auto& trapGuid : trapGuids) - { - Unit* trap = botAI->GetUnit(trapGuid); - if (!trap) - continue; - if (bot->GetDistance(trap) < SAFE_DISTANCE) - { - currentPositionSafe = false; - break; - } - } - - // If current position is safe, no need to move - if (currentPositionSafe) - return false; - - // Calculate current angle relative to ICC_LICH_POSITION - float currentX = bot->GetPositionX() - X; - float currentY = bot->GetPositionY() - Y; - float currentAngle = atan2(currentY, currentX); - - // Test clockwise positions first, then opposite position - std::vector testAngles; - // Add clockwise positions - for (int i = 1; i <= TEST_POSITIONS; ++i) - { - testAngles.push_back(currentAngle - (ANGLE_STEP * i)); - } - // Add opposite position as fallback - testAngles.push_back(currentAngle + M_PI); - - // Test all positions - for (float testAngle : testAngles) - { - // Calculate position on circle - float testX = X + cos(testAngle) * CIRCLE_RADIUS; - float testY = Y + sin(testAngle) * CIRCLE_RADIUS; - float testZ = Z; - - // Update Z coordinate for terrain - bot->UpdateAllowedPositionZ(testX, testY, testZ); - - // Check line of sight - if (!bot->IsWithinLOS(testX, testY, testZ)) - continue; - - // Check if this position is safe from all traps - bool isSafe = true; - for (auto& trapGuid : trapGuids) - { - Unit* trap = botAI->GetUnit(trapGuid); - if (!trap) - continue; - float distToTrap = trap->GetDistance2d(testX, testY); - if (distToTrap < SAFE_DISTANCE) - { - isSafe = false; - break; - } - } - - // Found a safe spot - move there - if (isSafe) - { - // Remove botAI->Reset() as it might interfere with movement - MoveTo(bot->GetMapId(), testX, testY, testZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, - true, false); - } - } - - // No safe position found - return false; -} - -bool IccLichKingNecroticPlagueAction::Execute(Event /*event*/) -{ - bool hasPlague = botAI->HasAura("Necrotic Plague", bot); - // Only execute if we have the plague - if (!hasPlague) - return false; - - // Find closest shambling horror - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - Unit* closestHorror = nullptr; - float minHorrorDist = 100.0f; - - for (auto& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || !unit->IsAlive()) - continue; - - uint32 entry = unit->GetEntry(); - if (entry == NPC_SHAMBLING_HORROR1 || entry == NPC_SHAMBLING_HORROR2 || - entry == NPC_SHAMBLING_HORROR3 || entry == NPC_SHAMBLING_HORROR4) - { - float distance = bot->GetDistance(unit); - if (distance < minHorrorDist) - { - minHorrorDist = distance; - closestHorror = unit; - } - } - } - - // If we found a shambling horror, handle movement - if (closestHorror) - { - // If we're close enough, stop and return success - if (minHorrorDist <= 2.0f) - { - bot->StopMoving(); - return true; - } - - // We need to move to the horror - botAI->Reset(); - MoveTo(closestHorror, 2.0f, MovementPriority::MOVEMENT_FORCED); - - return false; // Still moving, not finished yet - } - - // No shambling horror found, but we have plague - this shouldn't happen normally - return false; -} - -bool IccLichKingWinterAction::Execute(Event /*event*/) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - if (!boss) - return false; - - Unit* iceSphere = AI_VALUE2(Unit*, "find target", "ice sphere"); - - bool isVictim = false; - if (iceSphere && iceSphere->GetVictim() == bot && !botAI->IsTank(bot)) - isVictim = true; - - // First priority: Get out of Defile if we're in one - if (!IsPositionSafeFromDefile(bot->GetPositionX(), bot->GetPositionY(), 3.0f)) - { - // Find nearest safe position (use tank position as fallback) - const Position* safePos = botAI->IsTank(bot) ? GetMainTankPosition() : GetMainTankRangedPosition(); - TryMoveToPosition(safePos->GetPositionX(), safePos->GetPositionY(), 840.857f, true); - return true; - } - - float currentDistance = bot->GetDistance2d(boss); - - Difficulty diff = bot->GetRaidDifficulty(); - - if (sPlayerbotAIConfig.EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - { - //------CHEAT------- - if (!bot->HasAura(SPELL_EXPERIENCED)) - bot->AddAura(SPELL_EXPERIENCED, bot); - - if (!bot->HasAura(SPELL_AGEIS_OF_DALARAN)) - bot->AddAura(SPELL_AGEIS_OF_DALARAN, bot); - - if (!bot->HasAura(SPELL_NO_THREAT) && !botAI->IsTank(bot)) - bot->AddAura(SPELL_NO_THREAT, bot); - - if (!bot->HasAura(SPELL_PAIN_SUPPRESION)) - bot->AddAura(SPELL_PAIN_SUPPRESION, bot); - //------CHEAT------- - } - - if (currentDistance < 35.0f && !bot->HasAura(SPELL_NITRO_BOOSTS)) - bot->AddAura(SPELL_NITRO_BOOSTS, bot); - - // Handle group target management - if (Group* group = bot->GetGroup()) - { - const ObjectGuid currentSkullTarget = group->GetTargetIcon(7); - if (!currentSkullTarget.IsEmpty()) - group->SetTargetIcon(7, bot->GetGUID(), ObjectGuid::Empty); - } - - if (isVictim) - MoveFromGroup(6.0f); - - if (!isVictim) - { - HandlePositionCorrection(); - - // Handle tank positioning and add management FIRST - HandleTankPositioning(); // New method that handles both main and assist tanks - - // Then handle other roles - HandleMeleePositioning(); - HandleRangedPositioning(); - } - - return false; -} - -void IccLichKingWinterAction::HandlePositionCorrection() -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - Unit* currentTarget = AI_VALUE(Unit*, "current target"); - - // Fix underground bug - if (abs(bot->GetPositionZ() - 840.857f) > 1.0f) - bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), 840.857f, bot->GetOrientation()); - - // Reset targeting for specific conditions - if (currentTarget && boss && currentTarget == boss) - botAI->Reset(); - - if (botAI->IsTank(bot) && currentTarget && - ((currentTarget->GetEntry() == NPC_ICE_SPHERE1 || currentTarget->GetEntry() == NPC_ICE_SPHERE2 || - currentTarget->GetEntry() == NPC_ICE_SPHERE3 || currentTarget->GetEntry() == NPC_ICE_SPHERE4))) - botAI->Reset(); -} - -const Position* IccLichKingWinterAction::GetMainTankPosition() -{ - Unit* mainTank = AI_VALUE(Unit*, "main tank"); - if (!mainTank) - { - // FIXED: When no main tank, use the bot with lowest GUID to determine position - // This ensures ALL bots make the same decision collectively - - Unit* referenceBot = nullptr; - ObjectGuid lowestGuid; - - // Find the bot with lowest GUID in the group - Group* group = bot->GetGroup(); - if (group) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (member && member->IsAlive() && member->IsInWorld()) - { - if (lowestGuid.IsEmpty() || member->GetGUID() < lowestGuid) - { - lowestGuid = member->GetGUID(); - referenceBot = member; - } - } - } - } - - // If no group or reference bot found, fall back to current bot - if (!referenceBot) - referenceBot = bot; - - // Use the reference bot's position to determine closest tank position - float dist1 = - referenceBot->GetDistance2d(ICC_LK_FROST1_POSITION.GetPositionX(), ICC_LK_FROST1_POSITION.GetPositionY()); - float dist2 = - referenceBot->GetDistance2d(ICC_LK_FROST2_POSITION.GetPositionX(), ICC_LK_FROST2_POSITION.GetPositionY()); - float dist3 = - referenceBot->GetDistance2d(ICC_LK_FROST3_POSITION.GetPositionX(), ICC_LK_FROST3_POSITION.GetPositionY()); - - if (dist2 < dist1 && dist2 < dist3) - return &ICC_LK_FROST2_POSITION; - else if (dist3 < dist1 && dist3 < dist2) - return &ICC_LK_FROST3_POSITION; - else - return &ICC_LK_FROST1_POSITION; - } - - // Calculate which position the main tank is closest to - float dist1 = mainTank->GetDistance2d(ICC_LK_FROST1_POSITION.GetPositionX(), ICC_LK_FROST1_POSITION.GetPositionY()); - float dist2 = mainTank->GetDistance2d(ICC_LK_FROST2_POSITION.GetPositionX(), ICC_LK_FROST2_POSITION.GetPositionY()); - float dist3 = mainTank->GetDistance2d(ICC_LK_FROST3_POSITION.GetPositionX(), ICC_LK_FROST3_POSITION.GetPositionY()); - - if (dist2 < dist1 && dist2 < dist3) - return &ICC_LK_FROST2_POSITION; - else if (dist3 < dist1 && dist3 < dist2) - return &ICC_LK_FROST3_POSITION; - else - return &ICC_LK_FROST1_POSITION; -} - -const Position* IccLichKingWinterAction::GetMainTankRangedPosition() -{ - Unit* mainTank = AI_VALUE(Unit*, "main tank"); - if (!mainTank) - { - // FIXED: When no main tank, use the bot with lowest GUID to determine position - // This ensures ALL bots make the same decision collectively - - Unit* referenceBot = nullptr; - ObjectGuid lowestGuid; - - // Find the bot with lowest GUID in the group - Group* group = bot->GetGroup(); - if (group) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (member && member->IsAlive() && member->IsInWorld()) - { - if (lowestGuid.IsEmpty() || member->GetGUID() < lowestGuid) - { - lowestGuid = member->GetGUID(); - referenceBot = member; - } - } - } - } - - // If no group or reference bot found, fall back to current bot - if (!referenceBot) - referenceBot = bot; - - // Use the reference bot's position to determine closest ranged position - float dist1 = - referenceBot->GetDistance2d(ICC_LK_FROSTR1_POSITION.GetPositionX(), ICC_LK_FROSTR1_POSITION.GetPositionY()); - float dist2 = - referenceBot->GetDistance2d(ICC_LK_FROSTR2_POSITION.GetPositionX(), ICC_LK_FROSTR2_POSITION.GetPositionY()); - float dist3 = - referenceBot->GetDistance2d(ICC_LK_FROSTR3_POSITION.GetPositionX(), ICC_LK_FROSTR3_POSITION.GetPositionY()); - - if (dist2 < dist1 && dist2 < dist3) - return &ICC_LK_FROSTR2_POSITION; - else if (dist3 < dist1 && dist3 < dist2) - return &ICC_LK_FROSTR3_POSITION; - else - return &ICC_LK_FROSTR1_POSITION; - } - - // Map main tank's melee position to corresponding ranged position - const Position* tankMeleePos = GetMainTankPosition(); - - if (tankMeleePos == &ICC_LK_FROST1_POSITION) - return &ICC_LK_FROSTR1_POSITION; - else if (tankMeleePos == &ICC_LK_FROST2_POSITION) - return &ICC_LK_FROSTR2_POSITION; - else - return &ICC_LK_FROSTR3_POSITION; -} - -bool IccLichKingWinterAction::IsPositionSafeFromDefile(float x, float y, float minSafeDistance) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - if (!boss) - return true; // No boss, assume safe - - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - const float BASE_RADIUS = 6.0f; - const float SAFETY_MARGIN = 3.0f; - - for (auto& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (unit && unit->IsAlive() && unit->GetEntry() == DEFILE_NPC_ID) - { - // Calculate current defile radius including growth - float currentRadius = BASE_RADIUS; - Aura* growAura = nullptr; - - // Find growth aura (you'll need to define DEFILE_AURAS array) - for (size_t i = 0; i < DEFILE_AURA_COUNT; i++) - { - growAura = unit->GetAura(DEFILE_AURAS[i]); - if (growAura) - break; - } - - if (growAura) - { - uint8 stacks = growAura->GetStackAmount(); - float growthMultiplier = (bot->GetRaidDifficulty() == RAID_DIFFICULTY_10MAN_HEROIC || - bot->GetRaidDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL) - ? 1.4f - : 0.95f; - currentRadius = BASE_RADIUS + (stacks * growthMultiplier); - } - - float dx = x - unit->GetPositionX(); - float dy = y - unit->GetPositionY(); - float distance = sqrt(dx * dx + dy * dy); - - if (distance < (currentRadius + SAFETY_MARGIN + minSafeDistance)) - return false; - } - } - return true; -} - -bool IccLichKingWinterAction::TryMoveToPosition(float targetX, float targetY, float targetZ, bool isForced) -{ - float currentX = bot->GetPositionX(); - float currentY = bot->GetPositionY(); - float currentZ = bot->GetPositionZ(); - - float dx = targetX - currentX; - float dy = targetY - currentY; - float dz = targetZ - currentZ; - float distance = sqrtf(dx * dx + dy * dy + dz * dz); - - if (distance < 0.1f) - return true; // Already at the position - - dx /= distance; - dy /= distance; - - // First check if direct path is safe - if (bot->IsWithinLOS(targetX, targetY, targetZ) && IsPositionSafeFromDefile(targetX, targetY, 3.0f)) - { - if (isForced) - botAI->Reset(); - - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, - true, false); - return true; - } - - // If direct path isn't safe, try to find a safe path around defiles - const int MAX_ATTEMPTS = 8; - const float ANGLE_STEP = M_PI / 4.0f; - float attemptDistance = std::min(10.0f, distance); - - for (int i = 0; i < MAX_ATTEMPTS; i++) - { - float angle = i * ANGLE_STEP; - float offsetX = attemptDistance * cos(angle); - float offsetY = attemptDistance * sin(angle); - - // Try positions clockwise and counter-clockwise - for (int direction = -1; direction <= 1; direction += 2) - { - if (i == 0 && direction == 1) - continue; // Skip duplicate first attempt - - float testX = currentX + dx * attemptDistance + offsetX * direction; - float testY = currentY + dy * attemptDistance + offsetY * direction; - float testZ = targetZ; - - if (bot->IsWithinLOS(testX, testY, testZ) && IsPositionSafeFromDefile(testX, testY, 3.0f)) - { - if (isForced) - botAI->Reset(); - - MoveTo(bot->GetMapId(), testX, testY, testZ, false, false, false, true, - MovementPriority::MOVEMENT_FORCED, true, false); - return false; // Not at final position yet - } - } - } - - // If no safe path found, just move directly (better than standing in defile) - if (isForced) - botAI->Reset(); - - MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, - true, false); - return false; -} - -// Helper function to check if a unit is a valid collectible add -bool IccLichKingWinterAction::IsValidCollectibleAdd(Unit* unit) -{ - if (!unit || !unit->IsAlive()) - return false; - - uint32 entry = unit->GetEntry(); - - // Only spirits, shambling horrors, and ghouls are valid collectible adds - return (entry == NPC_SHAMBLING_HORROR1 || entry == NPC_SHAMBLING_HORROR2 || entry == NPC_SHAMBLING_HORROR3 || - entry == NPC_SHAMBLING_HORROR4 || entry == NPC_RAGING_SPIRIT1 || entry == NPC_RAGING_SPIRIT2 || - entry == NPC_RAGING_SPIRIT3 || entry == NPC_RAGING_SPIRIT4 || entry == NPC_DRUDGE_GHOUL1 || - entry == NPC_DRUDGE_GHOUL2 || entry == NPC_DRUDGE_GHOUL3 || entry == NPC_DRUDGE_GHOUL4); -} - -// FIXED HandleTankPositioning method -void IccLichKingWinterAction::HandleTankPositioning() -{ - if (!botAI->IsTank(bot)) - return; - - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - if (!boss) - return; - - // Get the target position based on main tank's choice - const Position* targetPos = GetMainTankPosition(); - - // First check if current position is safe - if (!IsPositionSafeFromDefile(bot->GetPositionX(), bot->GetPositionY(), 3.0f)) - { - // If in defile, prioritize getting out - TryMoveToPosition(targetPos->GetPositionX(), targetPos->GetPositionY(), 840.857f, true); - return; - } - - float distToTarget = bot->GetDistance2d(targetPos->GetPositionX(), targetPos->GetPositionY()); - - // MAIN TANK: Always stay at tank position - if (botAI->IsMainTank(bot)) - { - // Main tank should always maintain position at tank spot - if (distToTarget > 2.0f) - { - float targetX = targetPos->GetPositionX(); - float targetY = targetPos->GetPositionY(); - float targetZ = 840.857f; - - TryMoveToPosition(targetX, targetY, targetZ, true); - return; // Don't do add management until in position - } - - // Once in position, handle add management from tank position - HandleMainTankAddManagement(targetPos); - } - // ASSIST TANK: More flexible positioning based on add collection - else if (botAI->IsAssistTank(bot)) - { - // First ensure we're reasonably close to tank area - if (distToTarget > 15.0f) - { - float targetX = targetPos->GetPositionX() + 3.0f; // Slight offset from main tank - float targetY = targetPos->GetPositionY() + 2.0f; - float targetZ = 840.857f; - - TryMoveToPosition(targetX, targetY, targetZ, true); - return; - } - - // Handle assist tank add collection and positioning - HandleAssistTankAddManagement(targetPos); - } -} - -// Updated HandleMeleePositioning method - only for non-tanks -void IccLichKingWinterAction::HandleMeleePositioning() -{ - // Skip if this is a tank - they have their own positioning logic - if (botAI->IsTank(bot)) - return; - Unit* currentTarget = AI_VALUE(Unit*, "current target"); - // Handle melee positioning behind target (for DPS only) - if (currentTarget && !botAI->IsRanged(bot) && currentTarget->isInFront(bot) && currentTarget->IsAlive() && - currentTarget->GetEntry() != NPC_THE_LICH_KING && currentTarget->GetEntry() != NPC_ICE_SPHERE1 && - currentTarget->GetEntry() != NPC_ICE_SPHERE2 && currentTarget->GetEntry() != NPC_ICE_SPHERE3 && currentTarget->GetEntry() != NPC_ICE_SPHERE4) - { - // Calculate desired position (4.0f behind the target) - float orientation = currentTarget->GetOrientation() + M_PI + M_PI / 8; - float x = currentTarget->GetPositionX(); - float y = currentTarget->GetPositionY(); - float z = bot->GetPositionZ(); - float targetX = x + cos(orientation) * 4.0f; - float targetY = y + sin(orientation) * 4.0f; - Position botPos = bot->GetPosition(); - float dx = targetX - botPos.GetPositionX(); - float dy = targetY - botPos.GetPositionY(); - float distance = sqrt(dx * dx + dy * dy); - - if (distance <= 1.0f) - return; - - // Move in increments of 2 yards toward the target position - float moveDistance = std::min(2.0f, distance); - float normalizedDx = dx / distance; - float normalizedDy = dy / distance; - - float newX = botPos.GetPositionX() + normalizedDx * moveDistance; - float newY = botPos.GetPositionY() + normalizedDy * moveDistance; - - TryMoveToPosition(newX, newY, z, false); - } - // Handle non-ranged DPS positioning - USE MAIN TANK'S POSITION - if (!botAI->IsRanged(bot)) - { - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - const Position* targetPos = GetMainTankPosition(); - float distToTarget = bot->GetDistance2d(targetPos->GetPositionX(), targetPos->GetPositionY()); - if (distToTarget > 8.0f) - { - float targetX = targetPos->GetPositionX() - 5.0f; - float targetY = targetPos->GetPositionY() + 5.0f; - float targetZ = 840.857f; - if (boss && !boss->HealthAbovePct(50)) - { - targetX = targetPos->GetPositionX(); - targetY = targetPos->GetPositionY(); - targetZ = 840.857f; - } - - // Move in increments of 2 yards toward the tank position - Position botPos = bot->GetPosition(); - float dx = targetX - botPos.GetPositionX(); - float dy = targetY - botPos.GetPositionY(); - float distance = sqrt(dx * dx + dy * dy); - - if (distance > 2.0f) - { - float normalizedDx = dx / distance; - float normalizedDy = dy / distance; - - float newX = botPos.GetPositionX() + normalizedDx * 2.0f; - float newY = botPos.GetPositionY() + normalizedDy * 2.0f; - - TryMoveToPosition(newX, newY, targetZ); - } - else - { - TryMoveToPosition(targetX, targetY, targetZ); - } - } - } -} - -// Updated HandleRangedPositioning method -void IccLichKingWinterAction::HandleRangedPositioning() -{ - if (!botAI->IsRanged(bot)) - return; - - // Get the ranged position based on main tank's choice - const Position* targetPos = GetMainTankRangedPosition(); - - // First check if current position is safe - if (!IsPositionSafeFromDefile(bot->GetPositionX(), bot->GetPositionY(), 3.0f)) - { - // If in defile, prioritize getting out - TryMoveToPosition(targetPos->GetPositionX(), targetPos->GetPositionY(), 840.857f, true); - return; - } - - float distToTarget = bot->GetDistance2d(targetPos->GetPositionX(), targetPos->GetPositionY()); - - if (distToTarget > 2.0f) - { - float targetX = targetPos->GetPositionX(); - float targetY = targetPos->GetPositionY(); - float targetZ = 840.857f; - - TryMoveToPosition(targetX, targetY, targetZ); - } - - // Handle sphere targeting for ranged DPS - if (botAI->IsRangedDps(bot)) - { - bool hasHunter = false; - Group* group = bot->GetGroup(); - if (group) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (member && member->IsAlive() && member->IsInWorld() && member->getClass() == CLASS_HUNTER) - { - hasHunter = true; - break; - } - } - } - - if (bot->getClass() == CLASS_HUNTER || !hasHunter) - { - Unit* currentTarget = bot->GetVictim(); - if (currentTarget && currentTarget->IsAlive()) - { - uint32 entry = currentTarget->GetEntry(); - if (entry == NPC_ICE_SPHERE1 || entry == NPC_ICE_SPHERE2 || entry == NPC_ICE_SPHERE3 || entry == NPC_ICE_SPHERE4) - { - bot->SetFacingToObject(currentTarget); - Attack(currentTarget); - return; - } - } - - Unit* closestSphere = nullptr; - float closestDist = 100.0f; - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || !unit->IsAlive()) - continue; - uint32 entry = unit->GetEntry(); - if (entry == NPC_ICE_SPHERE1 || entry == NPC_ICE_SPHERE2 || entry == NPC_ICE_SPHERE3 || entry == NPC_ICE_SPHERE4) - { - float dist = bot->GetDistance(unit); - if (!closestSphere || dist < closestDist) - { - closestSphere = unit; - closestDist = dist; - } - } - } - if (closestSphere) - { - bot->SetTarget(closestSphere->GetGUID()); - bot->SetFacingToObject(closestSphere); - Attack(closestSphere); - } - } - } -} - -void IccLichKingWinterAction::HandleMainTankAddManagement(const Position* tankPos) -{ - if (!botAI->IsMainTank(bot)) - return; - - // First, ensure we're at the correct tank position - float distToTankPos = bot->GetDistance2d(tankPos->GetPositionX(), tankPos->GetPositionY()); - if (distToTankPos > 3.0f) - { - TryMoveToPosition(tankPos->GetPositionX(), tankPos->GetPositionY(), 840.857f, true); - return; // Wait until we're in position - } - - // Get all valid adds in the encounter area - GuidVector targets = AI_VALUE(GuidVector, "possible targets"); - std::vector validAdds; - Unit* currentTarget = bot->GetVictim(); - - // Collect all valid adds - for (auto i = targets.begin(); i != targets.end(); ++i) - { - Unit* unit = botAI->GetUnit(*i); - if (!IsValidCollectibleAdd(unit)) - continue; - - validAdds.push_back(unit); - } - - // If we have no adds, clear target if needed - if (validAdds.empty()) - { - if (currentTarget && !IsValidCollectibleAdd(currentTarget)) - { - bot->SetTarget(ObjectGuid::Empty); - } - return; - } - - // Strategy for add management: - // 1. First priority: Adds attacking non-tanks - // 2. Second priority: Adds not attacking us - // 3. Third priority: All other valid adds - Unit* priorityAdd = nullptr; - Unit* secondaryAdd = nullptr; - Unit* otherAdd = nullptr; - - for (Unit* add : validAdds) - { - Unit* addVictim = add->GetVictim(); - - // Highest priority: Adds attacking non-tanks - if (addVictim && addVictim->IsPlayer() && !botAI->IsTank(addVictim->ToPlayer())) - { - if (!priorityAdd || bot->GetDistance(add) < bot->GetDistance(priorityAdd)) - { - priorityAdd = add; - } - continue; - } - - // Medium priority: Adds not attacking us - if (addVictim != bot) - { - if (!secondaryAdd || bot->GetDistance(add) < bot->GetDistance(secondaryAdd)) - { - secondaryAdd = add; - } - continue; - } - - // Lowest priority: All other valid adds - if (!otherAdd || bot->GetDistance(add) < bot->GetDistance(otherAdd)) - { - otherAdd = add; - } - } - - // Select the highest priority add available - Unit* targetAdd = priorityAdd ? priorityAdd : (secondaryAdd ? secondaryAdd : otherAdd); - - if (targetAdd) - { - float addDist = bot->GetDistance(targetAdd); - - // If add is close enough (within melee range), attack it - if (addDist < 10.0f) - { - // If we're not already attacking this add, switch to it - if (currentTarget != targetAdd) - { - bot->SetTarget(targetAdd->GetGUID()); - bot->SetFacingToObject(targetAdd); - Attack(targetAdd); - } - } - else - { - // Add is too far - move toward it while staying near tank position - float moveX = targetAdd->GetPositionX(); - float moveY = targetAdd->GetPositionY(); - - // Don't move too far from tank position (max 15 yards) - float maxPullDistance = 15.0f; - float pullRatio = std::min(1.0f, maxPullDistance / addDist); - - float adjustedX = tankPos->GetPositionX() + (moveX - tankPos->GetPositionX()) * pullRatio; - float adjustedY = tankPos->GetPositionY() + (moveY - tankPos->GetPositionY()) * pullRatio; - - TryMoveToPosition(adjustedX, adjustedY, 840.857f, false); - - // Still try to attack while moving - bot->SetTarget(targetAdd->GetGUID()); - bot->SetFacingToObject(targetAdd); - Attack(targetAdd); - } - } - else if (currentTarget && !IsValidCollectibleAdd(currentTarget)) - { - // Clear invalid target - bot->SetTarget(ObjectGuid::Empty); - } -} - -void IccLichKingWinterAction::HandleAssistTankAddManagement(const Position* tankPos) -{ - if (!botAI->IsAssistTank(bot)) - return; - - Unit* mainTank = AI_VALUE(Unit*, "main tank"); - if (!mainTank) - return; - - // Look for priority adds that need to be collected - Unit* targetAdd = nullptr; - float closestDist = FLT_MAX; - bool foundPriorityAdd = false; - - GuidVector targets = AI_VALUE(GuidVector, "possible targets"); - - // Priority 1: Adds attacking non-tanks (players/healers) - for (auto i = targets.begin(); i != targets.end(); ++i) - { - Unit* unit = botAI->GetUnit(*i); - if (!IsValidCollectibleAdd(unit)) - continue; - - Unit* addVictim = unit->GetVictim(); - if (addVictim && addVictim->IsPlayer() && !botAI->IsTank(addVictim->ToPlayer())) - { - float addDist = bot->GetDistance(unit); - if (addDist < closestDist) - { - targetAdd = unit; - closestDist = addDist; - foundPriorityAdd = true; - } - } - } - - // Priority 2: Adds not attacking main tank (if no priority adds found) - if (!foundPriorityAdd) - { - closestDist = FLT_MAX; - for (auto i = targets.begin(); i != targets.end(); ++i) - { - Unit* unit = botAI->GetUnit(*i); - if (!IsValidCollectibleAdd(unit)) - continue; - - // Only target adds that are NOT attacking the main tank - if (unit->GetVictim() != mainTank) - { - float addDist = bot->GetDistance(unit); - if (addDist < closestDist) - { - targetAdd = unit; - closestDist = addDist; - } - } - } - } - - if (targetAdd) - { - // Calculate position between add and main tank - float pullX = (targetAdd->GetPositionX() + tankPos->GetPositionX()) / 2; - float pullY = (targetAdd->GetPositionY() + tankPos->GetPositionY()) / 2; - - // Move to intercept position if add is far from main tank - if (targetAdd->GetDistance2d(tankPos->GetPositionX(), tankPos->GetPositionY()) > 10.0f) - { - if (bot->GetDistance2d(pullX, pullY) > 3.0f) - { - TryMoveToPosition(pullX, pullY, 840.857f, false); - } - } - // Otherwise move toward the add - else if (closestDist > 5.0f) - { - TryMoveToPosition(targetAdd->GetPositionX(), targetAdd->GetPositionY(), 840.857f, false); - } - - // Attack the add - bot->SetTarget(targetAdd->GetGUID()); - bot->SetFacingToObject(targetAdd); - Attack(targetAdd); - } - else - { - // No adds to collect, position near main tank - float distToTankPos = bot->GetDistance2d(tankPos->GetPositionX(), tankPos->GetPositionY()); - if (distToTankPos > 2.0f) - { - float X = mainTank->GetPositionX(); - float Y = mainTank->GetPositionY(); - TryMoveToPosition(X, Y, 840.857f, false); - } - - // Check current target validity - Unit* currentTarget = bot->GetVictim(); - if (currentTarget && !IsValidCollectibleAdd(currentTarget)) - { - bot->SetTarget(ObjectGuid::Empty); - } - } -} - -bool IccLichKingAddsAction::Execute(Event /*event*/) -{ - if (bot->HasAura(SPELL_HARVEST_SOUL_VALKYR)) // Don't process actions if bot is picked up by Val'kyr - return false; - - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - - Difficulty diff = bot->GetRaidDifficulty(); - - if (sPlayerbotAIConfig.EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - { - //------CHEAT------- - if (!bot->HasAura(SPELL_EXPERIENCED)) - bot->AddAura(SPELL_EXPERIENCED, bot); - - if (!bot->HasAura(SPELL_AGEIS_OF_DALARAN)) - bot->AddAura(SPELL_AGEIS_OF_DALARAN, bot); - - if (boss && boss->HealthBelowPct(60) && boss->HealthAbovePct(40) && !bot->HasAura(SPELL_EMPOWERED_BLOOD)) - bot->AddAura(SPELL_EMPOWERED_BLOOD, bot); - - if (!bot->HasAura(SPELL_NO_THREAT) && !botAI->IsTank(bot)) - bot->AddAura(SPELL_NO_THREAT, bot); - - if (!bot->HasAura(SPELL_PAIN_SUPPRESION)) - bot->AddAura(SPELL_PAIN_SUPPRESION, bot); - //------CHEAT------- - } - - bool hasPlague = botAI->HasAura("Necrotic Plague", bot); - Unit* terenasMenethilHC = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f); - - Group* group = bot->GetGroup(); - if (group && boss && boss->HealthAbovePct(71)) - { - constexpr uint8_t skullIconId = 7; - ObjectGuid skullGuid = group->GetTargetIcon(skullIconId); - if (skullGuid != boss->GetGUID()) - group->SetTargetIcon(skullIconId, bot->GetGUID(), boss->GetGUID()); - } - - //-----------Valkyr bot suicide if group fails to kill Valkyr in time------------- comment out if you dont want it - if (bot->HasAura(30440)) // Random aura tracking whether bot has fallen off edge / been thrown by Val'kyr - { - if (bot->GetPositionZ() > 779.0f) - return JumpTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), 740.01f); - else - { - bot->Kill(bot, bot); // If bot has jumped past the kill Z (780), **Now it is fixed and bots will actually die instead of beeing frozen** - return true; - } - } - - bool hasWinterAura = false; - if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || - boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4))) - hasWinterAura = true; - - bool hasWinter2Aura = false; - if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || - boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8))) - hasWinter2Aura = true; - - if (boss && boss->GetHealthPct() < 70 && boss->GetHealthPct() > 40 && !hasWinterAura && - !hasWinter2Aura) // If boss is in p2, check if bot has been thrown off platform - { - float dx = bot->GetPositionX() - 503.0f; - float dy = bot->GetPositionY() - (-2124.0f); - float distance = sqrt(dx * dx + dy * dy); // Calculate distance from the center of the platform - - if (distance > 52.0f && distance < 70.0f && - bot->GetPositionZ() > 844) // If bot has fallen off edge, distance is over 52 - { - bot->AddAura(30440, bot); // Apply random 30 sec aura to track that we've initiated a jump - return JumpTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), - 740.01f); // Start jumping to the abyss - } - } - //-----------Valkyr bot suicide if group fails to kill Valkyr in time------------- comment out if you dont want it - - // Handle teleportation fixes - HandleTeleportationFixes(diff, terenasMenethilHC); - - // Handle heroic mode spirit bomb avoidance for main tank - if (HandleSpiritBombAvoidance(diff, terenasMenethilHC)) - return true; - - // Handle non-main tank positioning in heroic mode - HandleHeroicNonTankPositioning(diff, terenasMenethilHC); - - // Handle spirit marking and targeting in heroic mode - HandleSpiritMarkingAndTargeting(diff, terenasMenethilHC); - - if (terenasMenethilHC) - return false; - - // Handle quake mechanics - if (HandleQuakeMechanics(boss)) - return true; - - // Handle shambling horror interactions - HandleShamblingHorrors(); - - // Handle assist tank add management - if (HandleAssistTankAddManagement(boss, diff)) - return true; - - // Handle melee positioning - HandleMeleePositioning(boss, hasPlague, diff); - - // Handle main tank targeting in heroic - HandleMainTankTargeting(boss, diff); - - // Handle non-tank positioning in heroic - HandleNonTankHeroicPositioning(boss, diff, hasPlague); - - // Handle ranged positioning - HandleRangedPositioning(boss, hasPlague, diff); - - // Handle defile mechanics - HandleDefileMechanics(boss, diff); - - // Handle Val'kyr mechanics - HandleValkyrMechanics(diff); - - // Handle vile spirit mechanics - HandleVileSpiritMechanics(); - - return false; -} - -void IccLichKingAddsAction::HandleTeleportationFixes(Difficulty diff, Unit* terenasMenethilHC) -{ - // temp soultion for bots when they get teleport far away into another dimension (they are unable to attack spirit - // warden) in heroic they will be in safe spot while player is surviving vile spirits - if (!(diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC) && - abs(bot->GetPositionY() - -2095.7915f) > 200.0f) - { - bot->TeleportTo(bot->GetMapId(), ICC_LICH_KING_ADDS_POSITION.GetPositionX(), - ICC_LICH_KING_ADDS_POSITION.GetPositionY(), ICC_LICH_KING_ADDS_POSITION.GetPositionZ(), - bot->GetOrientation()); - } - - // temp solution for bots going underground due to buggy ice platfroms and adds that go underground - if (abs(bot->GetPositionZ() - 840.857f) > 1.0f && !botAI->GetAura("Harvest Soul", bot, false, false) && - !botAI->GetAura("Harvest Souls", bot, false, false)) - bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), 840.857f, bot->GetOrientation()); - - if (abs(bot->GetPositionZ() - 1049.865f) > 5.0f && botAI->GetAura("Harvest Soul", bot, false, false) && - terenasMenethilHC) - bot->TeleportTo(bot->GetMapId(), terenasMenethilHC->GetPositionX(), terenasMenethilHC->GetPositionY(), 1049.865f, - bot->GetOrientation()); -} - -bool IccLichKingAddsAction::HandleSpiritBombAvoidance(Difficulty diff, Unit* terenasMenethilHC) -{ - if (!botAI->IsMainTank(bot) || !terenasMenethilHC || !diff || - !(diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - return false; - - std::map spiritBombs; - - // Gather all spirit bombs using their GUIDs for reliable tracking - GuidVector npcs1 = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto& npcGuid : npcs1) - { - Unit* unit = botAI->GetUnit(npcGuid); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SPIRIT_BOMB) - { - spiritBombs[npcGuid] = unit; - } - } - - // Only proceed if there are actually spirit bombs present - if (spiritBombs.empty()) - return false; - - const float SAFE_DISTANCE = 14.0f; // Minimum safe horizontal distance - const float SAFE_HEIGHT_DIFF = 12.0f; // Safe if Z difference is greater than this - const float BOMB_DENSITY_RADIUS = 10.0f; // Radius for counting nearby bombs - const float BOMB_COUNT_PENALTY = 10.0f; // Score penalty per bomb in vicinity - const float MAX_HEIGHT_DIFF = 8.0f; // Increased from 5.0f for more flexibility - - // First check if current position is already safe - bool currentPositionSafe = true; - float minDistanceToAnyBomb = std::numeric_limits::max(); - - // Clean up invalid bombs and check current position safety - auto it = spiritBombs.begin(); - while (it != spiritBombs.end()) - { - Unit* verifiedBomb = botAI->GetUnit(it->first); - if (!verifiedBomb || !verifiedBomb->IsAlive() || verifiedBomb->GetEntry() != NPC_SPIRIT_BOMB) - { - it = spiritBombs.erase(it); - continue; - } - - float dx = bot->GetPositionX() - verifiedBomb->GetPositionX(); - float dy = bot->GetPositionY() - verifiedBomb->GetPositionY(); - float dz = bot->GetPositionZ() - verifiedBomb->GetPositionZ(); - float horizontalDistance = sqrt(dx * dx + dy * dy); - float verticalDistance = fabs(dz); - - minDistanceToAnyBomb = std::min(minDistanceToAnyBomb, horizontalDistance); - - // Position is dangerous if horizontally close AND not high enough above/below - if (horizontalDistance < SAFE_DISTANCE && verticalDistance <= SAFE_HEIGHT_DIFF) - { - currentPositionSafe = false; - } - ++it; - } - - // If no valid bombs remain after cleanup, exit early - if (spiritBombs.empty()) - { - return false; - } - - // Only move if current position is unsafe - if (!currentPositionSafe) - { - float bestScore = -std::numeric_limits::max(); - float bestX = 0, bestY = 0, bestZ = 0; - bool foundSafePosition = false; - - // Multi-distance search to avoid getting stuck - std::vector searchDistances = {6.0f, 10.0f, 15.0f, 20.0f, 25.0f}; - - for (float searchDistance : searchDistances) - { - // Try 36 different angles for thorough coverage - for (int i = 0; i < 36; i++) - { - float testAngle = i * 2 * M_PI / 36; - - float testX = bot->GetPositionX() + searchDistance * cos(testAngle); - float testY = bot->GetPositionY() + searchDistance * sin(testAngle); - float testZ = bot->GetPositionZ(); - - bot->UpdateAllowedPositionZ(testX, testY, testZ); - - // More lenient LOS and height checks - bool validPosition = true; - float heightDiff = fabs(testZ - bot->GetPositionZ()); - - // Skip positions that are too high/low or not in LOS - if (heightDiff >= MAX_HEIGHT_DIFF) - { - validPosition = false; - } - - // Only check LOS if height difference is reasonable - if (validPosition && !bot->IsWithinLOS(testX, testY, testZ)) - { - validPosition = false; - } - - if (!validPosition) - continue; - - // Check position safety and bomb density - bool positionSafe = true; - float minDistAtPos = std::numeric_limits::max(); - int bombCountInVicinity = 0; - - for (auto const& bombPair : spiritBombs) - { - Unit* bomb = bombPair.second; - if (!bomb || !bomb->IsAlive()) - continue; - - float dx = testX - bomb->GetPositionX(); - float dy = testY - bomb->GetPositionY(); - float dz = testZ - bomb->GetPositionZ(); - float horizontalDist = sqrt(dx * dx + dy * dy); - float verticalDist = fabs(dz); - - // Track minimum distance to any bomb - minDistAtPos = std::min(minDistAtPos, horizontalDist); - - // Count bombs within density radius - if (horizontalDist < BOMB_DENSITY_RADIUS) - { - bombCountInVicinity++; - } - - // Check safety condition - if (horizontalDist < SAFE_DISTANCE && verticalDist <= SAFE_HEIGHT_DIFF) - { - positionSafe = false; - break; - } - } - - // Skip unsafe positions - if (!positionSafe) - continue; - - // Calculate composite score with distance bonus to prefer closer safe positions - float distanceBonus = std::max(0.0f, 30.0f - searchDistance); // Prefer closer positions - float score = minDistAtPos - (bombCountInVicinity * BOMB_COUNT_PENALTY) + distanceBonus; - - // Update best position if this one is better - if (score > bestScore) - { - bestScore = score; - bestX = testX; - bestY = testY; - bestZ = testZ; - foundSafePosition = true; - } - } - - // If we found a safe position at this distance, use it (prefer closer positions) - if (foundSafePosition && searchDistance <= 15.0f) - break; - } - - // Move to the best position found - if (foundSafePosition) - { - // Final validation before moving - if (bot->IsWithinLOS(bestX, bestY, bestZ) && fabs(bestZ - bot->GetPositionZ()) <= MAX_HEIGHT_DIFF) - { - MoveTo(bot->GetMapId(), bestX, bestY, bestZ, false, false, false, true, - MovementPriority::MOVEMENT_FORCED); - } - } - } - return true; -} - -void IccLichKingAddsAction::HandleHeroicNonTankPositioning(Difficulty diff, Unit* terenasMenethilHC) -{ - if (!terenasMenethilHC || botAI->IsMainTank(bot) || !diff || - !(diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - return; - - Unit* mainTank = AI_VALUE(Unit*, "main tank"); - // Only move if significantly far from main tank (increased threshold to reduce jittery movement) - if (mainTank && bot->GetExactDist2d(mainTank->GetPositionX(), mainTank->GetPositionY()) > 2.0f) - { - MoveTo(bot->GetMapId(), mainTank->GetPositionX(), mainTank->GetPositionY(), mainTank->GetPositionZ(), false, - false, false, true, MovementPriority::MOVEMENT_FORCED); - } -} - -void IccLichKingAddsAction::HandleSpiritMarkingAndTargeting(Difficulty diff, Unit* terenasMenethilHC) -{ - if (!terenasMenethilHC || botAI->IsMainTank(bot) || !diff || - !(diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - return; - - Group* group = bot->GetGroup(); - if (!group) - return; - - static constexpr uint8_t STAR_ICON_INDEX = 0; - static constexpr float MAX_Z_DIFF = 20.0f; - - // Check if current marked target is still valid and threatening - Unit* currentMarkedTarget = botAI->GetUnit(group->GetTargetIcon(STAR_ICON_INDEX)); - bool needNewMark = !currentMarkedTarget || !currentMarkedTarget->IsAlive(); - - // Check if current marked spirit is targeting a group member - bool currentTargetingGroupMember = false; - if (currentMarkedTarget && currentMarkedTarget->IsAlive()) - { - Unit* spiritTarget = currentMarkedTarget->GetVictim(); - if (spiritTarget && spiritTarget->IsPlayer()) - { - if (Group* spiritTargetGroup = spiritTarget->ToPlayer()->GetGroup()) - { - if (spiritTargetGroup->GetGUID() == group->GetGUID()) - { - currentTargetingGroupMember = true; - } - } - } - } - - // Only search for new target if we need to mark OR if we can find a higher priority target - if (needNewMark || !currentTargetingGroupMember) - { - Unit* prioritySpirit = nullptr; // Spirit targeting group member - Unit* nearestSpirit = nullptr; // Fallback: nearest spirit - float priorityDist = 100.0f; - float nearestDist = 100.0f; - - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || !unit->IsAlive() || !unit->isTargetableForAttack()) - continue; - - uint32 entry = unit->GetEntry(); - if (entry == NPC_WICKED_SPIRIT1 || entry == NPC_WICKED_SPIRIT2 || entry == NPC_WICKED_SPIRIT3 || - entry == NPC_WICKED_SPIRIT4) - { - // Check Z-axis difference first - float zDiff = std::abs(unit->GetPositionZ() - bot->GetPositionZ()); - if (zDiff <= MAX_Z_DIFF) - { - float dist = bot->GetDistance(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ()); - - // Check if this spirit is targeting a group member - bool targetingGroupMember = false; - Unit* spiritTarget = unit->GetVictim(); - if (spiritTarget && spiritTarget->IsPlayer()) - { - if (Group* spiritTargetGroup = spiritTarget->ToPlayer()->GetGroup()) - { - if (spiritTargetGroup->GetGUID() == group->GetGUID()) - { - targetingGroupMember = true; - } - } - } - - // Priority: spirits targeting group members - if (targetingGroupMember) - { - if (!prioritySpirit || dist < priorityDist) - { - prioritySpirit = unit; - priorityDist = dist; - } - } - - // Fallback: track nearest spirit regardless of target - if (!nearestSpirit || dist < nearestDist) - { - nearestSpirit = unit; - nearestDist = dist; - } - } - } - } - - // Mark priority spirit if found, otherwise fall back to nearest - Unit* spiritToMark = prioritySpirit ? prioritySpirit : nearestSpirit; - - // Only mark if we found a better target or need a new mark - if (spiritToMark && (needNewMark || (prioritySpirit && !currentTargetingGroupMember))) - { - group->SetTargetIcon(STAR_ICON_INDEX, bot->GetGUID(), spiritToMark->GetGUID()); - } - } - - // Only ranged DPS use star for RTI - if (botAI->IsRangedDps(bot)) - { - context->GetValue("rti")->Set("star"); - Unit* starTarget = botAI->GetUnit(group->GetTargetIcon(STAR_ICON_INDEX)); - if (starTarget && starTarget->IsAlive()) - { - bot->SetTarget(starTarget->GetGUID()); - bot->SetFacingToObject(starTarget); - Attack(starTarget); - bot->Kill(bot, starTarget); //temp solution since bots struggle to kill spirits in time, they have to follow main tank closely so that they do not get hit by bomb, thus making them have very limited time to react - } - } -} - -bool IccLichKingAddsAction::HandleQuakeMechanics(Unit* boss) -{ - if (!boss || !boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_QUAKE)) - return false; - - float currentDistance = bot->GetExactDist2d(boss); - - // If already at ideal distance (40f), no need to move - if (currentDistance >= 35.0f && currentDistance <= 45.0f) - return false; - - if (bot->HasUnitState(UNIT_STATE_CASTING)) - botAI->Reset(); - - float botX = bot->GetPositionX(); - float botY = bot->GetPositionY(); - float targetX, targetY; - - if (!botAI->IsTank(bot)) - { - // Non-tanks: use offset position as direction guide - float offsetX = boss->GetPositionX() + 15.0f; - float offsetY = boss->GetPositionY() + 15.0f; - - // Calculate direction towards offset position - float dx = offsetX - botX; - float dy = offsetY - botY; - float distance = sqrt(dx * dx + dy * dy); - - if (distance > 0.0f) - { - // Move 10f towards offset position - float ratio = 10.0f / distance; - targetX = botX + dx * ratio; - targetY = botY + dy * ratio; - } - else - { - targetX = botX; - targetY = botY; - } - } - else - { - // Tanks: use offset position as direction guide - float offsetX = boss->GetPositionX() - 15.0f; - float offsetY = boss->GetPositionY() - 15.0f; - - // Calculate direction towards offset position - float dx = offsetX - botX; - float dy = offsetY - botY; - float distance = sqrt(dx * dx + dy * dy); - - if (distance > 0.0f) - { - // Move 10f towards offset position - float ratio = 10.0f / distance; - targetX = botX + dx * ratio; - targetY = botY + dy * ratio; - } - else - { - targetX = botX; - targetY = botY; - } - } - - MoveTo(bot->GetMapId(), targetX, targetY, boss->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); - - return false; -} - -void IccLichKingAddsAction::HandleShamblingHorrors() -{ - // Find closest shambling horror - GuidVector npcs2 = AI_VALUE(GuidVector, "nearest hostile npcs"); - Unit* closestHorror = nullptr; - float minHorrorDistance = std::numeric_limits::max(); - - for (auto& npc : npcs2) - { - Unit* unit = botAI->GetUnit(npc); - if (unit && unit->IsAlive() && - (unit->GetEntry() == NPC_SHAMBLING_HORROR1 || unit->GetEntry() == NPC_SHAMBLING_HORROR2 || unit->GetEntry() == NPC_SHAMBLING_HORROR3 || - unit->GetEntry() == NPC_SHAMBLING_HORROR4)) // Shambling horror entries - { - float distance = bot->GetDistance(unit); - if (distance < minHorrorDistance) - { - minHorrorDistance = distance; - closestHorror = unit; - } - } - } - - /* - if (!closestHorror || hasPlague) - { - } - else if (!hasPlague && closestHorror->isInFront(bot) && closestHorror->IsAlive() && !botAI->IsTank(bot) && - bot->GetDistance2d(closestHorror) < 3.0f) - return FleePosition(closestHorror->GetPosition(), 2.0f, 250U); - */ - - // If bot is hunter and shambling is enraged, use Tranquilizing Shot - if (bot->getClass() == CLASS_HUNTER && closestHorror && botAI->HasAura("Enrage", closestHorror)) - botAI->CastSpell("Tranquilizing Shot", closestHorror); -} - -bool IccLichKingAddsAction::HandleAssistTankAddManagement(Unit* boss, Difficulty diff) -{ - if (!botAI->IsAssistTank(bot) || !boss || boss->HealthBelowPct(71)) - return false; - - // Find all adds and categorize them by targeting status - GuidVector targets = AI_VALUE(GuidVector, "possible targets"); - std::vector addsNotTargetingUs; - std::vector addsTargetingUs; - - for (auto i = targets.begin(); i != targets.end(); ++i) - { - Unit* unit = botAI->GetUnit(*i); - if (unit && unit->IsAlive() && - (unit->GetEntry() == NPC_SHAMBLING_HORROR1 || unit->GetEntry() == NPC_SHAMBLING_HORROR2 || - unit->GetEntry() == NPC_SHAMBLING_HORROR3 || - unit->GetEntry() == NPC_SHAMBLING_HORROR4 || // Shambling entry - unit->GetEntry() == NPC_RAGING_SPIRIT1 || unit->GetEntry() == NPC_RAGING_SPIRIT2 || - unit->GetEntry() == NPC_RAGING_SPIRIT3 || unit->GetEntry() == NPC_RAGING_SPIRIT4 || // Spirits entry - unit->GetEntry() == NPC_DRUDGE_GHOUL1 || unit->GetEntry() == NPC_DRUDGE_GHOUL2 || - unit->GetEntry() == NPC_DRUDGE_GHOUL3 || unit->GetEntry() == NPC_DRUDGE_GHOUL4)) // Drudge Ghouls entry - { - if (unit->GetVictim() == bot) - { - addsTargetingUs.push_back(unit->GetGUID()); - } - else - { - addsNotTargetingUs.push_back(unit->GetGUID()); - } - } - } - - // If there are adds not targeting us, we need to collect them all - if (!addsNotTargetingUs.empty()) - { - // Find the highest priority target (Shamblings first, then closest) - Unit* priorityTarget = nullptr; - Unit* closestAdd = nullptr; - float closestDist = 999.0f; - - for (const ObjectGuid& addGuid : addsNotTargetingUs) - { - Unit* add = botAI->GetUnit(addGuid); - if (add && add->IsAlive()) - { - // Shambling takes absolute priority regardless of distance - if (add->GetEntry() == NPC_SHAMBLING_HORROR1 || add->GetEntry() == NPC_SHAMBLING_HORROR2 || - add->GetEntry() == NPC_SHAMBLING_HORROR3 || add->GetEntry() == NPC_SHAMBLING_HORROR4) - { - priorityTarget = add; - break; // Found shambling, stop looking - } - - // Track closest add as backup - float dist = bot->GetExactDist2d(add); - if (dist < closestDist) - { - closestDist = dist; - closestAdd = add; - } - } - } - - // Choose target: Shambling first, then closest - Unit* targetToAttack = priorityTarget ? priorityTarget : closestAdd; - - if (targetToAttack) - { - // Generate threat on ALL adds not targeting us using ranged abilities - for (const ObjectGuid& addGuid : addsNotTargetingUs) - { - Unit* add = botAI->GetUnit(addGuid); - if (add && add->IsAlive()) - { - float dist = bot->GetExactDist2d(add); - - // Use ranged threat generation if within range - if (dist <= 30.0f) - { - // Try taunt first if available - if (botAI->CastSpell("taunt", add)) - { - continue; - } - // Fall back to ranged attack - else if (botAI->CastSpell("shoot", add) || botAI->CastSpell("throw", add)) - { - continue; - } - // Last resort - basic attack state update - else - { - bot->AttackerStateUpdate(add); - } - } - } - } - - // Move towards and attack the priority target - float distToTarget = bot->GetExactDist2d(targetToAttack); - - // If we're too far from our priority target, move closer - if (distToTarget > 5.0f) - { - MoveTo(bot->GetMapId(), targetToAttack->GetPositionX(), targetToAttack->GetPositionY(), - targetToAttack->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED, - true, false); - } - else - { - // We're close enough, set target and attack - bot->SetTarget(targetToAttack->GetGUID()); - bot->SetFacingToObject(targetToAttack); - Attack(targetToAttack); - } - } - } - // If all adds are targeting us or there are no adds, maintain position based on difficulty - else - { - // In heroic mode, stay at melee position - if (diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - { - if (bot->GetExactDist2d(ICC_LICH_KING_ASSISTHC_POSITION.GetPositionX(), - ICC_LICH_KING_ASSISTHC_POSITION.GetPositionY()) > 2.0f) - { - MoveTo(bot->GetMapId(), ICC_LICH_KING_ASSISTHC_POSITION.GetPositionX(), - ICC_LICH_KING_ASSISTHC_POSITION.GetPositionY(), ICC_LICH_KING_ASSISTHC_POSITION.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); - } - } - // In normal mode, stay at adds position - else - { - if (bot->GetExactDist2d(ICC_LICH_KING_ADDS_POSITION) > 2.0f) - { - MoveTo(bot->GetMapId(), ICC_LICH_KING_ADDS_POSITION.GetPositionX(), - ICC_LICH_KING_ADDS_POSITION.GetPositionY(), ICC_LICH_KING_ADDS_POSITION.GetPositionZ(), false, - false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); - } - } - - // If we have adds targeting us, attack them with stable target selection - if (!addsTargetingUs.empty()) - { - Unit* currentTarget = bot->GetVictim(); - bool needNewTarget = true; - - // Check if current target is still valid (alive and attacking us) - if (currentTarget && currentTarget->IsAlive()) - { - for (const ObjectGuid& addGuid : addsTargetingUs) - { - if (addGuid == currentTarget->GetGUID()) - { - needNewTarget = false; - break; - } - } - } - - // Only pick new target if current one is invalid - if (needNewTarget) - { - currentTarget = nullptr; - - // Priority 1: Shambling Horror - for (const ObjectGuid& addGuid : addsTargetingUs) - { - Unit* add = botAI->GetUnit(addGuid); - if (add && add->IsAlive()) - { - if (add->GetEntry() == NPC_SHAMBLING_HORROR1 || add->GetEntry() == NPC_SHAMBLING_HORROR2 || - add->GetEntry() == NPC_SHAMBLING_HORROR3 || add->GetEntry() == NPC_SHAMBLING_HORROR4) - { - currentTarget = add; - break; - } - } - } - - // Priority 2: Any other add if no Shambling Horror - if (!currentTarget) - { - for (const ObjectGuid& addGuid : addsTargetingUs) - { - Unit* add = botAI->GetUnit(addGuid); - if (add && add->IsAlive()) - { - currentTarget = add; - break; - } - } - } - } - - if (currentTarget) - { - bot->SetTarget(currentTarget->GetGUID()); - bot->SetFacingToObject(currentTarget); - Attack(currentTarget); - } - } - } - return false; -} - -void IccLichKingAddsAction::HandleMeleePositioning(Unit* boss, bool hasPlague, Difficulty diff) -{ - if (!boss || !botAI->IsMelee(bot) || botAI->IsAssistTank(bot) || boss->HealthBelowPct(71) || hasPlague) - return; - - if (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC) - return; - - float currentDist = bot->GetDistance(ICC_LICH_KING_MELEE_POSITION); - - if (currentDist > 6.0f && !botAI->IsMainTank(bot)) - { - MoveTo(bot->GetMapId(), ICC_LICH_KING_MELEE_POSITION.GetPositionX(), - ICC_LICH_KING_MELEE_POSITION.GetPositionY(), ICC_LICH_KING_MELEE_POSITION.GetPositionZ(), false, false, - false, true, MovementPriority::MOVEMENT_FORCED, true, false); - } - - if (currentDist > 6.0f && botAI->IsMainTank(bot) && boss && boss->GetVictim() == bot) - { - Position currentPos = bot->GetPosition(); - Position targetPos = ICC_LICH_KING_MELEE_POSITION; - - // Calculate direction vector - float dx = targetPos.GetPositionX() - currentPos.GetPositionX(); - float dy = targetPos.GetPositionY() - currentPos.GetPositionY(); - - // Calculate distance and normalize direction - float distance = sqrt(dx * dx + dy * dy); - if (distance > 0.1) - { - dx /= distance; - dy /= distance; - } - - // Calculate intermediate position (3f towards target) - float step = std::min(3.0f, distance - 1.0f); // Don't overshoot the target - if (step > 0) - { - float intermediateX = currentPos.GetPositionX() + dx * step; - float intermediateY = currentPos.GetPositionY() + dy * step; - - MoveTo(bot->GetMapId(), intermediateX, intermediateY, bot->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_FORCED, true, false); - } - else - { - // If we're within 1.0f + 3.0f of the target, move directly to it - MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), bot->GetPositionZ(), false, - false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); - } - } -} - -void IccLichKingAddsAction::HandleMainTankTargeting(Unit* boss, Difficulty diff) -{ - if (!botAI->IsMainTank(bot) || !boss) - return; - - if (!(diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - return; - - if (boss->HealthBelowPct(71) || boss->GetVictim() == bot) - return; - - bot->SetTarget(boss->GetGUID()); - bot->SetFacingToObject(boss); - Attack(boss); -} - -void IccLichKingAddsAction::HandleNonTankHeroicPositioning(Unit* boss, Difficulty diff, bool hasPlague) -{ - if (botAI->IsTank(bot) || !boss) - return; - - if (!(diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - return; - - if (boss->HealthBelowPct(71) || hasPlague) - return; - - Unit* mainTank = AI_VALUE(Unit*, "main tank"); - if (!mainTank) - return; - - if (bot->GetDistance2d(mainTank->GetPositionX(), mainTank->GetPositionY()) > 20.0f && (bot->getClass() == CLASS_HUNTER)) - { - botAI->Reset(); - - // Calculate direction vector to main tank - float dx = mainTank->GetPositionX() - bot->GetPositionX(); - float dy = mainTank->GetPositionY() - bot->GetPositionY(); - - // Normalize and scale to 2f increments - float distance = sqrt(dx * dx + dy * dy); - if (distance > 0) - { - dx = dx / distance * 2.0f; - dy = dy / distance * 2.0f; - - // Calculate new position (2f closer to main tank) - float newX = bot->GetPositionX() + dx; - float newY = bot->GetPositionY() + dy; - - MoveTo(bot->GetMapId(), newX, newY, bot->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_FORCED, true, false); - } - } - - if (bot->GetDistance2d(mainTank->GetPositionX(), mainTank->GetPositionY()) > 1.0f && (bot->getClass() != CLASS_HUNTER)) - { - botAI->Reset(); - MoveTo(bot->GetMapId(), mainTank->GetPositionX(), mainTank->GetPositionY(), bot->GetPositionZ(), false, false, - false, true, MovementPriority::MOVEMENT_FORCED, true, false); - } -} - -void IccLichKingAddsAction::HandleRangedPositioning(Unit* boss, bool hasPlague, Difficulty diff) -{ - if (!boss || !botAI->IsRanged(bot) || boss->HealthBelowPct(71) || hasPlague) - return; - - if (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC) - return; - - float currentDist = bot->GetDistance(ICC_LICH_KING_RANGED_POSITION); - if (currentDist > 2.0f) - { - MoveTo(bot->GetMapId(), ICC_LICH_KING_RANGED_POSITION.GetPositionX(), - ICC_LICH_KING_RANGED_POSITION.GetPositionY(), ICC_LICH_KING_RANGED_POSITION.GetPositionZ(), false, false, - false, true, MovementPriority::MOVEMENT_FORCED, true, false); - } -} - -void IccLichKingAddsAction::HandleDefileMechanics(Unit* boss, Difficulty diff) -{ - if (!boss) - return; - - // Constants - const float BASE_RADIUS = 6.0f; - const float SAFETY_MARGIN = 3.0f; - const float MOVE_DISTANCE = 5.0f; - const float SPREAD_DISTANCE = 12.0f; - const float FIXED_Z = 840.857f; - const float MAX_HEIGHT_DIFF = 5.0f; - const float MIN_PLAYER_SPACING = 5.0f; - const float MAX_BOSS_DISTANCE = 40.0f; - const int ANGLE_TESTS = 16; - const int MAX_ANGLE_OFFSETS = 8; - - // Gather all defile units - std::vector defiles; - float closestDistance = std::numeric_limits::max(); - - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (unit && unit->IsAlive() && unit->GetEntry() == DEFILE_NPC_ID) - { - defiles.push_back(unit); - float dist = bot->GetDistance(unit); - if (dist < closestDistance) - closestDistance = dist; - } - } - - // Only process defile avoidance if defiles exist - if (!defiles.empty()) - { - // Check if we need to move away from defiles - bool needToMove = false; - float botX = bot->GetPositionX(); - float botY = bot->GetPositionY(); - - for (Unit* defile : defiles) - { - if (!defile || !defile->IsAlive()) - continue; - - // Calculate current defile radius including growth - float currentRadius = BASE_RADIUS; - Aura* growAura = nullptr; - - // Find growth aura - for (size_t i = 0; i < DEFILE_AURA_COUNT; i++) - { - growAura = defile->GetAura(DEFILE_AURAS[i]); - if (growAura) - break; - } - - if (growAura) - { - uint8 stacks = growAura->GetStackAmount(); - float growthMultiplier = - (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_10MAN_NORMAL) ? 1.4f : 0.95f; - currentRadius = BASE_RADIUS + (stacks * growthMultiplier); - } - - // Check if bot is too close to this defile - float dx = botX - defile->GetPositionX(); - float dy = botY - defile->GetPositionY(); - float distanceToCenter = sqrt(dx * dx + dy * dy); - - if (distanceToCenter < (currentRadius + SAFETY_MARGIN)) - { - needToMove = true; - break; - } - } - - // Move away from defiles if needed - if (needToMove) - { - float bestAngle = 0.0f; - float maxSafetyScore = 0.0f; - bool foundSafePosition = false; - - // Test multiple angles to find safest escape route - for (int i = 0; i < ANGLE_TESTS; i++) - { - float testAngle = i * M_PI / 8; - float testX = botX + MOVE_DISTANCE * cos(testAngle); - float testY = botY + MOVE_DISTANCE * sin(testAngle); - float testZ = FIXED_Z; - - bot->UpdateAllowedPositionZ(testX, testY, testZ); - - // Skip invalid positions (LOS and height check) - if (!bot->IsWithinLOS(testX, testY, testZ) || fabs(testZ - bot->GetPositionZ()) >= MAX_HEIGHT_DIFF) - continue; - - // Calculate minimum distance to any defile from this position - float minDefileDistance = std::numeric_limits::max(); - for (Unit* defile : defiles) - { - if (!defile || !defile->IsAlive()) - continue; - - float dx = testX - defile->GetPositionX(); - float dy = testY - defile->GetPositionY(); - float dist = sqrt(dx * dx + dy * dy); - minDefileDistance = std::min(minDefileDistance, dist); - } - - // Calculate scoring (safety + boss proximity) - float distanceToBoss = boss->GetDistance2d(testX, testY); - float safetyScore = minDefileDistance; - float bossScore = 100.0f - std::min(100.0f, distanceToBoss); - float totalScore = safetyScore + (bossScore * 0.5f); - - if (totalScore > maxSafetyScore) - { - maxSafetyScore = totalScore; - bestAngle = testAngle; - foundSafePosition = true; - } - } - - // Execute movement if safe position found - if (foundSafePosition && maxSafetyScore > 0) - { - float moveX = botX + MOVE_DISTANCE * cos(bestAngle); - float moveY = botY + MOVE_DISTANCE * sin(bestAngle); - float moveZ = FIXED_Z; - - if (bot->HasUnitState(UNIT_STATE_CASTING)) - botAI->Reset(); - - bot->UpdateAllowedPositionZ(moveX, moveY, moveZ); - MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, - MovementPriority::MOVEMENT_FORCED); - } - } - } - - // Handle Defile cast - spread positioning - if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(DEFILE_CAST_ID)) - { - // Count players and determine bot's index - uint32 playerCount = 0; - uint32 botIndex = 0; - uint32 currentIndex = 0; - - float botX = bot->GetPositionX(); - float botY = bot->GetPositionY(); - - Map::PlayerList const& players = bot->GetMap()->GetPlayers(); - for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr) - { - Player* player = itr->GetSource(); - if (!player || !player->IsAlive()) - continue; - - if (player == bot) - botIndex = currentIndex; - - currentIndex++; - playerCount++; - } - - // Calculate preferred spread angle based on bot index - float preferredAngle = (float(botIndex) / float(playerCount)) * 2 * M_PI; - bool foundSafeSpot = false; - float bestSpreadAngle = preferredAngle; - - // Try positions starting from preferred angle, expanding outward - for (int offset = 0; offset <= MAX_ANGLE_OFFSETS && !foundSafeSpot; offset++) - { - for (int direction = -1; direction <= 1; direction += 2) - { - if (offset == 0 && direction > 0) // Skip duplicate check of preferred angle - continue; - - float testAngle = preferredAngle + (direction * offset * M_PI / 16); - float testX = botX + SPREAD_DISTANCE * cos(testAngle); - float testY = botY + SPREAD_DISTANCE * sin(testAngle); - float testZ = FIXED_Z; - - bot->UpdateAllowedPositionZ(testX, testY, testZ); - - // Validate position basics (LOS and height) - if (!bot->IsWithinLOS(testX, testY, testZ) || fabs(testZ - bot->GetPositionZ()) >= MAX_HEIGHT_DIFF) - continue; - - // Check boss distance - if (boss->GetDistance2d(testX, testY) > MAX_BOSS_DISTANCE) - continue; - - // Check safety from all defiles (only if defiles exist) - bool safeFromDefiles = true; - for (Unit* defile : defiles) - { - if (!defile || !defile->IsAlive()) - continue; - - // Calculate current defile radius including growth - float currentRadius = BASE_RADIUS; - Aura* growAura = nullptr; - - // Find growth aura - for (size_t i = 0; i < DEFILE_AURA_COUNT; i++) - { - growAura = defile->GetAura(DEFILE_AURAS[i]); - if (growAura) - break; - } - - if (growAura) - { - uint8 stacks = growAura->GetStackAmount(); - float growthMultiplier = - (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_10MAN_NORMAL) ? 1.4f - : 0.95f; - currentRadius = BASE_RADIUS + (stacks * growthMultiplier); - } - - float dx = testX - defile->GetPositionX(); - float dy = testY - defile->GetPositionY(); - float distToDefile = sqrt(dx * dx + dy * dy); - - if (distToDefile < (currentRadius + SAFETY_MARGIN)) - { - safeFromDefiles = false; - break; - } - } - - if (!safeFromDefiles) - continue; - - // Check spacing from other players - bool tooCloseToPlayers = false; - for (Map::PlayerList::const_iterator playerItr = players.begin(); playerItr != players.end(); - ++playerItr) - { - Player* player = playerItr->GetSource(); - if (!player || !player->IsAlive() || player == bot) - continue; - - float dx = testX - player->GetPositionX(); - float dy = testY - player->GetPositionY(); - float dist = sqrt(dx * dx + dy * dy); - - if (dist < MIN_PLAYER_SPACING) - { - tooCloseToPlayers = true; - break; - } - } - - if (tooCloseToPlayers) - continue; - - // Found valid position - bestSpreadAngle = testAngle; - foundSafeSpot = true; - break; - } - } - - // Execute spread movement if safe spot found - if (foundSafeSpot) - { - float spreadX = botX + SPREAD_DISTANCE * cos(bestSpreadAngle); - float spreadY = botY + SPREAD_DISTANCE * sin(bestSpreadAngle); - float spreadZ = FIXED_Z; - - bot->UpdateAllowedPositionZ(spreadX, spreadY, spreadZ); - MoveTo(bot->GetMapId(), spreadX, spreadY, spreadZ, false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); - } - } -} - -void IccLichKingAddsAction::HandleValkyrMechanics(Difficulty diff) -{ - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - std::vector grabbingValkyrs; - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - - // Find grabbing Val'kyrs - for (auto& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || !unit->IsAlive()) - continue; - - if (unit->GetEntry() == NPC_VALKYR_SHADOWGUARD1 || unit->GetEntry() == NPC_VALKYR_SHADOWGUARD2 || - unit->GetEntry() == NPC_VALKYR_SHADOWGUARD3 || unit->GetEntry() == NPC_VALKYR_SHADOWGUARD4) - { - bool isGrabbing = false; - - if (diff && !(diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - { - if (unit->HasAura(SPELL_HARVEST_SOUL_VALKYR)) - isGrabbing = true; - } - else - { - if (unit->HasAura(SPELL_HARVEST_SOUL_VALKYR) && unit->HealthAbovePct(49)) - isGrabbing = true; - } - - if (isGrabbing) - grabbingValkyrs.push_back(unit); - } - } - - Group* group = bot->GetGroup(); - if (!group) - return; - - // If no grabbing Val'kyrs, mark the Lich King with skull - if (grabbingValkyrs.empty() || (boss && boss->HealthBelowPct(43))) - { - // Find the Lich King - for (auto& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_THE_LICH_KING && unit->HealthBelowPct(68) && unit->HealthAbovePct(40)) - { - ObjectGuid currentSkull = group->GetTargetIcon(7); // Skull icon - if (currentSkull != unit->GetGUID()) - { - group->SetTargetIcon(7, bot->GetGUID(), unit->GetGUID()); - } - break; - } - } - return; - } - - if (botAI->IsMainTank(bot)) - return; - - // Filter out dead Val'kyrs to ensure accurate group calculation - std::vector aliveGrabbingValkyrs; - for (Unit* valkyr : grabbingValkyrs) - { - if (valkyr && valkyr->IsAlive()) - aliveGrabbingValkyrs.push_back(valkyr); - } - - if (aliveGrabbingValkyrs.empty()) - return; - - HandleValkyrMarking(aliveGrabbingValkyrs, diff); - HandleValkyrAssignment(aliveGrabbingValkyrs); -} - -void IccLichKingAddsAction::HandleValkyrMarking(const std::vector& grabbingValkyrs, Difficulty diff) -{ - Group* group = bot->GetGroup(); - if (!group) - return; - - // Sort Val'kyrs by their GUID to ensure consistent ordering - std::vector sortedValkyrs = grabbingValkyrs; - std::sort(sortedValkyrs.begin(), sortedValkyrs.end(), [](Unit* a, Unit* b) { return a->GetGUID() < b->GetGUID(); }); - - static constexpr uint8_t ICON_INDICES[] = {7, 6, 0}; // Skull, Cross, Star - - // In heroic mode, clean up invalid markers for all possible icons - if (diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - { - for (size_t i = 0; i < 3; ++i) - { - ObjectGuid currentIcon = group->GetTargetIcon(ICON_INDICES[i]); - Unit* currentIconUnit = botAI->GetUnit(currentIcon); - - if (currentIconUnit && IsValkyr(currentIconUnit)) - { - bool shouldRemoveMarker = !currentIconUnit->HasAura(SPELL_HARVEST_SOUL_VALKYR) || - std::abs(currentIconUnit->GetPositionZ() - bot->GetPositionZ()) > 5.0f; - - if (shouldRemoveMarker) - group->SetTargetIcon(ICON_INDICES[i], bot->GetGUID(), ObjectGuid::Empty); - } - } - } - - // Clear unused markers if we have fewer Val'kyrs than icons - for (size_t i = sortedValkyrs.size(); i < 3; ++i) - { - ObjectGuid currentIcon = group->GetTargetIcon(ICON_INDICES[i]); - if (!currentIcon.IsEmpty()) - { - group->SetTargetIcon(ICON_INDICES[i], bot->GetGUID(), ObjectGuid::Empty); - } - } - - // Mark each alive Val'kyr with appropriate icon - for (size_t i = 0; i < sortedValkyrs.size() && i < 3; ++i) - { - ObjectGuid currentIcon = group->GetTargetIcon(ICON_INDICES[i]); - Unit* currentIconUnit = botAI->GetUnit(currentIcon); - - if (!currentIconUnit || currentIconUnit != sortedValkyrs[i]) - { - group->SetTargetIcon(ICON_INDICES[i], bot->GetGUID(), sortedValkyrs[i]->GetGUID()); - } - } -} - -void IccLichKingAddsAction::HandleValkyrAssignment(const std::vector& grabbingValkyrs) -{ - Group* group = bot->GetGroup(); - if (!group) - return; - - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - if (boss && boss->HealthBelowPct(40)) - return; - - // Double-check that all Val'kyrs in the list are actually alive and valid targets - std::vector validValkyrs; - for (Unit* valkyr : grabbingValkyrs) - { - if (valkyr && valkyr->IsAlive() && valkyr->HasAura(SPELL_HARVEST_SOUL_VALKYR)) - { - validValkyrs.push_back(valkyr); - } - } - - if (validValkyrs.empty()) - return; - - // Sort valid Val'kyrs for consistent assignment - std::sort(validValkyrs.begin(), validValkyrs.end(), [](Unit* a, Unit* b) { return a->GetGUID() < b->GetGUID(); }); - - // Get all non-main-tank members (DPS, healers, and off-tanks) - std::vector assistMembers; - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (member && !botAI->IsMainTank(member)) - assistMembers.push_back(member); - } - - if (assistMembers.empty()) - return; - - // Sort assist members by GUID for consistent assignment - std::sort(assistMembers.begin(), assistMembers.end(), - [](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); }); - - // Find our position among assist members - auto it = std::find(assistMembers.begin(), assistMembers.end(), bot); - if (it == assistMembers.end()) - return; // We're main tank, shouldn't handle Val'kyrs - - size_t myAssistIndex = std::distance(assistMembers.begin(), it); - size_t totalAssist = assistMembers.size(); - size_t aliveValkyrs = validValkyrs.size(); - - // Calculate balanced group sizes - std::vector groupSizes = CalculateBalancedGroupSizes(totalAssist, aliveValkyrs); - - // Determine which Val'kyr this bot should target - size_t assignedValkyrIndex = GetAssignedValkyrIndex(myAssistIndex, groupSizes); - - if (assignedValkyrIndex < validValkyrs.size()) - { - Unit* myValkyr = validValkyrs[assignedValkyrIndex]; - - // Set RTI context based on assignment - std::string rtiValue = GetRTIValueForValkyr(assignedValkyrIndex); - context->GetValue("rti")->Set(rtiValue); - - // Attack and apply CC - bot->SetTarget(myValkyr->GetGUID()); - bot->SetFacingToObject(myValkyr); - Difficulty diff = bot->GetRaidDifficulty(); - - if (sPlayerbotAIConfig.EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) - { - //---------CHEAT--------- - if (!myValkyr->HasAura(SPELL_HAMMER_OF_JUSTICE)) - bot->AddAura(SPELL_HAMMER_OF_JUSTICE, myValkyr); - //---------CHEAT--------- - } - ApplyCCToValkyr(myValkyr); - } -} - -std::vector IccLichKingAddsAction::CalculateBalancedGroupSizes(size_t totalAssist, size_t numValkyrs) -{ - std::vector groupSizes(numValkyrs, 0); - - if (numValkyrs == 0) - return groupSizes; - - // Base size for each group - size_t baseSize = totalAssist / numValkyrs; - size_t remainder = totalAssist % numValkyrs; - - // Distribute assist members as evenly as possible - for (size_t i = 0; i < numValkyrs; ++i) - { - groupSizes[i] = baseSize; - if (i < remainder) - groupSizes[i]++; // Add extra member to first 'remainder' groups - } - - return groupSizes; -} - -size_t IccLichKingAddsAction::GetAssignedValkyrIndex(size_t assistIndex, const std::vector& groupSizes) -{ - size_t currentIndex = 0; - - for (size_t valkyrIndex = 0; valkyrIndex < groupSizes.size(); ++valkyrIndex) - { - if (assistIndex < currentIndex + groupSizes[valkyrIndex]) - return valkyrIndex; - - currentIndex += groupSizes[valkyrIndex]; - } - - // Fallback - should not happen with correct logic - return 0; -} - -std::string IccLichKingAddsAction::GetRTIValueForValkyr(size_t valkyrIndex) -{ - switch (valkyrIndex) - { - case 0: - return "skull"; - case 1: - return "cross"; - case 2: - return "star"; - default: - return "skull"; // Fallback - } -} - -void IccLichKingAddsAction::ApplyCCToValkyr(Unit* valkyr) -{ - switch (bot->getClass()) - { - case CLASS_MAGE: - if (!botAI->HasAura("Frost Nova", valkyr)) - botAI->CastSpell("Frost Nova", valkyr); - break; - case CLASS_DRUID: - if (!botAI->HasAura("Entangling Roots", valkyr)) - botAI->CastSpell("Entangling Roots", valkyr); - break; - case CLASS_PALADIN: - if (!botAI->HasAura("Hammer of Justice", valkyr)) - botAI->CastSpell("Hammer of Justice", valkyr); - break; - case CLASS_WARRIOR: - if (!botAI->HasAura("Hamstring", valkyr)) - botAI->CastSpell("Hamstring", valkyr); - break; - case CLASS_HUNTER: - if (!botAI->HasAura("Concussive Shot", valkyr)) - botAI->CastSpell("Concussive Shot", valkyr); - break; - case CLASS_ROGUE: - if (!botAI->HasAura("Kidney Shot", valkyr)) - botAI->CastSpell("Kidney Shot", valkyr); - break; - case CLASS_SHAMAN: - if (!botAI->HasAura("Frost Shock", valkyr)) - botAI->CastSpell("Frost Shock", valkyr); - break; - case CLASS_DEATH_KNIGHT: - if (!botAI->HasAura("Chains of Ice", valkyr)) - botAI->CastSpell("Chains of Ice", valkyr); - break; - case CLASS_PRIEST: - if (!botAI->HasAura("Psychic Scream", valkyr)) - botAI->CastSpell("Psychic Scream", valkyr); - break; - case CLASS_WARLOCK: - if (!botAI->HasAura("Fear", valkyr)) - botAI->CastSpell("Fear", valkyr); - break; - default: - break; - } -} - -bool IccLichKingAddsAction::IsValkyr(Unit* unit) -{ - return unit->GetEntry() == NPC_VALKYR_SHADOWGUARD1 || unit->GetEntry() == NPC_VALKYR_SHADOWGUARD2 || - unit->GetEntry() == NPC_VALKYR_SHADOWGUARD3 || unit->GetEntry() == NPC_VALKYR_SHADOWGUARD4; -} - -void IccLichKingAddsAction::HandleVileSpiritMechanics() -{ - const float radiusVile = 12.0f; - - GuidVector npcs3 = AI_VALUE(GuidVector, "nearest hostile npcs"); - for (auto& npc : npcs3) - { - Unit* unit = botAI->GetUnit(npc); - if (!unit || (unit->GetEntry() != NPC_VILE_SPIRIT1 && unit->GetEntry() != NPC_VILE_SPIRIT2 && unit->GetEntry() != NPC_VILE_SPIRIT3 && - unit->GetEntry() != NPC_VILE_SPIRIT4)) - continue; - - // Only run away if the spirit is targeting us - if (unit->GetVictim() && unit->GetVictim()->GetGUID() == bot->GetGUID()) - { - float currentDistance = bot->GetDistance2d(unit); - - if (currentDistance < radiusVile) - { - botAI->Reset(); - MoveAway(unit, radiusVile - currentDistance); - } - } - } -} diff --git a/src/Ai/Raid/Icecrown/Action/RaidIccActions.h b/src/Ai/Raid/Icecrown/Action/RaidIccActions.h deleted file mode 100644 index 1d63a4e3696..00000000000 --- a/src/Ai/Raid/Icecrown/Action/RaidIccActions.h +++ /dev/null @@ -1,669 +0,0 @@ -#ifndef _PLAYERBOT_RAIDICCACTIONS_H -#define _PLAYERBOT_RAIDICCACTIONS_H - -#include "Action.h" -#include "MovementActions.h" -#include "PlayerbotAI.h" -#include "Playerbots.h" -#include "AttackAction.h" -#include "LastMovementValue.h" -#include "ObjectGuid.h" -#include "PlayerbotAIConfig.h" -#include "RaidIccStrategy.h" -#include "ScriptedCreature.h" -#include "SharedDefines.h" -#include "Trigger.h" -#include "CellImpl.h" -#include "GridNotifiers.h" -#include "GridNotifiersImpl.h" -#include "Vehicle.h" -#include "RaidIccTriggers.h" - -const Position ICC_LM_TANK_POSITION = Position(-391.0f, 2259.0f, 42.0f); -const Position ICC_DARK_RECKONING_SAFE_POSITION = Position(-523.33386f, 2211.2031f, 62.823116f); -const Position ICC_LDW_TANK_POSTION = Position(-570.1f, 2211.2456f, 49.476616f); //-590.0f -const Position ICC_ROTTING_FROST_GIANT_TANK_POSITION = Position(-328.5085f, 2225.5142f, 199.97298f); -const Position ICC_GUNSHIP_TELEPORT_ALLY = Position (-370.04645f, 1993.3536f, 466.65656f); -const Position ICC_GUNSHIP_TELEPORT_ALLY2 = Position (-392.66208f, 2064.893f, 466.5672f, 5.058196f); -const Position ICC_GUNSHIP_TELEPORT_HORDE = Position (-449.5343f, 2477.2024f, 470.17648f); -const Position ICC_GUNSHIP_TELEPORT_HORDE2 = Position (-429.81586f, 2400.6804f, 471.56537f); -const Position ICC_DBS_TANK_POSITION = Position(-494.26517f, 2211.549f, 541.11414f); -const Position ICC_FESTERGUT_TANK_POSITION = Position(4269.1772f, 3144.7673f, 360.38577f); -const Position ICC_FESTERGUT_RANGED_SPORE = Position(4261.143f, 3109.4146f, 360.38605f); -const Position ICC_FESTERGUT_MELEE_SPORE = Position(4269.1772f, 3144.7673f, 360.38577f); -const Position ICC_ROTFACE_TANK_POSITION = Position(4447.061f, 3150.9758f, 360.38568f); -const Position ICC_ROTFACE_BIG_OOZE_POSITION = Position(4432.687f, 3142.3035f, 360.38623f); -const Position ICC_ROTFACE_SAFE_POSITION = Position(4446.557f, 3065.6594f, 360.51843f); -const Position ICC_ROTFACE_CENTER_POSITION = Position(4446.0547f, 3144.8677f, 360.38593f); //actual center 4.74089 4445.6616f, 3137.1526f, 360.38608 -const Position ICC_PUTRICIDE_TANK_POSITION = Position(4373.227f, 3222.058f, 389.4029f); -const Position ICC_PUTRICIDE_GREEN_POSITION = Position(4423.4126f, 3194.2715f, 389.37683f); -const Position ICC_PUTRICIDE_BAD_POSITION = Position(4356.1724f, 3261.5232f, 389.3985f); -//const Position ICC_PUTRICIDE_GAS3_POSITION = Position(4367.753f, 3177.5894f, 389.39575f); -//const Position ICC_PUTRICIDE_GAS4_POSITION = Position(4321.8486f, 3206.464f, 389.3982f); -const Position ICC_BPC_OT_POSITION = Position(4649.2236f, 2796.0972f, 361.1815f); -const Position ICC_BPC_MT_POSITION = Position(4648.5674f, 2744.847f, 361.18222f); -const Position ICC_BQL_CENTER_POSITION = Position(4595.0f, 2769.0f, 400.0f); -const Position ICC_BQL_LWALL1_POSITION = Position(4624.685f, 2789.4895f, 400.13834f); -const Position ICC_BQL_LWALL2_POSITION = Position(4600.749f, 2805.7568f, 400.1374f); -const Position ICC_BQL_LWALL3_POSITION = Position(4572.857f, 2797.3872f, 400.1374f); -const Position ICC_BQL_RWALL1_POSITION = Position(4625.724f, 2748.9917f, 400.13693f); -const Position ICC_BQL_RWALL2_POSITION = Position(4608.3774f, 2735.7466f, 400.13693f); -const Position ICC_BQL_RWALL3_POSITION = Position(4576.813f, 2739.6067f, 400.13693f); -const Position ICC_BQL_LRWALL4_POSITION = Position(4539.345f, 2769.3853f, 403.7267f); -const Position ICC_BQL_TANK_POSITION = Position(4629.746f, 2769.6396f, 401.7479f); //old just in front of stairs 4616.102f, 2768.9167f, 400.13797f -const Position ICC_VDW_HEAL_POSITION = Position(4203.752f, 2483.4343f, 364.87274f); -const Position ICC_VDW_GROUP1_POSITION = Position(4203.585f, 2464.422f, 364.86887f); -const Position ICC_VDW_GROUP2_POSITION = Position(4203.5806f, 2505.2383f, 364.87677f); -const Position ICC_VDW_PORTALSTART_POSITION = Position(4202.637f, 2488.171f, 375.00256f); -const Position ICC_SINDRAGOSA_TANK_POSITION = Position(4408.016f, 2508.0647f, 203.37955f); -const Position ICC_SINDRAGOSA_FLYING_POSITION = Position(4525.6f, 2485.15f, 245.082f); -const Position ICC_SINDRAGOSA_RANGED_POSITION = Position(4441.572f, 2484.482f, 203.37836f); -const Position ICC_SINDRAGOSA_MELEE_POSITION = Position(4423.4546f, 2491.7175f, 203.37686f); -const Position ICC_SINDRAGOSA_BLISTERING_COLD_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f); -const Position ICC_SINDRAGOSA_THOMB1_POSITION = Position(4433.6484f, 2469.4133f, 203.3806f); -const Position ICC_SINDRAGOSA_THOMB2_POSITION = Position(4434.143f, 2486.201f, 203.37473f); -const Position ICC_SINDRAGOSA_THOMB3_POSITION = Position(4436.1147f, 2501.464f, 203.38266f); -const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC1_POSITION = Position(4444.9707f, 2455.7322f, 203.38701f); -const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC2_POSITION = Position(4461.3945f, 2463.5513f, 203.38727f); -const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC3_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f); -const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC4_POSITION = Position(4459.9336f, 2507.409f, 203.38606f); -const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC5_POSITION = Position(4442.3096f, 2512.4688f, 203.38647f); -const Position ICC_SINDRAGOSA_CENTER_POSITION = Position(4408.0464f, 2484.478f, 203.37529f); -const Position ICC_SINDRAGOSA_THOMBMB2_POSITION = Position(4436.895f, 2498.1401f, 203.38133f); -const Position ICC_SINDRAGOSA_FBOMB_POSITION = Position(4449.3647f, 2486.4524f, 203.379f); -const Position ICC_SINDRAGOSA_FBOMB10_POSITION = Position(4449.3647f, 2486.4524f, 203.379f); -const Position ICC_SINDRAGOSA_LOS2_POSITION = Position(4441.8286f, 2501.946f, 203.38435f); -const Position ICC_LICH_KING_ADDS_POSITION = Position(476.7332f, -2095.3894f, 840.857f); // old 486.63647f, -2095.7915f, 840.857f -const Position ICC_LICH_KING_MELEE_POSITION = Position(503.5546f, -2106.8213f, 840.857f); -const Position ICC_LICH_KING_RANGED_POSITION = Position(501.3563f, -2085.1816f, 840.857f); -const Position ICC_LICH_KING_ASSISTHC_POSITION = Position(517.2145f, -2125.0674f, 840.857f); -const Position ICC_LK_FROST1_POSITION = Position(503.96548f, -2183.216f, 840.857f); -const Position ICC_LK_FROST2_POSITION = Position(563.07166f, -2125.7578f, 840.857f); -const Position ICC_LK_FROST3_POSITION = Position(503.40182f, -2067.3435f, 840.857f); -const Position ICC_LK_FROSTR1_POSITION = Position(481.168f, -2177.8723f, 840.857f); -const Position ICC_LK_FROSTR2_POSITION = Position(562.20807f, -2100.2393f, 840.857f); -const Position ICC_LK_FROSTR3_POSITION = Position(526.35297f, -2071.0317f, 840.857f); - -//Lord Marrogwar -class IccLmTankPositionAction : public AttackAction -{ -public: - IccLmTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc lm tank position") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - bool MoveTowardPosition(const Position& position, float incrementSize); -}; - -class IccSpikeAction : public AttackAction -{ -public: - IccSpikeAction(PlayerbotAI* botAI) : AttackAction(botAI, "icc spike") {} - bool Execute(Event event) override; - - bool HandleSpikeTargeting(Unit* boss); - bool MoveTowardPosition(const Position& position, float incrementSize); - void UpdateRaidTargetIcon(Unit* target); -}; - -//Lady Deathwhisper -class IccDarkReckoningAction : public MovementAction -{ -public: - IccDarkReckoningAction(PlayerbotAI* botAI, std::string const name = "icc dark reckoning") - : MovementAction(botAI, name) {} - bool Execute(Event event) override; -}; - -class IccRangedPositionLadyDeathwhisperAction : public AttackAction -{ -public: - IccRangedPositionLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc ranged position lady deathwhisper") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - bool MaintainRangedSpacing(); -}; - -class IccAddsLadyDeathwhisperAction : public AttackAction -{ -public: - IccAddsLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc adds lady deathwhisper") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - bool IsTargetedByShade(uint32 shadeEntry); - bool MoveTowardPosition(const Position& position, float incrementSize); - bool HandleAddTargeting(Unit* boss); - void UpdateRaidTargetIcon(Unit* target); - -}; - -class IccShadeLadyDeathwhisperAction : public MovementAction -{ -public: - IccShadeLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc shade lady deathwhisper") - : MovementAction(botAI, name) {} - bool Execute(Event event) override; -}; - -//Gunship Battle -class IccRottingFrostGiantTankPositionAction : public AttackAction -{ -public: - IccRottingFrostGiantTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotting frost giant tank position") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; -}; - -class IccCannonFireAction : public Action -{ -public: - IccCannonFireAction(PlayerbotAI* botAI, std::string const name = "icc cannon fire") - : Action(botAI, name) {} - bool Execute(Event event) override; - - Unit* FindValidCannonTarget(); - bool TryCastCannonSpell(uint32 spellId, Unit* target, Unit* vehicleBase); -}; - -class IccGunshipEnterCannonAction : public MovementAction -{ -public: - IccGunshipEnterCannonAction(PlayerbotAI* botAI, std::string const name = "icc gunship enter cannon") - : MovementAction(botAI, name) {} - bool Execute(Event event) override; - - bool EnterVehicle(Unit* vehicleBase, bool moveIfFar); - Unit* FindBestAvailableCannon(); - bool IsValidCannon(Unit* vehicle, const uint32 validEntries[]); -}; - -class IccGunshipTeleportAllyAction : public AttackAction -{ -public: - IccGunshipTeleportAllyAction(PlayerbotAI* botAI, std::string const name = "icc gunship teleport ally") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - bool TeleportTo(const Position& position); - void CleanupSkullIcon(uint8_t SKULL_ICON_INDEX); - void UpdateBossSkullIcon(Unit* boss, uint8_t SKULL_ICON_INDEX); -}; - -class IccGunshipTeleportHordeAction : public AttackAction -{ -public: - IccGunshipTeleportHordeAction(PlayerbotAI* botAI, std::string const name = "icc gunship teleport horde") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - bool TeleportTo(const Position& position); - void CleanupSkullIcon(uint8_t SKULL_ICON_INDEX); - void UpdateBossSkullIcon(Unit* boss, uint8_t SKULL_ICON_INDEX); -}; - -//DBS -class IccDbsTankPositionAction : public AttackAction -{ -public: - IccDbsTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc dbs tank position") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - bool CrowdControlBloodBeasts(); - bool EvadeBloodBeasts(); - bool PositionInRangedFormation(); -}; - -class IccAddsDbsAction : public AttackAction -{ -public: - IccAddsDbsAction(PlayerbotAI* botAI, std::string const name = "icc adds dbs") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - Unit* FindPriorityTarget(Unit* boss); - void UpdateSkullMarker(Unit* priorityTarget); -}; - -//FESTERGUT -class IccFestergutGroupPositionAction : public AttackAction -{ -public: - IccFestergutGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc festergut group position") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - bool HasSporesInGroup(); - bool PositionNonTankMembers(); - int CalculatePositionIndex(Group* group); -}; - -class IccFestergutSporeAction : public AttackAction -{ -public: - IccFestergutSporeAction(PlayerbotAI* botAI, std::string const name = "icc festergut spore") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - Position CalculateSpreadPosition(); - struct SporeInfo - { - std::vector sporedPlayers; - ObjectGuid lowestGuid; - bool hasLowestGuid = false; - }; - SporeInfo FindSporedPlayers(); - Position DetermineTargetPosition(bool hasSpore, const SporeInfo& sporeInfo, const Position& spreadRangedPos); - bool CheckMainTankSpore(); -}; - -//Rotface -class IccRotfaceTankPositionAction : public AttackAction -{ -public: - IccRotfaceTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface tank position") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - void MarkBossWithSkull(Unit* boss); - bool PositionMainTankAndMelee(Unit *boss); - bool HandleAssistTankPositioning(Unit* boss); - bool HandleBigOozePositioning(Unit* boss); -}; - -class IccRotfaceGroupPositionAction : public AttackAction -{ -public: - IccRotfaceGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface group position") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - //bool MoveAwayFromBigOoze(Unit* bigOoze); - bool HandlePuddleAvoidance(Unit* boss); - bool MoveAwayFromPuddle(Unit* boss, Unit* puddle, float puddleDistance); - bool HandleOozeTargeting(); - bool HandleOozeMemberPositioning(); - bool PositionRangedAndHealers(Unit* boss,Unit* smallOoze); - bool FindAndMoveFromClosestMember(Unit* smallOoze); -}; - -class IccRotfaceMoveAwayFromExplosionAction : public MovementAction -{ -public: - IccRotfaceMoveAwayFromExplosionAction(PlayerbotAI* botAI, std::string const name = "icc rotface move away from explosion") - : MovementAction(botAI, name) {} - bool Execute(Event event) override; - - bool MoveToRandomSafeLocation(); - -}; - -//PP -class IccPutricideGrowingOozePuddleAction : public AttackAction -{ -public: - IccPutricideGrowingOozePuddleAction(PlayerbotAI* botAI, std::string const name = "icc putricide growing ooze puddle") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - Unit* FindClosestThreateningPuddle(); - Position CalculateSafeMovePosition(Unit* closestPuddle); - bool IsPositionTooCloseToOtherPuddles(float x, float y, Unit* ignorePuddle); -}; - -class IccPutricideVolatileOozeAction : public AttackAction -{ -public: - IccPutricideVolatileOozeAction(PlayerbotAI* botAI, std::string const name = "icc putricide volatile ooze") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - void MarkOozeWithSkull(Unit* ooze); - Unit* FindAuraTarget(); -}; - -class IccPutricideGasCloudAction : public AttackAction -{ -public: - IccPutricideGasCloudAction(PlayerbotAI* botAI, std::string const name = "icc putricide gas cloud") - : AttackAction(botAI, name) {} - bool Execute(Event event) override; - - bool HandleGaseousBloatMovement(Unit* gasCloud); - Position CalculateEmergencyPosition(const Position& botPos, float dx, float dy); - bool FindSafeMovementPosition(const Position& botPos, const Position& cloudPos, float dx, float dy, int numAngles, - Position& resultPos); - bool HandleGroupAuraSituation(Unit* gasCloud); - bool GroupHasGaseousBloat(Group* group); -}; - -class IccPutricideAvoidMalleableGooAction : public MovementAction -{ -public: - IccPutricideAvoidMalleableGooAction(PlayerbotAI* botAI, std::string const name = "icc putricide avoid malleable goo") - : MovementAction(botAI, name) {} - bool Execute(Event event) override; - - bool HandleTankPositioning(Unit* boss); - bool HandleUnboundPlague(Unit* boss); - bool HandleBossPositioning(Unit* boss); - Position CalculateBossPosition(Unit* boss, float distance); - bool HasObstacleBetween(const Position& from, const Position& to); - bool IsOnPath(const Position& from, const Position& to, const Position& point, float threshold); - Position CalculateArcPoint(const Position& current, const Position& target, const Position& center); - Position CalculateIncrementalMove(const Position& current, const Position& target, float maxDistance); -}; - -//BPC -class IccBpcKelesethTankAction : public AttackAction -{ -public: - IccBpcKelesethTankAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc bpc keleseth tank") {} - bool Execute(Event event) override; -}; - -class IccBpcMainTankAction : public AttackAction -{ -public: - IccBpcMainTankAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc bpc main tank") {} - bool Execute(Event event) override; - - void MarkEmpoweredPrince(); -}; - -class IccBpcEmpoweredVortexAction : public MovementAction -{ -public: - IccBpcEmpoweredVortexAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc bpc empowered vortex") {} - bool Execute(Event event) override; - - bool MaintainRangedSpacing(); - bool HandleEmpoweredVortexSpread(); -}; - -class IccBpcKineticBombAction : public AttackAction -{ -public: - IccBpcKineticBombAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc bpc kinetic bomb") {} - bool Execute(Event event) override; - - Unit* FindOptimalKineticBomb(); - bool IsBombAlreadyHandled(Unit* bomb, Group* group); -}; - -class IccBpcBallOfFlameAction : public MovementAction -{ -public: - IccBpcBallOfFlameAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc bpc ball of flame") {} - bool Execute(Event event) override; -}; - -//Blood Queen Lana'thel -class IccBqlGroupPositionAction : public AttackAction -{ -public: - IccBqlGroupPositionAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc group tank position") {} - bool Execute(Event event) override; - - bool HandleTankPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura); - bool HandleShadowsMovement(); - Position AdjustControlPoint(const Position& wall, const Position& center, float factor); - Position CalculateBezierPoint(float t, const Position path[4]); - bool HandleGroupPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura); - -private: - // Evaluate curves - struct CurveInfo - { - Position moveTarget; - int curveIdx = 0; - bool foundSafe = false; - float minDist = 0.0f; - float score = 0.0f; - Position closestPoint; - float t_closest = 0.0f; - }; -}; - -class IccBqlPactOfDarkfallenAction : public MovementAction -{ -public: - IccBqlPactOfDarkfallenAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc bql pact of darkfallen") {} - bool Execute(Event event) override; - - void CalculateCenterPosition(Position& targetPos, const std::vector& playersWithAura); - bool MoveToTargetPosition(const Position& targetPos, int auraCount); -}; - -class IccBqlVampiricBiteAction : public AttackAction -{ -public: - IccBqlVampiricBiteAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc bql vampiric bite") {} - bool Execute(Event event) override; - - Player* FindBestBiteTarget(Group* group); - bool IsInvalidTarget(Player* player); - bool MoveTowardsTarget(Player* target); - bool CastVampiricBite(Player* target); -}; - -// Sister Svalna -class IccValkyreSpearAction : public AttackAction -{ -public: - IccValkyreSpearAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc valkyre spear") {} - bool Execute(Event event) override; -}; - -class IccSisterSvalnaAction : public AttackAction -{ -public: - IccSisterSvalnaAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc sister svalna") {} - bool Execute(Event event) override; -}; - -// Valithria Dreamwalker - -class IccValithriaGroupAction : public AttackAction -{ -public: - IccValithriaGroupAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc valithria group") {} - bool Execute(Event event) override; - - bool MoveTowardsPosition(const Position& pos, float increment); - bool Handle25ManGroupLogic(); - bool HandleMarkingLogic(bool inGroup1, bool inGroup2, const Position& group1Pos, const Position& group2Pos); - bool Handle10ManGroupLogic(); -}; - -class IccValithriaPortalAction : public MovementAction -{ -public: - IccValithriaPortalAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc valithria portal") {} - bool Execute(Event event) override; -}; - -class IccValithriaHealAction : public AttackAction -{ -public: - IccValithriaHealAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc valithria heal") {} - bool Execute(Event event) override; -}; - -class IccValithriaDreamCloudAction : public MovementAction -{ -public: - IccValithriaDreamCloudAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc valithria dream cloud") {} - bool Execute(Event event) override; -}; - -//Sindragosa -class IccSindragosaGroupPositionAction : public AttackAction -{ -public: - IccSindragosaGroupPositionAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc sindragosa group position") {} - bool Execute(Event event) override; - - bool HandleTankPositioning(Unit* boss); - bool HandleNonTankPositioning(); - bool MoveIncrementallyToPosition(const Position& targetPos, float maxStep); -}; - -class IccSindragosaFrostBeaconAction : public MovementAction -{ -public: - IccSindragosaFrostBeaconAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc sindragosa frost beacon") {} - bool Execute(Event event) override; - - void HandleSupportActions(); - bool HandleBeaconedPlayer(const Unit* boss); - bool HandleNonBeaconedPlayer(const Unit* boss); - bool MoveToPositionIfNeeded(const Position& position, float tolerance); - bool MoveToPosition(const Position& position); - bool IsBossFlying(const Unit* boss); - - private: - static constexpr uint32 FROST_BEACON_AURA_ID = SPELL_FROST_BEACON; - static constexpr uint32 HAND_OF_FREEDOM_SPELL_ID = 1044; - static constexpr float POSITION_TOLERANCE = 1.0f; - static constexpr float TOMB_POSITION_TOLERANCE = 0.5f; - static constexpr float MIN_SAFE_DISTANCE = 13.0f; - static constexpr float MOVE_TOLERANCE = 2.0f; -}; - -class IccSindragosaBlisteringColdAction : public MovementAction -{ -public: - IccSindragosaBlisteringColdAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc sindragosa blistering cold") {} - bool Execute(Event event) override; -}; - -class IccSindragosaUnchainedMagicAction : public AttackAction -{ -public: - IccSindragosaUnchainedMagicAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc sindragosa unchained magic") {} - bool Execute(Event event) override; -}; - -class IccSindragosaChilledToTheBoneAction : public AttackAction -{ -public: - IccSindragosaChilledToTheBoneAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc sindragosa chilled to the bone") {} - bool Execute(Event event) override; -}; - -class IccSindragosaMysticBuffetAction : public MovementAction -{ -public: - IccSindragosaMysticBuffetAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc sindragosa mystic buffet") {} - bool Execute(Event event) override; -}; - -class IccSindragosaFrostBombAction : public MovementAction -{ -public: - IccSindragosaFrostBombAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc sindragosa frost bomb") {} - bool Execute(Event event) override; -}; - -class IccSindragosaTankSwapPositionAction : public AttackAction -{ - public: - IccSindragosaTankSwapPositionAction(PlayerbotAI* botAI) - : AttackAction(botAI, "sindragosa tank swap position") {} - bool Execute(Event event) override; -}; - -//LK -class IccLichKingShadowTrapAction : public MovementAction -{ - public: - IccLichKingShadowTrapAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc lich king shadow trap") {} - bool Execute(Event event) override; -}; - -class IccLichKingNecroticPlagueAction : public MovementAction -{ - public: - IccLichKingNecroticPlagueAction(PlayerbotAI* botAI) - : MovementAction(botAI, "icc lich king necrotic plague") {} - bool Execute(Event event) override; -}; - -class IccLichKingWinterAction : public AttackAction -{ - public: - IccLichKingWinterAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc lich king winter") {} - bool Execute(Event event) override; - - void HandlePositionCorrection(); - bool IsValidCollectibleAdd(Unit* unit); - bool IsPositionSafeFromDefile(float x, float y, float minSafeDistance); - void HandleTankPositioning(); - void HandleMeleePositioning(); - void HandleRangedPositioning(); - void HandleMainTankAddManagement(const Position* tankPos); - void HandleAssistTankAddManagement(const Position* tankPos); - - private: - const Position* GetMainTankPosition(); - const Position* GetMainTankRangedPosition(); - bool TryMoveToPosition(float targetX, float targetY, float targetZ, bool isForced = true); -}; - -class IccLichKingAddsAction : public AttackAction -{ - public: - IccLichKingAddsAction(PlayerbotAI* botAI) - : AttackAction(botAI, "icc lich king adds") {} - bool Execute(Event event) override; - - void HandleTeleportationFixes(Difficulty diff, Unit* terenasMenethilHC); - bool HandleSpiritBombAvoidance(Difficulty diff, Unit* terenasMenethilHC); - void HandleHeroicNonTankPositioning(Difficulty diff, Unit* terenasMenethilHC); - void HandleSpiritMarkingAndTargeting(Difficulty diff, Unit* terenasMenethilHC); - bool HandleQuakeMechanics(Unit* boss); - void HandleShamblingHorrors(); - bool HandleAssistTankAddManagement(Unit* boss, Difficulty diff); - void HandleMeleePositioning(Unit* boss, bool hasPlague, Difficulty diff); - void HandleMainTankTargeting(Unit* boss, Difficulty diff); - void HandleNonTankHeroicPositioning(Unit* boss, Difficulty diff, bool hasPlague); - void HandleRangedPositioning(Unit* boss, bool hasPlague, Difficulty diff); - void HandleDefileMechanics(Unit* boss, Difficulty diff); - void HandleValkyrMechanics(Difficulty diff); - std::vector CalculateBalancedGroupSizes(size_t totalAssist, size_t numValkyrs); - size_t GetAssignedValkyrIndex(size_t assistIndex, const std::vector& groupSizes); - std::string GetRTIValueForValkyr(size_t valkyrIndex); - void HandleValkyrMarking(const std::vector& grabbingValkyrs, Difficulty diff); - void HandleValkyrAssignment(const std::vector& grabbingValkyrs); - void ApplyCCToValkyr(Unit* valkyr); - bool IsValkyr(Unit* unit); - void HandleVileSpiritMechanics(); -}; - -#endif diff --git a/src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.cpp b/src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.cpp deleted file mode 100644 index 710c15e0a03..00000000000 --- a/src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.cpp +++ /dev/null @@ -1,872 +0,0 @@ -#include "RaidIccMultipliers.h" - -#include "ChooseTargetActions.h" -#include "DKActions.h" -#include "DruidActions.h" -#include "DruidBearActions.h" -#include "FollowActions.h" -#include "GenericActions.h" -#include "GenericSpellActions.h" -#include "HunterActions.h" -#include "MageActions.h" -#include "MovementActions.h" -#include "PaladinActions.h" -#include "PriestActions.h" -#include "RaidIccActions.h" -#include "ReachTargetActions.h" -#include "RogueActions.h" -#include "ShamanActions.h" -#include "UseMeetingStoneAction.h" -#include "WarriorActions.h" -#include "PlayerbotAI.h" -#include "RaidIccTriggers.h" - -// LK global variables -namespace -{ -std::map g_plagueTimes; -std::map g_allowCure; -std::mutex g_plagueMutex; // Lock before accessing shared variables -} - -// Lady Deathwhisper -float IccLadyDeathwhisperMultiplier::GetValue(Action* action) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); - if (!boss) - return 1.0f; - - if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - - static constexpr uint32 VENGEFUL_SHADE_ID = NPC_SHADE; - - // Get the nearest hostile NPCs - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - - // Allow the IccShadeLadyDeathwhisperAction to run - if (dynamic_cast(action)) - return 1.0f; - - for (auto const& npcGuid : npcs) - { - Unit* shade = botAI->GetUnit(npcGuid); - - if (!shade || shade->GetEntry() != VENGEFUL_SHADE_ID) - continue; - - if (!shade->GetVictim() || shade->GetVictim()->GetGUID() != bot->GetGUID()) - continue; - - return 0.0f; // Cancel all other actions when we need to handle Vengeful Shade - } - - return 1.0f; -} - -// dbs -float IccAddsDbsMultiplier::GetValue(Action* action) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); - if (!boss) - return 1.0f; - - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - - if (botAI->IsRanged(bot)) - if (dynamic_cast(action)) - return 0.0f; - - if (botAI->IsMainTank(bot)) - { - Aura* aura = botAI->GetAura("rune of blood", bot); - if (aura) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - } - - return 1.0f; -} - -// dogs -float IccDogsMultiplier::GetValue(Action* action) -{ - bool bossPresent = false; - if (AI_VALUE2(Unit*, "find target", "stinky") || AI_VALUE2(Unit*, "find target", "precious")) - bossPresent = true; - - if (!bossPresent) - return 1.0f; - - if (botAI->IsMainTank(bot)) - { - Aura* aura = botAI->GetAura("mortal wound", bot, false, true); - if (aura && aura->GetStackAmount() >= 8) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - } - return 1.0f; -} - -// Festergut -float IccFestergutMultiplier::GetValue(Action* action) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); - if (!boss) - return 1.0f; - - if (dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - - if (dynamic_cast(action)) - return 0.0f; - - if (botAI->IsMainTank(bot)) - { - Aura* aura = botAI->GetAura("gastric bloat", bot, false, true); - if (aura && aura->GetStackAmount() >= 6) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - } - - if (dynamic_cast(action)) - return 1.0f; - - if (bot->HasAura(SPELL_GAS_SPORE)) - return 0.0f; - - return 1.0f; -} - -// Rotface -float IccRotfaceMultiplier::GetValue(Action* action) -{ - Unit* boss1 = AI_VALUE2(Unit*, "find target", "rotface"); - if (!boss1) - return 1.0f; - - if (dynamic_cast(action)) - return 0.0f; - - if (dynamic_cast(action) && !(bot->getClass() == CLASS_HUNTER)) - return 0.0f; - - if (dynamic_cast(action)) - return 0.0f; - - if (botAI->IsAssistTank(bot) && (dynamic_cast(action) || dynamic_cast(action))) - return 0.0f; - - Unit* boss = AI_VALUE2(Unit*, "find target", "big ooze"); - if (!boss) - return 1.0f; - - static std::map lastExplosionTimes; - static std::map hasMoved; - - ObjectGuid botGuid = bot->GetGUID(); - - // When cast starts, record the time - if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION)) - { - if (lastExplosionTimes[botGuid] == 0) // Only set if not already set - { - lastExplosionTimes[botGuid] = time(nullptr); - hasMoved[botGuid] = false; - } - } - - // If explosion cast is no longer active, reset the timers - if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION)) - { - if (lastExplosionTimes[botGuid] > 0 && time(nullptr) - lastExplosionTimes[botGuid] >= 16) - { - lastExplosionTimes[botGuid] = 0; - hasMoved[botGuid] = false; - return 1.0f; // Allow normal actions to resume - } - } - - // If 9 seconds have passed since cast start and we haven't moved yet - if (lastExplosionTimes[botGuid] > 0 && !hasMoved[botGuid] && time(nullptr) - lastExplosionTimes[botGuid] >= 9) - { - if (dynamic_cast(action) - && !dynamic_cast(action)) - { - return 0.0f; // Block other movement actions - } - hasMoved[botGuid] = true; // Mark that we've initiated movement - } - - // Continue blocking other movements for 7 seconds after moving - if (hasMoved[botGuid] && time(nullptr) - lastExplosionTimes[botGuid] < 16 // 9 seconds wait + 7 seconds stay - && dynamic_cast(action) - && !dynamic_cast(action)) - return 0.0f; - - return 1.0f; -} - -// pp -float IccAddsPutricideMultiplier::GetValue(Action* action) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); - if (!boss) - return 1.0f; - - bool hasGaseousBloat = botAI->HasAura("Gaseous Bloat", bot); - bool hasUnboundPlague = botAI->HasAura("Unbound Plague", bot); - - if (!(bot->getClass() == CLASS_HUNTER) && dynamic_cast(action)) - return 0.0f; - - if (dynamic_cast(action)) - return 0.0f; - - if (dynamic_cast(action)) - return 0.0f; - - if (dynamic_cast(action)) - return 0.0f; - - if (botAI->IsMainTank(bot)) - { - Aura* aura = botAI->GetAura("mutated plague", bot, false, true); - if (aura && aura->GetStackAmount() >= 4) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - } - - if (hasGaseousBloat) - { - if (dynamic_cast(action)) - return 1.0f; - - if (dynamic_cast(action)) - return 1.0f; - - if (botAI->IsHeal(bot)) - return 1.0f; - else - return 0.0f; // Cancel all other actions when we need to handle Gaseous Bloat - } - - if (hasUnboundPlague && boss && !boss->HealthBelowPct(35)) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; // Cancel all other actions when we need to handle Unbound Plague - } - - if (dynamic_cast(action)) - { - if (dynamic_cast(action)) - return 0.0f; - if (dynamic_cast(action) && !botAI->IsMainTank(bot)) - return 0.0f; - //if (dynamic_cast(action) && !hasGaseousBloat) - //return 0.0f; - } - - return 1.0f; -} - -// bpc -float IccBpcAssistMultiplier::GetValue(Action* action) -{ - Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth"); - if (!keleseth) - return 1.0f; - - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - - Aura* aura = botAI->GetAura("Shadow Prison", bot, false, true); - if (aura) - { - if (aura->GetStackAmount() > 18 && botAI->IsTank(bot)) - { - if (dynamic_cast(action)) - return 0.0f; - } - - if (aura->GetStackAmount() > 12 && !botAI->IsTank(bot)) - { - if (dynamic_cast(action)) - return 0.0f; - } - } - - Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); - if (!valanar) - return 1.0f; - - if (valanar && valanar->HasUnitState(UNIT_STATE_CASTING) && - (valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX1) || - valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX2) || - valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX3) || - valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX4))) - { - if (dynamic_cast(action) || dynamic_cast(action)) - return 1.0f; - else - return 0.0f; // Cancel all other actions when we need to handle Empowered Vortex - } - - Unit* flame1 = bot->FindNearestCreature(NPC_BALL_OF_FLAME, 100.0f); - Unit* flame2 = bot->FindNearestCreature(NPC_BALL_OF_INFERNO_FLAME, 100.0f); - bool ballOfFlame = flame1 && flame1->GetVictim() == bot; - bool infernoFlame = flame2 && flame2->GetVictim() == bot; - - if (flame2) - { - if (dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - - if (dynamic_cast(action)) - return 1.0f; - } - - if (ballOfFlame || infernoFlame) - { - // If bot is tank, do nothing special - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; // Cancel all other actions when we need to handle Ball of Flame - } - - static const std::array bombEntries = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2, NPC_KINETIC_BOMB3, - NPC_KINETIC_BOMB4}; - const GuidVector bombs = AI_VALUE(GuidVector, "possible targets no los"); - - bool bombFound = false; - - for (const auto entry : bombEntries) - { - for (auto const& guid : bombs) - { - if (Unit* unit = botAI->GetUnit(guid)) - { - if (unit->GetEntry() == entry) - { - // Check if bomb is within valid Z-axis range - if (unit->GetPositionZ() - bot->GetPositionZ() < 25.0f) - { - bombFound = true; - break; - } - } - } - } - if (bombFound) - break; - } - - if (bombFound && !(aura && aura->GetStackAmount() > 12) && !botAI->IsTank(bot)) - { - // If kinetic bomb action is active, disable these actions - if (dynamic_cast(action)) - return 1.0f; - - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } - - // For assist tank during BPC fight - if (botAI->IsAssistTank(bot) && !(aura && aura->GetStackAmount() > 18)) - { - // Allow BPC-specific actions - if (dynamic_cast(action)) - return 1.0f; - - // Disable normal assist behavior - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - - } - - return 1.0f; -} - -//BQL -float IccBqlMultiplier::GetValue(Action* action) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); - if (!boss) - return 1.0f; - - Aura* aura2 = botAI->GetAura("Swarming Shadows", bot); - Aura* aura = botAI->GetAura("Frenzied Bloodthirst", bot); - - if (botAI->IsRanged(bot)) - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - - // If bot has Pact of Darkfallen aura, return 0 for all other actions - if (bot->HasAura(SPELL_PACT_OF_THE_DARKFALLEN)) - { - if (dynamic_cast(action)) - return 1.0f; // Allow Pact of Darkfallen action - else - return 0.0f; // Cancel all other actions when we need to handle Pact of Darkfallen - } - - if (botAI->IsMelee(bot) && ((boss->GetPositionZ() - ICC_BQL_CENTER_POSITION.GetPositionZ()) > 5.0f) && !aura) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - - // If bot has frenzied bloodthirst, allow highest priority for bite action - if (aura) // If bot has frenzied bloodthirst - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - - if (aura2 && !aura) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; // Cancel all other actions when we need to handle Swarming Shadows - } - - if ((boss->GetExactDist2d(ICC_BQL_TANK_POSITION.GetPositionX(), ICC_BQL_TANK_POSITION.GetPositionY()) > 10.0f) && - botAI->IsRanged(bot) && !((boss->GetPositionZ() - bot->GetPositionZ()) > 5.0f)) - { - if (dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - } - - return 1.0f; -} - -//VDW -float IccValithriaDreamCloudMultiplier::GetValue(Action* action) -{ - Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f); - - Aura* twistedNightmares = botAI->GetAura("Twisted Nightmares", bot); - Aura* emeraldVigor = botAI->GetAura("Emerald Vigor", bot); - - if (!boss && !bot->HasAura(SPELL_DREAM_STATE)) - return 1.0f; - - if (dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - - if (botAI->IsTank(bot)) - { - if (dynamic_cast(action)) - return 0.0f; - } - - if (botAI->IsHeal(bot) && (twistedNightmares || emeraldVigor)) - if (dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - - if (bot->HasAura(SPELL_DREAM_STATE) && !bot->HealthBelowPct(50)) - { - if (dynamic_cast(action)) - return 1.0f; // Allow Dream Cloud action - else - return 0.0f; // Cancel all other actions when we need to handle Dream Cloud - } - - return 1.0f; - -} - -//SINDRAGOSA - -float IccSindragosaMultiplier::GetValue(Action* action) -{ - Unit* boss = bot->FindNearestCreature(NPC_SINDRAGOSA, 200.0f); - if (!boss) - return 1.0f; - Aura* aura = botAI->GetAura("Unchained Magic", bot, false, true); - - Difficulty diff = bot->GetRaidDifficulty(); - - if (boss->HealthBelowPct(95)) - { - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - } - - if (aura && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC) && - !dynamic_cast(action)) - { - if (dynamic_cast(action) || dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - - // Check if boss is casting blistering cold (using both normal and heroic spell IDs) - if (boss->HasUnitState(UNIT_STATE_CASTING) && - (boss->FindCurrentSpellBySpellId(70123) || boss->FindCurrentSpellBySpellId(71047) || - boss->FindCurrentSpellBySpellId(71048) || boss->FindCurrentSpellBySpellId(71049))) - { - // If this is the blistering cold action, give it highest priority - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 1.0f; - - // Disable all other actions while blistering cold is casting - return 0.0f; - } - - // Highest priority if we have beacon - if (bot->HasAura(SPELL_FROST_BEACON)) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - - Group* group = bot->GetGroup(); - // Check if anyone in group has Frost Beacon (SPELL_FROST_BEACON) - bool anyoneHasFrostBeacon = false; - - if (group) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON)) - { - anyoneHasFrostBeacon = true; - break; - } - } - } - - if (anyoneHasFrostBeacon && boss && - boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), - ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f && - !boss->HealthBelowPct(25) && !boss->HealthAbovePct(99)) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - - if (anyoneHasFrostBeacon && !botAI->IsMainTank(bot)) - { - if (dynamic_cast(action)) - return 0.0f; - } - - if (botAI->IsMainTank(bot)) - { - Aura* aura = botAI->GetAura("mystic buffet", bot, false, true); - if (aura && aura->GetStackAmount() >= 6) - { - if (dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - } - - if (!botAI->IsTank(bot) && boss && boss->HealthBelowPct(35)) - { - if (dynamic_cast(action)) - return 0.0f; - } - - if (boss && botAI->IsTank(bot)) - { - if (boss->HealthBelowPct(35)) - { - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - } - - if (boss && boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f && !boss->HealthBelowPct(25) && !boss->HealthAbovePct(99)) - { - if (dynamic_cast(action)) - return 1.0f; - - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } - - return 1.0f; -} - -float IccLichKingAddsMultiplier::GetValue(Action* action) -{ - Unit* terenasMenethilHC = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f); - - if (!terenasMenethilHC) - if (dynamic_cast(action)) - return 0.0f; - - if (terenasMenethilHC) - { - Unit* mainTank = AI_VALUE(Unit*, "main tank"); - - if (!botAI->IsMainTank(bot) && mainTank && bot->GetExactDist2d(mainTank->GetPositionX(), mainTank->GetPositionY()) < 2.0f) - { - if (dynamic_cast(action)) - return 0.0f; - } - - if (botAI->IsMelee(bot) || (bot->getClass() == CLASS_WARLOCK)) - { - if (dynamic_cast(action) || dynamic_cast(action)) - return 1.0f; - else - return 0.0f; - } - - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - } - - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); - if (!boss) - return 1.0f; - - // Handle cure actions - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) - { - Group* group = bot->GetGroup(); - if (!group) - return 1.0f; - - // Check if any bot in the group has plague - bool anyBotHasPlague = false; - ObjectGuid plaguedPlayerGuid; // Track who has plague - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - if (Player* member = ref->GetSource()) - { - if (botAI->HasAura("Necrotic Plague", member)) - { - anyBotHasPlague = true; - plaguedPlayerGuid = member->GetGUID(); // Changed from GetObjectGuid() - break; - } - } - } - - uint32 currentTime = getMSTime(); - - // Reset state if no one has plague - if (!anyBotHasPlague) - { - std::lock_guard lock(g_plagueMutex); // Properly initialized - g_plagueTimes.clear(); - g_allowCure.clear(); - return 1.0f; - } - - { // New scope for lock_guard - std::lock_guard lock(g_plagueMutex); // Properly initialized - - // Start timer if this is a new plague - if (g_plagueTimes.find(plaguedPlayerGuid) == g_plagueTimes.end()) - { - g_plagueTimes[plaguedPlayerGuid] = currentTime; - g_allowCure[plaguedPlayerGuid] = false; - return 0.0f; - } - - // Once we allow cure, keep allowing it until plague is gone - if (g_allowCure[plaguedPlayerGuid]) - { - return 1.0f; - } - - // Check if enough time has passed (2,5 seconds) - if (currentTime - g_plagueTimes[plaguedPlayerGuid] >= 2500) - { - g_allowCure[plaguedPlayerGuid] = true; - return 1.0f; - } - } // lock_guard is automatically released here - - return 0.0f; - } - - if (dynamic_cast(action) && (bot->getClass() != CLASS_HUNTER)) - return 0.0f; - - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - - if (boss && !boss->HealthBelowPct(71)) - { - if (!botAI->IsTank(bot)) - if (dynamic_cast(action)) - return 0.0f; - - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; - } - - Unit* currentTarget = AI_VALUE(Unit*, "current target"); - - bool hasWinterAura = false; - if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || - boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4))) - hasWinterAura = true; - - bool hasWinter2Aura = false; - if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || - boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8))) - hasWinter2Aura = true; - - bool isCasting = false; - if (boss && boss->HasUnitState(UNIT_STATE_CASTING)) - isCasting = true; - - bool isWinter = false; - if (boss && (boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8))) - isWinter = true; - - if (hasWinterAura || hasWinter2Aura || (isCasting && isWinter)) - { - if (dynamic_cast(action) || dynamic_cast(action)) - return 1.0f; - - if (botAI->IsAssistTank(bot) && dynamic_cast(action)) - return 0.0f; - - if (dynamic_cast(action)) - return 0.0f; - - if (currentTarget && boss && bot->GetDistance2d(boss->GetPositionX(), boss->GetPositionY()) > 50.0f && currentTarget == boss) - { - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } - - if (currentTarget && (currentTarget->GetEntry() == NPC_ICE_SPHERE1 || currentTarget->GetEntry() == NPC_ICE_SPHERE2 || - currentTarget->GetEntry() == NPC_ICE_SPHERE3 || currentTarget->GetEntry() == NPC_ICE_SPHERE4)) - { - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } - - } - - if (botAI->IsRanged(bot) && !botAI->GetAura("Harvest Soul", bot, false, false)) - { - // Check for defile presence - GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - bool defilePresent = false; - for (auto& npc : npcs) - { - Unit* unit = botAI->GetUnit(npc); - if (unit && unit->IsAlive() && unit->GetEntry() == DEFILE_NPC_ID) // Defile entry - { - defilePresent = true; - break; - } - } - - // Only disable movement if defile is present - if (defilePresent && ( - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action))) - { - return 0.0f; - } - } - - if (botAI->IsAssistTank(bot) && boss && !boss->HealthBelowPct(71) && currentTarget == boss) - { - if (dynamic_cast(action)) - return 0.0f; - } - - return 1.0f; -} diff --git a/src/Ai/Raid/Icecrown/Util/RaidIccScripts.h b/src/Ai/Raid/Icecrown/Util/RaidIccScripts.h deleted file mode 100644 index e2fc782cad6..00000000000 --- a/src/Ai/Raid/Icecrown/Util/RaidIccScripts.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef _PLAYERBOT_RAIDICCSCRIPTS_H -#define _PLAYERBOT_RAIDICCSCRIPTS_H - -#include "../../../../src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.h" - -#endif diff --git a/src/Ai/Raid/RaidStrategyContext.h b/src/Ai/Raid/RaidStrategyContext.h index f307c994fde..732310b28b5 100644 --- a/src/Ai/Raid/RaidStrategyContext.h +++ b/src/Ai/Raid/RaidStrategyContext.h @@ -19,7 +19,7 @@ #include "RaidVoAStrategy.h" #include "RaidUlduarStrategy.h" #include "RaidOnyxiaStrategy.h" -#include "RaidIccStrategy.h" +#include "ICCStrategy.h" class RaidStrategyContext : public NamedObjectContext { diff --git a/src/Bot/Engine/BuildSharedActionContexts.cpp b/src/Bot/Engine/BuildSharedActionContexts.cpp index 3cd68a20a50..c2972a68f29 100644 --- a/src/Bot/Engine/BuildSharedActionContexts.cpp +++ b/src/Bot/Engine/BuildSharedActionContexts.cpp @@ -19,7 +19,7 @@ #include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h" #include "Ai/Raid/Ulduar/RaidUlduarActionContext.h" #include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h" -#include "Ai/Raid/Icecrown/RaidIccActionContext.h" +#include "Ai/Raid/ICC/ICCActionContext.h" #include "Ai/Dungeon/TbcDungeonActionContext.h" #include "Ai/Dungeon/WotlkDungeonActionContext.h" diff --git a/src/Bot/Engine/BuildSharedTriggerContexts.cpp b/src/Bot/Engine/BuildSharedTriggerContexts.cpp index 403e162699f..43b59542c87 100644 --- a/src/Bot/Engine/BuildSharedTriggerContexts.cpp +++ b/src/Bot/Engine/BuildSharedTriggerContexts.cpp @@ -19,7 +19,7 @@ #include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h" #include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h" #include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h" -#include "Ai/Raid/Icecrown/RaidIccTriggerContext.h" +#include "Ai/Raid/ICC/ICCTriggerContext.h" #include "Ai/Dungeon/TbcDungeonTriggerContext.h" #include "Ai/Dungeon/WotlkDungeonTriggerContext.h" diff --git a/src/Mgr/Item/BisListMgr.cpp b/src/Mgr/Item/BisListMgr.cpp new file mode 100644 index 00000000000..324b32a93a6 --- /dev/null +++ b/src/Mgr/Item/BisListMgr.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "BisListMgr.h" + +#include "DatabaseEnv.h" +#include "Field.h" +#include "Log.h" +#include "QueryResult.h" + +void BisListMgr::LoadAll() +{ + _bis.clear(); + + QueryResult result = PlayerbotsDatabase.Query( + "SELECT class, tab, slot, faction, auto_gear_score_limit, item_id FROM playerbots_bis_gear"); + if (!result) + { + LOG_INFO("server.loading", "playerbots_bis_gear table missing or empty"); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + uint8 cls = fields[0].Get(); + uint8 tab = fields[1].Get(); + uint8 slot = fields[2].Get(); + uint8 faction = fields[3].Get(); + uint16 autoGearScoreLimit = fields[4].Get(); + uint32 item = fields[5].Get(); + + _bis[autoGearScoreLimit][MakeKey(cls, tab)][faction][slot] = item; + ++count; + } while (result->NextRow()); + + LOG_INFO("server.loading", "Loaded {} BiS entries across {} item levels", + count, static_cast(_bis.size())); +} + +std::map BisListMgr::GetBisFor(uint16 autoGearScoreLimit, uint8 cls, uint8 tab, uint8 faction) const +{ + auto ilvlIt = _bis.find(autoGearScoreLimit); + if (ilvlIt == _bis.end()) + return {}; + + auto comboIt = ilvlIt->second.find(MakeKey(cls, tab)); + if (comboIt == ilvlIt->second.end()) + return {}; + + std::map result; + + // Base: faction=0 (Both). + auto bothIt = comboIt->second.find(0); + if (bothIt != comboIt->second.end()) + result = bothIt->second; + + // Faction-specific overrides Both. + if (faction == 1 || faction == 2) + { + auto facIt = comboIt->second.find(faction); + if (facIt != comboIt->second.end()) + for (auto const& kv : facIt->second) + result[kv.first] = kv.second; + } + + return result; +} + +std::map BisListMgr::GetBisForNearest(uint16 requestedIlvl, uint16 maxDrop, uint8 cls, uint8 tab, + uint8 faction, uint16* outResolved) const +{ + uint16 floor = requestedIlvl > maxDrop ? requestedIlvl - maxDrop : 1; + for (uint16 try_ilvl = requestedIlvl; try_ilvl >= floor; --try_ilvl) + { + auto result = GetBisFor(try_ilvl, cls, tab, faction); + if (!result.empty()) + { + if (outResolved) + *outResolved = try_ilvl; + return result; + } + if (try_ilvl == 0) + break; + } + if (outResolved) + *outResolved = 0; + return {}; +} diff --git a/src/Mgr/Item/BisListMgr.h b/src/Mgr/Item/BisListMgr.h new file mode 100644 index 00000000000..04401ec34b4 --- /dev/null +++ b/src/Mgr/Item/BisListMgr.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_BISLISTMGR_H +#define _PLAYERBOT_BISLISTMGR_H + +#include "Define.h" + +#include + +class BisListMgr +{ +public: + static BisListMgr* instance() + { + static BisListMgr inst; + return &inst; + } + + void LoadAll(); + + // faction: 1=Alliance, 2=Horde. Faction-specific rows override faction=0 (Both). + // Returns slot -> itemId for the matching auto_gear_score_limit tier. Empty map = no data. + std::map GetBisFor(uint16 autoGearScoreLimit, uint8 cls, uint8 tab, uint8 faction) const; + + // Closest-lower fallback: scan ilvls down from requested to (requested - maxDrop), return first non-empty set. + // outResolved receives the matched ilvl (0 if nothing matched within the window). + std::map GetBisForNearest(uint16 requestedIlvl, uint16 maxDrop, uint8 cls, uint8 tab, + uint8 faction, uint16* outResolved = nullptr) const; + +private: + BisListMgr() = default; + + static uint16 MakeKey(uint8 cls, uint8 tab) { return (uint16(cls) << 8) | tab; } + + // autoGearScoreLimit -> (cls<<8|tab) -> faction (0/1/2) -> slot -> itemId + std::map>>> _bis; +}; + +#define sBisListMgr BisListMgr::instance() + +#endif diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 8a0c6b0a0fb..af08edf64d3 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -5,6 +5,7 @@ #include "PlayerbotAIConfig.h" #include +#include "BisListMgr.h" #include "Config.h" #include "NewRpgInfo.h" #include "PlayerbotDungeonRepository.h" @@ -595,6 +596,7 @@ bool PlayerbotAIConfig::Initialize() autoGearCommand = sConfigMgr->GetOption("AiPlayerbot.AutoGearCommand", 1); autoGearCommandAltBots = sConfigMgr->GetOption("AiPlayerbot.AutoGearCommandAltBots", 1); + autoGearBisCommand = sConfigMgr->GetOption("AiPlayerbot.AutoGearBisCommand", 0); autoGearQualityLimit = sConfigMgr->GetOption("AiPlayerbot.AutoGearQualityLimit", 3); autoGearScoreLimit = sConfigMgr->GetOption("AiPlayerbot.AutoGearScoreLimit", 0); @@ -692,6 +694,7 @@ bool PlayerbotAIConfig::Initialize() PlayerbotGuildMgr::instance().Init(); sRandomItemMgr.Init(); sRandomItemMgr.InitAfterAhBot(); + sBisListMgr->LoadAll(); PlayerbotTextMgr::instance().LoadBotTexts(); PlayerbotTextMgr::instance().LoadBotTextChance(); PlayerbotFactory::Init(); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 1a343db4dac..715701231da 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -427,6 +427,7 @@ class PlayerbotAIConfig altMaintenanceKeyring, altMaintenanceGemsEnchants; int32 autoGearCommand, autoGearCommandAltBots, autoGearQualityLimit, autoGearScoreLimit; + int32 autoGearBisCommand; uint32 useGroundMountAtMinLevel; uint32 useFastGroundMountAtMinLevel; diff --git a/src/Script/Playerbots.cpp b/src/Script/Playerbots.cpp index ab92729a760..e9abd5b74f6 100644 --- a/src/Script/Playerbots.cpp +++ b/src/Script/Playerbots.cpp @@ -526,6 +526,7 @@ class PlayerbotsBattlefieldScript : public BattlefieldScript void AddPlayerbotsSecureLoginScripts(); void AddSC_TempestKeepBotScripts(); +void AddSC_IcecrownBotScripts(); void AddSC_HyjalSummitBotScripts(); void AddPlayerbotsScripts() @@ -542,5 +543,6 @@ void AddPlayerbotsScripts() AddPlayerbotsCommandscripts(); PlayerBotsGuildValidationScript(); AddSC_TempestKeepBotScripts(); + AddSC_IcecrownBotScripts(); AddSC_HyjalSummitBotScripts(); } From ab196cb532b2b939a25040f96d5653f6dfd58c4c Mon Sep 17 00:00:00 2001 From: HennyWilly <5954598+HennyWilly@users.noreply.github.com> Date: Sat, 23 May 2026 04:23:49 +0200 Subject: [PATCH 33/63] Refactoring of BWL strategy (#2397) ## Pull Request Description This PR refactors the Blackwing Lair strategy to align with newer raid strategy implementations. It extracts repeated Blackwing Lair spell IDs, game object IDs, and suppression device checks into a shared helper, so the action and trigger code use the same centralized definitions instead of duplicating literals and condition chains. No gameplay behavior changes are expected. This PR is intended as a base for follow-up PRs that introduce additional boss-specific strategy logic. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Move existing BWL-specific constants and the suppression device condition into a shared helper. - Keep all existing trigger names, action names, spell IDs, game object IDs, and condition checks unchanged. - Update the affected action/trigger implementations to call the helper instead of duplicating the same logic inline. - Describe the **processing cost** when this logic executes across many bots. - No meaningful increase. The logic executed per bot remains effectively the same. - This refactor only replaces duplicated inline checks with a shared helper function and named constants. - There are no additional scans, branches, state tracking, or new decision loops. ## How to Test the Changes Run the Blackwing Lair raid and check if the already implemented features still work: - _Onyxia Scale Cloak handling_: Confirm the bot still applies/checks the same aura as before. - _Suppression device handling_: Confirm bots still detect nearby active suppression devices and turn them off under the same conditions as before. - _Chromaggus bronze affliction handling_: Confirm bots still use Hourglass Sand if they have the bronze affliction. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [x] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) A small helper call was introduced for the suppression device check, so there is technically a minor additional call overhead compared to the previous inline condition. In practice, the executed logic, iteration scope, and decision flow remain unchanged, so the impact is negligible and does not introduce meaningful scaling risk. - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) AI was used for review support and drafting the pull request text. The refactoring itself was done manually. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers This PR is intended to be behavior-preserving only. The main change is consolidation of repeated Blackwing Lair constants and suppression device logic into a shared helper used by both actions and triggers. Review can focus on verifying that the extracted helper matches the previous conditions. --- .../BlackwingLair/Action/RaidBwlActions.cpp | 27 +++++++++++-------- .../BlackwingLair/Action/RaidBwlActions.h | 9 +++---- .../Strategy/RaidBwlStrategy.cpp | 14 +++++----- .../BlackwingLair/Strategy/RaidBwlStrategy.h | 6 ++--- .../BlackwingLair/Trigger/RaidBwlTriggers.cpp | 27 +++++++++++-------- .../BlackwingLair/Trigger/RaidBwlTriggers.h | 6 +++-- .../BlackwingLair/Util/RaidBwlHelpers.cpp | 12 +++++++++ .../Raid/BlackwingLair/Util/RaidBwlHelpers.h | 27 +++++++++++++++++++ 8 files changed, 88 insertions(+), 40 deletions(-) create mode 100644 src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.cpp create mode 100644 src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h diff --git a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp b/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp index 9014345cc02..2d018fc91e6 100644 --- a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp +++ b/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp @@ -1,35 +1,40 @@ #include "RaidBwlActions.h" #include "Playerbots.h" +#include "RaidBwlHelpers.h" + +using namespace BlackwingLairHelpers; + +// General bool BwlOnyxiaScaleCloakAuraCheckAction::Execute(Event /*event*/) { - bot->AddAura(22683, bot); + bot->AddAura(SPELL_ONYXIA_SCALE_CLOAK, bot); return true; } -bool BwlOnyxiaScaleCloakAuraCheckAction::isUseful() { return !bot->HasAura(22683); } +bool BwlOnyxiaScaleCloakAuraCheckAction::isUseful() +{ + return !bot->HasAura(SPELL_ONYXIA_SCALE_CLOAK); +} bool BwlTurnOffSuppressionDeviceAction::Execute(Event /*event*/) { GuidVector gos = AI_VALUE(GuidVector, "nearest game objects"); - for (GuidVector::iterator i = gos.begin(); i != gos.end(); i++) + for (auto i = gos.begin(); i != gos.end(); ++i) { GameObject* go = botAI->GetGameObject(*i); - if (!go) + if (IsActiveSuppressionDeviceInRange(go, bot)) { - continue; + go->SetGoState(GO_STATE_ACTIVE); } - if (go->GetEntry() != 179784 || go->GetDistance(bot) >= 15.0f || go->GetGoState() != GO_STATE_READY) - { - continue; - } - go->SetGoState(GO_STATE_ACTIVE); } return true; } +// Chromaggus + bool BwlUseHourglassSandAction::Execute(Event /*event*/) { - return botAI->CastSpell(23645, bot); + return botAI->CastSpell(SPELL_HOURGLASS_SAND, bot); } diff --git a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h b/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h index 24dcf3996e0..27037414aa1 100644 --- a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h +++ b/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h @@ -2,11 +2,8 @@ #define _PLAYERBOT_RAIDBWLACTIONS_H #include "Action.h" -#include "AttackAction.h" -#include "GenericActions.h" -#include "MovementActions.h" -#include "PlayerbotAI.h" -#include "Playerbots.h" + +// General class BwlOnyxiaScaleCloakAuraCheckAction : public Action { @@ -23,6 +20,8 @@ class BwlTurnOffSuppressionDeviceAction : public Action bool Execute(Event event) override; }; +// Chromaggus + class BwlUseHourglassSandAction : public Action { public: diff --git a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp b/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp index c65a80ecbaf..ec36b2cde64 100644 --- a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp +++ b/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp @@ -1,15 +1,13 @@ #include "RaidBwlStrategy.h" -#include "Strategy.h" - void RaidBwlStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode("often", - { NextAction("bwl check onyxia scale cloak", ACTION_RAID) })); + triggers.push_back(new TriggerNode("often", { + NextAction("bwl check onyxia scale cloak", ACTION_RAID) })); - triggers.push_back(new TriggerNode("bwl suppression device", - { NextAction("bwl turn off suppression device", ACTION_RAID) })); + triggers.push_back(new TriggerNode("bwl suppression device", { + NextAction("bwl turn off suppression device", ACTION_RAID) })); - triggers.push_back(new TriggerNode("bwl affliction bronze", - { NextAction("bwl use hourglass sand", ACTION_RAID) })); + triggers.push_back(new TriggerNode("bwl affliction bronze", { + NextAction("bwl use hourglass sand", ACTION_RAID) })); } diff --git a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h b/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h index e09ea2f3ee5..375ddad7198 100644 --- a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h +++ b/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h @@ -8,9 +8,9 @@ class RaidBwlStrategy : public Strategy { public: RaidBwlStrategy(PlayerbotAI* ai) : Strategy(ai) {} - virtual std::string const getName() override { return "bwl"; } - virtual void InitTriggers(std::vector& triggers) override; - // virtual void InitMultipliers(std::vector &multipliers) override; + std::string const getName() override { return "bwl"; } + void InitTriggers(std::vector& triggers) override; + // void InitMultipliers(std::vector &multipliers) override; }; #endif diff --git a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp b/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp index e34e7a9a6af..4b4bee1adf2 100644 --- a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp +++ b/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp @@ -1,24 +1,29 @@ #include "RaidBwlTriggers.h" -#include "SharedDefines.h" +#include "Playerbots.h" +#include "RaidBwlHelpers.h" + +using namespace BlackwingLairHelpers; + +// General bool BwlSuppressionDeviceTrigger::IsActive() { GuidVector gos = AI_VALUE(GuidVector, "nearest game objects"); - for (GuidVector::iterator i = gos.begin(); i != gos.end(); i++) + for (auto i = gos.begin(); i != gos.end(); ++i) { - GameObject* go = botAI->GetGameObject(*i); - if (!go) - { - continue; - } - if (go->GetEntry() != 179784 || go->GetDistance(bot) >= 15.0f || go->GetGoState() != GO_STATE_READY) + const GameObject* go = botAI->GetGameObject(*i); + if (IsActiveSuppressionDeviceInRange(go, bot)) { - continue; + return true; } - return true; } return false; } -bool BwlAfflictionBronzeTrigger::IsActive() { return bot->HasAura(23170); } +// Chromaggus + +bool BwlAfflictionBronzeTrigger::IsActive() +{ + return bot->HasAura(SPELL_BROOD_AFFLICTION_BRONZE); +} diff --git a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h b/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h index fc19acd5d35..0aa29000720 100644 --- a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h +++ b/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h @@ -1,10 +1,10 @@ #ifndef _PLAYERBOT_RAIDBWLTRIGGERS_H #define _PLAYERBOT_RAIDBWLTRIGGERS_H -#include "PlayerbotAI.h" -#include "Playerbots.h" #include "Trigger.h" +// General + class BwlSuppressionDeviceTrigger : public Trigger { public: @@ -12,6 +12,8 @@ class BwlSuppressionDeviceTrigger : public Trigger bool IsActive() override; }; +// Chromaggus + class BwlAfflictionBronzeTrigger : public Trigger { public: diff --git a/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.cpp b/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.cpp new file mode 100644 index 00000000000..cc0714bd134 --- /dev/null +++ b/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.cpp @@ -0,0 +1,12 @@ +#include "RaidBwlHelpers.h" + +namespace BlackwingLairHelpers +{ + bool IsActiveSuppressionDeviceInRange(const GameObject* go, const Player* bot) + { + return go && + go->GetEntry() == GO_SUPPRESSION_DEVICE && + go->GetDistance(bot) < 15.0f && + go->GetGoState() == GO_STATE_READY; + } +} diff --git a/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h b/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h new file mode 100644 index 00000000000..3333d826d9d --- /dev/null +++ b/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h @@ -0,0 +1,27 @@ +#ifndef _PLAYERBOT_RAIDBWLHELPERS_H +#define _PLAYERBOT_RAIDBWLHELPERS_H + +#include "Player.h" + +namespace BlackwingLairHelpers +{ + enum BlackwingLairSpells + { + // General + SPELL_ONYXIA_SCALE_CLOAK = 22683, + + // Chromaggus + SPELL_BROOD_AFFLICTION_BRONZE = 23170, + SPELL_HOURGLASS_SAND = 23645 + }; + + enum BlackwingLairGameObjects + { + // General + GO_SUPPRESSION_DEVICE = 179784 + }; + + bool IsActiveSuppressionDeviceInRange(const GameObject* go, const Player* bot); +} + +#endif //_PLAYERBOT_RAIDBWLHELPERS_H From 34f34ef13d154e45b7385e3af500a6e8c890dcb9 Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 23 May 2026 04:24:15 +0200 Subject: [PATCH 34/63] Fix for ru translation (#2400) ## Pull Request Description Fixed bug with finding russian texts (loc8) Related with: https://github.com/mod-playerbots/mod-playerbots/issues/1884 ## How to Test the Changes 1. Use ru client 2. Invite bot 3. Use command `focus heal ?" ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) Find and fix wrong places checking texts ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Bot/PlayerbotMgr.cpp | 2 +- src/Mgr/Text/PlayerbotTextMgr.cpp | 10 +++++----- src/Mgr/Text/PlayerbotTextMgr.h | 4 ++-- src/Util/BroadcastHelper.cpp | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index 7a8c311ce65..ab239d376ba 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -1639,7 +1639,7 @@ void PlayerbotMgr::OnPlayerLogin(Player* player) // For bot texts (DB-driven), prefer the database locale with a safe fallback. LocaleConstant usedLocale = databaseLocale; - if (usedLocale >= MAX_LOCALES) + if (usedLocale >= TOTAL_LOCALES) usedLocale = LOCALE_enUS; // fallback // set locale priority for bot texts diff --git a/src/Mgr/Text/PlayerbotTextMgr.cpp b/src/Mgr/Text/PlayerbotTextMgr.cpp index 0998a5392f9..e8be8f53302 100644 --- a/src/Mgr/Text/PlayerbotTextMgr.cpp +++ b/src/Mgr/Text/PlayerbotTextMgr.cpp @@ -38,7 +38,7 @@ void PlayerbotTextMgr::LoadBotTexts() text[0] = fields[1].Get(); uint8 sayType = fields[2].Get(); uint8 replyType = fields[3].Get(); - for (uint8 i = 1; i < MAX_LOCALES; ++i) + for (uint8 i = 1; i < TOTAL_LOCALES; ++i) { text[i] = fields[i + 3].Get(); } @@ -192,9 +192,9 @@ bool PlayerbotTextMgr::GetBotText(std::string name, std::string& text, std::map< void PlayerbotTextMgr::AddLocalePriority(uint32 locale) { - if (locale >= MAX_LOCALES) + if (locale >= TOTAL_LOCALES) { - LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds MAX_LOCALES ({})", locale, MAX_LOCALES - 1); + LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds TOTAL_LOCALES ({})", locale, TOTAL_LOCALES - 1); return; } @@ -212,7 +212,7 @@ uint32 PlayerbotTextMgr::GetLocalePriority() } uint32 topLocale = 0; - for (uint8 i = 0; i < MAX_LOCALES; ++i) + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) { if (botTextLocalePriority[i] > botTextLocalePriority[topLocale]) topLocale = i; @@ -223,7 +223,7 @@ uint32 PlayerbotTextMgr::GetLocalePriority() void PlayerbotTextMgr::ResetLocalePriority() { - for (uint8 i = 0; i < MAX_LOCALES; ++i) + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) { botTextLocalePriority[i] = 0; } diff --git a/src/Mgr/Text/PlayerbotTextMgr.h b/src/Mgr/Text/PlayerbotTextMgr.h index 398d7640abc..1613630f947 100644 --- a/src/Mgr/Text/PlayerbotTextMgr.h +++ b/src/Mgr/Text/PlayerbotTextMgr.h @@ -87,7 +87,7 @@ class PlayerbotTextMgr private: PlayerbotTextMgr() { - for (uint8 i = 0; i < MAX_LOCALES; ++i) + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) { botTextLocalePriority[i] = 0; } @@ -102,7 +102,7 @@ class PlayerbotTextMgr std::map> botTexts; std::map botTextChance; - uint32 botTextLocalePriority[MAX_LOCALES]; + uint32 botTextLocalePriority[TOTAL_LOCALES]; }; #endif diff --git a/src/Util/BroadcastHelper.cpp b/src/Util/BroadcastHelper.cpp index 246344bdafa..0804ab9fa94 100644 --- a/src/Util/BroadcastHelper.cpp +++ b/src/Util/BroadcastHelper.cpp @@ -11,7 +11,7 @@ uint8 BroadcastHelper::GetLocale() { uint8 locale = sWorld->GetDefaultDbcLocale(); // -- In case we're using auto detect on config file^M - if (locale >= MAX_LOCALES) + if (locale >= TOTAL_LOCALES) locale = LocaleConstant::LOCALE_enUS; return locale; } From 92081c9f1af999806640204cc88c56ab76b1f0c1 Mon Sep 17 00:00:00 2001 From: Crow Date: Fri, 22 May 2026 21:24:47 -0500 Subject: [PATCH 35/63] Expand PWS Usage by Disc Priests (#2403) ## Pull Request Description Disc Priests currently do not use Power Word: Shield until a party member is at "medium health" (configurable, default 65%). That is the second-level healing threshold; at the highest level, "almost full health" (configurable, default 85%), Disc Priests will use only Prayer of Mending and Renew. This PR adds PWS as the highest priority healing action starting at "almost full health." ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. Added new action "power word: shield on party" to "party member almost full health" trigger in HealPriestStrategy.cpp (the "heal" strategy, which is the default strategy for Disc Priests, as Holy Priests use the "holy heal" strategy). ## How to Test the Changes Group up with a Disc Priest and take damage; confirm that PWS is cast when you drop below your configured almost full health threshold. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) PWS is the signature ability of Disc. Priests, and delaying it to 65% is significantly limiting their effectiveness in group content, particularly with raid bosses that deal raidwide damage and in heroic dungeons, where waiting until the tank hits 65% is a death wish. This single change makes an enormous difference in their effectiveness. See below for before/after change for Eredar Twins (focus on the activity). Before: Screenshot 2026-05-18 201949 After: image - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Class/Priest/Strategy/HealPriestStrategy.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ai/Class/Priest/Strategy/HealPriestStrategy.cpp b/src/Ai/Class/Priest/Strategy/HealPriestStrategy.cpp index 35f764e4df2..89e9895abce 100644 --- a/src/Ai/Class/Priest/Strategy/HealPriestStrategy.cpp +++ b/src/Ai/Class/Priest/Strategy/HealPriestStrategy.cpp @@ -86,6 +86,7 @@ void HealPriestStrategy::InitTriggers(std::vector& triggers) new TriggerNode( "party member almost full health", { + NextAction("power word: shield on party", ACTION_LIGHT_HEAL + 3), NextAction("prayer of mending on party", ACTION_LIGHT_HEAL + 2), NextAction("renew on party", ACTION_LIGHT_HEAL + 1) } From cd2fe2f9a17f0b3ba175f82138e96158b953664b Mon Sep 17 00:00:00 2001 From: Crow Date: Fri, 22 May 2026 21:25:07 -0500 Subject: [PATCH 36/63] Use Stones for Prot Paladins & Reduce Oils (#2405) ## Pull Request Description Back when I did the Stones/Oils PR, I gave Wizard Oil to Prot Paladins. I was operating based on their mechanics in TBC and have since realized that their damage is much more physical-based in WotLK as opposed to spell-based in TBC. So I've changed them to now get stones upon maintenance and apply stones. I also reduced weapon oils given during maintenance from 4 to 2 stacks. I did 4 originally because oils stack to 5 so it would align with the amount given of stones and poisons (which stack to 20). But 4 inventory slots taken up by oils is excessive, and 2 still last far longer than buffing reagents in a raid context. So this is just to reduce inventory clutter. Other stuff is just little code stuff like reformatting or reordering things to be more efficient. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. There's nothing new--essentially with respect to consumables, prot paladins now are lumped with ret. ## How to Test the Changes Create a prot paladin, turn on selfbot. Use maintenance and the paladin should be given stones and no oils. The paladin should then apply the stone to its weapon (if it is equipping a level-eligible weapon). Create a caster, turn on selfbot. Use maintenance and they should be given 2 stacks of oils. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Prot paladins will now use stones in their inventory and will not use oils in their inventories. This is better for their mechanics. - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- .../GenericPaladinNonCombatStrategy.cpp | 4 +- src/Bot/Factory/PlayerbotFactory.cpp | 105 +++++++++--------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/src/Ai/Class/Paladin/Strategy/GenericPaladinNonCombatStrategy.cpp b/src/Ai/Class/Paladin/Strategy/GenericPaladinNonCombatStrategy.cpp index 670d7e629a3..84f449813e4 100644 --- a/src/Ai/Class/Paladin/Strategy/GenericPaladinNonCombatStrategy.cpp +++ b/src/Ai/Class/Paladin/Strategy/GenericPaladinNonCombatStrategy.cpp @@ -26,8 +26,8 @@ void GenericPaladinNonCombatStrategy::InitTriggers(std::vector& tr triggers.push_back(new TriggerNode("not sensing undead", { NextAction("sense undead", ACTION_IDLE + 1.0f) })); int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot()); - if (specTab == PALADIN_TAB_HOLY || specTab == PALADIN_TAB_PROTECTION) + if (specTab == PALADIN_TAB_HOLY) triggers.push_back(new TriggerNode("often", { NextAction("apply oil", ACTION_IDLE + 1.0f) })); - if (specTab == PALADIN_TAB_RETRIBUTION) + if (specTab == PALADIN_TAB_PROTECTION || specTab == PALADIN_TAB_RETRIBUTION) triggers.push_back(new TriggerNode("often", { NextAction("apply stone", ACTION_IDLE + 1.0f) })); } diff --git a/src/Bot/Factory/PlayerbotFactory.cpp b/src/Bot/Factory/PlayerbotFactory.cpp index f8e0e5dfe27..3b35ccb6e31 100644 --- a/src/Bot/Factory/PlayerbotFactory.cpp +++ b/src/Bot/Factory/PlayerbotFactory.cpp @@ -850,36 +850,36 @@ void PlayerbotFactory::Refresh() void PlayerbotFactory::InitConsumables() { - int specTab = AiFactory::GetPlayerSpecTab(bot); + uint8 specTab = AiFactory::GetPlayerSpecTab(bot); std::vector> items; switch (bot->getClass()) { case CLASS_PRIEST: { - // Discipline or Holy: Mana Oil - if (specTab == 0 || specTab == 1) + if (specTab == PRIEST_TAB_SHADOW) { - std::vector mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL}; - for (uint32 itemId : mana_oils) + std::vector wizard_oils = { + BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL }; + for (uint32 itemId : wizard_oils) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (proto->RequiredLevel > level || level > 75) continue; - items.push_back({itemId, 4}); + items.push_back({itemId, 2}); break; } } - // Shadow: Wizard Oil - if (specTab == 2) + else { - std::vector wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL}; - for (uint32 itemId : wizard_oils) + std::vector mana_oils = { + BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL }; + for (uint32 itemId : mana_oils) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (proto->RequiredLevel > level || level > 75) continue; - items.push_back({itemId, 4}); + items.push_back({itemId, 2}); break; } } @@ -887,38 +887,41 @@ void PlayerbotFactory::InitConsumables() } case CLASS_MAGE: { - // Always Wizard Oil - std::vector wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL}; + std::vector wizard_oils = { + BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL }; for (uint32 itemId : wizard_oils) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (proto->RequiredLevel > level || level > 75) continue; - items.push_back({itemId, 4}); + items.push_back({itemId, 2}); break; } break; } case CLASS_DRUID: { - // Balance: Wizard Oil - if (specTab == 0) + if (specTab == DRUID_TAB_BALANCE) { - std::vector wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL}; + std::vector wizard_oils = { + BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL }; for (uint32 itemId : wizard_oils) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (proto->RequiredLevel > level || level > 75) continue; - items.push_back({itemId, 4}); + items.push_back({itemId, 2}); break; } } - // Feral: Sharpening Stones & Weightstones - else if (specTab == 1) + else if (specTab == DRUID_TAB_FERAL) { - std::vector sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE}; - std::vector weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE}; + std::vector sharpening_stones = { + ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, + HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE }; + std::vector weightstones = { + ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, + HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE }; for (uint32 itemId : sharpening_stones) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); @@ -936,16 +939,16 @@ void PlayerbotFactory::InitConsumables() break; } } - // Restoration: Mana Oil - else if (specTab == 2) + else { - std::vector mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL}; + std::vector mana_oils = { + BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL }; for (uint32 itemId : mana_oils) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (proto->RequiredLevel > level || level > 75) continue; - items.push_back({itemId, 4}); + items.push_back({itemId, 2}); break; } } @@ -953,37 +956,27 @@ void PlayerbotFactory::InitConsumables() } case CLASS_PALADIN: { - // Holy: Mana Oil - if (specTab == 0) + if (specTab == PALADIN_TAB_HOLY) { - std::vector mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL}; + std::vector mana_oils = { + BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL }; for (uint32 itemId : mana_oils) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); if (proto->RequiredLevel > level || level > 75) continue; - items.push_back({itemId, 4}); - break; - } - } - // Protection: Wizard Oil (Protection prioritizes Superior over Brilliant) - else if (specTab == 1) - { - std::vector wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL}; - for (uint32 itemId : wizard_oils) - { - ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); - if (proto->RequiredLevel > level || level > 75) - continue; - items.push_back({itemId, 4}); + items.push_back({itemId, 2}); break; } } - // Retribution: Sharpening Stones & Weightstones - else if (specTab == 2) + else { - std::vector sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE}; - std::vector weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE}; + std::vector sharpening_stones = { + ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, + HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE }; + std::vector weightstones = { + ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, + HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE }; for (uint32 itemId : sharpening_stones) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); @@ -1006,9 +999,12 @@ void PlayerbotFactory::InitConsumables() case CLASS_WARRIOR: case CLASS_HUNTER: { - // Sharpening Stones & Weightstones - std::vector sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE}; - std::vector weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE}; + std::vector sharpening_stones = { + ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, + HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE }; + std::vector weightstones = { + ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, + HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE }; for (uint32 itemId : sharpening_stones) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); @@ -1029,9 +1025,12 @@ void PlayerbotFactory::InitConsumables() } case CLASS_ROGUE: { - // Poisons - std::vector instant_poisons = {INSTANT_POISON_IX, INSTANT_POISON_VIII, INSTANT_POISON_VII, INSTANT_POISON_VI, INSTANT_POISON_V, INSTANT_POISON_IV, INSTANT_POISON_III, INSTANT_POISON_II, INSTANT_POISON}; - std::vector deadly_poisons = {DEADLY_POISON_IX, DEADLY_POISON_VIII, DEADLY_POISON_VII, DEADLY_POISON_VI, DEADLY_POISON_V, DEADLY_POISON_IV, DEADLY_POISON_III, DEADLY_POISON_II, DEADLY_POISON}; + std::vector instant_poisons = { + INSTANT_POISON_IX, INSTANT_POISON_VIII, INSTANT_POISON_VII, INSTANT_POISON_VI, INSTANT_POISON_V, + INSTANT_POISON_IV, INSTANT_POISON_III, INSTANT_POISON_II, INSTANT_POISON }; + std::vector deadly_poisons = { + DEADLY_POISON_IX, DEADLY_POISON_VIII, DEADLY_POISON_VII, DEADLY_POISON_VI, DEADLY_POISON_V, + DEADLY_POISON_IV, DEADLY_POISON_III, DEADLY_POISON_II, DEADLY_POISON }; for (uint32 itemId : deadly_poisons) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); From 3a7e3e2719605eeb3cde8a698e2a44bd83865c2d Mon Sep 17 00:00:00 2001 From: Crow Date: Fri, 22 May 2026 21:25:48 -0500 Subject: [PATCH 37/63] Fix Mages' Armor Strategies & Light Refactor (#2390) ## Pull Request Description Mages have a "bdps" strategy to use Molten Armor (default for Fire and Arcane) and "bmana" strategy to use Mage Armor (default for Frost). The existing code uses a series of alternatives for armor (Molten->Mage->Ice->Frost), which is needed for Mages that have not learned Molten or Mage. However, I was noticing that sometimes my Fire and Arcane Mages would end up with Mage Armor, presumably because there could be a situation in which the casting of Molten Armor failed and the fallback to Mage Armor kicked in (for example, due to there being not enough mana to cast Molten Armor). This PR makes bdps always mean Molten Armor, if it is learned, and bmana always mean Mage Armor, if it is learned, by gating through triggers. Other changes: - Added bdps and bmana to default Mage combat strategies (still bdps for Fire and Arcane and bmana for Frost) so that Mages will reapply armor if it expires in combat. - Deleted Arcane Explosion strategy--it was not fully implemented because there was no associated action (such as through a CastArcaneExplosionAction class). I debated implementing it, but there isn't a suitable targeting mechanism that exists in the code from what I can tell. Arcane Mages generally just use Blizzard for AoE (and Flamestrike with PoM); Arcane Explosion is useful to use if (1) a player is moving or (2) mobs are almost dead in an AoE situation. Scenario (1) is irrelevant for bots since they cannot cast while moving. With respect to scenario (2), the existing AoE triggers in fact look for highest HP mobs so to implement Arcane Explosion in a useful manner would probably require a new Value, and that is not worth it for what would be miniscule benefit anyway. - General cleanups of Mage code (e.g., deleted empty ActionNodes). These were based on a quick review; I did not do any sort of detailed or comprehensive review and have no desire to with this PR. Note: I know that FrostMageStrategy.cpp had a Fireball alternative for Frostfire Bolt, but I deleted it anyway because the same ActionNode is already in GenericMageStrategy.cpp. - General cleanups of AiFactory default combat/noncombat strategies (e.g., removal of deprecated bdps and bmana strategies for Shamans). ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. Added getAlternatives for the armor strategies; this approach already exists for Druids. Added one new trigger for Molten Armor that is throttled by 10s like the existing Mage Armor trigger. ## How to Test the Changes Give a Mage the "bdps" strategy. They should cast Molten Armor. Make them cast a different armor. After the trigger throttle period (10s), they should reapply Molten Armor. Same goes for bmana and Mage Armor. Try this in combat, and it should work too. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) The point of this PR is to fix the default Mage armor buffing behavior. - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) I had GPT-5.4 present a couple of possibilities to fix the issue of Mage Armor being cast with bdps, and from there I settled on the getAlternatives approach. I did everything else. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers @Dreathean Perhaps the wiki can be updated to document the bdps and bmana strategies for Mages? --- src/Ai/Class/Mage/Action/MageActions.cpp | 50 +++++++---- src/Ai/Class/Mage/Action/MageActions.h | 14 +-- src/Ai/Class/Mage/MageAiObjectContext.cpp | 2 + .../Mage/Strategy/ArcaneMageStrategy.cpp | 28 +----- .../Class/Mage/Strategy/FireMageStrategy.cpp | 28 +----- src/Ai/Class/Mage/Strategy/FireMageStrategy.h | 1 + .../Mage/Strategy/FrostFireMageStrategy.cpp | 28 +----- .../Class/Mage/Strategy/FrostMageStrategy.cpp | 35 +------- .../Strategy/GenericMageNonCombatStrategy.cpp | 20 +---- .../Mage/Strategy/GenericMageStrategy.cpp | 85 ++----------------- src/Ai/Class/Mage/Trigger/MageTriggers.cpp | 64 +++++++------- src/Ai/Class/Mage/Trigger/MageTriggers.h | 40 ++++----- src/Bot/Factory/AiFactory.cpp | 53 +++++------- 13 files changed, 128 insertions(+), 320 deletions(-) diff --git a/src/Ai/Class/Mage/Action/MageActions.cpp b/src/Ai/Class/Mage/Action/MageActions.cpp index 63ba0d9d596..9c190d54c0e 100644 --- a/src/Ai/Class/Mage/Action/MageActions.cpp +++ b/src/Ai/Class/Mage/Action/MageActions.cpp @@ -11,6 +11,22 @@ #include "ServerFacade.h" #include "SharedDefines.h" +std::vector CastMoltenArmorAction::getAlternatives() +{ + if (!AI_VALUE2(uint32, "spell id", "molten armor")) + return NextAction::merge({ NextAction("mage armor") }, CastBuffSpellAction::getAlternatives()); + + return CastBuffSpellAction::getAlternatives(); +} + +std::vector CastMageArmorAction::getAlternatives() +{ + if (!AI_VALUE2(uint32, "spell id", "mage armor")) + return NextAction::merge({ NextAction("ice armor") }, CastBuffSpellAction::getAlternatives()); + + return CastBuffSpellAction::getAlternatives(); +} + bool UseManaSapphireAction::isUseful() { Player* bot = botAI->GetBot(); @@ -50,22 +66,23 @@ bool UseManaAgateAction::isUseful() bool CastFrostNovaAction::isUseful() { Unit* target = AI_VALUE(Unit*, "current target"); - if (!target || !target->IsInWorld()) - return false; - - if (target->ToCreature() && target->ToCreature()->HasMechanicTemplateImmunity(1 << (MECHANIC_FREEZE - 1))) - return false; - - if (target->isFrozen()) + if (!target || !target->IsInWorld() || target->isFrozen() || + (target->ToCreature() && + target->ToCreature()->HasMechanicTemplateImmunity(1 << (MECHANIC_FREEZE - 1)))) + { return false; + } - return ServerFacade::instance().IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", GetTargetName()), 10.f); + return ServerFacade::instance().IsDistanceLessOrEqualThan( + AI_VALUE2(float, "distance", GetTargetName()), 10.f); } bool CastConeOfColdAction::isUseful() { bool facingTarget = AI_VALUE2(bool, "facing", "current target"); - bool targetClose = ServerFacade::instance().IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", GetTargetName()), 10.f); + bool targetClose = ServerFacade::instance().IsDistanceLessOrEqualThan( + AI_VALUE2(float, "distance", GetTargetName()), 10.f); + return facingTarget && targetClose; } @@ -74,6 +91,7 @@ bool CastDragonsBreathAction::isUseful() Unit* target = AI_VALUE(Unit*, "current target"); if (!target) return false; + bool facingTarget = AI_VALUE2(bool, "facing", "current target"); bool targetClose = bot->IsWithinCombatRange(target, 10.0f); return facingTarget && targetClose; @@ -84,6 +102,7 @@ bool CastBlastWaveAction::isUseful() Unit* target = AI_VALUE(Unit*, "current target"); if (!target) return false; + bool targetClose = bot->IsWithinCombatRange(target, 10.0f); return targetClose; } @@ -100,14 +119,11 @@ Unit* CastFocusMagicOnPartyAction::GetTarget() for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || member == bot || !member->IsAlive()) - continue; - - if (member->GetMap() != bot->GetMap() || bot->GetDistance(member) > sPlayerbotAIConfig.spellDistance) - continue; - - if (member->HasAura(54646)) + if (!member || member == bot || !member->IsAlive() || member->GetMap() != bot->GetMap() || + bot->GetDistance(member) > sPlayerbotAIConfig.spellDistance || member->HasAura(54646)) // Focus Magic + { continue; + } if (member->getClass() == CLASS_MAGE) return member; @@ -136,7 +152,7 @@ bool CastBlinkBackAction::Execute(Event event) Unit* target = AI_VALUE(Unit*, "current target"); if (!target) return false; - // can cast spell check passed in isUseful() + bot->SetOrientation(bot->GetAngle(target) + M_PI); return CastSpellAction::Execute(event); } diff --git a/src/Ai/Class/Mage/Action/MageActions.h b/src/Ai/Class/Mage/Action/MageActions.h index abd3020526a..4c7f76a68fa 100644 --- a/src/Ai/Class/Mage/Action/MageActions.h +++ b/src/Ai/Class/Mage/Action/MageActions.h @@ -18,12 +18,14 @@ class CastMoltenArmorAction : public CastBuffSpellAction { public: CastMoltenArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "molten armor") {} + std::vector getAlternatives() override; }; class CastMageArmorAction : public CastBuffSpellAction { public: CastMageArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mage armor") {} + std::vector getAlternatives() override; }; class CastIceArmorAction : public CastBuffSpellAction @@ -60,7 +62,8 @@ class CastFocusMagicOnPartyAction : public CastSpellAction class CastSummonWaterElementalAction : public CastBuffSpellAction { public: - CastSummonWaterElementalAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "summon water elemental") {} + CastSummonWaterElementalAction(PlayerbotAI* botAI) + : CastBuffSpellAction(botAI, "summon water elemental") {} }; // Boost Actions @@ -236,7 +239,8 @@ class CastCounterspellAction : public CastSpellAction class CastCounterspellOnEnemyHealerAction : public CastSpellOnEnemyHealerAction { public: - CastCounterspellOnEnemyHealerAction(PlayerbotAI* botAI) : CastSpellOnEnemyHealerAction(botAI, "counterspell") {} + CastCounterspellOnEnemyHealerAction(PlayerbotAI* botAI) + : CastSpellOnEnemyHealerAction(botAI, "counterspell") {} }; class CastFrostNovaAction : public CastSpellAction @@ -275,9 +279,7 @@ class CastRemoveLesserCurseOnPartyAction : public CurePartyMemberAction { public: CastRemoveLesserCurseOnPartyAction(PlayerbotAI* botAI) - : CurePartyMemberAction(botAI, "remove lesser curse", DISPEL_CURSE) - { - } + : CurePartyMemberAction(botAI, "remove lesser curse", DISPEL_CURSE) {} }; // Damage and Debuff Actions @@ -331,7 +333,6 @@ class CastLivingBombAction : public CastDebuffSpellAction CastLivingBombAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "living bomb", true) {} bool isUseful() override { - // Bypass TTL check return CastAuraSpellAction::isUseful(); } }; @@ -342,7 +343,6 @@ class CastLivingBombOnAttackersAction : public CastDebuffSpellOnAttackerAction CastLivingBombOnAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "living bomb", true) {} bool isUseful() override { - // Bypass TTL check return CastAuraSpellAction::isUseful(); } }; diff --git a/src/Ai/Class/Mage/MageAiObjectContext.cpp b/src/Ai/Class/Mage/MageAiObjectContext.cpp index 477c0e075d2..b2b03166008 100644 --- a/src/Ai/Class/Mage/MageAiObjectContext.cpp +++ b/src/Ai/Class/Mage/MageAiObjectContext.cpp @@ -89,6 +89,7 @@ class MageTriggerFactoryInternal : public NamedObjectContext creators["arcane intellect"] = &MageTriggerFactoryInternal::arcane_intellect; creators["arcane intellect on party"] = &MageTriggerFactoryInternal::arcane_intellect_on_party; creators["mage armor"] = &MageTriggerFactoryInternal::mage_armor; + creators["molten armor"] = &MageTriggerFactoryInternal::molten_armor; creators["remove curse"] = &MageTriggerFactoryInternal::remove_curse; creators["remove curse on party"] = &MageTriggerFactoryInternal::remove_curse_on_party; creators["counterspell"] = &MageTriggerFactoryInternal::counterspell; @@ -143,6 +144,7 @@ class MageTriggerFactoryInternal : public NamedObjectContext static Trigger* arcane_intellect(PlayerbotAI* botAI) { return new ArcaneIntellectTrigger(botAI); } static Trigger* arcane_intellect_on_party(PlayerbotAI* botAI) { return new ArcaneIntellectOnPartyTrigger(botAI); } static Trigger* mage_armor(PlayerbotAI* botAI) { return new MageArmorTrigger(botAI); } + static Trigger* molten_armor(PlayerbotAI* botAI) { return new MoltenArmorTrigger(botAI); } static Trigger* remove_curse(PlayerbotAI* botAI) { return new RemoveCurseTrigger(botAI); } static Trigger* remove_curse_on_party(PlayerbotAI* botAI) { return new PartyMemberRemoveCurseTrigger(botAI); } static Trigger* counterspell(PlayerbotAI* botAI) { return new CounterspellInterruptSpellTrigger(botAI); } diff --git a/src/Ai/Class/Mage/Strategy/ArcaneMageStrategy.cpp b/src/Ai/Class/Mage/Strategy/ArcaneMageStrategy.cpp index 4707231db13..4ba7d42b981 100644 --- a/src/Ai/Class/Mage/Strategy/ArcaneMageStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/ArcaneMageStrategy.cpp @@ -6,35 +6,9 @@ #include "ArcaneMageStrategy.h" #include "Playerbots.h" -// ===== Action Node Factory ===== -class ArcaneMageStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - ArcaneMageStrategyActionNodeFactory() - { - creators["arcane blast"] = &arcane_blast; - creators["arcane barrage"] = &arcane_barrage; - creators["arcane missiles"] = &arcane_missiles; - creators["fire blast"] = &fire_blast; - creators["frostbolt"] = &frostbolt; - creators["arcane power"] = &arcane_power; - creators["icy veins"] = &icy_veins; - } - -private: - static ActionNode* arcane_blast(PlayerbotAI*) { return new ActionNode("arcane blast", {}, {}, {}); } - static ActionNode* arcane_barrage(PlayerbotAI*) { return new ActionNode("arcane barrage", {}, {}, {}); } - static ActionNode* arcane_missiles(PlayerbotAI*) { return new ActionNode("arcane missiles", {}, {}, {}); } - static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", {}, {}, {}); } - static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", {}, {}, {}); } - static ActionNode* arcane_power(PlayerbotAI*) { return new ActionNode("arcane power", {}, {}, {}); } - static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", {}, {}, {}); } -}; - -// ===== Single Target Strategy ===== ArcaneMageStrategy::ArcaneMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) { - actionNodeFactories.Add(new ArcaneMageStrategyActionNodeFactory()); + // No custom ActionNodeFactory needed } // ===== Default Actions ===== diff --git a/src/Ai/Class/Mage/Strategy/FireMageStrategy.cpp b/src/Ai/Class/Mage/Strategy/FireMageStrategy.cpp index 4914c72df65..7075b1f2dfa 100644 --- a/src/Ai/Class/Mage/Strategy/FireMageStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/FireMageStrategy.cpp @@ -7,35 +7,9 @@ #include "Playerbots.h" #include "Strategy.h" -// ===== Action Node Factory ===== -class FireMageStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - FireMageStrategyActionNodeFactory() - { - creators["fireball"] = &fireball; - creators["frostbolt"] = &frostbolt; - creators["fire blast"] = &fire_blast; - creators["pyroblast"] = &pyroblast; - creators["scorch"] = &scorch; - creators["living bomb"] = &living_bomb; - creators["combustion"] = &combustion; - } - -private: - static ActionNode* fireball(PlayerbotAI*) { return new ActionNode("fireball", {}, {}, {}); } - static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", {}, {}, {}); } - static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", {}, {}, {}); } - static ActionNode* pyroblast(PlayerbotAI*) { return new ActionNode("pyroblast", {}, {}, {}); } - static ActionNode* scorch(PlayerbotAI*) { return new ActionNode("scorch", {}, {}, {}); } - static ActionNode* living_bomb(PlayerbotAI*) { return new ActionNode("living bomb", {}, {}, {}); } - static ActionNode* combustion(PlayerbotAI*) { return new ActionNode("combustion", {}, {}, {}); } -}; - -// ===== Single Target Strategy ===== FireMageStrategy::FireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) { - actionNodeFactories.Add(new FireMageStrategyActionNodeFactory()); + // No custom ActionNodeFactory needed } // ===== Default Actions ===== diff --git a/src/Ai/Class/Mage/Strategy/FireMageStrategy.h b/src/Ai/Class/Mage/Strategy/FireMageStrategy.h index 03d50641b6b..14655de3a4c 100644 --- a/src/Ai/Class/Mage/Strategy/FireMageStrategy.h +++ b/src/Ai/Class/Mage/Strategy/FireMageStrategy.h @@ -28,4 +28,5 @@ class FirestarterStrategy : public CombatStrategy void InitTriggers(std::vector& triggers) override; std::string const getName() override { return "firestarter"; } }; + #endif diff --git a/src/Ai/Class/Mage/Strategy/FrostFireMageStrategy.cpp b/src/Ai/Class/Mage/Strategy/FrostFireMageStrategy.cpp index 4448a434937..44baf4afcc0 100644 --- a/src/Ai/Class/Mage/Strategy/FrostFireMageStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/FrostFireMageStrategy.cpp @@ -6,35 +6,9 @@ #include "FrostFireMageStrategy.h" #include "Playerbots.h" -// ===== Action Node Factory ===== -class FrostFireMageStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - FrostFireMageStrategyActionNodeFactory() - { - creators["frostfire bolt"] = &frostfire_bolt; - creators["fire blast"] = &fire_blast; - creators["pyroblast"] = &pyroblast; - creators["combustion"] = &combustion; - creators["icy veins"] = &icy_veins; - creators["scorch"] = &scorch; - creators["living bomb"] = &living_bomb; - } - -private: - static ActionNode* frostfire_bolt(PlayerbotAI*) { return new ActionNode("frostfire bolt", {}, {}, {}); } - static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", {}, {}, {}); } - static ActionNode* pyroblast(PlayerbotAI*) { return new ActionNode("pyroblast", {}, {}, {}); } - static ActionNode* combustion(PlayerbotAI*) { return new ActionNode("combustion", {}, {}, {}); } - static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", {}, {}, {}); } - static ActionNode* scorch(PlayerbotAI*) { return new ActionNode("scorch", {}, {}, {}); } - static ActionNode* living_bomb(PlayerbotAI*) { return new ActionNode("living bomb", {}, {}, {}); } -}; - -// ===== Single Target Strategy ===== FrostFireMageStrategy::FrostFireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) { - actionNodeFactories.Add(new FrostFireMageStrategyActionNodeFactory()); + // No custom ActionNodeFactory needed } // ===== Default Actions ===== diff --git a/src/Ai/Class/Mage/Strategy/FrostMageStrategy.cpp b/src/Ai/Class/Mage/Strategy/FrostMageStrategy.cpp index fe703f354d2..4f50cee72aa 100644 --- a/src/Ai/Class/Mage/Strategy/FrostMageStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/FrostMageStrategy.cpp @@ -4,44 +4,11 @@ */ #include "FrostMageStrategy.h" - #include "Playerbots.h" -// ===== Action Node Factory ===== -class FrostMageStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - FrostMageStrategyActionNodeFactory() - { - creators["cold snap"] = &cold_snap; - creators["ice barrier"] = &ice_barrier; - creators["summon water elemental"] = &summon_water_elemental; - creators["deep freeze"] = &deep_freeze; - creators["icy veins"] = &icy_veins; - creators["frostbolt"] = &frostbolt; - creators["ice lance"] = &ice_lance; - creators["fire blast"] = &fire_blast; - creators["fireball"] = &fireball; - creators["frostfire bolt"] = &frostfire_bolt; - } - -private: - static ActionNode* cold_snap(PlayerbotAI*) { return new ActionNode("cold snap", {}, {}, {}); } - static ActionNode* ice_barrier(PlayerbotAI*) { return new ActionNode("ice barrier", {}, {}, {}); } - static ActionNode* summon_water_elemental(PlayerbotAI*) { return new ActionNode("summon water elemental", {}, {}, {}); } - static ActionNode* deep_freeze(PlayerbotAI*) { return new ActionNode("deep freeze", {}, {}, {}); } - static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", {}, {}, {}); } - static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", {}, {}, {}); } - static ActionNode* ice_lance(PlayerbotAI*) { return new ActionNode("ice lance", {}, {}, {}); } - static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", {}, {}, {}); } - static ActionNode* fireball(PlayerbotAI*) { return new ActionNode("fireball", {}, {}, {}); } - static ActionNode* frostfire_bolt(PlayerbotAI*) { return new ActionNode("frostfire bolt", {}, { NextAction("fireball") }, {}); } -}; - -// ===== Single Target Strategy ===== FrostMageStrategy::FrostMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) { - actionNodeFactories.Add(new FrostMageStrategyActionNodeFactory()); + // No custom ActionNodeFactory needed } // ===== Default Actions ===== diff --git a/src/Ai/Class/Mage/Strategy/GenericMageNonCombatStrategy.cpp b/src/Ai/Class/Mage/Strategy/GenericMageNonCombatStrategy.cpp index eab98ea5a22..e93f7589edb 100644 --- a/src/Ai/Class/Mage/Strategy/GenericMageNonCombatStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/GenericMageNonCombatStrategy.cpp @@ -12,28 +12,10 @@ class GenericMageNonCombatStrategyActionNodeFactory : public NamedObjectFactory< public: GenericMageNonCombatStrategyActionNodeFactory() { - creators["molten armor"] = &molten_armor; - creators["mage armor"] = &mage_armor; creators["ice armor"] = &ice_armor; } private: - static ActionNode* molten_armor([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode("molten armor", - /*P*/ {}, - /*A*/ { NextAction("mage armor") }, - /*C*/ {}); - } - - static ActionNode* mage_armor([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode("mage armor", - /*P*/ {}, - /*A*/ { NextAction("ice armor") }, - /*C*/ {}); - } - static ActionNode* ice_armor([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("ice armor", @@ -65,7 +47,7 @@ void MageBuffManaStrategy::InitTriggers(std::vector& triggers) void MageBuffDpsStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode("mage armor", { NextAction("molten armor", 19.0f) })); + triggers.push_back(new TriggerNode("molten armor", { NextAction("molten armor", 19.0f) })); } void MageBuffStrategy::InitTriggers(std::vector& triggers) diff --git a/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp b/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp index 0e26c692d95..69389a8c443 100644 --- a/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp @@ -15,16 +15,8 @@ class GenericMageStrategyActionNodeFactory : public NamedObjectFactory& triggers) Player* bot = botAI->GetBot(); int tab = AiFactory::GetPlayerSpecTab(bot); - if (tab == 0) // Arcane + if (tab == MAGE_TAB_ARCANE) { triggers.push_back(new TriggerNode("arcane power", { NextAction("arcane power", 29.0f) })); triggers.push_back(new TriggerNode("icy veins", { NextAction("icy veins", 28.5f) })); triggers.push_back(new TriggerNode("mirror image", { NextAction("mirror image", 28.0f) })); } - else if (tab == 1) + else if (tab == MAGE_TAB_FIRE) { if (bot->HasSpell(44614) /*Frostfire Bolt*/ && bot->HasAura(15047) /*Ice Shards*/) { // Frostfire @@ -226,7 +154,7 @@ void MageBoostStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("mirror image", { NextAction("mirror image", 17.5f) })); } } - else if (tab == 2) // Frost + else if (tab == MAGE_TAB_FROST) // Frost { triggers.push_back(new TriggerNode("cold snap", { NextAction("cold snap", 28.0f) })); triggers.push_back(new TriggerNode("icy veins", { NextAction("icy veins", 27.5f) })); @@ -254,15 +182,14 @@ void MageAoeStrategy::InitTriggers(std::vector& triggers) Player* bot = botAI->GetBot(); int tab = AiFactory::GetPlayerSpecTab(bot); - if (tab == 0) // Arcane + if (tab == MAGE_TAB_ARCANE) { triggers.push_back(new TriggerNode("flamestrike active and medium aoe", { NextAction("blizzard", 24.0f) })); triggers.push_back(new TriggerNode("medium aoe", { NextAction("flamestrike", 23.0f), NextAction("blizzard", 22.0f) })); - triggers.push_back(new TriggerNode("light aoe", { NextAction("arcane explosion", 21.0f) })); } - else if (tab == 1) // Fire and Frostfire + else if (tab == MAGE_TAB_FIRE) { triggers.push_back( new TriggerNode("medium aoe", { @@ -275,7 +202,7 @@ void MageAoeStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("firestarter", { NextAction("flamestrike", 40.0f) })); triggers.push_back(new TriggerNode("living bomb on attackers", { NextAction("living bomb on attackers", 21.0f) })); } - else if (tab == 2) // Frost + else if (tab == MAGE_TAB_FROST) { triggers.push_back(new TriggerNode("flamestrike active and medium aoe", { NextAction("blizzard", 24.0f) })); triggers.push_back(new TriggerNode("medium aoe", { diff --git a/src/Ai/Class/Mage/Trigger/MageTriggers.cpp b/src/Ai/Class/Mage/Trigger/MageTriggers.cpp index e222107988b..7e7ba9ab732 100644 --- a/src/Ai/Class/Mage/Trigger/MageTriggers.cpp +++ b/src/Ai/Class/Mage/Trigger/MageTriggers.cpp @@ -4,7 +4,6 @@ */ #include "MageTriggers.h" -#include "MageActions.h" #include "Playerbots.h" #include "Player.h" #include "Spell.h" @@ -23,7 +22,7 @@ bool NoManaGemTrigger::IsActive() 5513, // Mana Jade 5514 // Mana Agate }; - Player* bot = botAI->GetBot(); + for (uint32 gemId : gemIds) { if (bot->GetItemCount(gemId, false) > 0) // false = only in bags @@ -45,17 +44,35 @@ bool ArcaneIntellectTrigger::IsActive() bool MageArmorTrigger::IsActive() { Unit* target = GetTarget(); + if (botAI->HasAura("mage armor", target)) + return false; + + if (AI_VALUE2(uint32, "spell id", "mage armor")) + return true; + + return !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) && + !botAI->HasAura("molten armor", target); +} + +bool MoltenArmorTrigger::IsActive() +{ + Unit* target = GetTarget(); + if (botAI->HasAura("molten armor", target)) + return false; + + if (AI_VALUE2(uint32, "spell id", "molten armor")) + return true; + return !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) && - !botAI->HasAura("molten armor", target) && !botAI->HasAura("mage armor", target); + !botAI->HasAura("mage armor", target); } bool FrostNovaOnTargetTrigger::IsActive() { Unit* target = GetTarget(); if (!target || !target->IsAlive() || !target->IsInWorld()) - { return false; - } + return botAI->HasAura(spell, target); } @@ -63,15 +80,14 @@ bool FrostbiteOnTargetTrigger::IsActive() { Unit* target = GetTarget(); if (!target || !target->IsAlive() || !target->IsInWorld()) - { return false; - } + return botAI->HasAura(spell, target); } bool NoFocusMagicTrigger::IsActive() { - if (!bot->HasSpell(54646)) + if (!bot->HasSpell(54646)) // Focus Magic return false; Group* group = bot->GetGroup(); @@ -92,24 +108,19 @@ bool NoFocusMagicTrigger::IsActive() bool DeepFreezeCooldownTrigger::IsActive() { - Player* bot = botAI->GetBot(); - static const uint32 DEEP_FREEZE_SPELL_ID = 44572; - // If the bot does NOT have Deep Freeze, treat as "on cooldown" - if (!bot->HasSpell(DEEP_FREEZE_SPELL_ID)) + if (!bot->HasSpell(44572)) // Deep Freeze return true; - // Otherwise, use the default cooldown logic return SpellCooldownTrigger::IsActive(); } -const std::set FlamestrikeNearbyTrigger::FLAMESTRIKE_SPELL_IDS = {2120, 2121, 8422, 8423, 10215, - 10216, 27086, 42925, 42926}; +const std::unordered_set FlamestrikeNearbyTrigger::FLAMESTRIKE_SPELL_IDS = { + 2120, 2121, 8422, 8423, 10215, 10216, 27086, 42925, 42926 +}; bool FlamestrikeNearbyTrigger::IsActive() { - Player* bot = botAI->GetBot(); - for (uint32 spellId : FLAMESTRIKE_SPELL_IDS) { Aura* aura = bot->GetAura(spellId, bot->GetGUID()); @@ -133,7 +144,6 @@ bool ImprovedScorchTrigger::IsActive() if (!target || !target->IsAlive() || !target->IsInWorld()) return false; - // List of all spell IDs for Improved Scorch, Winter's Chill, and Shadow Mastery static const uint32 ImprovedScorchExclusiveDebuffs[] = {// Shadow Mastery 17794, 17797, 17798, 17799, 17800, // Winter's Chill @@ -147,11 +157,10 @@ bool ImprovedScorchTrigger::IsActive() return false; } - // Use default DebuffTrigger logic for the rest (only trigger if debuff is missing or expiring) return DebuffTrigger::IsActive(); } -const std::set BlizzardChannelCheckTrigger::BLIZZARD_SPELL_IDS = { +const std::unordered_set BlizzardChannelCheckTrigger::BLIZZARD_SPELL_IDS = { 10, // Blizzard Rank 1 6141, // Blizzard Rank 2 8427, // Blizzard Rank 3 @@ -165,19 +174,12 @@ const std::set BlizzardChannelCheckTrigger::BLIZZARD_SPELL_IDS = { bool BlizzardChannelCheckTrigger::IsActive() { - Player* bot = botAI->GetBot(); - - // Check if the bot is channeling a spell - if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) + if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL); + spell && BLIZZARD_SPELL_IDS.count(spell->m_spellInfo->Id)) { - // Only trigger if the spell being channeled is Blizzard - if (BLIZZARD_SPELL_IDS.count(spell->m_spellInfo->Id)) - { - uint8 attackerCount = AI_VALUE(uint8, "attacker count"); - return attackerCount < minEnemies; - } + uint8 attackerCount = AI_VALUE(uint8, "attacker count"); + return attackerCount < minEnemies; } - // Not channeling Blizzard return false; } diff --git a/src/Ai/Class/Mage/Trigger/MageTriggers.h b/src/Ai/Class/Mage/Trigger/MageTriggers.h index e769916f47e..566b6b61eed 100644 --- a/src/Ai/Class/Mage/Trigger/MageTriggers.h +++ b/src/Ai/Class/Mage/Trigger/MageTriggers.h @@ -11,19 +11,15 @@ #include "SharedDefines.h" #include "Trigger.h" #include "Playerbots.h" -#include "PlayerbotAI.h" -#include #include -class PlayerbotAI; - // Buff and Out of Combat Triggers class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger { public: - ArcaneIntellectOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "arcane intellect", 2 * 2000) {} - + ArcaneIntellectOnPartyTrigger(PlayerbotAI* botAI) + : BuffOnPartyTrigger(botAI, "arcane intellect", 2 * 2000) {} bool IsActive() override; }; @@ -41,6 +37,13 @@ class MageArmorTrigger : public BuffTrigger bool IsActive() override; }; +class MoltenArmorTrigger : public BuffTrigger +{ +public: + MoltenArmorTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "molten armor", 5 * 2000) {} + bool IsActive() override; +}; + class NoFocusMagicTrigger : public Trigger { public: @@ -58,7 +61,6 @@ class NoManaGemTrigger : public Trigger { public: NoManaGemTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no mana gem") {} - bool IsActive() override; }; @@ -109,10 +111,8 @@ class ArcaneBlastStackTrigger : public HasAuraStackTrigger class ArcaneBlast4StacksAndMissileBarrageTrigger : public TwoTriggers { public: - ArcaneBlast4StacksAndMissileBarrageTrigger(PlayerbotAI* ai) - : TwoTriggers(ai, "arcane blast stack", "missile barrage") - { - } + ArcaneBlast4StacksAndMissileBarrageTrigger(PlayerbotAI* botAI) + : TwoTriggers(botAI, "arcane blast stack", "missile barrage") {} }; class CombustionTrigger : public BoostTrigger @@ -138,7 +138,7 @@ class DeepFreezeCooldownTrigger : public SpellCooldownTrigger class ColdSnapTrigger : public TwoTriggers { public: - ColdSnapTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "icy veins on cd", "deep freeze on cd") {} + ColdSnapTrigger(PlayerbotAI* botAI) : TwoTriggers(botAI, "icy veins on cd", "deep freeze on cd") {} }; class MirrorImageTrigger : public BoostTrigger @@ -181,9 +181,8 @@ class RemoveCurseTrigger : public NeedCureTrigger class PartyMemberRemoveCurseTrigger : public PartyMemberNeedCureTrigger { public: - PartyMemberRemoveCurseTrigger(PlayerbotAI* botAI) : PartyMemberNeedCureTrigger(botAI, "remove curse", DISPEL_CURSE) - { - } + PartyMemberRemoveCurseTrigger(PlayerbotAI* botAI) + : PartyMemberNeedCureTrigger(botAI, "remove curse", DISPEL_CURSE) {} }; class SpellstealTrigger : public TargetAuraDispelTrigger @@ -216,7 +215,7 @@ class LivingBombTrigger : public DebuffTrigger class LivingBombOnAttackersTrigger : public DebuffOnAttackerTrigger { public: - LivingBombOnAttackersTrigger(PlayerbotAI* ai) : DebuffOnAttackerTrigger(ai, "living bomb", true) {} + LivingBombOnAttackersTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "living bomb", true) {} bool IsActive() override { return BuffTrigger::IsActive(); } }; @@ -282,13 +281,13 @@ class FlamestrikeNearbyTrigger : public Trigger protected: float radius; - static const std::set FLAMESTRIKE_SPELL_IDS; + static const std::unordered_set FLAMESTRIKE_SPELL_IDS; }; class FlamestrikeBlizzardTrigger : public TwoTriggers { public: - FlamestrikeBlizzardTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "flamestrike nearby", "medium aoe") {} + FlamestrikeBlizzardTrigger(PlayerbotAI* botAI) : TwoTriggers(botAI, "flamestrike nearby", "medium aoe") {} }; class BlizzardChannelCheckTrigger : public Trigger @@ -301,7 +300,7 @@ class BlizzardChannelCheckTrigger : public Trigger protected: uint32 minEnemies; - static const std::set BLIZZARD_SPELL_IDS; + static const std::unordered_set BLIZZARD_SPELL_IDS; }; class BlastWaveOffCdTrigger : public SpellNoCooldownTrigger @@ -313,7 +312,8 @@ class BlastWaveOffCdTrigger : public SpellNoCooldownTrigger class BlastWaveOffCdTriggerAndMediumAoeTrigger : public TwoTriggers { public: - BlastWaveOffCdTriggerAndMediumAoeTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "blast wave off cd", "medium aoe") {} + BlastWaveOffCdTriggerAndMediumAoeTrigger(PlayerbotAI* botAI) + : TwoTriggers(botAI, "blast wave off cd", "medium aoe") {} }; class NoFirestarterStrategyTrigger : public Trigger diff --git a/src/Bot/Factory/AiFactory.cpp b/src/Bot/Factory/AiFactory.cpp index 6c638e8071c..c95180538e7 100644 --- a/src/Bot/Factory/AiFactory.cpp +++ b/src/Bot/Factory/AiFactory.cpp @@ -91,9 +91,6 @@ uint8 AiFactory::GetPlayerSpecTab(Player* bot) case CLASS_WARLOCK: tab = WARLOCK_TAB_DEMONOLOGY; break; - case CLASS_SHAMAN: - tab = SHAMAN_TAB_ELEMENTAL; - break; } return tab; @@ -292,24 +289,24 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa if (tab == PRIEST_TAB_SHADOW) engine->addStrategiesNoInit("dps", "shadow debuff", "shadow aoe", nullptr); else if (tab == PRIEST_TAB_DISCIPLINE) - engine->addStrategiesNoInit("heal", nullptr); - else - engine->addStrategiesNoInit("holy heal", nullptr); + engine->addStrategy("heal", false); + else // if (tab == PRIEST_TAB_HOLY) + engine->addStrategy("holy heal", false); engine->addStrategiesNoInit("dps assist", "cure", nullptr); break; case CLASS_MAGE: if (tab == MAGE_TAB_ARCANE) - engine->addStrategiesNoInit("arcane", nullptr); + engine->addStrategiesNoInit("arcane", "bdps", nullptr); else if (tab == MAGE_TAB_FIRE) { if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/) - engine->addStrategiesNoInit("frostfire", nullptr); + engine->addStrategiesNoInit("frostfire", "bdps", nullptr); else - engine->addStrategiesNoInit("fire", nullptr); + engine->addStrategiesNoInit("fire", "bdps", nullptr); } - else - engine->addStrategiesNoInit("frost", nullptr); + else // if (tab == MAGE_TAB_FROST) + engine->addStrategiesNoInit("frost", "bmana", nullptr); engine->addStrategiesNoInit("dps", "dps assist", "cure", "cc", "aoe", nullptr); break; @@ -318,7 +315,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa engine->addStrategiesNoInit("tank", "tank assist", "pull", "pull back", "aoe", nullptr); else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(1680)) // Whirlwind engine->addStrategiesNoInit("arms", "aoe", "dps assist", nullptr); - else + else // if (tab == WARRIOR_TAB_FURY) engine->addStrategiesNoInit("fury", "aoe", "dps assist", nullptr); break; case CLASS_SHAMAN: @@ -326,7 +323,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa engine->addStrategiesNoInit("ele", "stoneskin", "wrath", "mana spring", "wrath of air", nullptr); else if (tab == SHAMAN_TAB_RESTORATION) engine->addStrategiesNoInit("resto", "stoneskin", "flametongue", "mana spring", "wrath of air", nullptr); - else + else // if (tab == SHAMAN_TAB_ENHANCEMENT) engine->addStrategiesNoInit("enh", "strength of earth", "magma", "healing stream", "windfury", nullptr); engine->addStrategiesNoInit("dps assist", "cure", "aoe", nullptr); @@ -336,18 +333,15 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa engine->addStrategiesNoInit("tank", "tank assist", "pull", "pull back", "bthreat", "barmor", "cure", nullptr); else if (tab == PALADIN_TAB_HOLY) engine->addStrategiesNoInit("heal", "dps assist", "cure", "bcast", nullptr); - else + else // if (tab == PALADIN_TAB_RETRIBUTION) engine->addStrategiesNoInit("dps", "dps assist", "cure", "baoe", nullptr); break; case CLASS_DRUID: if (tab == DRUID_TAB_BALANCE) - { - engine->addStrategiesNoInit("caster", "cure", "caster aoe", "dps assist", nullptr); - engine->addStrategy("caster debuff", false); - } + engine->addStrategiesNoInit("caster", "cure", "caster aoe", "caster debuff", "dps assist", nullptr); else if (tab == DRUID_TAB_RESTORATION) engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr); - else + else // if (tab == DRUID_TAB_FERAL) { if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/) engine->addStrategiesNoInit("cat", "dps assist", nullptr); @@ -357,18 +351,18 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa break; case CLASS_HUNTER: if (tab == HUNTER_TAB_BEAST_MASTERY) - engine->addStrategiesNoInit("bm", nullptr); + engine->addStrategy("bm", false); else if (tab == HUNTER_TAB_MARKSMANSHIP) - engine->addStrategiesNoInit("mm", nullptr); - else - engine->addStrategiesNoInit("surv", nullptr); + engine->addStrategy("mm", false); + else // if (tab == HUNTER_TAB_SURVIVAL) + engine->addStrategy("surv", false); engine->addStrategiesNoInit("cc", "dps assist", "aoe", "bdps", nullptr); break; case CLASS_ROGUE: if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) engine->addStrategiesNoInit("melee", "dps assist", "aoe", nullptr); - else + else // if (tab == ROGUE_TAB_COMBAT) engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr); break; case CLASS_WARLOCK: @@ -376,7 +370,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa engine->addStrategiesNoInit("affli", "curse of agony", nullptr); else if (tab == WARLOCK_TAB_DEMONOLOGY) engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr); - else + else // if (tab == WARLOCK_TAB_DESTRUCTION) engine->addStrategiesNoInit("destro", "curse of elements", nullptr); engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr); @@ -386,7 +380,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa engine->addStrategiesNoInit("blood", "tank assist", "pull", "pull back", nullptr); else if (tab == DEATH_KNIGHT_TAB_FROST) engine->addStrategiesNoInit("frost", "frost aoe", "dps assist", nullptr); - else + else // if (tab == DEATH_KNIGHT_TAB_UNHOLY) engine->addStrategiesNoInit("unholy", "unholy aoe", "dps assist", nullptr); break; } @@ -434,7 +428,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa case CLASS_SHAMAN: { if (tab == SHAMAN_TAB_RESTORATION) - engine->addStrategiesNoInit("caster", "caster aoe", "bmana", nullptr); + engine->addStrategiesNoInit("caster", "caster aoe", nullptr); break; } case CLASS_PALADIN: @@ -527,11 +521,6 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const nonCombatEngine->addStrategiesNoInit("bdps", "dps assist", "pet", nullptr); break; case CLASS_SHAMAN: - if (tab == SHAMAN_TAB_ELEMENTAL || tab == SHAMAN_TAB_RESTORATION) - nonCombatEngine->addStrategy("bmana", false); - else - nonCombatEngine->addStrategy("bdps", false); - nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr); break; case CLASS_MAGE: From 55c5d29e2db7a270a4721241486696b1afd5252c Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 23 May 2026 06:41:24 +0200 Subject: [PATCH 38/63] Replace hardcoded bot texts (#2408) ## Pull Request Description Replaced hardcoded bot text with translationable. Related with: #1295 ## How to Test the Changes 1. Invite bots 2. Check that text after "follow" and "stay" return texts "Following" and "Staying" ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) Summary hardcoded text and checking that they already exists to reuse. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- ...2026_05_19_00_ai_playerbot_audit_texts.sql | 489 ++++++++++++++++++ .../Base/Actions/AcceptInvitationAction.cpp | 3 +- src/Ai/Base/Actions/AcceptQuestAction.cpp | 16 +- src/Ai/Base/Actions/AreaTriggerAction.cpp | 9 +- src/Ai/Base/Actions/ArenaTeamActions.cpp | 9 +- src/Ai/Base/Actions/AttackAction.cpp | 43 +- src/Ai/Base/Actions/BankAction.cpp | 4 +- src/Ai/Base/Actions/ChatShortcutActions.cpp | 28 +- src/Ai/Base/Actions/DropQuestAction.cpp | 47 +- src/Ai/Base/Actions/GenericActions.cpp | 4 +- src/Ai/Base/Actions/GuildAcceptAction.cpp | 10 +- src/Ai/Base/Actions/LeaveGroupAction.cpp | 5 +- src/Ai/Base/Actions/OutfitAction.cpp | 59 ++- src/Ai/Base/Actions/ReleaseSpiritAction.cpp | 11 +- .../Base/Actions/ReviveFromCorpseAction.cpp | 3 +- src/Ai/Base/Actions/SendMailAction.cpp | 37 +- src/Ai/Base/Actions/SetCraftAction.cpp | 31 +- src/Ai/Base/Actions/SetHomeAction.cpp | 10 +- src/Ai/Base/Actions/ShareQuestAction.cpp | 7 +- src/Ai/Base/Actions/TameAction.cpp | 90 +++- src/Ai/Base/Actions/TaxiAction.cpp | 10 +- src/Ai/Base/Actions/TradeStatusAction.cpp | 63 ++- src/Ai/Base/Actions/UseItemAction.cpp | 60 ++- src/Ai/Base/Actions/UseMeetingStoneAction.cpp | 54 +- src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp | 26 +- src/Ai/World/Rpg/Action/RpgSubActions.cpp | 19 +- src/Bot/PlayerbotAI.cpp | 10 +- src/Bot/PlayerbotMgr.cpp | 10 +- src/Bot/RandomPlayerbotMgr.cpp | 4 +- 29 files changed, 969 insertions(+), 202 deletions(-) create mode 100644 data/sql/playerbots/updates/2026_05_19_00_ai_playerbot_audit_texts.sql diff --git a/data/sql/playerbots/updates/2026_05_19_00_ai_playerbot_audit_texts.sql b/data/sql/playerbots/updates/2026_05_19_00_ai_playerbot_audit_texts.sql new file mode 100644 index 00000000000..39639b968e4 --- /dev/null +++ b/data/sql/playerbots/updates/2026_05_19_00_ai_playerbot_audit_texts.sql @@ -0,0 +1,489 @@ +DELETE FROM ai_playerbot_texts WHERE name IN ( + 'quest_accept_debug', + 'quest_already_have_error', + 'quest_cant_take_error', + 'arena_team_already_in_team', + 'arena_team_thanks_for_invite', + 'area_trigger_follow_too_far_error', + 'area_trigger_wait_for_me', + 'attack_no_target_error', + 'attack_target_not_in_world_error', + 'attack_in_flight_error', + 'attack_pvp_prohibited_error', + 'attack_target_friendly_error', + 'attack_target_dead_error', + 'attack_target_not_in_sight_error', + 'attack_already_attacking_error', + 'attack_invalid_target_error', + 'bank_no_banker_nearby_error', + 'move_from_group', + 'running_away', + 'clean_quest_log_started', + 'quest_trivial_will_remove', + 'quest_has_been_removed', + 'quest_not_trivial_kept', + 'quest_removed_debug', + 'quest_removed_with_name', + 'guild_accept_inviter_not_in_guild', + 'guild_accept_already_in_guild', + 'guild_accept_declined', + 'outfit_usage_add', + 'outfit_usage_remove', + 'outfit_usage_equip', + 'outfit_set_as', + 'outfit_equipping', + 'outfit_replace_current', + 'outfit_resetting', + 'outfit_updating_current', + 'outfit_item_removed_from', + 'outfit_item_added_to', + 'release_spirit_not_dead_wait', + 'release_spirit_already_spirit', + 'release_spirit_releasing', + 'release_spirit_meet_graveyard', + 'send_mail_no_mailbox_nearby', + 'send_mail_one_item_only', + 'send_mail_cannot_send_money', + 'send_mail_not_enough_money', + 'send_mail_sending_to', + 'send_mail_cannot_send_item', + 'send_mail_item_not_for_sale', + 'send_mail_sent_to', + 'craft_reset', + 'craft_usage', + 'craft_cannot_craft', + 'craft_summary', + 'set_home_success', + 'set_home_no_innkeeper_error', + 'quest_shared', + 'tame_invalid_id_error', + 'tame_usage_error', + 'tame_pet_changed', + 'tame_pet_changed_initialized', + 'tame_exotic_requires_beast_mastery', + 'tame_no_pet_by_name', + 'tame_no_pet_by_id', + 'tame_no_pet_by_family', + 'tame_no_pet_to_rename', + 'tame_pet_name_length_error', + 'tame_pet_name_alpha_error', + 'tame_pet_name_forbidden_error', + 'tame_pet_renamed', + 'tame_pet_rename_refresh_hint', + 'tame_only_hunters_level_10', + 'tame_creature_template_not_found', + 'tame_create_pet_failed', + 'tame_pet_abandoned', + 'tame_no_hunter_pet_to_abandon', + 'taxi_ready_next_flight', + 'taxi_cant_fly_with_you', + 'taxi_no_flightmaster_nearby', + 'trade_busy_now', + 'trade_disabled', + 'trade_thank_you_player', + 'trade_selling_disabled', + 'trade_buying_disabled', + 'trade_item_not_for_sale', + 'trade_item_not_needed', + 'trade_no_items_error', + 'trade_discount_buy_only', + 'trade_success_pleasure', + 'trade_success_fair_trade', + 'trade_success_thanks', + 'trade_success_off_with_you', + 'trade_want_money_for_this', + 'use_item_none_available', + 'use_gameobject', + 'socket_does_not_fit', + 'use_item_on_target', + 'use_item', + 'socketing_item_with_gem', + 'meeting_stone_in_combat', + 'meeting_stone_welcome', + 'meeting_stone_none_nearby', + 'meeting_stone_none_near_you', + 'meeting_stone_no_hearthstone_self', + 'meeting_stone_no_hearthstone_you', + 'meeting_stone_hearthstone_not_ready_self', + 'meeting_stone_hearthstone_not_ready_you', + 'meeting_stone_no_innkeepers_nearby', + 'meeting_stone_no_innkeepers_near_you', + 'meeting_stone_cannot_summon_vehicle', + 'meeting_stone_cannot_summon_master_in_combat', + 'meeting_stone_cannot_summon_master_dead', + 'meeting_stone_cannot_summon_bot_dead', + 'meeting_stone_revived', + 'meeting_stone_not_enough_space', + 'new_rpg_quest_accepted', + 'new_rpg_quest_rewarded', + 'new_rpg_quest_dropped', + 'rpg_item_better_for_player', + 'rpg_start_trade_with_player' +); + +DELETE FROM ai_playerbot_texts_chance WHERE name IN ( + 'quest_accept_debug', + 'quest_already_have_error', + 'quest_cant_take_error', + 'arena_team_already_in_team', + 'arena_team_thanks_for_invite', + 'area_trigger_follow_too_far_error', + 'area_trigger_wait_for_me', + 'attack_no_target_error', + 'attack_target_not_in_world_error', + 'attack_in_flight_error', + 'attack_pvp_prohibited_error', + 'attack_target_friendly_error', + 'attack_target_dead_error', + 'attack_target_not_in_sight_error', + 'attack_already_attacking_error', + 'attack_invalid_target_error', + 'bank_no_banker_nearby_error', + 'move_from_group', + 'running_away', + 'clean_quest_log_started', + 'quest_trivial_will_remove', + 'quest_has_been_removed', + 'quest_not_trivial_kept', + 'quest_removed_debug', + 'quest_removed_with_name', + 'guild_accept_inviter_not_in_guild', + 'guild_accept_already_in_guild', + 'guild_accept_declined', + 'outfit_usage_add', + 'outfit_usage_remove', + 'outfit_usage_equip', + 'outfit_set_as', + 'outfit_equipping', + 'outfit_replace_current', + 'outfit_resetting', + 'outfit_updating_current', + 'outfit_item_removed_from', + 'outfit_item_added_to', + 'release_spirit_not_dead_wait', + 'release_spirit_already_spirit', + 'release_spirit_releasing', + 'release_spirit_meet_graveyard', + 'send_mail_no_mailbox_nearby', + 'send_mail_one_item_only', + 'send_mail_cannot_send_money', + 'send_mail_not_enough_money', + 'send_mail_sending_to', + 'send_mail_cannot_send_item', + 'send_mail_item_not_for_sale', + 'send_mail_sent_to', + 'craft_reset', + 'craft_usage', + 'craft_cannot_craft', + 'craft_summary', + 'set_home_success', + 'set_home_no_innkeeper_error', + 'quest_shared', + 'tame_invalid_id_error', + 'tame_usage_error', + 'tame_pet_changed', + 'tame_pet_changed_initialized', + 'tame_exotic_requires_beast_mastery', + 'tame_no_pet_by_name', + 'tame_no_pet_by_id', + 'tame_no_pet_by_family', + 'tame_no_pet_to_rename', + 'tame_pet_name_length_error', + 'tame_pet_name_alpha_error', + 'tame_pet_name_forbidden_error', + 'tame_pet_renamed', + 'tame_pet_rename_refresh_hint', + 'tame_only_hunters_level_10', + 'tame_creature_template_not_found', + 'tame_create_pet_failed', + 'tame_pet_abandoned', + 'tame_no_hunter_pet_to_abandon', + 'taxi_ready_next_flight', + 'taxi_cant_fly_with_you', + 'taxi_no_flightmaster_nearby', + 'trade_busy_now', + 'trade_disabled', + 'trade_thank_you_player', + 'trade_selling_disabled', + 'trade_buying_disabled', + 'trade_item_not_for_sale', + 'trade_item_not_needed', + 'trade_no_items_error', + 'trade_discount_buy_only', + 'trade_success_pleasure', + 'trade_success_fair_trade', + 'trade_success_thanks', + 'trade_success_off_with_you', + 'trade_want_money_for_this', + 'use_item_none_available', + 'use_gameobject', + 'socket_does_not_fit', + 'use_item_on_target', + 'use_item', + 'socketing_item_with_gem', + 'meeting_stone_in_combat', + 'meeting_stone_welcome', + 'meeting_stone_none_nearby', + 'meeting_stone_none_near_you', + 'meeting_stone_no_hearthstone_self', + 'meeting_stone_no_hearthstone_you', + 'meeting_stone_hearthstone_not_ready_self', + 'meeting_stone_hearthstone_not_ready_you', + 'meeting_stone_no_innkeepers_nearby', + 'meeting_stone_no_innkeepers_near_you', + 'meeting_stone_cannot_summon_vehicle', + 'meeting_stone_cannot_summon_master_in_combat', + 'meeting_stone_cannot_summon_master_dead', + 'meeting_stone_cannot_summon_bot_dead', + 'meeting_stone_revived', + 'meeting_stone_not_enough_space', + 'new_rpg_quest_accepted', + 'new_rpg_quest_rewarded', + 'new_rpg_quest_dropped', + 'rpg_item_better_for_player', + 'rpg_start_trade_with_player' +); + +INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES +(1779, 'quest_accept_debug', 'Quest [%quest] accepted', 0, 0, '퀘스트 [%quest] 수락', 'Quête [%quest] acceptée', 'Quest [%quest] angenommen', '任务 [%quest] 已接受', '任務 [%quest] 已接受', 'Misión [%quest] aceptada', 'Misión [%quest] aceptada', 'Задание [%quest] принято'), +(1780, 'quest_already_have_error', 'I have this quest', 0, 0, '이미 이 퀘스트를 가지고 있습니다', 'J''ai déjà cette quête', 'Ich habe diese Quest bereits', '我已经有这个任务了', '我已經有這個任務了', 'Ya tengo esta misión', 'Ya tengo esta misión', 'У меня уже есть это задание'), +(1781, 'quest_cant_take_error', 'I can''t take this quest', 0, 0, '이 퀘스트를 받을 수 없습니다', 'Je ne peux pas prendre cette quête', 'Ich kann diese Quest nicht annehmen', '我无法接受这个任务', '我無法接受這個任務', 'No puedo aceptar esta misión', 'No puedo aceptar esta misión', 'Я не могу взять это задание'), +(1782, 'arena_team_already_in_team', 'Sorry, I am already in such team', 0, 0, '죄송하지만 이미 그런 팀에 속해 있습니다', 'Désolé, je suis déjà dans une telle équipe', 'Entschuldigung, ich bin bereits in so einem Team', '抱歉,我已经在这样的队伍里了', '抱歉,我已經在這樣的隊伍裡了', 'Lo siento, ya estoy en un equipo así', 'Lo siento, ya estoy en un equipo así', 'Извините, я уже состою в такой команде'), +(1783, 'arena_team_thanks_for_invite', 'Thanks for the invite!', 0, 0, '초대해 주셔서 감사합니다!', 'Merci pour l''invitation !', 'Danke für die Einladung!', '谢谢你的邀请!', '謝謝你的邀請!', 'Gracias por la invitación!', 'Gracias por la invitación!', 'Спасибо за приглашение!'), +(1784, 'area_trigger_follow_too_far_error', 'I won''t follow: too far away', 0, 0, '너무 멀어서 따라가지 않겠습니다', 'Je ne suivrai pas : c''est trop loin', 'Ich werde nicht folgen: zu weit entfernt', '我不会跟随:距离太远了', '我不會跟隨:距離太遠了', 'No te seguiré: estás demasiado lejos', 'No te seguiré: estás demasiado lejos', 'Я не пойду следом: слишком далеко'), +(1785, 'area_trigger_wait_for_me', 'Wait for me', 0, 0, '잠깐만 기다려 주세요', 'Attendez-moi', 'Warte auf mich', '等等我', '等等我', 'Espérame', 'Espérame', 'Подожди меня'), +(1786, 'attack_no_target_error', 'I have no target', 0, 0, '대상이 없습니다', 'Je n''ai pas de cible', 'Ich habe kein Ziel', '我没有目标', '我沒有目標', 'No tengo objetivo', 'No tengo objetivo', 'У меня нет цели'), +(1787, 'attack_target_not_in_world_error', '%target is no longer in the world.', 0, 0, '%target은(는) 더 이상 월드에 없습니다.', '%target n''est plus dans le monde.', '%target ist nicht mehr in der Welt.', '%target 已不在世界中。', '%target 已不在世界中。', '%target ya no está en el mundo.', '%target ya no está en el mundo.', '%target больше не находится в мире.'), +(1788, 'attack_in_flight_error', 'I cannot attack in flight', 0, 0, '비행 중에는 공격할 수 없습니다', 'Je ne peux pas attaquer en vol', 'Ich kann im Flug nicht angreifen', '飞行中无法攻击', '飛行中無法攻擊', 'No puedo atacar en vuelo', 'No puedo atacar en vuelo', 'Я не могу атаковать в полёте'), +(1789, 'attack_pvp_prohibited_error', 'I cannot attack other players in PvP prohibited areas.', 0, 0, 'PvP 금지 지역에서는 다른 플레이어를 공격할 수 없습니다.', 'Je ne peux pas attaquer d''autres joueurs dans les zones où le PvP est interdit.', 'Ich kann andere Spieler in PvP-verbotenen Gebieten nicht angreifen.', '我不能在禁止 PvP 的区域攻击其他玩家。', '我不能在禁止 PvP 的區域攻擊其他玩家。', 'No puedo atacar a otros jugadores en zonas donde el JcJ está prohibido.', 'No puedo atacar a otros jugadores en zonas donde el JcJ está prohibido.', 'Я не могу атаковать других игроков в зонах, где PvP запрещено.'), +(1790, 'attack_target_friendly_error', '%target is friendly to me.', 0, 0, '%target은(는) 우호적인 대상입니다.', '%target m''est amical.', '%target ist mir gegenüber freundlich.', '%target 对我是友方目标。', '%target 對我是友方目標。', '%target es amistoso conmigo.', '%target es amistoso conmigo.', '%target дружелюбен ко мне.'), +(1791, 'attack_target_dead_error', '%target is dead.', 0, 0, '%target은(는) 죽었습니다.', '%target est mort.', '%target ist tot.', '%target 已经死了。', '%target 已經死了。', '%target está muerto.', '%target está muerto.', '%target мёртв.'), +(1792, 'attack_target_not_in_sight_error', '%target is not in my sight.', 0, 0, '%target이(가) 시야에 없습니다.', '%target n''est pas dans mon champ de vision.', '%target ist nicht in Sichtweite.', '%target 不在我的视野中。', '%target 不在我的視野中。', '%target no está a mi vista.', '%target no está a mi vista.', '%target вне поля моего зрения.'), +(1793, 'attack_already_attacking_error', 'I am already attacking %target.', 0, 0, '이미 %target을(를) 공격하고 있습니다.', 'J''attaque déjà %target.', 'Ich greife %target bereits an.', '我已经在攻击 %target。', '我已經在攻擊 %target。', 'Ya estoy atacando a %target.', 'Ya estoy atacando a %target.', 'Я уже атакую %target.'), +(1794, 'attack_invalid_target_error', 'I cannot attack an invalid target.', 0, 0, '유효하지 않은 대상은 공격할 수 없습니다.', 'Je ne peux pas attaquer une cible invalide.', 'Ich kann kein ungültiges Ziel angreifen.', '我不能攻击无效目标。', '我不能攻擊無效目標。', 'No puedo atacar un objetivo no válido.', 'No puedo atacar un objetivo no válido.', 'Я не могу атаковать недопустимую цель.'), +(1795, 'bank_no_banker_nearby_error', 'Cannot find banker nearby', 0, 0, '근처에 은행원이 없습니다', 'Impossible de trouver un banquier à proximité', 'Kein Bankier in der Nähe gefunden', '附近找不到银行职员', '附近找不到銀行職員', 'No encuentro un banquero cerca', 'No encuentro un banquero cerca', 'Поблизости нет банкира'), +(1796, 'move_from_group', 'Moving away from group', 0, 0, '파티에서 멀어지는 중입니다', 'Je m''éloigne du groupe', 'Ich entferne mich von der Gruppe', '正在远离队伍', '正在遠離隊伍', 'Me estoy alejando del grupo', 'Me estoy alejando del grupo', 'Отхожу от группы'), +(1797, 'running_away', 'Running away', 0, 0, '도망치는 중입니다', 'Je m''enfuis', 'Ich laufe weg', '正在逃跑', '正在逃跑', 'Estoy huyendo', 'Estoy huyendo', 'Убегаю'), +(1798, 'clean_quest_log_started', 'Clean Quest Log command received, removing grey/trivial quests...', 0, 0, '퀘스트 로그 정리 명령을 받았습니다. 회색/사소한 퀘스트를 제거하는 중입니다...', 'Commande de nettoyage du journal des quêtes reçue, suppression des quêtes grises/triviales...', 'Befehl zum Bereinigen des Questlogs erhalten, graue/triviale Quests werden entfernt...', '已收到清理任务日志命令,正在移除灰色/琐碎任务...', '已收到清理任務日誌命令,正在移除灰色/瑣碎任務...', 'Comando para limpiar el registro de misiones recibido, eliminando misiones grises/triviales...', 'Comando para limpiar el registro de misiones recibido, eliminando misiones grises/triviales...', 'Получена команда очистки журнала заданий, удаляю серые/простые задания...'), +(1799, 'quest_trivial_will_remove', 'Quest [%title] will be removed because it is trivial (grey).', 0, 0, '퀘스트 [%title]은(는) 사소한(회색) 퀘스트이므로 제거됩니다.', 'La quête [%title] sera supprimée car elle est triviale (grise).', 'Quest [%title] wird entfernt, weil sie trivial (grau) ist.', '任务 [%title] 将被移除,因为它是琐碎的(灰色)任务。', '任務 [%title] 將被移除,因為它是瑣碎的(灰色)任務。', 'La misión [%title] se eliminará porque es trivial (gris).', 'La misión [%title] se eliminará porque es trivial (gris).', 'Задание [%title] будет удалено, так как оно простое (серое).'), +(1800, 'quest_has_been_removed', 'Quest [%title] has been removed.', 0, 0, '퀘스트 [%title]이(가) 제거되었습니다.', 'La quête [%title] a été supprimée.', 'Quest [%title] wurde entfernt.', '任务 [%title] 已被移除。', '任務 [%title] 已被移除。', 'La misión [%title] ha sido eliminada.', 'La misión [%title] ha sido eliminada.', 'Задание [%title] было удалено.'), +(1801, 'quest_not_trivial_kept', 'Quest [%title] is not trivial and will be kept.', 0, 0, '퀘스트 [%title]은(는) 사소하지 않으므로 유지됩니다.', 'La quête [%title] n''est pas triviale et sera conservée.', 'Quest [%title] ist nicht trivial und wird behalten.', '任务 [%title] 并不琐碎,将被保留。', '任務 [%title] 並不瑣碎,將被保留。', 'La misión [%title] no es trivial y se conservará.', 'La misión [%title] no es trivial y se conservará.', 'Задание [%title] не является простым и будет сохранено.'), +(1802, 'quest_removed_debug', 'Quest [%quest] removed', 0, 0, '퀘스트 [%quest] 제거됨', 'Quête [%quest] supprimée', 'Quest [%quest] entfernt', '任务 [%quest] 已移除', '任務 [%quest] 已移除', 'Misión [%quest] eliminada', 'Misión [%quest] eliminada', 'Задание [%quest] удалено'), +(1803, 'quest_removed_with_name', 'Quest removed %quest', 0, 0, '퀘스트 제거됨 %quest', 'Quête supprimée %quest', 'Quest entfernt %quest', '任务已移除 %quest', '任務已移除 %quest', 'Misión eliminada %quest', 'Misión eliminada %quest', 'Задание удалено %quest'), +(1804, 'guild_accept_inviter_not_in_guild', 'You are not in a guild!', 0, 0, '당신은 길드에 속해 있지 않습니다!', 'Vous n''êtes pas dans une guilde !', 'Du bist in keiner Gilde!', '你不在公会中!', '你不在公會中!', 'No estás en un gremio!', 'No estás en un gremio!', 'Вы не состоите в гильдии!'), +(1805, 'guild_accept_already_in_guild', 'Sorry, I am in a guild already', 0, 0, '죄송하지만 이미 길드에 속해 있습니다', 'Désolé, je suis déjà dans une guilde', 'Entschuldigung, ich bin bereits in einer Gilde', '抱歉,我已经在公会里了', '抱歉,我已經在公會裡了', 'Lo siento, ya estoy en un gremio', 'Lo siento, ya estoy en un gremio', 'Извините, я уже в гильдии'), +(1806, 'guild_accept_declined', 'Sorry, I don''t want to join your guild :(', 0, 0, '죄송하지만 당신의 길드에 가입하고 싶지 않습니다 :(', 'Désolé, je ne veux pas rejoindre votre guilde :(', 'Entschuldigung, ich möchte deiner Gilde nicht beitreten :(', '抱歉,我不想加入你的公会 :(', '抱歉,我不想加入你的公會 :(', 'Lo siento, no quiero unirme a tu gremio :(', 'Lo siento, no quiero unirme a tu gremio :(', 'Извините, я не хочу вступать в вашу гильдию :('), +(1807, 'outfit_usage_add', 'outfit +[item] to add items', 0, 0, '아이템을 추가하려면 outfit +[item]', 'outfit +[item] pour ajouter des objets', 'outfit +[item], um Gegenstände hinzuzufügen', '使用 outfit +[item] 添加物品', '使用 outfit +[item] 添加物品', 'outfit +[item] para agregar objetos', 'outfit +[item] para agregar objetos', 'outfit +[item], чтобы добавить предметы'), +(1808, 'outfit_usage_remove', 'outfit -[item] to remove items', 0, 0, '아이템을 제거하려면 outfit -[item]', 'outfit -[item] pour retirer des objets', 'outfit -[item], um Gegenstände zu entfernen', '使用 outfit -[item] 移除物品', '使用 outfit -[item] 移除物品', 'outfit -[item] para eliminar objetos', 'outfit -[item] para eliminar objetos', 'outfit -[item], чтобы убрать предметы'), +(1809, 'outfit_usage_equip', 'outfit equip/replace to equip items', 0, 0, '아이템을 장착하려면 outfit equip/replace', 'outfit equip/replace pour équiper des objets', 'outfit equip/replace, um Gegenstände anzulegen', '使用 outfit equip/replace 装备物品', '使用 outfit equip/replace 裝備物品', 'outfit equip/replace para equipar objetos', 'outfit equip/replace para equipar objetos', 'outfit equip/replace, чтобы экипировать предметы'), +(1810, 'outfit_set_as', 'Setting outfit %name as %param', 0, 0, '복장 %name을(를) %param(으)로 설정하는 중입니다', 'Définition de la tenue %name comme %param', 'Setze Outfit %name als %param', '正在将装备方案 %name 设置为 %param', '正在將裝備方案 %name 設定為 %param', 'Estableciendo el atuendo %name como %param', 'Estableciendo el atuendo %name como %param', 'Устанавливаю комплект %name как %param'), +(1811, 'outfit_equipping', 'Equipping outfit %name', 0, 0, '복장 %name을(를) 장착하는 중입니다', 'Équipement de la tenue %name', 'Outfit %name wird angelegt', '正在装备方案 %name', '正在裝備方案 %name', 'Equipando el atuendo %name', 'Equipando el atuendo %name', 'Экипирую комплект %name'), +(1812, 'outfit_replace_current', 'Replacing current equip with outfit %name', 0, 0, '현재 장비를 복장 %name으로 교체하는 중입니다', 'Remplacement de l''équipement actuel par la tenue %name', 'Aktuelle Ausrüstung wird durch Outfit %name ersetzt', '正在用装备方案 %name 替换当前装备', '正在用裝備方案 %name 替換目前裝備', 'Reemplazando el equipo actual con el atuendo %name', 'Reemplazando el equipo actual con el atuendo %name', 'Заменяю текущую экипировку комплектом %name'), +(1813, 'outfit_resetting', 'Resetting outfit %name', 0, 0, '복장 %name을(를) 초기화하는 중입니다', 'Réinitialisation de la tenue %name', 'Outfit %name wird zurückgesetzt', '正在重置装备方案 %name', '正在重置裝備方案 %name', 'Restableciendo el atuendo %name', 'Restableciendo el atuendo %name', 'Сбрасываю комплект %name'), +(1814, 'outfit_updating_current', 'Updating with current items outfit %name', 0, 0, '현재 아이템으로 복장 %name을(를) 업데이트하는 중입니다', 'Mise à jour de la tenue %name avec les objets actuels', 'Outfit %name wird mit der aktuellen Ausrüstung aktualisiert', '正在用当前物品更新装备方案 %name', '正在用目前物品更新裝備方案 %name', 'Actualizando el atuendo %name con los objetos actuales', 'Actualizando el atuendo %name con los objetos actuales', 'Обновляю комплект %name текущими предметами'), +(1815, 'outfit_item_removed_from', '%item removed from %name', 0, 0, '%name에서 %item이(가) 제거되었습니다', '%item retiré de %name', '%item aus %name entfernt', '已从 %name 中移除 %item', '已從 %name 中移除 %item', '%item eliminado de %name', '%item eliminado de %name', '%item удалён из %name'), +(1816, 'outfit_item_added_to', '%item added to %name', 0, 0, '%item이(가) %name에 추가되었습니다', '%item ajouté à %name', '%item zu %name hinzugefügt', '已将 %item 添加到 %name', '已將 %item 添加到 %name', '%item agregado a %name', '%item agregado a %name', '%item добавлен в %name'), +(1817, 'release_spirit_not_dead_wait', 'I am not dead, will wait here', 0, 0, '저는 죽지 않았습니다. 여기서 기다리겠습니다', 'Je ne suis pas mort, j''attendrai ici', 'Ich bin nicht tot und werde hier warten', '我还没死,会在这里等', '我還沒死,會在這裡等', 'No estoy muerto, esperaré aquí', 'No estoy muerto, esperaré aquí', 'Я не мёртв, буду ждать здесь'), +(1818, 'release_spirit_already_spirit', 'I am already a spirit', 0, 0, '저는 이미 유령입니다', 'Je suis déjà un esprit', 'Ich bin bereits ein Geist', '我已经是灵魂状态了', '我已經是靈魂狀態了', 'Ya soy un espíritu', 'Ya soy un espíritu', 'Я уже дух'), +(1819, 'release_spirit_releasing', 'Releasing...', 0, 0, '해방 중...', 'Je libère mon esprit...', 'Geist wird freigesetzt...', '正在释放灵魂...', '正在釋放靈魂...', 'Liberando espíritu...', 'Liberando espíritu...', 'Освобождаю дух...'), +(1820, 'release_spirit_meet_graveyard', 'Meet me at the graveyard', 0, 0, '묘지에서 만나요', 'Retrouvez-moi au cimetière', 'Triff mich auf dem Friedhof', '墓地见', '墓地見', 'Encuéntrame en el cementerio', 'Encuéntrame en el cementerio', 'Встречаемся на кладбище'), +(1821, 'send_mail_no_mailbox_nearby', 'There is no mailbox nearby', 0, 0, '근처에 우체통이 없습니다', 'Il n''y a pas de boîte aux lettres à proximité', 'Kein Briefkasten in der Nähe', '附近没有邮箱', '附近沒有郵箱', 'No hay un buzón cerca', 'No hay un buzón cerca', 'Поблизости нет почтового ящика'), +(1822, 'send_mail_one_item_only', 'You can not request more than one item', 0, 0, '둘 이상의 아이템은 요청할 수 없습니다', 'Vous ne pouvez pas demander plus d''un objet', 'Du kannst nicht mehr als einen Gegenstand anfordern', '你不能请求多于一件物品', '你不能要求超過一件物品', 'No puedes solicitar más de un objeto', 'No puedes solicitar más de un objeto', 'Нельзя запрашивать больше одного предмета'), +(1823, 'send_mail_cannot_send_money', 'I cannot send money', 0, 0, '돈은 보낼 수 없습니다', 'Je ne peux pas envoyer d''argent', 'Ich kann kein Geld senden', '我不能寄送钱', '我不能寄送金錢', 'No puedo enviar dinero', 'No puedo enviar dinero', 'Я не могу отправить деньги'), +(1824, 'send_mail_not_enough_money', 'I don''t have enough money', 0, 0, '돈이 충분하지 않습니다', 'Je n''ai pas assez d''argent', 'Ich habe nicht genug Geld', '我没有足够的钱', '我沒有足夠的錢', 'No tengo suficiente dinero', 'No tengo suficiente dinero', 'У меня недостаточно денег'), +(1825, 'send_mail_sending_to', 'Sending mail to %receiver', 0, 0, '%receiver에게 우편을 보내는 중입니다', 'Envoi du courrier à %receiver', 'Sende Post an %receiver', '正在向 %receiver 发送邮件', '正在向 %receiver 發送郵件', 'Enviando correo a %receiver', 'Enviando correo a %receiver', 'Отправляю почту %receiver'), +(1826, 'send_mail_cannot_send_item', 'Cannot send %item', 0, 0, '%item을(를) 보낼 수 없습니다', 'Impossible d''envoyer %item', '%item kann nicht gesendet werden', '无法发送 %item', '無法發送 %item', 'No se puede enviar %item', 'No se puede enviar %item', 'Невозможно отправить %item'), +(1827, 'send_mail_item_not_for_sale', '%item: it is not for sale', 0, 0, '%item: 판매 대상이 아닙니다', '%item : ce n''est pas à vendre', '%item: Das steht nicht zum Verkauf', '%item:这不是出售物品', '%item:這不是出售物品', '%item: esto no está en venta', '%item: esto no está en venta', '%item: это не продаётся'), +(1828, 'send_mail_sent_to', 'Sent mail to %receiver', 0, 0, '%receiver에게 우편을 보냈습니다', 'Courrier envoyé à %receiver', 'Post an %receiver gesendet', '已向 %receiver 发送邮件', '已向 %receiver 發送郵件', 'Correo enviado a %receiver', 'Correo enviado a %receiver', 'Почта отправлена %receiver'), +(1829, 'craft_reset', 'I will not craft anything', 0, 0, '아무것도 제작하지 않겠습니다', 'Je ne fabriquerai rien', 'Ich werde nichts herstellen', '我不会制作任何东西', '我不會製作任何東西', 'No fabricaré nada', 'No fabricaré nada', 'Я ничего не буду создавать'), +(1830, 'craft_usage', 'Usage: ''craft [itemId]'' or ''craft reset''', 0, 0, '사용법: ''craft [itemId]'' 또는 ''craft reset''', 'Utilisation : ''craft [itemId]'' ou ''craft reset''', 'Verwendung: ''craft [itemId]'' oder ''craft reset''', '用法:''craft [itemId]'' 或 ''craft reset''', '用法:''craft [itemId]'' 或 ''craft reset''', 'Uso: ''craft [itemId]'' o ''craft reset''', 'Uso: ''craft [itemId]'' o ''craft reset''', 'Использование: ''craft [itemId]'' или ''craft reset'''), +(1831, 'craft_cannot_craft', 'I cannot craft this', 0, 0, '이것은 제작할 수 없습니다', 'Je ne peux pas fabriquer ceci', 'Ich kann das nicht herstellen', '我无法制作这个', '我無法製作這個', 'No puedo fabricar esto', 'No puedo fabricar esto', 'Я не могу это создать'), +(1832, 'craft_summary', 'I will craft %item using reagents: %reagents (craft fee: %money)', 0, 0, '재료 %reagents을(를) 사용해 %item을(를) 제작하겠습니다 (제작 수수료: %money)', 'Je fabriquerai %item en utilisant les composants : %reagents (frais de fabrication : %money)', 'Ich werde %item mit folgenden Reagenzien herstellen: %reagents (Herstellungsgebühr: %money)', '我将使用材料 %reagents 制作 %item(制作费用:%money)', '我將使用材料 %reagents 製作 %item(製作費用:%money)', 'Fabricaré %item usando los materiales: %reagents (tarifa de fabricación: %money)', 'Fabricaré %item usando los materiales: %reagents (tarifa de fabricación: %money)', 'Я изготовлю %item, используя реагенты: %reagents (плата за изготовление: %money)'), +(1833, 'set_home_success', 'This inn is my new home', 0, 0, '이 여관을 새로운 집으로 설정했습니다', 'Cette auberge est ma nouvelle maison', 'Dieses Gasthaus ist mein neues Zuhause', '这家旅店是我的新家', '這間旅店是我的新家', 'Esta posada es mi nuevo hogar', 'Esta posada es mi nuevo hogar', 'Этот трактир теперь мой новый дом'), +(1834, 'set_home_no_innkeeper_error', 'Can''t find any innkeeper around', 0, 0, '주변에서 여관주인을 찾을 수 없습니다', 'Impossible de trouver un aubergiste aux alentours', 'Kein Gastwirt in der Nähe gefunden', '附近找不到旅店老板', '附近找不到旅店老闆', 'No encuentro ningún posadero cerca', 'No encuentro ningún posadero cerca', 'Не удалось найти поблизости трактирщика'), +(1835, 'quest_shared', 'Quest shared', 0, 0, '퀘스트 공유됨', 'Quête partagée', 'Quest geteilt', '任务已共享', '任務已共享', 'Misión compartida', 'Misión compartida', 'Задание поделено'), +(1836, 'tame_invalid_id_error', 'Invalid tame id.', 0, 0, '잘못된 길들이기 ID입니다.', 'Identifiant de dressage invalide.', 'Ungültige Zähm-ID.', '无效的驯服 ID。', '無效的馴服 ID。', 'ID de domesticación no válido.', 'ID de domesticación no válido.', 'Недопустимый ID приручения.'), +(1837, 'tame_usage_error', 'Usage: tame name | tame id | tame family | tame rename | tame abandon', 0, 0, '사용법: tame name | tame id | tame family | tame rename | tame abandon', 'Utilisation : tame name | tame id | tame family | tame rename | tame abandon', 'Verwendung: tame name | tame id | tame family | tame rename | tame abandon', '用法:tame name | tame id | tame family | tame rename | tame abandon', '用法:tame name | tame id | tame family | tame rename | tame abandon', 'Uso: tame name | tame id | tame family | tame rename | tame abandon', 'Uso: tame name | tame id | tame family | tame rename | tame abandon', 'Использование: tame name | tame id | tame family | tame rename | tame abandon'), +(1838, 'tame_pet_changed', 'Pet changed to %name, ID: %id.', 0, 0, '소환수가 %name, ID: %id(으)로 변경되었습니다.', 'Familier changé en %name, ID : %id.', 'Begleiter geändert zu %name, ID: %id.', '宠物已更改为 %name,ID:%id。', '寵物已更改為 %name,ID:%id。', 'Mascota cambiada a %name, ID: %id.', 'Mascota cambiada a %name, ID: %id.', 'Питомец изменён на %name, ID: %id.'), +(1839, 'tame_pet_changed_initialized', 'Pet changed and initialized!', 0, 0, '소환수가 변경되고 초기화되었습니다!', 'Familier changé et initialisé !', 'Begleiter geändert und initialisiert!', '宠物已更改并初始化!', '寵物已更改並初始化!', 'La mascota ha sido cambiada e inicializada!', 'La mascota ha sido cambiada e inicializada!', 'Питомец изменён и инициализирован!'), +(1840, 'tame_exotic_requires_beast_mastery', 'I cannot use exotic pets unless I have the Beast Mastery talent.', 0, 0, '야수 지배 특성이 없으면 특수 야수 소환수를 사용할 수 없습니다.', 'Je ne peux pas utiliser de familiers exotiques sans le talent Maîtrise des bêtes.', 'Ich kann ohne das Talent Tierherrschaft keine exotischen Begleiter benutzen.', '如果没有野兽掌握天赋,我无法使用异种宠物。', '如果沒有野獸控制天賦,我無法使用異種寵物。', 'No puedo usar mascotas exóticas a menos que tenga el talento Dominio de bestias.', 'No puedo usar mascotas exóticas a menos que tenga el talento Dominio de bestias.', 'Я не могу использовать экзотических питомцев без таланта Повелитель зверей.'), +(1841, 'tame_no_pet_by_name', 'No tameable pet found with name: %name', 0, 0, '이름이 %name인 길들일 수 있는 소환수를 찾을 수 없습니다', 'Aucun familier apprivoisable trouvé avec le nom : %name', 'Kein zähmbares Tier mit dem Namen %name gefunden', '未找到名为 %name 的可驯服宠物', '未找到名為 %name 的可馴服寵物', 'No se encontró ninguna mascota domesticable con el nombre: %name', 'No se encontró ninguna mascota domesticable con el nombre: %name', 'Не найден приручаемый питомец с именем: %name'), +(1842, 'tame_no_pet_by_id', 'No tameable pet found with id: %id', 0, 0, 'ID가 %id인 길들일 수 있는 소환수를 찾을 수 없습니다', 'Aucun familier apprivoisable trouvé avec l''id : %id', 'Kein zähmbares Tier mit der ID %id gefunden', '未找到 ID 为 %id 的可驯服宠物', '未找到 ID 為 %id 的可馴服寵物', 'No se encontró ninguna mascota domesticable con el id: %id', 'No se encontró ninguna mascota domesticable con el id: %id', 'Не найден приручаемый питомец с id: %id'), +(1843, 'tame_no_pet_by_family', 'No tameable pet found with family: %family', 0, 0, '계열이 %family인 길들일 수 있는 소환수를 찾을 수 없습니다', 'Aucun familier apprivoisable trouvé avec la famille : %family', 'Kein zähmbares Tier mit der Familie %family gefunden', '未找到家族为 %family 的可驯服宠物', '未找到家族為 %family 的可馴服寵物', 'No se encontró ninguna mascota domesticable con la familia: %family', 'No se encontró ninguna mascota domesticable con la familia: %family', 'Не найден приручаемый питомец семейства: %family'), +(1844, 'tame_no_pet_to_rename', 'You have no pet to rename.', 0, 0, '이름을 바꿀 소환수가 없습니다.', 'Vous n''avez pas de familier à renommer.', 'Du hast kein Tier zum Umbenennen.', '你没有可重命名的宠物。', '你沒有可重新命名的寵物。', 'No tienes ninguna mascota para renombrar.', 'No tienes ninguna mascota para renombrar.', 'У вас нет питомца для переименования.'), +(1845, 'tame_pet_name_length_error', 'Pet name must be between 1 and 12 alphabetic characters.', 0, 0, '소환수 이름은 1자에서 12자의 알파벳 문자여야 합니다.', 'Le nom du familier doit contenir entre 1 et 12 caractères alphabétiques.', 'Der Name des Begleiters muss zwischen 1 und 12 alphabetischen Zeichen lang sein.', '宠物名字必须为 1 到 12 个字母字符。', '寵物名字必須為 1 到 12 個字母字元。', 'El nombre de la mascota debe tener entre 1 y 12 caracteres alfabéticos.', 'El nombre de la mascota debe tener entre 1 y 12 caracteres alfabéticos.', 'Имя питомца должно содержать от 1 до 12 буквенных символов.'), +(1846, 'tame_pet_name_alpha_error', 'Pet name must only contain alphabetic characters (A-Z, a-z).', 0, 0, '소환수 이름에는 알파벳 문자(A-Z, a-z)만 포함될 수 있습니다.', 'Le nom du familier ne doit contenir que des caractères alphabétiques (A-Z, a-z).', 'Der Name des Begleiters darf nur alphabetische Zeichen (A-Z, a-z) enthalten.', '宠物名字只能包含字母字符(A-Z, a-z)。', '寵物名字只能包含字母字元(A-Z, a-z)。', 'El nombre de la mascota solo puede contener caracteres alfabéticos (A-Z, a-z).', 'El nombre de la mascota solo puede contener caracteres alfabéticos (A-Z, a-z).', 'Имя питомца должно содержать только буквенные символы (A-Z, a-z).'), +(1847, 'tame_pet_name_forbidden_error', 'That pet name is forbidden. Please choose another name.', 0, 0, '그 소환수 이름은 사용할 수 없습니다. 다른 이름을 선택해 주세요.', 'Ce nom de familier est interdit. Veuillez en choisir un autre.', 'Dieser Name für den Begleiter ist verboten. Bitte wähle einen anderen Namen.', '该宠物名称被禁止使用。请选择其他名字。', '該寵物名稱被禁止使用。請選擇其他名字。', 'Ese nombre de mascota está prohibido. Por favor, elige otro nombre.', 'Ese nombre de mascota está prohibido. Por favor, elige otro nombre.', 'Это имя для питомца запрещено. Пожалуйста, выберите другое имя.'), +(1848, 'tame_pet_renamed', 'Your pet has been renamed to %name!', 0, 0, '당신의 소환수 이름이 %name(으)로 변경되었습니다!', 'Votre familier a été renommé en %name !', 'Dein Begleiter wurde in %name umbenannt!', '你的宠物已重命名为 %name!', '你的寵物已重新命名為 %name!', 'Tu mascota ha sido renombrada a %name!', 'Tu mascota ha sido renombrada a %name!', 'Ваш питомец был переименован в %name!'), +(1849, 'tame_pet_rename_refresh_hint', 'If you do not see the new name, please dismiss and recall your pet.', 0, 0, '새 이름이 보이지 않으면 소환수를 해제했다가 다시 소환해 주세요.', 'Si vous ne voyez pas le nouveau nom, veuillez renvoyer puis rappeler votre familier.', 'Wenn du den neuen Namen nicht siehst, schicke deinen Begleiter weg und rufe ihn erneut.', '如果你看不到新名字,请先解散再召回你的宠物。', '如果你看不到新名字,請先解散再召回你的寵物。', 'Si no ves el nuevo nombre, por favor retira y vuelve a invocar a tu mascota.', 'Si no ves el nuevo nombre, por favor retira y vuelve a invocar a tu mascota.', 'Если вы не видите новое имя, отпустите и снова призовите питомца.'), +(1850, 'tame_only_hunters_level_10', 'Only level 10+ hunters can have pets.', 0, 0, '10레벨 이상의 사냥꾼만 소환수를 가질 수 있습니다.', 'Seuls les chasseurs de niveau 10+ peuvent avoir des familiers.', 'Nur Jäger ab Stufe 10 können Begleiter haben.', '只有 10 级以上的猎人才能拥有宠物。', '只有 10 級以上的獵人才能擁有寵物。', 'Solo los cazadores de nivel 10 o superior pueden tener mascotas.', 'Solo los cazadores de nivel 10 o superior pueden tener mascotas.', 'Только охотники 10 уровня и выше могут иметь питомцев.'), +(1851, 'tame_creature_template_not_found', 'Creature template not found.', 0, 0, '생물 템플릿을 찾을 수 없습니다.', 'Modèle de créature introuvable.', 'Kreaturvorlage nicht gefunden.', '未找到生物模板。', '未找到生物範本。', 'No se encontró la plantilla de criatura.', 'No se encontró la plantilla de criatura.', 'Шаблон существа не найден.'), +(1852, 'tame_create_pet_failed', 'Failed to create pet.', 0, 0, '소환수 생성에 실패했습니다.', 'Échec de la création du familier.', 'Erstellen des Begleiters fehlgeschlagen.', '创建宠物失败。', '建立寵物失敗。', 'No se pudo crear la mascota.', 'No se pudo crear la mascota.', 'Не удалось создать питомца.'), +(1853, 'tame_pet_abandoned', 'Your pet has been abandoned.', 0, 0, '당신의 소환수를 버렸습니다.', 'Votre familier a été abandonné.', 'Dein Begleiter wurde freigelassen.', '你的宠物已被放弃。', '你的寵物已被放棄。', 'Tu mascota ha sido abandonada.', 'Tu mascota ha sido abandonada.', 'Ваш питомец был брошен.'), +(1854, 'tame_no_hunter_pet_to_abandon', 'You have no hunter pet to abandon.', 0, 0, '버릴 사냥꾼 소환수가 없습니다.', 'Vous n''avez pas de familier de chasseur à abandonner.', 'Du hast kein Jägertier zum Freilassen.', '你没有可放弃的猎人宠物。', '你沒有可放棄的獵人寵物。', 'No tienes ninguna mascota de cazador que abandonar.', 'No tienes ninguna mascota de cazador que abandonar.', 'У вас нет питомца охотника, которого можно бросить.'), +(1855, 'taxi_ready_next_flight', 'I am ready for the next flight', 0, 0, '다음 비행을 탈 준비가 되었습니다', 'Je suis prêt pour le prochain vol', 'Ich bin bereit für den nächsten Flug', '我已准备好进行下一次飞行', '我已準備好進行下一次飛行', 'Estoy listo para el siguiente vuelo', 'Estoy listo para el siguiente vuelo', 'Я готов к следующему перелёту'), +(1856, 'taxi_cant_fly_with_you', 'I can''t fly with you', 0, 0, '당신과 함께 날 수 없습니다', 'Je ne peux pas voler avec vous', 'Ich kann nicht mit dir fliegen', '我不能和你一起飞', '我不能和你一起飛', 'No puedo volar contigo', 'No puedo volar contigo', 'Я не могу лететь с вами'), +(1857, 'taxi_no_flightmaster_nearby', 'Cannot find any flightmaster to talk', 0, 0, '대화할 비행 조련사를 찾을 수 없습니다', 'Impossible de trouver un maître de vol à qui parler', 'Keinen Flugmeister zum Ansprechen gefunden', '找不到可以交谈的飞行管理员', '找不到可以交談的飛行管理員', 'No encuentro ningún maestro de vuelo con quien hablar', 'No encuentro ningún maestro de vuelo con quien hablar', 'Не могу найти распорядителя полётов, с которым можно поговорить'), +(1858, 'trade_busy_now', 'I''m kind of busy now', 0, 0, '지금은 좀 바쁩니다', 'Je suis un peu occupé en ce moment', 'Ich bin gerade etwas beschäftigt', '我现在有点忙', '我現在有點忙', 'Ahora estoy un poco ocupado', 'Ahora estoy un poco ocupado', 'Сейчас я немного занят'), +(1859, 'trade_disabled', 'Trading is disabled', 0, 0, '거래가 비활성화되어 있습니다', 'L''échange est désactivé', 'Handel ist deaktiviert', '交易已禁用', '交易已停用', 'El comercio está deshabilitado', 'El comercio está deshabilitado', 'Торговля отключена'), +(1860, 'trade_thank_you_player', 'Thank you %player', 0, 0, '고마워요 %player', 'Merci %player', 'Danke %player', '谢谢你 %player', '謝謝你 %player', 'Gracias %player', 'Gracias %player', 'Спасибо, %player'), +(1861, 'trade_selling_disabled', 'Selling is disabled.', 0, 0, '판매가 비활성화되어 있습니다.', 'La vente est désactivée.', 'Verkaufen ist deaktiviert.', '出售已禁用。', '出售已停用。', 'La venta está deshabilitada.', 'La venta está deshabilitada.', 'Продажа отключена.'), +(1862, 'trade_buying_disabled', 'Buying is disabled.', 0, 0, '구매가 비활성화되어 있습니다.', 'L''achat est désactivé.', 'Kaufen ist deaktiviert.', '购买已禁用。', '購買已停用。', 'La compra está deshabilitada.', 'La compra está deshabilitada.', 'Покупка отключена.'), +(1863, 'trade_item_not_for_sale', '%item - This is not for sale', 0, 0, '%item - 이것은 판매용이 아닙니다', '%item - Ceci n''est pas à vendre', '%item - Das steht nicht zum Verkauf', '%item - 这不是出售物品', '%item - 這不是出售物品', '%item - Esto no está en venta', '%item - Esto no está en venta', '%item - это не продаётся'), +(1864, 'trade_item_not_needed', '%item - I don''t need this', 0, 0, '%item - 저는 이것이 필요 없습니다', '%item - Je n''en ai pas besoin', '%item - Das brauche ich nicht', '%item - 我不需要这个', '%item - 我不需要這個', '%item - No necesito esto', '%item - No necesito esto', '%item - мне это не нужно'), +(1865, 'trade_no_items_error', 'There are no items to trade', 0, 0, '거래할 아이템이 없습니다', 'Il n''y a aucun objet à échanger', 'Es gibt keine Gegenstände zum Handeln', '没有可交易的物品', '沒有可交易的物品', 'No hay objetos para comerciar', 'No hay objetos para comerciar', 'Нет предметов для обмена'), +(1866, 'trade_discount_buy_only', 'You can use discount to buy items only', 0, 0, '할인은 아이템 구매에만 사용할 수 있습니다', 'Vous ne pouvez utiliser la réduction que pour acheter des objets', 'Rabatte können nur zum Kauf von Gegenständen verwendet werden', '折扣只能用于购买物品', '折扣只能用於購買物品', 'Solo puedes usar el descuento para comprar objetos', 'Solo puedes usar el descuento para comprar objetos', 'Скидку можно использовать только для покупки предметов'), +(1867, 'trade_success_pleasure', 'A pleasure doing business with you', 0, 0, '당신과 거래해서 즐거웠습니다', 'Un plaisir de faire affaire avec vous', 'Es war mir ein Vergnügen, mit dir Geschäfte zu machen', '很高兴和你做生意', '很高興和你做生意', 'Un placer hacer negocios contigo', 'Un placer hacer negocios contigo', 'Приятно было иметь с вами дело'), +(1868, 'trade_success_fair_trade', 'Fair trade', 0, 0, '공정한 거래입니다', 'Échange équitable', 'Fairer Handel', '公平交易', '公平交易', 'Intercambio justo', 'Intercambio justo', 'Честная сделка'), +(1869, 'trade_success_thanks', 'Thanks', 0, 0, '감사합니다', 'Merci', 'Danke', '谢谢', '謝謝', 'Gracias', 'Gracias', 'Спасибо'), +(1870, 'trade_success_off_with_you', 'Off with you', 0, 0, '이제 가보세요', 'Allez-vous-en maintenant', 'Nun geh deiner Wege', '走吧你', '走吧你', 'Ahora vete', 'Ahora vete', 'Иди уже'), +(1871, 'trade_want_money_for_this', 'I want %money for this', 0, 0, '이것에 대해 %money을(를) 원합니다', 'Je veux %money pour ceci', 'Ich möchte %money dafür', '这个我想要 %money', '這個我想要 %money', 'Quiero %money por esto', 'Quiero %money por esto', 'Я хочу %money за это'), +(1872, 'use_item_none_available', 'No items (or game objects) available', 0, 0, '사용할 수 있는 아이템(또는 게임 오브젝트)이 없습니다', 'Aucun objet (ou objet interactif) disponible', 'Keine Gegenstände (oder Spielobjekte) verfügbar', '没有可用的物品(或游戏物体)', '沒有可用的物品(或遊戲物件)', 'No hay objetos (ni objetos del juego) disponibles', 'No hay objetos (ni objetos del juego) disponibles', 'Нет доступных предметов (или игровых объектов)'), +(1873, 'use_gameobject', 'Using %gameobject', 0, 0, '%gameobject 사용 중', 'Utilisation de %gameobject', 'Benutze %gameobject', '正在使用 %gameobject', '正在使用 %gameobject', 'Usando %gameobject', 'Usando %gameobject', 'Использую %gameobject'), +(1874, 'socket_does_not_fit', 'Socket does not fit', 0, 0, '소켓이 맞지 않습니다', 'La châsse ne correspond pas', 'Sockel passt nicht', '插槽不匹配', '插槽不匹配', 'La ranura no encaja', 'La ranura no encaja', 'Гнездо не подходит'), +(1875, 'use_item_on_target', 'Using %item on %target', 0, 0, '%target에게 %item을(를) 사용하는 중', 'Utilisation de %item sur %target', 'Benutze %item auf %target', '正在对 %target 使用 %item', '正在對 %target 使用 %item', 'Usando %item en %target', 'Usando %item en %target', 'Использую %item на %target'), +(1876, 'use_item', 'Using %item', 0, 0, '%item 사용 중', 'Utilisation de %item', 'Benutze %item', '正在使用 %item', '正在使用 %item', 'Usando %item', 'Usando %item', 'Использую %item'), +(1877, 'socketing_item_with_gem', 'Socketing %item with %gem', 0, 0, '%item에 %gem 보석을 장착하는 중', 'Sertissage de %item avec %gem', 'Sockele %item mit %gem', '正在将 %gem 镶嵌到 %item 上', '正在將 %gem 鑲嵌到 %item 上', 'Engarzando %item con %gem', 'Engarzando %item con %gem', 'Вставляю %gem в %item'), +(1878, 'meeting_stone_in_combat', 'I am in combat', 0, 0, '전투 중입니다', 'Je suis en combat', 'Ich bin im Kampf', '我在战斗中', '我在戰鬥中', 'Estoy en combate', 'Estoy en combate', 'Я в бою'), +(1879, 'meeting_stone_welcome', 'Welcome!', 0, 0, '환영합니다!', 'Bienvenue !', 'Willkommen!', '欢迎!', '歡迎!', 'Bienvenido!', 'Bienvenido!', 'Добро пожаловать!'), +(1880, 'meeting_stone_none_nearby', 'There is no meeting stone nearby', 0, 0, '근처에 집결의 돌이 없습니다', 'Il n''y a pas de pierre de rencontre à proximité', 'Es gibt keinen Beschwörungsstein in der Nähe', '附近没有集合石', '附近沒有集合石', 'No hay ninguna piedra de encuentro cerca', 'No hay ninguna piedra de encuentro cerca', 'Поблизости нет камня встреч'), +(1881, 'meeting_stone_none_near_you', 'There is no meeting stone near you', 0, 0, '당신 근처에 집결의 돌이 없습니다', 'Il n''y a pas de pierre de rencontre près de vous', 'Es gibt keinen Beschwörungsstein in deiner Nähe', '你附近没有集合石', '你附近沒有集合石', 'No hay ninguna piedra de encuentro cerca de ti', 'No hay ninguna piedra de encuentro cerca de ti', 'Рядом с вами нет камня встреч'), +(1882, 'meeting_stone_no_hearthstone_self', 'I have no hearthstone', 0, 0, '귀환석이 없습니다', 'Je n''ai pas de pierre de foyer', 'Ich habe keinen Ruhestein', '我没有炉石', '我沒有爐石', 'No tengo piedra de hogar', 'No tengo piedra de hogar', 'У меня нет камня возвращения'), +(1883, 'meeting_stone_no_hearthstone_you', 'You have no hearthstone', 0, 0, '당신에게 귀환석이 없습니다', 'Vous n''avez pas de pierre de foyer', 'Du hast keinen Ruhestein', '你没有炉石', '你沒有爐石', 'No tienes piedra de hogar', 'No tienes piedra de hogar', 'У вас нет камня возвращения'), +(1884, 'meeting_stone_hearthstone_not_ready_self', 'My hearthstone is not ready', 0, 0, '제 귀환석은 아직 준비되지 않았습니다', 'Ma pierre de foyer n''est pas prête', 'Mein Ruhestein ist nicht bereit', '我的炉石还没准备好', '我的爐石還沒準備好', 'Mi piedra de hogar no está lista', 'Mi piedra de hogar no está lista', 'Мой камень возвращения ещё не готов'), +(1885, 'meeting_stone_hearthstone_not_ready_you', 'Your hearthstone is not ready', 0, 0, '당신의 귀환석은 아직 준비되지 않았습니다', 'Votre pierre de foyer n''est pas prête', 'Dein Ruhestein ist nicht bereit', '你的炉石还没准备好', '你的爐石還沒準備好', 'Tu piedra de hogar no está lista', 'Tu piedra de hogar no está lista', 'Ваш камень возвращения ещё не готов'), +(1886, 'meeting_stone_no_innkeepers_nearby', 'There are no innkeepers nearby', 0, 0, '근처에 여관주인이 없습니다', 'Il n''y a pas d''aubergistes à proximité', 'Es gibt keine Gastwirte in der Nähe', '附近没有旅店老板', '附近沒有旅店老闆', 'No hay posaderos cerca', 'No hay posaderos cerca', 'Поблизости нет трактирщиков'), +(1887, 'meeting_stone_no_innkeepers_near_you', 'There are no innkeepers near you', 0, 0, '당신 근처에 여관주인이 없습니다', 'Il n''y a pas d''aubergistes près de vous', 'Es gibt keine Gastwirte in deiner Nähe', '你附近没有旅店老板', '你附近沒有旅店老闆', 'No hay posaderos cerca de ti', 'No hay posaderos cerca de ti', 'Рядом с вами нет трактирщиков'), +(1888, 'meeting_stone_cannot_summon_vehicle', 'You cannot summon me while I''m on a vehicle', 0, 0, '제가 탈것/차량에 타고 있는 동안에는 소환할 수 없습니다', 'Vous ne pouvez pas m''invoquer tant que je suis sur un véhicule', 'Du kannst mich nicht beschwören, solange ich auf einem Fahrzeug bin', '当我在载具上时,你不能召唤我', '當我在載具上時,你不能召喚我', 'No puedes invocarme mientras esté en un vehículo', 'No puedes invocarme mientras esté en un vehículo', 'Вы не можете призвать меня, пока я на транспорте'), +(1889, 'meeting_stone_cannot_summon_master_in_combat', 'You cannot summon me while you''re in combat', 0, 0, '당신이 전투 중일 때는 저를 소환할 수 없습니다', 'Vous ne pouvez pas m''invoquer pendant que vous êtes en combat', 'Du kannst mich nicht beschwören, solange du im Kampf bist', '当你在战斗中时,不能召唤我', '當你在戰鬥中時,不能召喚我', 'No puedes invocarme mientras estés en combate', 'No puedes invocarme mientras estés en combate', 'Вы не можете призвать меня, пока вы в бою'), +(1890, 'meeting_stone_cannot_summon_master_dead', 'You cannot summon me while you''re dead', 0, 0, '당신이 죽어 있는 동안에는 저를 소환할 수 없습니다', 'Vous ne pouvez pas m''invoquer pendant que vous êtes mort', 'Du kannst mich nicht beschwören, solange du tot bist', '当你死亡时,不能召唤我', '當你死亡時,不能召喚我', 'No puedes invocarme mientras estés muerto', 'No puedes invocarme mientras estés muerto', 'Вы не можете призвать меня, пока вы мертвы'), +(1891, 'meeting_stone_cannot_summon_bot_dead', 'You cannot summon me while I''m dead, you need to release my spirit first', 0, 0, '제가 죽어 있는 동안에는 저를 소환할 수 없습니다. 먼저 제 영혼을 해방해야 합니다', 'Vous ne pouvez pas m''invoquer tant que je suis mort, vous devez d''abord libérer mon esprit', 'Du kannst mich nicht beschwören, solange ich tot bin; du musst zuerst meinen Geist freisetzen', '当我死亡时,你不能召唤我,你需要先释放我的灵魂', '當我死亡時,你不能召喚我,你需要先釋放我的靈魂', 'No puedes invocarme mientras esté muerto, primero debes liberar mi espíritu', 'No puedes invocarme mientras esté muerto, primero debes liberar mi espíritu', 'Вы не можете призвать меня, пока я мёртв, сначала нужно освободить мой дух'), +(1892, 'meeting_stone_revived', 'I live, again!', 0, 0, '다시 살아났습니다!', 'Je vis à nouveau !', 'Ich lebe wieder!', '我又活了!', '我又活了!', 'Vivo de nuevo!', 'Vivo de nuevo!', 'Я снова жив!'), +(1893, 'meeting_stone_not_enough_space', 'Not enough place to summon', 0, 0, '소환할 공간이 부족합니다', 'Pas assez de place pour invoquer', 'Nicht genug Platz zum Beschwören', '没有足够的空间进行召唤', '沒有足夠的空間進行召喚', 'No hay suficiente espacio para invocar', 'No hay suficiente espacio para invocar', 'Недостаточно места для призыва'), +(1894, 'new_rpg_quest_accepted', 'Quest accepted %quest', 0, 0, '퀘스트 수락 %quest', 'Quête acceptée %quest', 'Quest angenommen %quest', '任务已接受 %quest', '任務已接受 %quest', 'Misión aceptada %quest', 'Misión aceptada %quest', 'Задание принято %quest'), +(1895, 'new_rpg_quest_rewarded', 'Quest rewarded %quest', 0, 0, '퀘스트 보상 받음 %quest', 'Quête récompensée %quest', 'Quest abgeschlossen %quest', '任务已奖励 %quest', '任務已獎勵 %quest', 'Misión completada %quest', 'Misión completada %quest', 'Награда за задание получена %quest'), +(1896, 'new_rpg_quest_dropped', 'Quest dropped %quest', 0, 0, '퀘스트 포기 %quest', 'Quête abandonnée %quest', 'Quest abgebrochen %quest', '任务已放弃 %quest', '任務已放棄 %quest', 'Misión abandonada %quest', 'Misión abandonada %quest', 'Задание отменено %quest'), +(1897, 'rpg_item_better_for_player', 'You can use this %item better than me, %player.', 0, 0, '%player님, 이 %item은(는) 저보다 당신에게 더 잘 맞습니다.', 'Vous pouvez mieux utiliser cet objet %item que moi, %player.', 'Du kannst diesen %item besser gebrauchen als ich, %player.', '%player,这个 %item 比我更适合你使用。', '%player,這個 %item 比我更適合你使用。', 'Tú puedes usar este %item mejor que yo, %player.', 'Tú puedes usar este %item mejor que yo, %player.', '%player, ты можешь использовать %item лучше, чем я.'), +(1898, 'rpg_start_trade_with_player', 'Start trade with %player', 0, 0, '%player와(과) 거래를 시작합니다', 'Début de l''échange avec %player', 'Beginne Handel mit %player', '开始与 %player 交易', '開始與 %player 交易', 'Iniciando intercambio con %player', 'Iniciando intercambio con %player', 'Начинаю обмен с %player'); + +INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES +('quest_accept_debug', 100), +('quest_already_have_error', 100), +('quest_cant_take_error', 100), +('arena_team_already_in_team', 100), +('arena_team_thanks_for_invite', 100), +('area_trigger_follow_too_far_error', 100), +('area_trigger_wait_for_me', 100), +('attack_no_target_error', 100), +('attack_target_not_in_world_error', 100), +('attack_in_flight_error', 100), +('attack_pvp_prohibited_error', 100), +('attack_target_friendly_error', 100), +('attack_target_dead_error', 100), +('attack_target_not_in_sight_error', 100), +('attack_already_attacking_error', 100), +('attack_invalid_target_error', 100), +('bank_no_banker_nearby_error', 100), +('move_from_group', 100), +('running_away', 100), +('clean_quest_log_started', 100), +('quest_trivial_will_remove', 100), +('quest_has_been_removed', 100), +('quest_not_trivial_kept', 100), +('quest_removed_debug', 100), +('quest_removed_with_name', 100), +('guild_accept_inviter_not_in_guild', 100), +('guild_accept_already_in_guild', 100), +('guild_accept_declined', 100), +('outfit_usage_add', 100), +('outfit_usage_remove', 100), +('outfit_usage_equip', 100), +('outfit_set_as', 100), +('outfit_equipping', 100), +('outfit_replace_current', 100), +('outfit_resetting', 100), +('outfit_updating_current', 100), +('outfit_item_removed_from', 100), +('outfit_item_added_to', 100), +('release_spirit_not_dead_wait', 100), +('release_spirit_already_spirit', 100), +('release_spirit_releasing', 100), +('release_spirit_meet_graveyard', 100), +('send_mail_no_mailbox_nearby', 100), +('send_mail_one_item_only', 100), +('send_mail_cannot_send_money', 100), +('send_mail_not_enough_money', 100), +('send_mail_sending_to', 100), +('send_mail_cannot_send_item', 100), +('send_mail_item_not_for_sale', 100), +('send_mail_sent_to', 100), +('craft_reset', 100), +('craft_usage', 100), +('craft_cannot_craft', 100), +('craft_summary', 100), +('set_home_success', 100), +('set_home_no_innkeeper_error', 100), +('quest_shared', 100), +('tame_invalid_id_error', 100), +('tame_usage_error', 100), +('tame_pet_changed', 100), +('tame_pet_changed_initialized', 100), +('tame_exotic_requires_beast_mastery', 100), +('tame_no_pet_by_name', 100), +('tame_no_pet_by_id', 100), +('tame_no_pet_by_family', 100), +('tame_no_pet_to_rename', 100), +('tame_pet_name_length_error', 100), +('tame_pet_name_alpha_error', 100), +('tame_pet_name_forbidden_error', 100), +('tame_pet_renamed', 100), +('tame_pet_rename_refresh_hint', 100), +('tame_only_hunters_level_10', 100), +('tame_creature_template_not_found', 100), +('tame_create_pet_failed', 100), +('tame_pet_abandoned', 100), +('tame_no_hunter_pet_to_abandon', 100), +('taxi_ready_next_flight', 100), +('taxi_cant_fly_with_you', 100), +('taxi_no_flightmaster_nearby', 100), +('trade_busy_now', 100), +('trade_disabled', 100), +('trade_thank_you_player', 100), +('trade_selling_disabled', 100), +('trade_buying_disabled', 100), +('trade_item_not_for_sale', 100), +('trade_item_not_needed', 100), +('trade_no_items_error', 100), +('trade_discount_buy_only', 100), +('trade_success_pleasure', 100), +('trade_success_fair_trade', 100), +('trade_success_thanks', 100), +('trade_success_off_with_you', 100), +('trade_want_money_for_this', 100), +('use_item_none_available', 100), +('use_gameobject', 100), +('socket_does_not_fit', 100), +('use_item_on_target', 100), +('use_item', 100), +('socketing_item_with_gem', 100), +('meeting_stone_in_combat', 100), +('meeting_stone_welcome', 100), +('meeting_stone_none_nearby', 100), +('meeting_stone_none_near_you', 100), +('meeting_stone_no_hearthstone_self', 100), +('meeting_stone_no_hearthstone_you', 100), +('meeting_stone_hearthstone_not_ready_self', 100), +('meeting_stone_hearthstone_not_ready_you', 100), +('meeting_stone_no_innkeepers_nearby', 100), +('meeting_stone_no_innkeepers_near_you', 100), +('meeting_stone_cannot_summon_vehicle', 100), +('meeting_stone_cannot_summon_master_in_combat', 100), +('meeting_stone_cannot_summon_master_dead', 100), +('meeting_stone_cannot_summon_bot_dead', 100), +('meeting_stone_revived', 100), +('meeting_stone_not_enough_space', 100), +('new_rpg_quest_accepted', 100), +('new_rpg_quest_rewarded', 100), +('new_rpg_quest_dropped', 100), +('rpg_item_better_for_player', 100), +('rpg_start_trade_with_player', 100); diff --git a/src/Ai/Base/Actions/AcceptInvitationAction.cpp b/src/Ai/Base/Actions/AcceptInvitationAction.cpp index 1be3c0921a5..deb1159a124 100644 --- a/src/Ai/Base/Actions/AcceptInvitationAction.cpp +++ b/src/Ai/Base/Actions/AcceptInvitationAction.cpp @@ -9,6 +9,7 @@ #include "ObjectAccessor.h" #include "PlayerbotAIConfig.h" #include "PlayerbotSecurity.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "WorldPacket.h" @@ -55,7 +56,7 @@ bool AcceptInvitationAction::Execute(Event event) botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT); botAI->Reset(); - botAI->TellMaster("Hello"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault("hello", "Hello", {})); if (sPlayerbotAIConfig.summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig.sightDistance) { diff --git a/src/Ai/Base/Actions/AcceptQuestAction.cpp b/src/Ai/Base/Actions/AcceptQuestAction.cpp index 005cfb08a94..e80bdbe1158 100644 --- a/src/Ai/Base/Actions/AcceptQuestAction.cpp +++ b/src/Ai/Base/Actions/AcceptQuestAction.cpp @@ -6,6 +6,7 @@ #include "AcceptQuestAction.h" #include "Event.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver) @@ -18,7 +19,11 @@ bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver) if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) { LOG_INFO("playerbots", "{} => Quest [{}] accepted", bot->GetName(), quest->GetTitle()); - bot->Say("Quest [" + text_quest + "] accepted", LANG_UNIVERSAL); + std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_accept_debug", + "Quest [%quest] accepted", + {{"%quest", text_quest}}); + bot->Say(text, LANG_UNIVERSAL); } return true; @@ -113,7 +118,8 @@ bool AcceptQuestShareAction::Execute(Event event) if (bot->HasQuest(quest)) { bot->SetDivider(ObjectGuid::Empty); - botAI->TellError("I have this quest"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_already_have_error", "I have this quest", {})); return false; } @@ -121,7 +127,8 @@ bool AcceptQuestShareAction::Execute(Event event) { // can't take quest bot->SetDivider(ObjectGuid::Empty); - botAI->TellError("I can't take this quest"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_cant_take_error", "I can't take this quest", {})); return false; } @@ -149,7 +156,8 @@ bool AcceptQuestShareAction::Execute(Event event) bot->CastSpell(bot, qInfo->GetSrcSpell(), true); } - botAI->TellMaster("Quest accepted"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_accept", "Quest accepted", {})); return true; } diff --git a/src/Ai/Base/Actions/AreaTriggerAction.cpp b/src/Ai/Base/Actions/AreaTriggerAction.cpp index a3339ea5c89..deb242bf702 100644 --- a/src/Ai/Base/Actions/AreaTriggerAction.cpp +++ b/src/Ai/Base/Actions/AreaTriggerAction.cpp @@ -7,6 +7,7 @@ #include "Event.h" #include "LastMovementValue.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "Transport.h" @@ -36,7 +37,8 @@ bool ReachAreaTriggerAction::Execute(Event event) if (bot->GetMapId() != at->map) { - botAI->TellError("I won't follow: too far away"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "area_trigger_follow_too_far_error", "I won't follow: too far away", {})); return true; } @@ -51,7 +53,8 @@ bool ReachAreaTriggerAction::Execute(Event event) float distance = bot->GetDistance(at->x, at->y, at->z); float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig.reactDelay; - botAI->TellError("Wait for me"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "area_trigger_wait_for_me", "Wait for me", {})); botAI->SetNextCheckDelay(delay); context->GetValue("last area trigger")->Get().lastAreaTrigger = triggerId; @@ -76,6 +79,6 @@ bool AreaTriggerAction::Execute(Event /*event*/) p.rpos(0); bot->GetSession()->HandleAreaTriggerOpcode(p); - botAI->TellMaster("Hello"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault("hello", "Hello", {})); return true; } diff --git a/src/Ai/Base/Actions/ArenaTeamActions.cpp b/src/Ai/Base/Actions/ArenaTeamActions.cpp index 82672cd6d7c..b40cd687df2 100644 --- a/src/Ai/Base/Actions/ArenaTeamActions.cpp +++ b/src/Ai/Base/Actions/ArenaTeamActions.cpp @@ -6,6 +6,7 @@ #include "ArenaTeamActions.h" #include "ArenaTeamMgr.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool ArenaTeamAcceptAction::Execute(Event event) @@ -31,7 +32,9 @@ bool ArenaTeamAcceptAction::Execute(Event event) if (bot->GetArenaTeamId(at->GetSlot())) { // bot is already in an arena team - bot->Say("Sorry, I am already in such team", LANG_UNIVERSAL); + std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "arena_team_already_in_team", "Sorry, I am already in such team", {}); + bot->Say(text, LANG_UNIVERSAL); accept = false; } @@ -39,7 +42,9 @@ bool ArenaTeamAcceptAction::Execute(Event event) { WorldPacket data(CMSG_ARENA_TEAM_ACCEPT); bot->GetSession()->HandleArenaTeamAcceptOpcode(data); - bot->Say("Thanks for the invite!", LANG_UNIVERSAL); + std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "arena_team_thanks_for_invite", "Thanks for the invite!", {}); + bot->Say(text, LANG_UNIVERSAL); LOG_INFO("playerbots", "Bot {} <{}> accepts Arena Team invite", bot->GetGUID().ToString().c_str(), bot->GetName().c_str()); return true; diff --git a/src/Ai/Base/Actions/AttackAction.cpp b/src/Ai/Base/Actions/AttackAction.cpp index a537dd218a5..0a91eab5aa2 100644 --- a/src/Ai/Base/Actions/AttackAction.cpp +++ b/src/Ai/Base/Actions/AttackAction.cpp @@ -10,6 +10,7 @@ #include "LastMovementValue.h" #include "LootObjectStack.h" #include "PlayerbotAI.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "ServerFacade.h" #include "SharedDefines.h" @@ -38,7 +39,8 @@ bool AttackMyTargetAction::Execute(Event /*event*/) if (!guid) { if (verbose) - botAI->TellError("You have no target"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "pull_no_target_error", "You have no target", {})); return false; } @@ -56,7 +58,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) if (!target) { if (verbose) - botAI->TellError("I have no target"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attack_no_target_error", "I have no target", {})); return false; } @@ -64,7 +67,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) if (!target->IsInWorld()) { if (verbose) - botAI->TellError(std::string(target->GetName()) + " is no longer in the world."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attack_target_not_in_world_error", + "%target is no longer in the world.", + {{"%target", target->GetName()}})); return false; } @@ -73,7 +79,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) bot->HasUnitState(UNIT_STATE_IN_FLIGHT)) { if (verbose) - botAI->TellError("I cannot attack in flight"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attack_in_flight_error", "I cannot attack in flight", {})); return false; } @@ -85,7 +92,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) sPlayerbotAIConfig.IsPvpProhibited(target->GetZoneId(), target->GetAreaId()))) { if (verbose) - botAI->TellError("I cannot attack other players in PvP prohibited areas."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attack_pvp_prohibited_error", + "I cannot attack other players in PvP prohibited areas.", + {})); return false; } @@ -93,7 +103,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) if (bot->IsFriendlyTo(target)) { if (verbose) - botAI->TellError(std::string(target->GetName()) + " is friendly to me."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attack_target_friendly_error", + "%target is friendly to me.", + {{"%target", target->GetName()}})); return false; } @@ -101,7 +114,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) if (target->isDead()) { if (verbose) - botAI->TellError(std::string(target->GetName()) + " is dead."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attack_target_dead_error", + "%target is dead.", + {{"%target", target->GetName()}})); return false; } @@ -109,7 +125,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) if (!bot->IsWithinLOSInMap(target)) { if (verbose) - botAI->TellError(std::string(target->GetName()) + " is not in my sight."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attack_target_not_in_sight_error", + "%target is not in my sight.", + {{"%target", target->GetName()}})); return false; } @@ -129,7 +148,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) if (sameTarget && inCombat && sameAttackMode) { if (verbose) - botAI->TellError("I am already attacking " + std::string(target->GetName()) + "."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attack_already_attacking_error", + "I am already attacking %target.", + {{"%target", target->GetName()}})); return false; } @@ -137,7 +159,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) if (!bot->IsValidAttackTarget(target)) { if (verbose) - botAI->TellError("I cannot attack an invalid target."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attack_invalid_target_error", "I cannot attack an invalid target.", {})); return false; } diff --git a/src/Ai/Base/Actions/BankAction.cpp b/src/Ai/Base/Actions/BankAction.cpp index 5a4975f77b2..d5bce50ed7d 100644 --- a/src/Ai/Base/Actions/BankAction.cpp +++ b/src/Ai/Base/Actions/BankAction.cpp @@ -7,6 +7,7 @@ #include "Event.h" #include "ItemCountValue.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool BankAction::Execute(Event event) @@ -23,7 +24,8 @@ bool BankAction::Execute(Event event) return ExecuteBank(text, npc); } - botAI->TellError("Cannot find banker nearby"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "bank_no_banker_nearby_error", "Cannot find banker nearby", {})); return false; } diff --git a/src/Ai/Base/Actions/ChatShortcutActions.cpp b/src/Ai/Base/Actions/ChatShortcutActions.cpp index b715cb2bb54..a12d842eaaa 100644 --- a/src/Ai/Base/Actions/ChatShortcutActions.cpp +++ b/src/Ai/Base/Actions/ChatShortcutActions.cpp @@ -7,6 +7,7 @@ #include "Event.h" #include "Formations.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "PositionValue.h" @@ -85,7 +86,8 @@ bool FollowChatShortcutAction::Execute(Event /*event*/) if (moved) { - botAI->TellMaster("Following"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "following", "Following", {})); return true; } } @@ -108,7 +110,8 @@ bool FollowChatShortcutAction::Execute(Event /*event*/) } */ - botAI->TellMaster("Following"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "following", "Following", {})); return true; } @@ -125,7 +128,8 @@ bool StayChatShortcutAction::Execute(Event /*event*/) SetReturnPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); SetStayPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); - botAI->TellMaster("Staying"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "staying", "Staying", {})); return true; } @@ -140,7 +144,8 @@ bool MoveFromGroupChatShortcutAction::Execute(Event /*event*/) botAI->ChangeStrategy("+move from group", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("+move from group", BOT_STATE_COMBAT); - botAI->TellMaster("Moving away from group"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "move_from_group", "Moving away from group", {})); return true; } @@ -159,11 +164,13 @@ bool FleeChatShortcutAction::Execute(Event /*event*/) if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig.sightDistance) { - botAI->TellError("I will not flee with you - too far away"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "fleeing_far", "I will not flee with you - too far away", {})); return true; } - botAI->TellMaster("Fleeing"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "fleeing", "Fleeing", {})); return true; } @@ -180,7 +187,8 @@ bool GoawayChatShortcutAction::Execute(Event /*event*/) ResetReturnPosition(); ResetStayPosition(); - botAI->TellMaster("Running away"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "running_away", "Running away", {})); return true; } @@ -196,7 +204,8 @@ bool GrindChatShortcutAction::Execute(Event /*event*/) ResetReturnPosition(); ResetStayPosition(); - botAI->TellMaster("Grinding"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "grinding", "Grinding", {})); return true; } @@ -216,7 +225,8 @@ bool TankAttackChatShortcutAction::Execute(Event /*event*/) ResetReturnPosition(); ResetStayPosition(); - botAI->TellMaster("Attacking"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "attacking", "Attacking", {})); return true; } diff --git a/src/Ai/Base/Actions/DropQuestAction.cpp b/src/Ai/Base/Actions/DropQuestAction.cpp index f6712abf3c7..f47eb41af0f 100644 --- a/src/Ai/Base/Actions/DropQuestAction.cpp +++ b/src/Ai/Base/Actions/DropQuestAction.cpp @@ -7,6 +7,7 @@ #include "ChatHelper.h" #include "Event.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool DropQuestAction::Execute(Event event) @@ -51,10 +52,15 @@ bool DropQuestAction::Execute(Event event) const Quest* pQuest = sObjectMgr->GetQuestTemplate(entry); const std::string text_quest = ChatHelper::FormatQuest(pQuest); LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), pQuest->GetTitle()); - bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL); + std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_removed_debug", + "Quest [%quest] removed", + {{"%quest", text_quest}}); + bot->Say(text, LANG_UNIVERSAL); } - botAI->TellMaster("Quest removed"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_remove", "Quest removed", {})); return true; } @@ -69,7 +75,10 @@ bool CleanQuestLogAction::Execute(Event event) // Only output this message if "debug rpg" strategy is enabled if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) - botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests..."); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "clean_quest_log_started", + "Clean Quest Log command received, removing grey/trivial quests...", + {})); uint8 botLevel = bot->GetLevel(); // Get bot's level @@ -103,7 +112,10 @@ bool CleanQuestLogAction::Execute(Event event) { // Output only if "debug rpg" strategy is enabled if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) - botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey)."); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_trivial_will_remove", + "Quest [%title] will be removed because it is trivial (grey).", + {{"%title", quest->GetTitle()}})); // Remove quest botAI->rpgStatistic.questDropped++; @@ -116,17 +128,27 @@ bool CleanQuestLogAction::Execute(Event event) { const std::string text_quest = ChatHelper::FormatQuest(quest); LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle()); - bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL); + std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_removed_debug", + "Quest [%quest] removed", + {{"%quest", text_quest}}); + bot->Say(text, LANG_UNIVERSAL); } if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) - botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed."); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_has_been_removed", + "Quest [%title] has been removed.", + {{"%title", quest->GetTitle()}})); } else { // Only output if "debug rpg" strategy is enabled if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) - botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept."); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_not_trivial_kept", + "Quest [%title] is not trivial and will be kept.", + {{"%title", quest->GetTitle()}})); } } @@ -204,9 +226,16 @@ void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isG { const std::string text_quest = ChatHelper::FormatQuest(quest); LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle()); - bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL); + std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_removed_debug", + "Quest [%quest] removed", + {{"%quest", text_quest}}); + bot->Say(text, LANG_UNIVERSAL); } - botAI->TellMaster("Quest removed" + chat->FormatQuest(quest)); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_removed_with_name", + "Quest removed %quest", + {{"%quest", chat->FormatQuest(quest)}})); } } diff --git a/src/Ai/Base/Actions/GenericActions.cpp b/src/Ai/Base/Actions/GenericActions.cpp index 35fa51b316d..a4873d86a22 100644 --- a/src/Ai/Base/Actions/GenericActions.cpp +++ b/src/Ai/Base/Actions/GenericActions.cpp @@ -8,6 +8,7 @@ #include "Player.h" #include "Pet.h" #include "PlayerbotAIConfig.h" +#include "PlayerbotTextMgr.h" #include "CreatureAI.h" #include "Playerbots.h" #include "CharmInfo.h" @@ -178,7 +179,8 @@ bool SetPetStanceAction::Execute(Event /*event*/) // If there are no controlled pets or guardians, notify the player and exit if (targets.empty()) { - botAI->TellError("You have no pet or guardian pet."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "pet_no_pet_error", "You have no pet or guardian pet.", {})); return false; } diff --git a/src/Ai/Base/Actions/GuildAcceptAction.cpp b/src/Ai/Base/Actions/GuildAcceptAction.cpp index cd7635a97b2..b087472929e 100644 --- a/src/Ai/Base/Actions/GuildAcceptAction.cpp +++ b/src/Ai/Base/Actions/GuildAcceptAction.cpp @@ -8,6 +8,7 @@ #include "Event.h" #include "GuildPackets.h" #include "PlayerbotSecurity.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool GuildAcceptAction::Execute(Event event) @@ -28,17 +29,20 @@ bool GuildAcceptAction::Execute(Event event) uint32 guildId = inviter->GetGuildId(); if (!guildId) { - botAI->TellError("You are not in a guild!"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "guild_accept_inviter_not_in_guild", "You are not in a guild!", {})); accept = false; } else if (bot->GetGuildId()) { - botAI->TellError("Sorry, I am in a guild already"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "guild_accept_already_in_guild", "Sorry, I am in a guild already", {})); accept = false; } else if (!botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, false, inviter, true)) { - botAI->TellError("Sorry, I don't want to join your guild :("); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "guild_accept_declined", "Sorry, I don't want to join your guild :(", {})); accept = false; } diff --git a/src/Ai/Base/Actions/LeaveGroupAction.cpp b/src/Ai/Base/Actions/LeaveGroupAction.cpp index 337d115444c..6618cbbe13d 100644 --- a/src/Ai/Base/Actions/LeaveGroupAction.cpp +++ b/src/Ai/Base/Actions/LeaveGroupAction.cpp @@ -7,6 +7,7 @@ #include "Event.h" #include "PlayerbotAIConfig.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool LeaveGroupAction::Execute(Event event) @@ -86,7 +87,9 @@ bool LeaveGroupAction::Leave() Player* master = botAI -> GetMaster(); if (master) - botAI->TellMaster("Goodbye!", PLAYERBOT_SECURITY_TALK); + botAI->TellMaster( + PlayerbotTextMgr::instance().GetBotTextOrDefault("goodbye", "Goodbye!", {}), + PLAYERBOT_SECURITY_TALK); botAI->LeaveOrDisbandGroup(); return true; diff --git a/src/Ai/Base/Actions/OutfitAction.cpp b/src/Ai/Base/Actions/OutfitAction.cpp index cbb41219466..07abc4a074d 100644 --- a/src/Ai/Base/Actions/OutfitAction.cpp +++ b/src/Ai/Base/Actions/OutfitAction.cpp @@ -7,6 +7,7 @@ #include "Event.h" #include "ItemVisitors.h" +#include "PlayerbotTextMgr.h" #include "PlayerbotRepository.h" #include "Playerbots.h" #include "ItemPackets.h" @@ -18,9 +19,12 @@ bool OutfitAction::Execute(Event event) if (param == "?") { List(); - botAI->TellMaster("outfit +[item] to add items"); - botAI->TellMaster("outfit -[item] to remove items"); - botAI->TellMaster("outfit equip/replace to equip items"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_usage_add", "outfit +[item] to add items", {})); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_usage_remove", "outfit -[item] to remove items", {})); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_usage_equip", "outfit equip/replace to equip items", {})); } else { @@ -32,8 +36,10 @@ bool OutfitAction::Execute(Event event) PlayerbotRepository::instance().Save(botAI); std::ostringstream out; - out << "Setting outfit " << name << " as " << param; - botAI->TellMaster(out); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_set_as", + "Setting outfit %name as %param", + {{"%name", name}, {"%param", param}})); return true; } @@ -49,18 +55,20 @@ bool OutfitAction::Execute(Event event) std::string const command = param.substr(space + 1); if (command == "equip") { - std::ostringstream out; - out << "Equipping outfit " << name; - botAI->TellMaster(out); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_equipping", + "Equipping outfit %name", + {{"%name", name}})); EquipItems(outfit); return true; } else if (command == "replace") { - std::ostringstream out; - out << "Replacing current equip with outfit " << name; - botAI->TellMaster(out); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_replace_current", + "Replacing current equip with outfit %name", + {{"%name", name}})); for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++) { @@ -83,9 +91,10 @@ bool OutfitAction::Execute(Event event) } else if (command == "reset") { - std::ostringstream out; - out << "Resetting outfit " << name; - botAI->TellMaster(out); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_resetting", + "Resetting outfit %name", + {{"%name", name}})); Save(name, ItemIds()); PlayerbotRepository::instance().Save(botAI); @@ -93,9 +102,10 @@ bool OutfitAction::Execute(Event event) } else if (command == "update") { - std::ostringstream out; - out << "Updating with current items outfit " << name; - botAI->TellMaster(out); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_updating_current", + "Updating with current items outfit %name", + {{"%name", name}})); Update(name); PlayerbotRepository::instance().Save(botAI); @@ -107,24 +117,25 @@ bool OutfitAction::Execute(Event event) { ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid); - std::ostringstream out; - out << chat->FormatItem(proto); if (remove) { std::set::iterator j = outfit.find(itemid); if (j != outfit.end()) outfit.erase(j); - out << " removed from "; + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_item_removed_from", + "%item removed from %name", + {{"%item", chat->FormatItem(proto)}, {"%name", name}})); } else { outfit.insert(itemid); - out << " added to "; + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "outfit_item_added_to", + "%item added to %name", + {{"%item", chat->FormatItem(proto)}, {"%name", name}})); } - - out << name; - botAI->TellMaster(out.str()); } Save(name, outfit); diff --git a/src/Ai/Base/Actions/ReleaseSpiritAction.cpp b/src/Ai/Base/Actions/ReleaseSpiritAction.cpp index 19bb870db20..78897c08201 100644 --- a/src/Ai/Base/Actions/ReleaseSpiritAction.cpp +++ b/src/Ai/Base/Actions/ReleaseSpiritAction.cpp @@ -10,6 +10,7 @@ #include "NearestNpcsValue.h" #include "ObjectDefines.h" #include "ObjectGuid.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "ServerFacade.h" #include "Corpse.h" @@ -22,7 +23,8 @@ bool ReleaseSpiritAction::Execute(Event event) { if (!bot->InBattleground()) { - botAI->TellMasterNoFacing("I am not dead, will wait here"); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "release_spirit_not_dead_wait", "I am not dead, will wait here", {})); // -follow in bg is overwriten each tick with +follow // +stay in bg causes stuttering effect as bot is cycled between +stay and +follow each tick botAI->ChangeStrategy("-follow,+stay", BOT_STATE_NON_COMBAT); @@ -33,14 +35,15 @@ bool ReleaseSpiritAction::Execute(Event event) if (bot->GetCorpse() && bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) { - botAI->TellMasterNoFacing("I am already a spirit"); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "release_spirit_already_spirit", "I am already a spirit", {})); return false; } const WorldPacket& packet = event.getPacket(); const std::string message = !packet.empty() && packet.GetOpcode() == CMSG_REPOP_REQUEST - ? "Releasing..." - : "Meet me at the graveyard"; + ? PlayerbotTextMgr::instance().GetBotTextOrDefault("release_spirit_releasing", "Releasing...", {}) + : PlayerbotTextMgr::instance().GetBotTextOrDefault("release_spirit_meet_graveyard", "Meet me at the graveyard", {}); botAI->TellMasterNoFacing(message); IncrementDeathCount(); diff --git a/src/Ai/Base/Actions/ReviveFromCorpseAction.cpp b/src/Ai/Base/Actions/ReviveFromCorpseAction.cpp index 2599fb1cff7..3efca28d1e7 100644 --- a/src/Ai/Base/Actions/ReviveFromCorpseAction.cpp +++ b/src/Ai/Base/Actions/ReviveFromCorpseAction.cpp @@ -9,6 +9,7 @@ #include "FleeManager.h" #include "GameGraveyard.h" #include "MapMgr.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "RandomPlayerbotMgr.h" #include "ServerFacade.h" @@ -322,7 +323,7 @@ bool SpiritHealerAction::Execute(Event /*event*/) bot->SpawnCorpseBones(); context->GetValue("current target")->Set(nullptr); bot->SetTarget(); - botAI->TellMaster("Hello"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault("hello", "Hello", {})); if (dCount > 20) context->GetValue("death count")->Set(0); diff --git a/src/Ai/Base/Actions/SendMailAction.cpp b/src/Ai/Base/Actions/SendMailAction.cpp index 9be9d553abf..549e6b71610 100644 --- a/src/Ai/Base/Actions/SendMailAction.cpp +++ b/src/Ai/Base/Actions/SendMailAction.cpp @@ -9,6 +9,7 @@ #include "Event.h" #include "ItemVisitors.h" #include "Mail.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool SendMailAction::Execute(Event event) @@ -53,14 +54,18 @@ bool SendMailAction::Execute(Event event) if (!mailboxFound && !randomBot) { - bot->Whisper("There is no mailbox nearby", LANG_UNIVERSAL, tellTo); + bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "send_mail_no_mailbox_nearby", "There is no mailbox nearby", {}), + LANG_UNIVERSAL, tellTo); return false; } ItemIds ids = chat->parseItems(text); if (ids.size() > 1) { - bot->Whisper("You can not request more than one item", LANG_UNIVERSAL, tellTo); + bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "send_mail_one_item_only", "You can not request more than one item", {}), + LANG_UNIVERSAL, tellTo); return false; } @@ -72,13 +77,16 @@ bool SendMailAction::Execute(Event event) if (randomBot) { - bot->Whisper("I cannot send money", LANG_UNIVERSAL, tellTo); + bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "send_mail_cannot_send_money", "I cannot send money", {}), + LANG_UNIVERSAL, tellTo); return false; } if (bot->GetMoney() < money) { - botAI->TellError("I don't have enough money"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "send_mail_not_enough_money", "I don't have enough money", {})); return false; } @@ -100,8 +108,10 @@ bool SendMailAction::Execute(Event event) CharacterDatabase.CommitTransaction(trans); std::ostringstream out; - out << "Sending mail to " << receiver->GetName(); - botAI->TellMaster(out.str()); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "send_mail_sending_to", + "Sending mail to %receiver", + {{"%receiver", receiver->GetName()}})); return true; } @@ -125,7 +135,10 @@ bool SendMailAction::Execute(Event event) if (item->IsSoulBound() || item->IsConjuredConsumable()) { std::ostringstream out; - out << "Cannot send " << ChatHelper::FormatItem(item->GetTemplate()); + out << PlayerbotTextMgr::instance().GetBotTextOrDefault( + "send_mail_cannot_send_item", + "Cannot send %item", + {{"%item", ChatHelper::FormatItem(item->GetTemplate())}}); bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo); continue; } @@ -140,7 +153,10 @@ bool SendMailAction::Execute(Event event) if (!price) { std::ostringstream out; - out << ChatHelper::FormatItem(item->GetTemplate()) << ": it is not for sale"; + out << PlayerbotTextMgr::instance().GetBotTextOrDefault( + "send_mail_item_not_for_sale", + "%item: it is not for sale", + {{"%item", ChatHelper::FormatItem(item->GetTemplate())}}); bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo); return false; } @@ -160,7 +176,10 @@ bool SendMailAction::Execute(Event event) CharacterDatabase.CommitTransaction(trans); std::ostringstream out; - out << "Sent mail to " << receiver->GetName(); + out << PlayerbotTextMgr::instance().GetBotTextOrDefault( + "send_mail_sent_to", + "Sent mail to %receiver", + {{"%receiver", receiver->GetName()}}); bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo); return true; } diff --git a/src/Ai/Base/Actions/SetCraftAction.cpp b/src/Ai/Base/Actions/SetCraftAction.cpp index 40134821f1b..ca475a52efe 100644 --- a/src/Ai/Base/Actions/SetCraftAction.cpp +++ b/src/Ai/Base/Actions/SetCraftAction.cpp @@ -8,6 +8,7 @@ #include "ChatHelper.h" #include "CraftValue.h" #include "Event.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" std::map SetCraftAction::skillSpells; @@ -24,7 +25,8 @@ bool SetCraftAction::Execute(Event event) if (link == "reset") { data.Reset(); - botAI->TellMaster("I will not craft anything"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "craft_reset", "I will not craft anything", {})); return true; } @@ -37,7 +39,8 @@ bool SetCraftAction::Execute(Event event) ItemIds itemIds = chat->parseItems(link); if (itemIds.empty()) { - botAI->TellMaster("Usage: 'craft [itemId]' or 'craft reset'"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "craft_usage", "Usage: 'craft [itemId]' or 'craft reset'", {})); return false; } @@ -94,7 +97,8 @@ bool SetCraftAction::Execute(Event event) if (data.required.empty()) { - botAI->TellMaster("I cannot craft this"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "craft_cannot_craft", "I cannot craft this", {})); return false; } @@ -109,7 +113,8 @@ void SetCraftAction::TellCraft() CraftData& data = AI_VALUE(CraftData&, "craft"); if (data.IsEmpty()) { - botAI->TellMaster("I will not craft anything"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "craft_reset", "I will not craft anything", {})); return; } @@ -117,8 +122,7 @@ void SetCraftAction::TellCraft() if (!proto) return; - std::ostringstream out; - out << "I will craft " << chat->FormatItem(proto) << " using reagents: "; + std::ostringstream reagentsOut; bool first = true; for (std::map::iterator i = data.required.begin(); i != data.required.end(); ++i) @@ -130,20 +134,23 @@ void SetCraftAction::TellCraft() { if (first) first = false; - else - out << ", "; + reagentsOut << ", "; - out << chat->FormatItem(reagent, required); + reagentsOut << chat->FormatItem(reagent, required); uint32 given = data.obtained[item]; if (given) - out << "|cffffff00(x" << given << " given)|r "; + reagentsOut << "|cffffff00(x" << given << " given)|r "; } } - out << " (craft fee: " << chat->formatMoney(GetCraftFee(data)) << ")"; - botAI->TellMaster(out.str()); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "craft_summary", + "I will craft %item using reagents: %reagents (craft fee: %money)", + {{"%item", chat->FormatItem(proto)}, + {"%reagents", reagentsOut.str()}, + {"%money", chat->formatMoney(GetCraftFee(data))}})); } uint32 SetCraftAction::GetCraftFee(CraftData& data) diff --git a/src/Ai/Base/Actions/SetHomeAction.cpp b/src/Ai/Base/Actions/SetHomeAction.cpp index ed6bcbfaa9c..85828c26e78 100644 --- a/src/Ai/Base/Actions/SetHomeAction.cpp +++ b/src/Ai/Base/Actions/SetHomeAction.cpp @@ -6,6 +6,7 @@ #include "SetHomeAction.h" #include "Event.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool SetHomeAction::Execute(Event /*event*/) @@ -28,7 +29,8 @@ bool SetHomeAction::Execute(Event /*event*/) { Creature* creature = botAI->GetCreature(selection); bot->GetSession()->SendBindPoint(creature); - botAI->TellMaster("This inn is my new home"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "set_home_success", "This inn is my new home", {})); return true; } @@ -40,10 +42,12 @@ bool SetHomeAction::Execute(Event /*event*/) continue; bot->GetSession()->SendBindPoint(unit); - botAI->TellMaster("This inn is my new home"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "set_home_success", "This inn is my new home", {})); return true; } - botAI->TellError("Can't find any innkeeper around"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "set_home_no_innkeeper_error", "Can't find any innkeeper around", {})); return false; } diff --git a/src/Ai/Base/Actions/ShareQuestAction.cpp b/src/Ai/Base/Actions/ShareQuestAction.cpp index 50ce2ac3224..492b3ad6e6f 100644 --- a/src/Ai/Base/Actions/ShareQuestAction.cpp +++ b/src/Ai/Base/Actions/ShareQuestAction.cpp @@ -6,6 +6,7 @@ #include "ShareQuestAction.h" #include "Event.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool ShareQuestAction::Execute(Event event) @@ -32,7 +33,8 @@ bool ShareQuestAction::Execute(Event event) WorldPacket p; p << entry; bot->GetSession()->HandlePushQuestToParty(p); - botAI->TellMaster("Quest shared"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_shared", "Quest shared", {})); return true; } } @@ -98,7 +100,8 @@ bool AutoShareQuestAction::Execute(Event /*event*/) WorldPacket p; p << logQuest; bot->GetSession()->HandlePushQuestToParty(p); - botAI->TellMaster("Quest shared"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "quest_shared", "Quest shared", {})); shared = true; } diff --git a/src/Ai/Base/Actions/TameAction.cpp b/src/Ai/Base/Actions/TameAction.cpp index b19626e5949..dfeb61a47f5 100644 --- a/src/Ai/Base/Actions/TameAction.cpp +++ b/src/Ai/Base/Actions/TameAction.cpp @@ -15,6 +15,7 @@ #include "Player.h" #include "PlayerbotAI.h" #include "PlayerbotFactory.h" +#include "PlayerbotTextMgr.h" #include "SpellMgr.h" #include "WorldSession.h" @@ -123,7 +124,8 @@ bool TameAction::Execute(Event event) } catch (...) { - botAI->TellError("Invalid tame id."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_invalid_id_error", "Invalid tame id.", {})); } } else if (mode == "family" && !value.empty()) @@ -137,8 +139,10 @@ bool TameAction::Execute(Event event) else { // Unrecognized command or missing argument; show usage - botAI->TellError( - "Usage: tame name | tame id | tame family | tame rename | tame abandon"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_usage_error", + "Usage: tame name | tame id | tame family | tame rename | tame abandon", + {})); return false; } @@ -157,12 +161,15 @@ bool TameAction::Execute(Event event) if (!lastPetName.empty() && lastPetId != 0) { std::ostringstream oss; - oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << "."; - botAI->TellMaster(oss.str()); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_pet_changed", + "Pet changed to %name, ID: %id.", + {{"%name", lastPetName}, {"%id", std::to_string(lastPetId)}})); } else { - botAI->TellMaster("Pet changed and initialized!"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_pet_changed_initialized", "Pet changed and initialized!", {})); } } @@ -197,7 +204,10 @@ bool TameAction::SetPetByName(const std::string& name) // If the creature is exotic and the bot doesn't have Beast Mastery, show error and fail if (IsExoticPet(&creature) && !HasBeastMastery(bot)) { - botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_exotic_requires_beast_mastery", + "I cannot use exotic pets unless I have the Beast Mastery talent.", + {})); return false; } @@ -214,7 +224,8 @@ bool TameAction::SetPetByName(const std::string& name) } // If no suitable pet found, show an error and return failure - botAI->TellError("No tameable pet found with name: " + name); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_no_pet_by_name", "No tameable pet found with name: %name", {{"%name", name}})); return false; } @@ -231,21 +242,26 @@ bool TameAction::SetPetById(uint32 id) if (!creature->IsTameable(true)) { // If not tameable at all, show an error and fail - botAI->TellError("No tameable pet found with id: " + std::to_string(id)); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_no_pet_by_id", "No tameable pet found with id: %id", {{"%id", std::to_string(id)}})); return false; } // If it's an exotic pet, make sure the bot has the Beast Mastery talent if (IsExoticPet(creature) && !HasBeastMastery(bot)) { - botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_exotic_requires_beast_mastery", + "I cannot use exotic pets unless I have the Beast Mastery talent.", + {})); return false; } // Check if the bot is actually allowed to tame this pet (honoring exotic pet rules) if (!creature->IsTameable(bot->CanTameExoticPets())) { - botAI->TellError("No tameable pet found with id: " + std::to_string(id)); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_no_pet_by_id", "No tameable pet found with id: %id", {{"%id", std::to_string(id)}})); return false; } @@ -257,7 +273,8 @@ bool TameAction::SetPetById(uint32 id) } // If no valid creature was found by id, show an error - botAI->TellError("No tameable pet found with id: " + std::to_string(id)); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_no_pet_by_id", "No tameable pet found with id: %id", {{"%id", std::to_string(id)}})); return false; } @@ -315,9 +332,13 @@ bool TameAction::SetPetByFamily(const std::string& family) if (candidates.empty()) { if (foundExotic && !HasBeastMastery(bot)) - botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_exotic_requires_beast_mastery", + "I cannot use exotic pets unless I have the Beast Mastery talent.", + {})); else - botAI->TellError("No tameable pet found with family: " + family); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_no_pet_by_family", "No tameable pet found with family: %family", {{"%family", family}})); return false; } @@ -342,14 +363,18 @@ bool TameAction::RenamePet(const std::string& newName) // Check if the bot currently has a pet if (!pet) { - botAI->TellError("You have no pet to rename."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_no_pet_to_rename", "You have no pet to rename.", {})); return false; } // Validate the new name: must not be empty and max 12 characters if (newName.empty() || newName.length() > 12) { - botAI->TellError("Pet name must be between 1 and 12 alphabetic characters."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_pet_name_length_error", + "Pet name must be between 1 and 12 alphabetic characters.", + {})); return false; } @@ -358,7 +383,10 @@ bool TameAction::RenamePet(const std::string& newName) { if (!std::isalpha(static_cast(c))) { - botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z)."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_pet_name_alpha_error", + "Pet name must only contain alphabetic characters (A-Z, a-z).", + {})); return false; } } @@ -372,7 +400,10 @@ bool TameAction::RenamePet(const std::string& newName) // Check if the new name is reserved or forbidden if (sObjectMgr->IsReservedName(normalized)) { - botAI->TellError("That pet name is forbidden. Please choose another name."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_pet_name_forbidden_error", + "That pet name is forbidden. Please choose another name.", + {})); return false; } @@ -382,8 +413,12 @@ bool TameAction::RenamePet(const std::string& newName) bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry()); // Notify the master about the rename and give a tip to update the client name display - botAI->TellMaster("Your pet has been renamed to " + normalized + "!"); - botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet."); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_pet_renamed", "Your pet has been renamed to %name!", {{"%name", normalized}})); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_pet_rename_refresh_hint", + "If you do not see the new name, please dismiss and recall your pet.", + {})); // Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true); @@ -401,7 +436,8 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry) // Ensure the player is a hunter and at least level 10 (required for pets) if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10) { - botAI->TellError("Only level 10+ hunters can have pets."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_only_hunters_level_10", "Only level 10+ hunters can have pets.", {})); return false; } @@ -409,7 +445,8 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry) CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry); if (!creature) { - botAI->TellError("Creature template not found."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_creature_template_not_found", "Creature template not found.", {})); return false; } @@ -430,7 +467,8 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry) Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0); if (!pet) { - botAI->TellError("Failed to create pet."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_create_pet_failed", "Failed to create pet.", {})); return false; } @@ -485,13 +523,15 @@ bool TameAction::AbandonPet() // Remove the pet from the bot and mark it as deleted in the database bot->RemovePet(pet, PET_SAVE_AS_DELETED); // Inform the bot's master/player that the pet was abandoned - botAI->TellMaster("Your pet has been abandoned."); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_pet_abandoned", "Your pet has been abandoned.", {})); return true; } else { // If there is no hunter pet, show an error message - botAI->TellError("You have no hunter pet to abandon."); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "tame_no_hunter_pet_to_abandon", "You have no hunter pet to abandon.", {})); return false; } } diff --git a/src/Ai/Base/Actions/TaxiAction.cpp b/src/Ai/Base/Actions/TaxiAction.cpp index 0bbbb8a1090..d79a992e22b 100644 --- a/src/Ai/Base/Actions/TaxiAction.cpp +++ b/src/Ai/Base/Actions/TaxiAction.cpp @@ -7,6 +7,7 @@ #include "Event.h" #include "LastMovementValue.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "PlayerbotAIConfig.h" #include "Config.h" @@ -24,7 +25,8 @@ bool TaxiAction::Execute(Event event) { movement.taxiNodes.clear(); movement.Set(nullptr); - botAI->TellMaster("I am ready for the next flight"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "taxi_ready_next_flight", "I am ready for the next flight", {})); return true; } @@ -120,13 +122,15 @@ bool TaxiAction::Execute(Event event) { movement.taxiNodes.clear(); movement.Set(nullptr); - botAI->TellError("I can't fly with you"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "taxi_cant_fly_with_you", "I can't fly with you", {})); return false; } return true; } - botAI->TellError("Cannot find any flightmaster to talk"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "taxi_no_flightmaster_nearby", "Cannot find any flightmaster to talk", {})); return false; } diff --git a/src/Ai/Base/Actions/TradeStatusAction.cpp b/src/Ai/Base/Actions/TradeStatusAction.cpp index 96a7180a85a..c3a63ee7c9d 100644 --- a/src/Ai/Base/Actions/TradeStatusAction.cpp +++ b/src/Ai/Base/Actions/TradeStatusAction.cpp @@ -12,6 +12,7 @@ #include "ItemVisitors.h" #include "PlayerbotMgr.h" #include "PlayerbotSecurity.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "RandomPlayerbotMgr.h" #include "SetCraftAction.h" @@ -28,13 +29,17 @@ bool TradeStatusAction::Execute(Event event) // Allow the master and group members to trade if (trader != master && !traderBotAI && (!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID()))) { - bot->Whisper("I'm kind of busy now", LANG_UNIVERSAL, trader); + bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_busy_now", "I'm kind of busy now", {}), + LANG_UNIVERSAL, trader); return false; } if (sPlayerbotAIConfig.enableRandomBotTrading == 0 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot))) { - bot->Whisper("Trading is disabled", LANG_UNIVERSAL, trader); + bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_disabled", "Trading is disabled", {}), + LANG_UNIVERSAL, trader); return false; } @@ -180,9 +185,15 @@ bool TradeStatusAction::CheckTrade() { if (bot->GetGroup() && bot->GetGroup()->IsMember(bot->GetTrader()->GetGUID()) && botAI->HasRealPlayerMaster()) - botAI->TellMasterNoFacing("Thank you " + chat->FormatWorldobject(bot->GetTrader())); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_thank_you_player", + "Thank you %player", + {{"%player", chat->FormatWorldobject(bot->GetTrader())}})); else - bot->Say("Thank you " + chat->FormatWorldobject(bot->GetTrader()), + bot->Say(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_thank_you_player", + "Thank you %player", + {{"%player", chat->FormatWorldobject(bot->GetTrader())}}), (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); } return isGettingItem; @@ -210,12 +221,16 @@ bool TradeStatusAction::CheckTrade() int32 playerMoney = trader->GetTradeData()->GetMoney() + playerItemsMoney; if (botItemsMoney > 0 && sPlayerbotAIConfig.enableRandomBotTrading == 2 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot))) { - bot->Whisper("Selling is disabled.", LANG_UNIVERSAL, trader); + bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_selling_disabled", "Selling is disabled.", {}), + LANG_UNIVERSAL, trader); return false; } if (playerItemsMoney && sPlayerbotAIConfig.enableRandomBotTrading == 3 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot))) { - bot->Whisper("Buying is disabled.", LANG_UNIVERSAL, trader); + bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_buying_disabled", "Buying is disabled.", {}), + LANG_UNIVERSAL, trader); return false; } for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot) @@ -224,8 +239,10 @@ bool TradeStatusAction::CheckTrade() if (item && !item->GetTemplate()->SellPrice && !item->GetTemplate()->IsConjuredConsumable()) { std::ostringstream out; - out << chat->FormatItem(item->GetTemplate()) << " - This is not for sale"; - botAI->TellMaster(out); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_item_not_for_sale", + "%item - This is not for sale", + {{"%item", chat->FormatItem(item->GetTemplate())}})); botAI->PlaySound(TEXT_EMOTE_NO); return false; } @@ -239,8 +256,10 @@ bool TradeStatusAction::CheckTrade() if ((botMoney && !item->GetTemplate()->BuyPrice) || usage == ITEM_USAGE_NONE) { std::ostringstream out; - out << chat->FormatItem(item->GetTemplate()) << " - I don't need this"; - botAI->TellMaster(out); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_item_not_needed", + "%item - I don't need this", + {{"%item", chat->FormatItem(item->GetTemplate())}})); botAI->PlaySound(TEXT_EMOTE_NO); return false; } @@ -252,7 +271,8 @@ bool TradeStatusAction::CheckTrade() if (!botItemsMoney && !playerItemsMoney) { - botAI->TellError("There are no items to trade"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_no_items_error", "There are no items to trade", {})); return false; } @@ -266,7 +286,8 @@ bool TradeStatusAction::CheckTrade() { if (moneyDelta < 0) { - botAI->TellError("You can use discount to buy items only"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_discount_buy_only", "You can use discount to buy items only", {})); botAI->PlaySound(TEXT_EMOTE_NO); return false; } @@ -282,16 +303,20 @@ bool TradeStatusAction::CheckTrade() switch (urand(0, 4)) { case 0: - botAI->TellMaster("A pleasure doing business with you"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_success_pleasure", "A pleasure doing business with you", {})); break; case 1: - botAI->TellMaster("Fair trade"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_success_fair_trade", "Fair trade", {})); break; case 2: - botAI->TellMaster("Thanks"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_success_thanks", "Thanks", {})); break; case 3: - botAI->TellMaster("Off with you"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_success_off_with_you", "Off with you", {})); break; } @@ -300,8 +325,10 @@ bool TradeStatusAction::CheckTrade() } std::ostringstream out; - out << "I want " << chat->formatMoney(-(delta + discount)) << " for this"; - botAI->TellMaster(out); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "trade_want_money_for_this", + "I want %money for this", + {{"%money", chat->formatMoney(-(delta + discount))}})); botAI->PlaySound(TEXT_EMOTE_NO); return false; } diff --git a/src/Ai/Base/Actions/UseItemAction.cpp b/src/Ai/Base/Actions/UseItemAction.cpp index dde06906c9d..403dbc0568c 100644 --- a/src/Ai/Base/Actions/UseItemAction.cpp +++ b/src/Ai/Base/Actions/UseItemAction.cpp @@ -9,6 +9,7 @@ #include "Event.h" #include "ItemPackets.h" #include "ItemUsageValue.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" bool UseItemAction::Execute(Event event) @@ -35,7 +36,8 @@ bool UseItemAction::Execute(Event event) return UseItemOnGameObject(*items.begin(), *gos.begin()); } - botAI->TellError("No items (or game objects) available"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "use_item_none_available", "No items (or game objects) available", {})); return false; } @@ -48,8 +50,10 @@ bool UseItemAction::UseGameObject(ObjectGuid guid) go->Use(bot); std::ostringstream out; - out << "Using " << chat->FormatGameobject(go); - botAI->TellMasterNoFacing(out.str()); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "use_gameobject", + "Using %gameobject", + {{"%gameobject", chat->FormatGameobject(go)}})); return true; } @@ -92,16 +96,16 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni bool targetSelected = false; - std::ostringstream out; - out << "Using " << chat->FormatItem(item->GetTemplate()); + std::string itemText = chat->FormatItem(item->GetTemplate()); + std::string targetText; if (item->GetTemplate()->Stackable > 1) { uint32 count = item->GetCount(); if (count > 1) - out << " (" << count << " available) "; + itemText += " (" + std::to_string(count) + " available)"; else - out << " (the last one!)"; + itemText += " (the last one!)"; } if (goGuid) @@ -114,7 +118,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni packet << targetFlag; packet << goGuid.WriteAsPacked(); - out << " on " << chat->FormatGameobject(go); + targetText = chat->FormatGameobject(go); targetSelected = true; } @@ -124,7 +128,8 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni { bool fit = SocketItem(itemTarget, item) || SocketItem(itemTarget, item, true); if (!fit) - botAI->TellMaster("Socket does not fit"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "socket_does_not_fit", "Socket does not fit", {})); return fit; } @@ -133,7 +138,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni targetFlag = TARGET_FLAG_ITEM; packet << targetFlag; packet << itemTarget->GetGUID().WriteAsPacked(); - out << " on " << chat->FormatItem(itemTarget->GetTemplate()); + targetText = chat->FormatItem(itemTarget->GetTemplate()); targetSelected = true; } } @@ -149,7 +154,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni { targetFlag = TARGET_FLAG_UNIT; packet << targetFlag << masterSelection.WriteAsPacked(); - out << " on " << unit->GetName(); + targetText = unit->GetName(); targetSelected = true; } } @@ -159,7 +164,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni { targetFlag = TARGET_FLAG_UNIT; packet << targetFlag << unitTarget->GetGUID().WriteAsPacked(); - out << " on " << unitTarget->GetName(); + targetText = unitTarget->GetName(); targetSelected = true; } @@ -173,9 +178,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni packet << uint32(0); bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(packet); - std::ostringstream out; - out << "Got quest " << chat->FormatQuest(qInfo); - botAI->TellMasterNoFacing(out.str()); + botAI->TellMasterNoFacing("Got quest " + chat->FormatQuest(qInfo)); return true; } } @@ -217,7 +220,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni targetFlag = TARGET_FLAG_TRADE_ITEM; packet << targetFlag << (uint8)1 << ObjectGuid((uint64)TRADE_SLOT_NONTRADED).WriteAsPacked(); targetSelected = true; - out << " on traded item"; + targetText = "traded item"; } else { @@ -225,7 +228,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni packet << targetFlag; packet << itemForSpell->GetGUID().WriteAsPacked(); targetSelected = true; - out << " on " << chat->FormatItem(itemForSpell->GetTemplate()); + targetText = chat->FormatItem(itemForSpell->GetTemplate()); } uint32 castTime = spellInfo->CalcCastTime(); botAI->SetNextCheckDelay(castTime + sPlayerbotAIConfig.reactDelay); @@ -246,17 +249,17 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni targetSelected = true; if (unitTarget == bot || !unitTarget->IsInWorld() || unitTarget->IsDuringRemoveFromWorld()) - out << " on self"; + targetText = "self"; else if (unitTarget->IsHostileTo(bot)) - out << " on self"; + targetText = "self"; else - out << " on " << unitTarget->GetName(); + targetText = unitTarget->GetName(); } else { packet << bot->GetPackGUID(); targetSelected = true; - out << " on self"; + targetText = "self"; } } @@ -307,7 +310,12 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni return false; // botAI->SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown); - botAI->TellMasterNoFacing(out.str()); + std::string useText = targetSelected + ? PlayerbotTextMgr::instance().GetBotTextOrDefault( + "use_item_on_target", "Using %item on %target", {{"%item", itemText}, {"%target", targetText}}) + : PlayerbotTextMgr::instance().GetBotTextOrDefault( + "use_item", "Using %item", {{"%item", itemText}}); + botAI->TellMasterNoFacing(useText); bot->GetSession()->HandleUseItemOpcode(packet); return true; } @@ -372,10 +380,10 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace) if (fits) { - std::ostringstream out; - out << "Socketing " << chat->FormatItem(item->GetTemplate()); - out << " with " << chat->FormatItem(gem->GetTemplate()); - botAI->TellMaster(out); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "socketing_item_with_gem", + "Socketing %item with %gem", + {{"%item", chat->FormatItem(item->GetTemplate())}, {"%gem", chat->FormatItem(gem->GetTemplate())}})); WorldPackets::Item::SocketGems nicePacket(std::move(packet)); nicePacket.Read(); diff --git a/src/Ai/Base/Actions/UseMeetingStoneAction.cpp b/src/Ai/Base/Actions/UseMeetingStoneAction.cpp index d6032acb12d..382425db3e7 100644 --- a/src/Ai/Base/Actions/UseMeetingStoneAction.cpp +++ b/src/Ai/Base/Actions/UseMeetingStoneAction.cpp @@ -11,6 +11,7 @@ #include "GridNotifiersImpl.h" #include "NearestGameObjects.h" #include "PlayerbotAIConfig.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "PositionValue.h" @@ -36,7 +37,8 @@ bool UseMeetingStoneAction::Execute(Event event) if (bot->IsInCombat()) { - botAI->TellError("I am in combat"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "meeting_stone_in_combat", "I am in combat", {})); return false; } @@ -73,13 +75,15 @@ bool SummonAction::Execute(Event /*event*/) if (SummonUsingGos(master, bot, true) || SummonUsingNpcs(master, bot, true)) { - botAI->TellMasterNoFacing("Hello!"); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "hello", "Hello!", {})); return true; } if (SummonUsingGos(bot, master, true) || SummonUsingNpcs(bot, master, true)) { - botAI->TellMasterNoFacing("Welcome!"); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "meeting_stone_welcome", "Welcome!", {})); return true; } @@ -99,7 +103,10 @@ bool SummonAction::SummonUsingGos(Player* summoner, Player* player, bool preserv return Teleport(summoner, player, preserveAuras); } - botAI->TellError(summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + summoner == bot ? "meeting_stone_none_nearby" : "meeting_stone_none_near_you", + summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you", + {})); return false; } @@ -119,13 +126,19 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preser { if (!player->HasItemCount(6948, 1, false)) { - botAI->TellError(player == bot ? "I have no hearthstone" : "You have no hearthstone"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + player == bot ? "meeting_stone_no_hearthstone_self" : "meeting_stone_no_hearthstone_you", + player == bot ? "I have no hearthstone" : "You have no hearthstone", + {})); return false; } if (player->HasSpellCooldown(8690)) { - botAI->TellError(player == bot ? "My hearthstone is not ready" : "Your hearthstone is not ready"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + player == bot ? "meeting_stone_hearthstone_not_ready_self" : "meeting_stone_hearthstone_not_ready_you", + player == bot ? "My hearthstone is not ready" : "Your hearthstone is not ready", + {})); return false; } @@ -141,7 +154,10 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preser } } - botAI->TellError(summoner == bot ? "There are no innkeepers nearby" : "There are no innkeepers near you"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + summoner == bot ? "meeting_stone_no_innkeepers_nearby" : "meeting_stone_no_innkeepers_near_you", + summoner == bot ? "There are no innkeepers nearby" : "There are no innkeepers near you", + {})); return false; } @@ -153,7 +169,8 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras if (player->GetVehicle()) { - botAI->TellError("You cannot summon me while I'm on a vehicle"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "meeting_stone_cannot_summon_vehicle", "You cannot summon me while I'm on a vehicle", {})); return false; } @@ -174,20 +191,29 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras if (summoner->IsInCombat() && !sPlayerbotAIConfig.allowSummonInCombat) { - botAI->TellError("You cannot summon me while you're in combat"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "meeting_stone_cannot_summon_master_in_combat", + "You cannot summon me while you're in combat", + {})); return false; } if (!summoner->IsAlive() && !sPlayerbotAIConfig.allowSummonWhenMasterIsDead) { - botAI->TellError("You cannot summon me while you're dead"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "meeting_stone_cannot_summon_master_dead", + "You cannot summon me while you're dead", + {})); return false; } if (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST) && !sPlayerbotAIConfig.allowSummonWhenBotIsDead) { - botAI->TellError("You cannot summon me while I'm dead, you need to release my spirit first"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "meeting_stone_cannot_summon_bot_dead", + "You cannot summon me while I'm dead, you need to release my spirit first", + {})); return false; } @@ -199,7 +225,8 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras { bot->ResurrectPlayer(1.0f, false); bot->SpawnCorpseBones(); - botAI->TellMasterNoFacing("I live, again!"); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "meeting_stone_revived", "I live, again!", {})); botAI->GetAiObjectContext()->GetValue("prioritized targets")->Reset(); } @@ -229,6 +256,7 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras } if (summoner != player) - botAI->TellError("Not enough place to summon"); + botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "meeting_stone_not_enough_space", "Not enough place to summon", {})); return false; } diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp index 336c7599dea..5ae65e14e01 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp @@ -20,6 +20,7 @@ #include "Player.h" #include "PlayerbotAI.h" #include "PlayerbotAIConfig.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "Position.h" #include "QuestDef.h" @@ -310,7 +311,10 @@ bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid) { AcceptQuest(quest, guid); if (botAI->GetMaster()) - botAI->TellMasterNoFacing("Quest accepted " + ChatHelper::FormatQuest(quest)); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "new_rpg_quest_accepted", + "Quest accepted %quest", + {{"%quest", ChatHelper::FormatQuest(quest)}})); BroadcastHelper::BroadcastQuestAccepted(botAI, bot, quest); botAI->rpgStatistic.questAccepted++; LOG_DEBUG("playerbots", "[New RPG] {} accept quest {}", bot->GetName(), quest->GetQuestId()); @@ -319,7 +323,10 @@ bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid) { TurnInQuest(quest, guid); if (botAI->GetMaster()) - botAI->TellMasterNoFacing("Quest rewarded " + ChatHelper::FormatQuest(quest)); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "new_rpg_quest_rewarded", + "Quest rewarded %quest", + {{"%quest", ChatHelper::FormatQuest(quest)}})); BroadcastHelper::BroadcastQuestTurnedIn(botAI, bot, quest); botAI->rpgStatistic.questRewarded++; LOG_DEBUG("playerbots", "[New RPG] {} turned in quest {}", bot->GetName(), quest->GetQuestId()); @@ -599,7 +606,10 @@ bool NewRpgBaseAction::OrganizeQuestLog() packet << (uint8)i; bot->GetSession()->HandleQuestLogRemoveQuest(packet); if (botAI->GetMaster()) - botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest)); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "new_rpg_quest_dropped", + "Quest dropped %quest", + {{"%quest", ChatHelper::FormatQuest(quest)}})); botAI->rpgStatistic.questDropped++; dropped++; } @@ -626,7 +636,10 @@ bool NewRpgBaseAction::OrganizeQuestLog() packet << (uint8)i; bot->GetSession()->HandleQuestLogRemoveQuest(packet); if (botAI->GetMaster()) - botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest)); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "new_rpg_quest_dropped", + "Quest dropped %quest", + {{"%quest", ChatHelper::FormatQuest(quest)}})); botAI->rpgStatistic.questDropped++; dropped++; } @@ -648,7 +661,10 @@ bool NewRpgBaseAction::OrganizeQuestLog() packet << (uint8)i; bot->GetSession()->HandleQuestLogRemoveQuest(packet); if (botAI->GetMaster()) - botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest)); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "new_rpg_quest_dropped", + "Quest dropped %quest", + {{"%quest", ChatHelper::FormatQuest(quest)}})); botAI->rpgStatistic.questDropped++; } diff --git a/src/Ai/World/Rpg/Action/RpgSubActions.cpp b/src/Ai/World/Rpg/Action/RpgSubActions.cpp index 727f67a8fb0..24d03b7f358 100644 --- a/src/Ai/World/Rpg/Action/RpgSubActions.cpp +++ b/src/Ai/World/Rpg/Action/RpgSubActions.cpp @@ -13,6 +13,7 @@ #include "GuildCreateActions.h" #include "LastMovementValue.h" #include "MovementActions.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "PossibleRpgTargetsValue.h" #include "SocialMgr.h" @@ -430,12 +431,15 @@ bool RpgTradeUsefulAction::Execute(Event /*event*/) if (bot->GetTradeData() && bot->GetTradeData()->HasItem(item->GetGUID())) { if (bot->GetGroup() && bot->GetGroup()->IsMember(guidP) && botAI->HasRealPlayerMaster()) - botAI->TellMasterNoFacing( - "You can use this " + chat->FormatItem(item->GetTemplate()) + " better than me, " + - guidP.GetPlayer()->GetName() /*chat->FormatWorldobject(guidP.GetPlayer())*/ + "."); + botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "rpg_item_better_for_player", + "You can use this %item better than me, %player.", + {{"%item", chat->FormatItem(item->GetTemplate())}, {"%player", guidP.GetPlayer()->GetName()}})); else - bot->Say("You can use this " + chat->FormatItem(item->GetTemplate()) + " better than me, " + - player->GetName() /*chat->FormatWorldobject(player)*/ + ".", + bot->Say(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "rpg_item_better_for_player", + "You can use this %item better than me, %player.", + {{"%item", chat->FormatItem(item->GetTemplate())}, {"%player", player->GetName()}}), (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); if (!urand(0, 4) || items.size() < 2) @@ -449,7 +453,10 @@ bool RpgTradeUsefulAction::Execute(Event /*event*/) } } else - bot->Say("Start trade with" + chat->FormatWorldobject(player), + bot->Say(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "rpg_start_trade_with_player", + "Start trade with %player", + {{"%player", chat->FormatWorldobject(player)}}), (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); botAI->SetNextCheckDelay(sPlayerbotAIConfig.rpgDelay); diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index a9eb598e5fa..e1096b0cce2 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -38,6 +38,7 @@ #include "ObjectMgr.h" #include "PerfMonitor.h" #include "Player.h" +#include "PlayerbotTextMgr.h" #include "PlayerbotAIConfig.h" #include "PlayerbotMgr.h" #include "PlayerbotGuildMgr.h" @@ -451,9 +452,11 @@ void PlayerbotAI::UpdateAIGroupMaster() botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT); if (botAI->GetMaster() == botAI->GetGroupLeader()) - botAI->TellMaster("Hello, I follow you!"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "hello_follow", "Hello, I follow you!", {})); else - botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "hello", "Hello!", {})); } else { @@ -857,7 +860,8 @@ void PlayerbotAI::Reset(bool full) { WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); bot->GetSession()->HandleLogoutCancelOpcode(data); - TellMaster("Logout cancelled!"); + TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "logout_cancel", "Logout cancelled!", {})); } currentEngine = engines[BOT_STATE_NON_COMBAT]; diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index ab239d376ba..0634bbf781f 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -27,6 +27,7 @@ #include "PlayerbotFactory.h" #include "PlayerbotOperations.h" #include "PlayerbotSecurity.h" +#include "PlayerbotTextMgr.h" #include "PlayerbotWorldThreadProcessor.h" #include "Playerbots.h" #include "PlayerbotGuildMgr.h" @@ -320,7 +321,8 @@ void PlayerbotMgr::CancelLogout() { WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); bot->GetSession()->HandleLogoutCancelOpcode(data); - botAI->TellMaster("Logout cancelled!"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "logout_cancel", "Logout cancelled!", {})); } } @@ -411,7 +413,8 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid) { return; } - botAI->TellMaster("Goodbye!"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "goodbye", "Goodbye!", {})); bot->StopMoving(); bot->GetMotionMaster()->Clear(); @@ -544,7 +547,8 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) // set delay on login botAI->SetNextCheckDelay(urand(2000, 4000)); - botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "hello", "Hello!", {}), PLAYERBOT_SECURITY_TALK); // Queue group operations for world thread if (master && master->GetGroup() && !group) diff --git a/src/Bot/RandomPlayerbotMgr.cpp b/src/Bot/RandomPlayerbotMgr.cpp index 3c0a9054c09..bca11966446 100644 --- a/src/Bot/RandomPlayerbotMgr.cpp +++ b/src/Bot/RandomPlayerbotMgr.cpp @@ -34,6 +34,7 @@ #include "PlayerbotAI.h" #include "PlayerbotAIConfig.h" #include "PlayerbotFactory.h" +#include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "Position.h" #include "RaceMgr.h" @@ -2589,7 +2590,8 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) { botAI->SetMaster(player); botAI->ResetStrategies(); - botAI->TellMaster("Hello"); + botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "hello", "Hello", {})); } break; From 14436686941d65563a98b8abebadb004868c2e8b Mon Sep 17 00:00:00 2001 From: dillyns <49765217+dillyns@users.noreply.github.com> Date: Sat, 23 May 2026 00:47:38 -0400 Subject: [PATCH 39/63] Make arcane barrage the alternate for arcane blast for level 60 mages (#2401) ## Pull Request Description Adds arcane barrage as the alternate for arcane blast so level 60 arcane mages who dont have arcane blast yet will use arcane barrage whenever available. For level 60 arcane mages, its actually better dps to alternate between arcane barrage and arcane missiles to fish for Missile Barrage procs. Before: Screenshot_20260518_024601 After: Screenshot_20260518_025040 ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes Have a level 60 arcane mage dps a dummy. To compare to the old "rotation" of just using arcane missiles you can "ss Arcane Barrage" ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> --- .../Mage/Strategy/ArcaneMageStrategy.cpp | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Ai/Class/Mage/Strategy/ArcaneMageStrategy.cpp b/src/Ai/Class/Mage/Strategy/ArcaneMageStrategy.cpp index 4ba7d42b981..b9ed1e3dd6c 100644 --- a/src/Ai/Class/Mage/Strategy/ArcaneMageStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/ArcaneMageStrategy.cpp @@ -6,9 +6,31 @@ #include "ArcaneMageStrategy.h" #include "Playerbots.h" +// ===== Action Node Factory ===== +class ArcaneMageStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + ArcaneMageStrategyActionNodeFactory() + { + creators["arcane blast"] = &arcane_blast; + } + +private: + // Arcane Barrage is the alternate for Arcane Blast (cast while moving, or + // when Arcane Blast is unavailable - e.g. not yet learned at low levels). + static ActionNode* arcane_blast([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("arcane blast", + /*P*/ {}, + /*A*/ { NextAction("arcane barrage") }, + /*C*/ {}); + } +}; + +// ===== Single Target Strategy ===== ArcaneMageStrategy::ArcaneMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) { - // No custom ActionNodeFactory needed + actionNodeFactories.Add(new ArcaneMageStrategyActionNodeFactory()); } // ===== Default Actions ===== From 62941993435f755de3b120813efc95415fa90877 Mon Sep 17 00:00:00 2001 From: dillyns <49765217+dillyns@users.noreply.github.com> Date: Sat, 23 May 2026 00:57:20 -0400 Subject: [PATCH 40/63] DKs should use weapon stones (#2407) ## Pull Request Description Death Knights were not using weapon stones. This PR lets them use and initialize with weapon stones. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes Create an rndbot DK. They should be init with weaponstones and use them on their weapon. Or give a weapon stone to an altbot. They should use it on their weapon. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Class/Dk/Strategy/GenericDKNonCombatStrategy.cpp | 1 + src/Bot/Factory/PlayerbotFactory.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Ai/Class/Dk/Strategy/GenericDKNonCombatStrategy.cpp b/src/Ai/Class/Dk/Strategy/GenericDKNonCombatStrategy.cpp index 28179d74ece..9bf2b54b16e 100644 --- a/src/Ai/Class/Dk/Strategy/GenericDKNonCombatStrategy.cpp +++ b/src/Ai/Class/Dk/Strategy/GenericDKNonCombatStrategy.cpp @@ -41,6 +41,7 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector& trigger { NonCombatStrategy::InitTriggers(triggers); + triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) })); triggers.push_back( new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) })); triggers.push_back( diff --git a/src/Bot/Factory/PlayerbotFactory.cpp b/src/Bot/Factory/PlayerbotFactory.cpp index 3b35ccb6e31..161410c3f61 100644 --- a/src/Bot/Factory/PlayerbotFactory.cpp +++ b/src/Bot/Factory/PlayerbotFactory.cpp @@ -998,6 +998,7 @@ void PlayerbotFactory::InitConsumables() } case CLASS_WARRIOR: case CLASS_HUNTER: + case CLASS_DEATH_KNIGHT: { std::vector sharpening_stones = { ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, From 8ca6e42f108468c509a485f5d3d7c9d9447af543 Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Sat, 23 May 2026 11:42:08 -0700 Subject: [PATCH 41/63] Druid Overhaul (#2392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit druids Hello playerbots community!! After my pvp gear update, I was itching to get back to class strategies. I was testing raids, and noticed that druids... Well, they kinda sucked. They had glaring issues, would randomly die from bugs, and overall felt bad. Looking at issues discussed on github/discord, I decided to start working on a druid update. I started with the boomkin, and made a boomkin PR, but there were some technical issues with the storing of the eclipse mapping not clearing, and it was not good... I closed that PR, and went back to the drawing board, with one goal in mind: Make the druid class function as best as possible WHILST keeping the code consistent with what already exists. There is a TON of _yoink and twist_ (copy and paste with or without slight edits), and anything that is custom/new is discussed in the section below. I am very proud and excited to release this though - after 45 days of coding and testing, the druid finally feels good to have in the group. Disclaimer - this PR aims to address bugs, utility, and overall performance of Druids. It will not magically make them top DPS. I have done hours of testing, and druids can occasionally top the charts - but inconsistently. Boomkins are inconsistent due to Eclipse (until later gear phases), and Feral Cats are incredibly dependent on positioning, timing, combo points, energy, clearcasting procs... The stars have to align for things to go right for the Cat (It doesn't help that all movement, targeting, and actions are ran through the same engine, so cats really suffer in boss fights with scripted movement) But the changes I have made help those situations occur a fair bit more frequently. Let's dive in! ## Pull Request Description A druid overhaul across all four specs — new Cat stealth and CC systems, Eclipse tracking and rotation fixes for Balance, a Bear threat rotation rework, a Resto healing priority overhaul, and a restructured CC and AoE strategy architecture. --- ## Talent & Glyph Config - **Balance:** Moved points out of Improved Moonfire and into Nature's Reach for threat reduction and extended cast range. - **Bear:** Moved points out of King of the Jungle (15% enrage damage) and into Feral Instinct for increased Swipe damage and AoE threat. - **Cat:** Swapped the Glyph of Typhoon (useless for feral) for Glyph of Dash, which benefits the cat a ton in prowl. - **Resto:** Swapped Glyph of Rejuvenation for Glyph of Nourish (better HPS for difficult content), and Glyph of Typhoon (useless for resto) for Glyph of Dash. Moved one point from Nature's Bounty to Empowered Touch. --- ## Balance - **New skill - Typhoon:** Added Typhoon, triggered by "enemy within melee," using cone targeting logic ported from Cone of Cold. Typhoon is from the "balance pvp" spec only, and is housed in the shared AoE strategy. - **New skill - Cyclone:** Added Cyclone targeting the RTI CC-marked target (default moon). Incapacitates for 6 seconds with full damage immunity — cannot be broken by AoE. Priority 24.0f > Hibernate (23.0f) > Entangling Roots (22.0f). *(See CC Implementation in the code notes below.)* - **Bug fix - Eclipse cooldown tracking:** Eclipse procs referenced a cooldown not registered in the database, so boomkins immediately reverted to the wrong filler after an Eclipse buff fell off. Fixed with manual timestamp tracking. *(See Eclipse Cooldown Tracking in the code notes below.)* - **Bug fix — Starfall no longer pulls out-of-combat hostile enemies:** Previously fired on cooldown regardless of surroundings — its 36-yard radius (the largest AoE in the game) could silently pull entire unengaged packs. Now suppressed if any non-combat hostile NPC is within 40 yards. *(See Starfall Pull Safety in the code notes below.)* - **Filler changed to Starfire:** Starfire is now the filler (priority 5.4) over Wrath (5.3). Starfire has a 100% spellpower coefficient vs. Wrath's 12%, is more mana-efficient per point of damage, and has a 100% eclipse proc chance on crit vs. Wrath's 40%. In practice this significantly reduced mana consumption, especially before level 40 when Moonkin Form's mana-on-crit passive isn't available. - **Moonfire / Insect Swarm on Attacker rework:** Previously tied to the light AoE trigger (2+ enemies, 13.0f), causing low-level boomkins to multi-dot instead of casting fillers — mana-inefficient at low levels where targets rarely live long enough to tick the full DoT. Re-added as dedicated on-attacker triggers at lower priority than the fillers, so they fire only as a movement fallback. The triggers have been changed to override the TTL check (time to life), similar to the warlocks DoTs. - **Hurricane channel check rework:** Previously cancelled based on distance from the bot — enemies could leave the AoE but remain within 30 yards, keeping the channel alive. Now reads the Hurricane DynamicObject's actual radius and counts only attackers physically inside it. *(See Hurricane Channel Cancel in the code notes below.)* --- ## Cat - **Prowl (Stealth):** Implemented using the same logic as the existing Rogue stealth system. The bot enters Prowl when out of combat and a target is within range. Engagement distances are: - 30 yards baseline - −10 yards if the target already has a victim (engaged in combat) - −10 more yards if the target is also moving (minimum 10 yards) - +15 yards in Battlegrounds or Arenas - Enemy player targets take priority over grind/DPS targets when evaluating distance. - **Prowl openers:** The bot approaches the target in Prowl and opens based on approach angle and level: - **From behind:** Ravage (learned at level 32). Before Ravage is learned, Shred is used as the opener. - **From the front:** Pounce (stun + bleed, learned at level 36). Before Pounce is learned, Claw is used as the opener. - **New skill - Maim:** Added Maim as a 5 second stun-finisher at 5 combo points, but only against player targets. It will only fire when Rip and Savage Roar are already active. - **Innervate on healer:** Cats now cast Innervate when a healer drops below the low mana threshold (`AiPlayerbot.LowMana`, default 15%). *(See Healer Low Mana Framework in the code notes below for the shared value/trigger infrastructure backing this.)* - **Predator's Swiftness with CC spells:** Added a twotrigger pairing Predator's Swiftness (the instant-cast proc from finishing moves) with the existing CC triggers (Cyclone, Hibernate, Entangling Roots). Feral cats can now instant-cast CC the RTI CC-marked target (default moon) after a finisher using the Predator's Swiftness proc. - **Predator's Swiftness with Rebirth:** Added a twotrigger pairing Predator's Swiftness with the combat resurrection trigger. Cats can now use a Predator's Swiftness proc to instantly cast Rebirth on a dead party member. - **Bug fix - Autoattack no longer breaks prowl:** `MeleeAction::isUseful()` now returns false while the bot has the Prowl aura, preventing autoattack from breaking stealth before an opener spell fires. The code comment notes this pattern should be reused for a future Rogue autoattack in stealth fix. - **Bug fix - Non-prowl skills no longer break prowl:** `isUseful()` overrides were added to Feral Charge (Cat), Mangle (Cat), Swipe (Cat), Rake to return false while Prowl is active, preventing accidental prowl breaks before the opener fires. - **Bug fix - Clearcasting proc with energy spells:** Added dedicated `ClearcastingTrigger` / action pairings to ensure Clearcasting procs are consumed immediately. On single target, Shred is used; on AoE, Swipe (Cat) is used. This prevents the cat from using it's valuable clearcasting proc on a low energy spell (rake, feral charge - cat, cower, etc). This also fixes a bug where Clearcasting would sometimes linger for 4+ seconds without being used, as energy-based spells do not recognize the free cast and would not fire until their energy condition was met. - **Tiger's Fury rework:** TF previously fired on cooldown as a default action, meaning the bot would use it at full or near-full energy and gain no benefit from the energy it generates. The trigger now requires energy to be below 30 before firing, ensuring the bot recovers the full 60 energy granted by the King of the Jungle talent. - **Faerie Fire (Feral) rework for cat:** With Omen of Clarity, spams on cooldown to fish for Clearcasting procs. Without it, applies as a normal debuff and does not reapply while active. *(See Faerie Fire (Feral) Trigger in the code notes below.)* - **Feral Charge toggleable strategy:** Feral Charge (Cat) is now housed in a dedicated `feral charge` strategy, enabled by default for Cat druids. It can be disabled with `co -feral charge` and re-enabled with `co +feral charge`. Disabling this strategy can be useful for encounters where charging in would be unfavorable. - **Rake on Melee Attackers removed:** The Rake on Melee Attackers action was removed from the Cat AoE strategy. Applying Rake to multiple attackers and spreading out combo points produced far lower AoE DPS than simply continuing the single-target rotation. - **Antiquated Omen of Clarity framework removed:** `OmenOfClarityTrigger` and `CastOmenOfClarityAction` were not functional and have been removed. The `ClearcastingTrigger` appropriately tracks Omen of Clarity procs. I believe this was from TBC when Omen of Clarity was a spell. --- ## Bear - **Berserk tracking for Mangle:** A `berserk active` trigger fires Mangle (Bear) at priority 25.0f while Berserk is up. Previously, Mangle sat at 5.5f in the default actions while Swipe (Bear) sat at 25.0f on the light AoE trigger — meaning in any 2+ enemy encounter, Swipe would always win regardless of Berserk. Now Mangle (25.0f) sits above the AoE triggers (24.5f), so it takes priority during Berserk. It's pretty cool to see a Bear's dps on pull! - **Faerie Fire (Feral) rework:** Previously applied once and stopped. Now spams on cooldown for continuous threat generation (~3.5k threat per cast at level 80), and serves as a ranged soft-taunt fallback on the `lose aggro` trigger when Growl is on cooldown. *(See Faerie Fire (Feral) Trigger in the code notes below.)* - **Lacerate rework:** Previously, lacerate was a low priority default action and bears had no duration awareness. They would occasionally let 5 stacks of Lacerate fall off, resulting in pretty significant threat loss. Now, `LacerateTrigger` fires when the target has no Lacerate debuff, the stack count is below 5, or the remaining duration is ≤ 6 seconds. - **Demoralizing Roar on single target:** Previously, Demoralizing Roar only fired on the medium AoE trigger (3+ enemies, skipped on bosses). A dedicated trigger now applies it in any encounter, provided Vindication, Demoralizing Shout, or Curse of Weakness are not already present, as they don't stack with Demoralizing Roar. - **Bug fix - Rebirth on bears:** Bears no longer attempt to cast Rebirth in combat. The generic combat resurrection trigger was firing for all druid specs, causing bears to shift out of Dire Bear Form mid-fight to cast Rebirth — dropping their armor and HP while still holding aggro. Lots of sudden tank deaths... - **Feral Charge toggleable strategy:** Feral Charge (Bear) is now housed in a dedicated `feral charge` strategy, enabled by default for Bear druids. It can be disabled with `co -feral charge` and re-enabled with `co +feral charge`. Disabling this strategy can be useful for encounters where charging in would be unfavorable. --- ## Resto - **Blanketing strategy:** Added a `blanketing` strategy (enabled by default, `co -blanketing` to disable) that pre-HoTs group members with Wild Growth and Rejuvenation regardless of current health, prioritizing tanks → melee → ranged to maximize Revitalize uptime. *(See Blanketing Strategy in the code notes below.)* - **Nature's Swiftness → instant Healing Touch combo:** Nature's Swiftness was previously included in the boost strategy, where it would fire proactively on cooldown regardless of context. This wasted the proc on situations where it provided no benefit. It is now exclusively reactive — triggered at high priority (56.0f) when a party member hits critical health. A paired `nature's swiftness active` trigger then immediately fires Healing Touch (55.0f) on the lowest-health party member, consuming the proc as an instant-cast emergency heal. - **Lifebloom priority lowered (29.0f → 13.0f):** Lifebloom on the main tank is cast on Omen of Clarity procs. At 29.0f it previously outprioritised all low health reactive healing (21.4f), meaning a Clearcasting proc while a party member was at 25–44% HP would cause the bot to cast Lifebloom on the tank instead of Swiftmend or Nourish on the injured target. Lowered to 13.0f so it fires only when no reactive healing is queued. - **Healing spell priority order reworked:** All three reactive categories (critical, low, medium) now follow the same sequence: Swiftmend → Wild Growth → Nourish → Regrowth → Healing Touch. - **Tranquility toggleable strategy:** Tranquility is now housed in a dedicated `tranquility` strategy, enabled by default for Resto druids. It can be disabled with `co -tranquility`. In raids, Tranquility only heals the druid's own group — not the full raid — making it situationally poor during raid-wide damage or heavy movement phases. Disabling this strategy lets players suppress the cast on those encounters without affecting the rest of the healing rotation. --- ## CC & Strategy - **Boost strategy:** The boost strategy is assigned to all druid specs by default. It is spec-gated internally — Balance druids use it for Force of Nature (treants on cooldown), and Feral druids (both cat and bear) use it for Berserk. - **CC strategy enabled by default:** The CC strategy was previously not assigned to druids by default. It is now enabled for Balance and Feral Cat, with behavior gated by spec: - Balance receives the full RTI CC trigger set (Cyclone, Hibernate, Entangling Roots). - Feral cats only receive RTI CC triggers when Predator's Swiftness is active (see above). - **AoE strategy reorganized:** All AoE spells — Hurricane, Starfall, Typhoon, Swipe (Cat), and DoTs on attackers (Moonfire, Insect Swarm) — are now handled by the shared AoE strategy, consistent with how the rest of the playerbot project structures AoE logic. This does not affect Bear druids. - **Aquatic Form while submerged:** The non-combat strategy now shifts into Aquatic Form when the bot is fully submerged out of combat (`LIQUID_MAP_UNDER_WATER`). If the bot is in another shapeshift (Bear, Cat, Moonkin, Tree), it first shifts to caster form as a prerequisite before entering Aquatic Form. The trigger intentionally does not fire at the water surface (`LIQUID_MAP_IN_WATER`), or use the "swimming" trigger, because it caused the druid to loop caster form and aquatic form endlessly while surfaced. --- ## Code Consolidation & Refactoring - The Bear, Cat, Heal, and Caster strategy files have been renamed to Bear, Cat, Balance, and Resto respectively, to match the naming conventions used elsewhere in the project. - The Melee and Offheal strategy files have been deleted. The offheal healing logic is preserved in full — it now lives as an optional strategy (`CatOffhealStrategy`) inside `CatDruidStrategy`, sharing the same action node factories as the base cat strategy rather than duplicating them. - All action relevance values across the druid strategies have been converted from named constants (e.g. `ACTION_NORMAL`, `ACTION_HIGH + 4`) to explicit numerical floats (e.g. `10.0f`, `24.0f`). This makes priority ordering immediately visible in the source without needing to cross-reference the constant definitions. --- ## New Code & Project References ### Eclipse Cooldown Tracking (`DruidActions.cpp`) The previous implementation tracked the Eclipse cooldown using `EclipseSolarCooldownTrigger` and `EclipseLunarCooldownTrigger`, both of which extended `SpellCooldownTrigger` and called `bot->HasSpellCooldown(48517/48518)`. `SpellCooldownTrigger` is the standard project pattern for this — it works correctly for spells whose cooldowns are registered in the database. However, Eclipse (Solar) and Eclipse (Lunar) both have **Cooldown: n/a** in the DB. `HasSpellCooldown` always returned false, so boomkins never respected the cooldown and would revert to the wrong filler immediately after an Eclipse buff fell off. Since the cooldown can't be read from the DB, the fix tracks it manually. When `CastWrathAction::isUseful()` or `CastStarfireAction::isUseful()` detects that the corresponding Eclipse aura has become active, it records the current timestamp and suppresses the opposing filler for 30 seconds — the actual in-game cooldown duration. The timestamps are stored using `ManualSetValue`, the same pattern as `LastSpellCastTimeValue` (`src/Ai/Base/Value/LastSpellCastTimeValue.h`), which is already used throughout the project to record when spells were last cast. Two new value classes — `EclipseSolarProcTimeValue` and `EclipseLunarProcTimeValue` — are registered in a new `DruidValueContextInternal` factory inside `DruidAiObjectContext.cpp`, following the same factory pattern as `DruidTriggerFactoryInternal` and `DruidAiObjectContextInternal` in the same file. Because these values live in the per-bot `AiObjectContext`, they are automatically destroyed when the bot logs out — no manual cleanup needed, and no shared state between bots. --- ### Healer Low Mana Framework (`PartyMemberToHeal.h/.cpp`, `HealthTriggers.h/.cpp`, `ValueContext.h`, `TriggerContext.h`) `HealerLowMana` and `HealerLowManaTrigger` are added to the shared base framework rather than the druid-specific code. Currently used by the Cat Innervate trigger; designed so Mana Tide Totem, Hymn of Hope, and similar spells from other classes can hook into the same trigger without duplicating the group-scanning logic. The pair follows the same pattern as the existing `PartyMemberToHeal` / `PartyMemberLowHealthTrigger` — the project's standard design for "scan the group for the most in-need member, then trigger when that member crosses a threshold." **Value** (`HealerLowMana : PartyMemberValue`): `Calculate()` walks the group reference list, skips non-healers via the existing `IsHeal()` check, and uses `MinValueCalculator` to return the lowest-mana healer as a `Unit*`. Registered in `ValueContext.h` under the key `"healer low mana"`. **Trigger** (`HealerLowManaTrigger : Trigger`): `GetTargetName()` returns `"healer low mana"`, which the base `Trigger::GetTarget()` resolves against the value context — exactly how `PartyMemberLowHealthTrigger::GetTargetName()` returns `"party member to heal"`. `IsActive()` calls `GetTarget()` and checks `GetPowerPct(POWER_MANA) < sPlayerbotAIConfig.lowMana`. The trigger doesn't extend `HealthInRangeTrigger` because that class is specifically for health (it reads the `"health"` value). Mana requires a direct `GetPowerPct(POWER_MANA)` call, so a plain `Trigger` with a custom `IsActive()` is used instead. Both are registered in the global `ValueContext.h` and `TriggerContext.h` rather than a class-specific factory, consistent with how all other `PartyMemberValue` subclasses are registered in the project. --- ### Blanketing Strategy (`RestoDruidStrategy.h/.cpp`, `DruidActions.h/.cpp`, `DruidAiObjectContext.cpp`) **Strategy structure:** `DruidBlanketStrategy` is a standalone `Strategy` overlay, not embedded inside `RestoDruidStrategy`. This is the same pattern as `DruidTranquilityStrategy`, `DruidBoostStrategy`, and `DruidCcStrategy` — additive overlays that layer behavior on top of the base strategy and can be toggled independently via `co +/-blanketing`. **Triggers:** Both `"wild growth blanket"` and `"rejuvenation blanket"` are instantiated as `BuffOnPartyTrigger(ai, spellName)` — the project's existing class from `GenericTriggers.h` for party-wide buff maintenance, used throughout the codebase for things like Blessings and Mark of the Wild. `BuffOnPartyTrigger` extends `BuffTrigger` and fires when any party member is missing the named aura. No custom trigger class was needed. **Actions:** Both actions inherit from a shared `CastBlanketHotAction` base that itself extends `CastSpellAction`. Inheriting from `CastSpellAction` means `isPossible()` is handled for free — spell known, off cooldown, target reachable, resources available. The constructor sets `range = botAI->GetRange("heal")` to use the standard healing range. **`GetBlanketTarget(auraName)`:** The custom part of the implementation. Walks the group in three prioritized passes — tanks first, then melee non-tanks, then ranged — returning the first eligible member found. Eligible is defined as: alive, not a GM, within `spellDistance`, and `!botAI->HasAura(auraName, member, false, true)` (not already carrying the HoT). Returns nullptr if every member is already covered. **`isUseful()`:** On both actions simply returns `GetTarget() != nullptr` — fires as long as `GetBlanketTarget` finds someone without the HoT, and suppresses itself the moment all targets are covered. --- ### CC Implementation — Cyclone, Hibernate, Entangling Roots (`DruidTriggers.h/.cpp`, `DruidActions.h/.cpp`, `GenericDruidStrategy.cpp`) The druid CC spells use the project's strict RTI-only pattern rather than the fallback "best candidate" pattern used by other classes (e.g., Mage Polymorph). **`"rti cc target"` value:** A direct raid icon lookup. Reads the `"rti cc"` string value (default `"moon"`, configurable per-bot via `rti cc `), converts it to a raid icon index, and returns the live `Unit*` for that GUID. If no CC icon is set, it returns `nullptr`. There is no fallback to a best-candidate scan. **Triggers** (`CycloneTrigger`, `HibernateTrigger`, `EntanglingRootsTrigger`): All three extend `HasCcTargetTrigger` and override `IsActive()`. The first check is always `"rti cc target"` — if it returns `nullptr`, the trigger is immediately silent. If an icon is set, it checks `"cc target"` (with the spell name as a qualifier) to verify the RTI target matches and delegates to `HasCcTargetTrigger::IsActive()`, which handles the "don't re-cast while already CC'd" check. **Actions** (`CastCycloneCcAction`, `CastHibernateCcAction`, `CastEntanglingRootsCcAction`): All three extend `CastCrowdControlSpellAction` rather than plain `CastSpellAction`. The action names are `"cyclone on cc"`, `"hibernate on cc"`, `"entangling roots on cc"` — not the raw spell names. This matters because `CastSpellAction` stores its constructor argument as both the action name and the spell name, and `isPossible()` calls `CanCastSpell(spell, target)` using that string. Passing `"cyclone on cc"` to `CastSpellAction` would resolve to spell ID 0 and silently return false forever. `CastCrowdControlSpellAction` keeps the spell name separate from the action name, avoiding this. `GetTargetValue()` on all three returns `context->GetValue("rti cc target")` directly. **Form prerequisite:** The action nodes for `"cyclone on cc"` and `"hibernate on cc"` have `NextAction("caster form")` as a prerequisite, so the bot automatically shifts out of Bear, Cat, or Moonkin form before casting. Entangling Roots has the same prerequisite. **Priority order:** Cyclone (24.0f) > Hibernate (23.0f) > Entangling Roots (22.0f). Cyclone is preferred because it works on any target type and the target is immune to all damage and healing while cycloned — it cannot be broken by AoE. Hibernate is beast/dragonkin only. Entangling Roots can be broken by damage. **Feral Cat CC:** Wired through `TwoTrigger` pairings with `"predator's swiftness"` (see Cat section above). Because the Predator's Swiftness proc makes the spell instant-cast, no form shift is needed — the cat casts directly from Cat Form after a finisher. --- ### Ferocious Bite Execute (`DruidTriggers.h`, `DruidCatActions.h`, `CatDruidStrategy.cpp`) Two separate triggers fire the same `CastFerociousBiteAction`, which is a plain `CastMeleeSpellAction` with no custom logic — all the intelligence lives in the triggers. **`FerociousBiteTimeTrigger`** ("ferocious bite time", 22.5f) — the normal rotation path. Requires 5 combo points, Savage Roar active with >10 seconds remaining, and Rip active on the target with >10 seconds remaining. The duration checks prevent spending combo points on Ferocious Bite when either buff is about to fall off and needs to be refreshed first. **`FerociousBiteExecuteTrigger`** ("ferocious bite execute", 24.0f) — the execute window, higher priority than the time trigger. Requires only 1 combo point, and fires when the target is below **both** 25% HP and 20,000 absolute HP. The dual condition is the key design detail: the 25% threshold alone would trigger on a raid boss at 25% health — which could still be millions of HP remaining. The 20,000 HP cap ensures the execute behavior only activates when the target is genuinely close to death, at which point dumping even a partial combo point buildup into Ferocious Bite is better than continuing a normal builder-spender cycle. --- ### Faerie Fire (Feral) Trigger (`DruidTriggers.h`) A single `FaerieFireFeralTrigger` class handles both Bear and Cat with spec-branched behavior inside `IsActive()`. It extends `DebuffTrigger` — the project's standard class for debuff maintenance on the current target — but overrides `IsActive()` to produce three distinct behaviors depending on form and talent state: **Bear:** Bypasses `DebuffTrigger::IsActive()` entirely. Returns true whenever the target is alive and in world, regardless of whether the debuff is already present. Every cast generates immediate threat and damage, so there is no reason to wait for it to fall off before recasting. **Cat with Omen of Clarity (talent aura 16864):** Same bypass — spams on cooldown to fish for Clearcasting procs. Faerie Fire (Feral) has no energy cost, making it a free input that can proc Omen of Clarity on any hit. **Cat without Omen of Clarity:** Falls through to `DebuffTrigger::IsActive()` — the standard base class behavior, which checks: target alive and in world, debuff not already present (`!botAI->HasAura("faerie fire (feral)", target)`), and estimated remaining lifetime of the target is at least `needLifeTime` seconds (default 8.0f — no point applying a 30-second debuff to something about to die). Applied as a normal debuff; does not reapply while active. Both spam paths additionally guard against Prowl — `IsActive()` returns false while the bot has the Prowl aura to prevent casting from breaking stealth. **Strategy wiring:** - Bear: standard rotation slot at 17.0f, plus wired into the `"lose aggro"` trigger at 25.5f as a soft-taunt fallback when Growl is on cooldown. - Cat: low-priority filler at 5.0f. --- ### Starfall Pull Safety (`DruidActions.cpp`) Starfall's 36-yard AoE radius is the largest in the game. A single cast near an unengaged patrol or mob pack would silently pull everything in that area. The previous implementation fired on cooldown with no awareness of the surrounding area. `CastStarfallAction::isUseful()` now applies two guards before allowing the cast: **CC safety check** (standard project pattern): reads `"current cc target"` and `"aoe position"`; suppresses the cast if the CC'd target is within `aoeRadius` of the bot's AoE position. **Unengaged hostile NPC scan (custom)**: reads `"nearest hostile npcs"` (`NearestHostileNpcsValue`), which uses the project's standard `Acore::AnyUnitInObjectRangeCheck` + `Cell::VisitObjects` grid searcher at `sightDistance` (~50 yards). The value pre-filters via `AcceptUnit()`: non-players only, and `unit->IsHostileTo(bot)` must be true — this excludes neutral-faction trigger creatures, dummies, and invisible spawns that would otherwise appear in a raw range scan. The loop then applies four additional filters: - Skip null / dead / out-of-world units (standard guard). - Skip the current target — it is the reason we're in combat; its in-combat flag is already covered. - Skip `!bot->IsValidAttackTarget(unit)` — safety net for hostile-faction trigger creatures carrying `UNIT_FLAG_NON_ATTACKABLE` that `IsHostileTo` alone doesn't filter. - Skip units beyond 40 yards — Starfall's listed radius is 36; 40 adds a small buffer for patrols about to enter range. If any remaining unit is `!unit->IsInCombat()`, the cast is suppressed — that mob is unengaged and would be pulled. **Why `"nearest hostile npcs"` and not `"attackers"`:** `attackers` only contains units currently targeting the bot. We need to scan all hostile units in the area, not just those already aggro'd. --- ### Hurricane Channel Cancel (`DruidTriggers.h/.cpp`, `GenericDruidStrategy.cpp`) The previous cancel condition checked whether fewer than 3 enemies were within 30 yards of the bot. This is a poor proxy — enemies could scatter laterally but still sit within that radius, keeping the channel alive while none of them were taking damage. The replacement is `HurricaneChannelCheckTrigger`, which locates the actual Hurricane `DynamicObject` on the field and measures from it directly. **`IsActive()` logic:** 1. Checks `bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL)` — if the bot isn't channeling at all, returns false immediately. If it is channeling but the spell isn't a Hurricane rank, also returns false. This check is necessary because `CURRENT_CHANNELED_SPELL` is a slot, not a specific spell — the same cancel action is reused for other channeled spells in the codebase, so the trigger must verify it's specifically Hurricane before acting. 2. Iterates through `HURRICANE_SPELL_IDS` (all five ranks: 16914, 17401, 17402, 27012, 48467) calling `bot->GetDynObject(spellId)` until a non-null result is found. Hurricane places a `DynamicObject` on the field that the server uses as the actual AoE cylinder — each damage tick queries which units are inside it. The DynamicObject is keyed by spell ID, so the trigger must try each rank to find whichever one the bot currently has learned and placed. 3. Reads `dynObj->GetRadius()` — the actual radius stored on the DynamicObject itself rather than a hardcoded constant. This matches exactly what the server uses to calculate damage, so the trigger's cancel condition is spatially identical to the server's hit detection. 4. Walks the `"attackers"` GuidVector and counts how many live attackers are within `dynObj->GetRadius()` of the DynamicObject's position using `unit->GetDistance(dynObj->GetPosition()) <= radius`. 5. Returns `count < minEnemies` (default 3). The trigger fires — cancelling the channel — when fewer than 3 attackers are physically inside the Hurricane AoE. **Why `"attackers"` and not a full area scan:** Hurricane only deals damage to units that are attacking the bot (or in its threat list). Scanning all nearby hostile units would cause premature cancellation if non-aggro'd enemies happened to be standing outside the AoE. Attackers is the right scope. **Strategy wiring:** The trigger is paired with `NextAction("cancel channel", 22.0f)` in the AoE strategy for both Balance and Resto druids. The cancel priority (22.0f) sits below the Hurricane cast priority (23.0f), so if the medium AoE trigger re-activates on the same tick the cancel fires — meaning enemies came back into range — the new cast wins over the cancel. --- ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. Most new triggers are simple aura or cooldown checks. The heavier ones are the group scans (for the blanketing HoTs and the healer mana check), but these are identical in cost to group scans already running throughout the project (all of the party member health checks). The Starfall safety check is the only genuinely new scan — it looks for nearby hostile NPCs before allowing a cast, using the same grid search the project already uses elsewhere. That being said, it's loaded on the end of the trigger/action pairing - so in the StarfallNoCDTrigger, the bot has to already have learned starfall, already be in combat, and have Starfall off of cooldown and ready to use. The Hurricane cancel check only runs while the bot is actively channeling, so it's tightly gated. - Describe the **processing cost** when this logic executes across many bots. Negligible for almost everything in this PR. The vast majority of new logic is aura/buff/debuff lookups and cooldown checks that cost nothing at scale. The group scans for blanketing and healer mana follow the same pattern as existing party scans that already run on every healer bot every tick. No new unbounded operations, no shared state between bots. ## How to Test the Changes All druids perform a bit better now - I'd say test the branch out with the druids y'all currently use. JUST REMEMBER TO DO reset botAI or talents spec "x" again, since there have been some strategies changed!! The big one being the blanketing strategy for resto druids. They heal so much better now. Also being able to control when they pre-hot is really great. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) Tested before an after with the same performance logs. I tested it with a 25 man group of only druids versus my normal 25 man group on several raid bosses - no difference in pmon. - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Druid bots currently have several bugs/issues with them. This doesn't exactly change the skills they were already using - just refines the scenarios in which they should be used. For example, a boomkin won't use starfall when there is a pack within range but not aggro'd. You can turn off feral charge for cat druids now, so they don't fly into a bosses aoe (locust swarm on anub, overload on iron council). Bear druids don't battle rez anymore. They just feel less clunky and heal/hold aggro better. - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) There are only 2 changes to files outside of the druid strategy, which is the healer low mana framework and the modification to autoattack not being used while in prowl. ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) AI was used heavily in the process to make this PR. First in the research necessary into how systems work, to the initial code implementation, to the testing results (explaining the outcome/why it sucks), to the fix, and then to the review of the code at the end. I will say that after I started researching how to use AI, use .md files for context, clearing sessions, I got a lot better results. I'll be the first to admit that it is 10 times easier to introduce a bug with AI than it is to solve one or implement something new. That is why every time it proposed a change, I asked it if the code was consistent with the project (Already present somewhere else) and if it wasn't, it was heavily scrutinized. It was written with Claude Code with Sonnet 4.6 (high), and peer reviewed by Github copilot. AI also made the description part of the PR, in which I modified myself. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). **Wiki commands** @Dreathean While this PR does add strategies, they are all enabled by default: co +feral charge (feral druids, both cat and bear) - enabled by default, allows/prevents the use of feral charge co +tranquility (resto druids) - enabled by default, allows/prevents the use of tranquility co +blanketing (resto druids) - enabled by default, allows the druid to pre-hot with wild growth and rejuvenation But it would be worth a mention on the wiki - there are scenarios where having these strategies disabled would be beneficial. ## Notes for Reviewers @kadeshar @Celandriel @brighton-chi Thank you for taking the time to look this over. There is a lot of copied code, a bit of new code (which is explained in the code explanation part of it, but please still ask questions), and a lot of refactoring. Please remember to reset the bot strategies before/after you test this branch, due to the several changes (blanketing, feral charge strategies). Reset with reset botAI or "talents spec balance pve" for any testers out there that didn't know. If/When this PR goes to the master branch, it will need to be noted to the people this same thing about resetting strategies. --- conf/playerbots.conf.dist | 14 +- src/Ai/Base/Actions/GenericActions.cpp | 5 +- src/Ai/Base/Trigger/GenericTriggers.cpp | 5 + src/Ai/Base/Trigger/GenericTriggers.h | 11 + src/Ai/Base/Trigger/HealthTriggers.cpp | 9 + src/Ai/Base/Trigger/HealthTriggers.h | 9 + src/Ai/Base/Trigger/RtiTriggers.cpp | 16 + src/Ai/Base/Trigger/RtiTriggers.h | 9 + src/Ai/Base/TriggerContext.h | 4 + src/Ai/Base/Value/PartyMemberToHeal.cpp | 26 + src/Ai/Base/Value/PartyMemberToHeal.h | 9 + src/Ai/Base/ValueContext.h | 2 + src/Ai/Class/Druid/Action/DruidActions.cpp | 186 +++++++- src/Ai/Class/Druid/Action/DruidActions.h | 96 +++- src/Ai/Class/Druid/Action/DruidCatActions.h | 81 +++- src/Ai/Class/Druid/DruidAiObjectContext.cpp | 125 +++-- ...dStrategy.cpp => BalanceDruidStrategy.cpp} | 176 +++---- .../Druid/Strategy/BalanceDruidStrategy.h | 25 + ...ruidStrategy.cpp => BearDruidStrategy.cpp} | 116 ++--- ...ankDruidStrategy.h => BearDruidStrategy.h} | 8 +- .../Druid/Strategy/CasterDruidStrategy.h | 45 -- .../Druid/Strategy/CatDpsDruidStrategy.cpp | 314 ------------ .../Class/Druid/Strategy/CatDruidStrategy.cpp | 446 ++++++++++++++++++ ...tDpsDruidStrategy.h => CatDruidStrategy.h} | 18 +- .../Druid/Strategy/FeralDruidStrategy.cpp | 41 +- .../Class/Druid/Strategy/FeralDruidStrategy.h | 21 +- .../GenericDruidNonCombatStrategy.cpp | 29 +- .../Druid/Strategy/GenericDruidStrategy.cpp | 143 +++++- .../Druid/Strategy/GenericDruidStrategy.h | 9 + .../Druid/Strategy/HealDruidStrategy.cpp | 108 ----- .../Class/Druid/Strategy/HealDruidStrategy.h | 24 - .../Druid/Strategy/MeleeDruidStrategy.cpp | 32 -- .../Class/Druid/Strategy/MeleeDruidStrategy.h | 21 - .../Strategy/OffhealDruidCatStrategy.cpp | 307 ------------ .../Druid/Strategy/OffhealDruidCatStrategy.h | 27 -- .../Druid/Strategy/RestoDruidStrategy.cpp | 120 +++++ .../Class/Druid/Strategy/RestoDruidStrategy.h | 42 ++ src/Ai/Class/Druid/Trigger/DruidTriggers.cpp | 76 ++- src/Ai/Class/Druid/Trigger/DruidTriggers.h | 193 +++++++- .../Class/Warlock/Action/WarlockActions.cpp | 3 + src/Ai/Class/Warlock/Action/WarlockActions.h | 2 + .../Class/Warlock/Trigger/WarlockTriggers.cpp | 16 - .../Class/Warlock/Trigger/WarlockTriggers.h | 11 +- src/Bot/Factory/AiFactory.cpp | 15 +- 44 files changed, 1734 insertions(+), 1261 deletions(-) rename src/Ai/Class/Druid/Strategy/{CasterDruidStrategy.cpp => BalanceDruidStrategy.cpp} (52%) create mode 100644 src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.h rename src/Ai/Class/Druid/Strategy/{BearTankDruidStrategy.cpp => BearDruidStrategy.cpp} (59%) rename src/Ai/Class/Druid/Strategy/{BearTankDruidStrategy.h => BearDruidStrategy.h} (75%) delete mode 100644 src/Ai/Class/Druid/Strategy/CasterDruidStrategy.h delete mode 100644 src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.cpp create mode 100644 src/Ai/Class/Druid/Strategy/CatDruidStrategy.cpp rename src/Ai/Class/Druid/Strategy/{CatDpsDruidStrategy.h => CatDruidStrategy.h} (59%) delete mode 100644 src/Ai/Class/Druid/Strategy/HealDruidStrategy.cpp delete mode 100644 src/Ai/Class/Druid/Strategy/HealDruidStrategy.h delete mode 100644 src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.cpp delete mode 100644 src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.h delete mode 100644 src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.cpp delete mode 100644 src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.h create mode 100644 src/Ai/Class/Druid/Strategy/RestoDruidStrategy.cpp create mode 100644 src/Ai/Class/Druid/Strategy/RestoDruidStrategy.h diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index c1d7407745b..6ea92b2d686 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1786,18 +1786,18 @@ AiPlayerbot.PremadeSpecLink.9.5.80 = -2032003311302-05230015220331351005031051 AiPlayerbot.PremadeSpecName.11.0 = balance pve AiPlayerbot.PremadeSpecGlyph.11.0 = 40916,43331,40921,43335,44922,40919 -AiPlayerbot.PremadeSpecLink.11.0.60 = 5022203105331003213005301231 -AiPlayerbot.PremadeSpecLink.11.0.80 = 5032203105331303213305301231--205003012 +AiPlayerbot.PremadeSpecLink.11.0.60 = 5032003125031003213304301231 +AiPlayerbot.PremadeSpecLink.11.0.80 = 5032003125331303213305301231--205003012 AiPlayerbot.PremadeSpecName.11.1 = bear pve AiPlayerbot.PremadeSpecGlyph.11.1 = 40897,43331,46372,43335,43332,40899 -AiPlayerbot.PremadeSpecLink.11.1.60 = -500232130322110353100301310501 +AiPlayerbot.PremadeSpecLink.11.1.60 = -503232132322010303120300013501 AiPlayerbot.PremadeSpecLink.11.1.80 = -503232132322010353120303013511-20350001 AiPlayerbot.PremadeSpecName.11.2 = resto pve -AiPlayerbot.PremadeSpecGlyph.11.2 = 40913,43331,40906,43335,43674,45602 -AiPlayerbot.PremadeSpecLink.11.2.60 = --230033312031500531050113051 -AiPlayerbot.PremadeSpecLink.11.2.80 = 05320131003--230023312131500531050313051 +AiPlayerbot.PremadeSpecGlyph.11.2 = 40906,43331,45602,43335,43674,45603 +AiPlayerbot.PremadeSpecLink.11.2.60 = --230033312031502331050313031 +AiPlayerbot.PremadeSpecLink.11.2.80 = 05320031--230033312031502431053313051 AiPlayerbot.PremadeSpecName.11.3 = cat pve -AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43335,43674,45604 +AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43674,43335,45604 AiPlayerbot.PremadeSpecLink.11.3.60 = -552202032322010053100030310501 AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053120030310511-203503012 AiPlayerbot.PremadeSpecName.11.4 = balance pvp diff --git a/src/Ai/Base/Actions/GenericActions.cpp b/src/Ai/Base/Actions/GenericActions.cpp index a4873d86a22..354cb456eff 100644 --- a/src/Ai/Base/Actions/GenericActions.cpp +++ b/src/Ai/Base/Actions/GenericActions.cpp @@ -50,7 +50,10 @@ bool MeleeAction::isUseful() if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true)) return false; - return true; + // Do not start autoattack while prowled — let opener spells break stealth intentionally. + // Future rogue stealth implementation should use this instead: + // return !(botAI->HasAura("stealth", bot) || botAI->HasAura("prowl", bot)); + return !botAI->HasAura("prowl", bot); } bool TogglePetSpellAutoCastAction::Execute(Event /*event*/) diff --git a/src/Ai/Base/Trigger/GenericTriggers.cpp b/src/Ai/Base/Trigger/GenericTriggers.cpp index 27dd5999cbf..2035e1c29ca 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.cpp +++ b/src/Ai/Base/Trigger/GenericTriggers.cpp @@ -34,6 +34,11 @@ bool MediumManaTrigger::IsActive() AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.mediumMana; } +bool LowEnergyTrigger::IsActive() +{ + return AI_VALUE2(uint8, "energy", "self target") < threshold; +} + bool NoPetTrigger::IsActive() { return (bot->GetMinionGUID().IsEmpty()) && (!AI_VALUE(Unit*, "pet target")) && (!bot->GetGuardianPet()) && diff --git a/src/Ai/Base/Trigger/GenericTriggers.h b/src/Ai/Base/Trigger/GenericTriggers.h index 7a44112f36f..a3931c24619 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.h +++ b/src/Ai/Base/Trigger/GenericTriggers.h @@ -550,6 +550,17 @@ class MediumManaTrigger : public Trigger bool IsActive() override; }; +class LowEnergyTrigger : public Trigger +{ +public: + LowEnergyTrigger(PlayerbotAI* botAI, uint8 threshold = 30) : Trigger(botAI, "low energy"), threshold(threshold) {} + + bool IsActive() override; + +private: + uint8 threshold; +}; + BEGIN_TRIGGER(PanicTrigger, Trigger) // cppcheck-suppress unknownMacro std::string const getName() override { return "panic"; } END_TRIGGER() diff --git a/src/Ai/Base/Trigger/HealthTriggers.cpp b/src/Ai/Base/Trigger/HealthTriggers.cpp index a2b36962d53..746198acffa 100644 --- a/src/Ai/Base/Trigger/HealthTriggers.cpp +++ b/src/Ai/Base/Trigger/HealthTriggers.cpp @@ -22,6 +22,15 @@ bool DeadTrigger::IsActive() { return AI_VALUE2(bool, "dead", GetTargetName()); bool AoeHealTrigger::IsActive() { return AI_VALUE2(uint8, "aoe heal", type) >= count; } +bool HealerLowManaTrigger::IsActive() +{ + Unit* target = GetTarget(); + if (!target) + return false; + + return target->GetPowerPct(POWER_MANA) < sPlayerbotAIConfig.lowMana; +} + bool AoeInGroupTrigger::IsActive() { int32 member = botAI->GetNearGroupMemberCount(); diff --git a/src/Ai/Base/Trigger/HealthTriggers.h b/src/Ai/Base/Trigger/HealthTriggers.h index 0fbd403d72c..14e68c7e3d7 100644 --- a/src/Ai/Base/Trigger/HealthTriggers.h +++ b/src/Ai/Base/Trigger/HealthTriggers.h @@ -143,6 +143,15 @@ class TargetCriticalHealthTrigger : public TargetLowHealthTrigger TargetCriticalHealthTrigger(PlayerbotAI* botAI) : TargetLowHealthTrigger(botAI, 20) {} }; +class HealerLowManaTrigger : public Trigger +{ +public: + HealerLowManaTrigger(PlayerbotAI* botAI) : Trigger(botAI, "healer low mana") {} + + std::string const GetTargetName() override { return "healer low mana"; } + bool IsActive() override; +}; + class PartyMemberDeadTrigger : public Trigger { public: diff --git a/src/Ai/Base/Trigger/RtiTriggers.cpp b/src/Ai/Base/Trigger/RtiTriggers.cpp index c7dc737cf33..ba934c5d7a0 100644 --- a/src/Ai/Base/Trigger/RtiTriggers.cpp +++ b/src/Ai/Base/Trigger/RtiTriggers.cpp @@ -18,3 +18,19 @@ bool NoRtiTrigger::IsActive() Unit* target = AI_VALUE(Unit*, "rti target"); return target == nullptr; } + +// Fires when the RTI CC target should be crowd controlled by this spell. +// Standard path: the target is already in the attackers list and "cc target" matches the RTI +// mark — delegates to HasCcTargetTrigger to confirm no one else is already CCing it. +bool RtiCcTrigger::IsActive() +{ + Unit* rtiCcTarget = AI_VALUE(Unit*, "rti cc target"); + if (!rtiCcTarget) + return false; + + Unit* ccTarget = AI_VALUE2(Unit*, "cc target", getName()); + if (ccTarget && ccTarget == rtiCcTarget) + return HasCcTargetTrigger::IsActive(); + + return botAI->CanCastSpell(getName(), rtiCcTarget); +} diff --git a/src/Ai/Base/Trigger/RtiTriggers.h b/src/Ai/Base/Trigger/RtiTriggers.h index a8ecab4d93a..9ff128ec24f 100644 --- a/src/Ai/Base/Trigger/RtiTriggers.h +++ b/src/Ai/Base/Trigger/RtiTriggers.h @@ -6,6 +6,7 @@ #ifndef _PLAYERBOT_RTITRIGGERS_H #define _PLAYERBOT_RTITRIGGERS_H +#include "GenericTriggers.h" #include "Trigger.h" class PlayerbotAI; @@ -18,4 +19,12 @@ class NoRtiTrigger : public Trigger bool IsActive() override; }; +class RtiCcTrigger : public HasCcTargetTrigger +{ +public: + RtiCcTrigger(PlayerbotAI* botAI, std::string const name) : HasCcTargetTrigger(botAI, name) {} + + bool IsActive() override; +}; + #endif diff --git a/src/Ai/Base/TriggerContext.h b/src/Ai/Base/TriggerContext.h index 54edbb01794..d440e5b5ff3 100644 --- a/src/Ai/Base/TriggerContext.h +++ b/src/Ai/Base/TriggerContext.h @@ -51,6 +51,7 @@ class TriggerContext : public NamedObjectContext creators["low mana"] = &TriggerContext::LowMana; creators["medium mana"] = &TriggerContext::MediumMana; + creators["low energy"] = &TriggerContext::LowEnergy; creators["high mana"] = &TriggerContext::HighMana; creators["almost full mana"] = &TriggerContext::AlmostFullMana; creators["enough mana"] = &TriggerContext::EnoughMana; @@ -59,6 +60,7 @@ class TriggerContext : public NamedObjectContext creators["party member low health"] = &TriggerContext::PartyMemberLowHealth; creators["party member medium health"] = &TriggerContext::PartyMemberMediumHealth; creators["party member almost full health"] = &TriggerContext::PartyMemberAlmostFullHealth; + creators["healer low mana"] = &TriggerContext::HealerLowMana; creators["generic boost"] = &TriggerContext::generic_boost; creators["loss of control"] = &TriggerContext::loss_of_control; @@ -312,6 +314,7 @@ class TriggerContext : public NamedObjectContext static Trigger* TargetCriticalHealth(PlayerbotAI* botAI) { return new TargetCriticalHealthTrigger(botAI); } static Trigger* LowMana(PlayerbotAI* botAI) { return new LowManaTrigger(botAI); } static Trigger* MediumMana(PlayerbotAI* botAI) { return new MediumManaTrigger(botAI); } + static Trigger* LowEnergy(PlayerbotAI* botAI) { return new LowEnergyTrigger(botAI); } static Trigger* HighMana(PlayerbotAI* botAI) { return new HighManaTrigger(botAI); } static Trigger* AlmostFullMana(PlayerbotAI* botAI) { return new AlmostFullManaTrigger(botAI); } static Trigger* EnoughMana(PlayerbotAI* botAI) { return new EnoughManaTrigger(botAI); } @@ -383,6 +386,7 @@ class TriggerContext : public NamedObjectContext { return new PartyMemberCriticalHealthTrigger(botAI); } + static Trigger* HealerLowMana(PlayerbotAI* botAI) { return new HealerLowManaTrigger(botAI); } static Trigger* protect_party_member(PlayerbotAI* botAI) { return new ProtectPartyMemberTrigger(botAI); } static Trigger* no_pet(PlayerbotAI* botAI) { return new NoPetTrigger(botAI); } static Trigger* has_pet(PlayerbotAI* botAI) { return new HasPetTrigger(botAI); } diff --git a/src/Ai/Base/Value/PartyMemberToHeal.cpp b/src/Ai/Base/Value/PartyMemberToHeal.cpp index 9845bc11912..80621775a7a 100644 --- a/src/Ai/Base/Value/PartyMemberToHeal.cpp +++ b/src/Ai/Base/Value/PartyMemberToHeal.cpp @@ -135,6 +135,32 @@ bool PartyMemberToHeal::Check(Unit* player) bot->GetDistance2d(player) < sPlayerbotAIConfig.healDistance * 2 && bot->IsWithinLOSInMap(player); } +Unit* HealerLowMana::Calculate() +{ + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + MinValueCalculator calc(100); + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->GetSource(); + if (!player || player == bot) + continue; + if (player->IsGameMaster() || !player->IsAlive()) + continue; + if (!botAI->IsHeal(player)) + continue; + + float mana = player->GetPowerPct(POWER_MANA); + if (mana < calc.minValue) + calc.probe(mana, player); + } + + return (Unit*)calc.param; +} + Unit* PartyMemberToProtect::Calculate() { return nullptr; diff --git a/src/Ai/Base/Value/PartyMemberToHeal.h b/src/Ai/Base/Value/PartyMemberToHeal.h index f86035c09b1..6a255b724e7 100644 --- a/src/Ai/Base/Value/PartyMemberToHeal.h +++ b/src/Ai/Base/Value/PartyMemberToHeal.h @@ -37,4 +37,13 @@ class PartyMemberToProtect : public PartyMemberValue Unit* Calculate() override; }; +class HealerLowMana : public PartyMemberValue +{ +public: + HealerLowMana(PlayerbotAI* botAI) : PartyMemberValue(botAI, "healer low mana") {} + +protected: + Unit* Calculate() override; +}; + #endif diff --git a/src/Ai/Base/ValueContext.h b/src/Ai/Base/ValueContext.h index bac5fd835c4..4070da80b56 100644 --- a/src/Ai/Base/ValueContext.h +++ b/src/Ai/Base/ValueContext.h @@ -132,6 +132,7 @@ class ValueContext : public NamedObjectContext creators["attacker without aura"] = &ValueContext::attacker_without_aura; creators["melee attacker without aura"] = &ValueContext::melee_attacker_without_aura; creators["party member to heal"] = &ValueContext::party_member_to_heal; + creators["healer low mana"] = &ValueContext::healer_low_mana; creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect; creators["current target"] = &ValueContext::current_target; creators["self target"] = &ValueContext::self_target; @@ -451,6 +452,7 @@ class ValueContext : public NamedObjectContext return new MeleeAttackerWithoutAuraTargetValue(botAI); } static UntypedValue* party_member_to_heal(PlayerbotAI* botAI) { return new PartyMemberToHeal(botAI); } + static UntypedValue* healer_low_mana(PlayerbotAI* botAI) { return new HealerLowMana(botAI); } static UntypedValue* party_member_to_resurrect(PlayerbotAI* botAI) { return new PartyMemberToResurrect(botAI); } static UntypedValue* party_member_to_dispel(PlayerbotAI* botAI) { return new PartyMemberToDispel(botAI); } static UntypedValue* party_member_to_protect(PlayerbotAI* botAI) { return new PartyMemberToProtect(botAI); } diff --git a/src/Ai/Class/Druid/Action/DruidActions.cpp b/src/Ai/Class/Druid/Action/DruidActions.cpp index c01cc4342e3..e32a5ea04f7 100644 --- a/src/Ai/Class/Druid/Action/DruidActions.cpp +++ b/src/Ai/Class/Druid/Action/DruidActions.cpp @@ -11,6 +11,9 @@ #include "AoeValues.h" #include "TargetValue.h" +constexpr uint32 SPELL_ECLIPSE_SOLAR = 48517; +constexpr uint32 SPELL_ECLIPSE_LUNAR = 48518; + namespace { bool PrepareThornsTarget(PlayerbotAI* botAI, Unit* target) @@ -64,16 +67,89 @@ bool CastThornsOnMainTankAction::Execute(Event event) return PrepareThornsTarget(botAI, GetTarget()) && BuffOnMainTankAction::Execute(event); } +bool CastWrathAction::isUseful() +{ + time_t now = time(nullptr); + time_t solarTime = context->GetValue("eclipse solar proc time")->Get(); + time_t lunarTime = context->GetValue("eclipse lunar proc time")->Get(); + + // --- Update Solar Eclipse tracking --- + // Wrath is selected during Solar Eclipse (eclipse trigger at 20.0f), so we reliably see it here. + if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && !solarTime) + context->GetValue("eclipse solar proc time")->Set(now); + // Lunar procced — Solar fishing window is over, new cycle begins + if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && solarTime) + context->GetValue("eclipse solar proc time")->Set(0); + // 30 s cooldown window expired + if (solarTime && (now - solarTime) >= 30) + context->GetValue("eclipse solar proc time")->Set(0); + + // --- Update Lunar Eclipse tracking (belt-and-suspenders in case Starfire isn't evaluated) --- + if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && !lunarTime) + context->GetValue("eclipse lunar proc time")->Set(now); + // Solar procced — Lunar fishing window is over + if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && lunarTime) + context->GetValue("eclipse lunar proc time")->Set(0); + if (lunarTime && (now - lunarTime) >= 30) + context->GetValue("eclipse lunar proc time")->Set(0); + + // Block Wrath while in Lunar Eclipse / post-Lunar fishing window + if (context->GetValue("eclipse lunar proc time")->Get()) + return false; + + return CastSpellAction::isUseful(); +} + +bool CastStarfireAction::isUseful() +{ + time_t now = time(nullptr); + time_t solarTime = context->GetValue("eclipse solar proc time")->Get(); + time_t lunarTime = context->GetValue("eclipse lunar proc time")->Get(); + + // --- Update Lunar Eclipse tracking --- + // Starfire is selected during Lunar Eclipse (eclipse trigger at 20.0f), so we reliably see it here. + if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && !lunarTime) + context->GetValue("eclipse lunar proc time")->Set(now); + // Solar procced — Lunar fishing window is over, new cycle begins + if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && lunarTime) + context->GetValue("eclipse lunar proc time")->Set(0); + // 30 s cooldown window expired + if (lunarTime && (now - lunarTime) >= 30) + context->GetValue("eclipse lunar proc time")->Set(0); + + // --- Update Solar Eclipse tracking (belt-and-suspenders in case Wrath isn't evaluated) --- + if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && !solarTime) + context->GetValue("eclipse solar proc time")->Set(now); + // Lunar procced — Solar fishing window is over + if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && solarTime) + context->GetValue("eclipse solar proc time")->Set(0); + if (solarTime && (now - solarTime) >= 30) + context->GetValue("eclipse solar proc time")->Set(0); + + // Block Starfire while in Solar Eclipse / post-Solar fishing window + if (context->GetValue("eclipse solar proc time")->Get()) + return false; + + return CastSpellAction::isUseful(); +} + Value* CastEntanglingRootsCcAction::GetTargetValue() { - return context->GetValue("cc target", "entangling roots"); + return context->GetValue("rti cc target"); } -bool CastEntanglingRootsCcAction::Execute(Event /*event*/) { return botAI->CastSpell("entangling roots", GetTarget()); } +Value* CastHibernateCcAction::GetTargetValue() { return context->GetValue("rti cc target"); } + +Value* CastCycloneCcAction::GetTargetValue() { return context->GetValue("rti cc target"); } -Value* CastHibernateCcAction::GetTargetValue() { return context->GetValue("cc target", "hibernate"); } +bool CastTyphoonAction::isUseful() +{ + bool facingTarget = AI_VALUE2(bool, "facing", "current target"); + bool targetClose = ServerFacade::instance().IsDistanceLessOrEqualThan( + AI_VALUE2(float, "distance", GetTargetName()), 15.f); + return facingTarget && targetClose; +} -bool CastHibernateCcAction::Execute(Event /*event*/) { return botAI->CastSpell("hibernate", GetTarget()); } bool CastStarfallAction::isUseful() { if (!CastSpellAction::isUseful()) @@ -89,12 +165,26 @@ bool CastStarfallAction::isUseful() return false; } - // Avoid single-target usage on initial pull - uint8 aoeCount = *context->GetValue("aoe count"); - if (aoeCount < 2) + // Suppress if any unengaged hostile unit is within 40 yards — Starfall's 36-yard radius would pull them. + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + GuidVector const& nearbyNpcs = AI_VALUE(GuidVector, "possible targets"); + for (ObjectGuid const& guid : nearbyNpcs) { - Unit* target = context->GetValue("current target")->Get(); - if (!target || (!botAI->HasAura("moonfire", target) && !botAI->HasAura("insect swarm", target))) + Unit* unit = botAI->GetUnit(guid); + // Standard null/world-state guard before touching the unit. + if (!unit || !unit->IsAlive() || !unit->IsInWorld()) + continue; + // Already our target — its in-combat flag covers it. + if (unit == currentTarget) + continue; + // Safety net for any hostile-faction trigger creature that carries NON_ATTACKABLE flags. + if (!bot->IsValidAttackTarget(unit)) + continue; + // Outside Starfall's actual radius; no pull risk. + if (ServerFacade::instance().GetDistance2d(bot, unit) > 40.0f) + continue; + // Unengaged mob within range — casting would pull it. + if (!unit->IsInCombat()) return false; } @@ -119,6 +209,24 @@ bool CastRebirthAction::isUseful() AI_VALUE2(float, "distance", GetTargetName()) <= sPlayerbotAIConfig.spellDistance; } +bool CastInnervateOnHealerAction::isPossible() +{ + Unit* target = GetTarget(); + if (!target || !target->IsInWorld()) + return false; + + if (botAI->HasAura("innervate", target)) + return false; + + uint32 spellId = AI_VALUE2(uint32, "spell id", "innervate"); + return spellId && !bot->HasSpellCooldown(spellId); +} + +std::vector CastInnervateOnHealerAction::getPrerequisites() +{ + return { NextAction("caster form") }; +} + Unit* CastRejuvenationOnNotFullAction::GetTarget() { Group* group = bot->GetGroup(); @@ -149,3 +257,63 @@ bool CastRejuvenationOnNotFullAction::isUseful() { return GetTarget(); } + +// --- Blanket HoT actions --- + +Unit* CastBlanketHotAction::GetBlanketTarget(std::string const& auraName) +{ + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + auto eligible = [&](Player* member) -> bool + { + return member && member->IsAlive() && + !member->IsGameMaster() && + bot->GetDistance2d(member) <= sPlayerbotAIConfig.spellDistance && + !botAI->HasAura(auraName, member, false, true); + }; + + Player* firstMelee = nullptr; + Player* firstRanged = nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!eligible(member)) + continue; + + if (PlayerbotAI::IsTank(member)) + return member; + else if (!firstMelee && PlayerbotAI::IsMelee(member) && !PlayerbotAI::IsTank(member)) + firstMelee = member; + else if (!firstRanged && PlayerbotAI::IsRanged(member)) + firstRanged = member; + + if (firstMelee && firstRanged) + break; + } + + if (firstMelee) return firstMelee; + return firstRanged; +} + +Unit* CastRejuvenationBlanketAction::GetTarget() +{ + return GetBlanketTarget("rejuvenation"); +} + +bool CastRejuvenationBlanketAction::isUseful() +{ + return GetTarget() != nullptr; +} + +Unit* CastWildGrowthBlanketAction::GetTarget() +{ + return GetBlanketTarget("wild growth"); +} + +bool CastWildGrowthBlanketAction::isUseful() +{ + return GetTarget() != nullptr; +} diff --git a/src/Ai/Class/Druid/Action/DruidActions.h b/src/Ai/Class/Druid/Action/DruidActions.h index 7e02a985fd0..e386ff05d00 100644 --- a/src/Ai/Class/Druid/Action/DruidActions.h +++ b/src/Ai/Class/Druid/Action/DruidActions.h @@ -8,6 +8,7 @@ #include "GenericSpellActions.h" #include "SharedDefines.h" +#include "Value.h" class PlayerbotAI; class Unit; @@ -64,7 +65,7 @@ class CastHealingTouchOnPartyAction : public HealPartyMemberAction { public: CastHealingTouchOnPartyAction(PlayerbotAI* botAI) - : HealPartyMemberAction(botAI, "healing touch", 50.0f, HealingManaEfficiency::LOW) + : HealPartyMemberAction(botAI, "healing touch", 50.0f, HealingManaEfficiency::MEDIUM) { } }; @@ -142,16 +143,11 @@ class CastLifebloomOnMainTankAction : public BuffOnMainTankAction bool isUseful() override; }; -class CastOmenOfClarityAction : public CastBuffSpellAction -{ -public: - CastOmenOfClarityAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "omen of clarity") {} -}; - class CastWrathAction : public CastSpellAction { public: CastWrathAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "wrath") {} + bool isUseful() override; }; class CastStarfallAction : public CastSpellAction @@ -169,6 +165,14 @@ class CastHurricaneAction : public CastSpellAction ActionThreatType getThreatType() override { return ActionThreatType::Aoe; } }; +class CastTyphoonAction : public CastSpellAction +{ +public: + CastTyphoonAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "typhoon") {} + ActionThreatType getThreatType() override { return ActionThreatType::Aoe; } + bool isUseful() override; +}; + class CastMoonfireAction : public CastDebuffSpellAction { public: @@ -185,6 +189,7 @@ class CastStarfireAction : public CastSpellAction { public: CastStarfireAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "starfire") {} + bool isUseful() override; }; class CastEntanglingRootsAction : public CastSpellAction @@ -193,12 +198,11 @@ class CastEntanglingRootsAction : public CastSpellAction CastEntanglingRootsAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "entangling roots") {} }; -class CastEntanglingRootsCcAction : public CastSpellAction +class CastEntanglingRootsCcAction : public CastCrowdControlSpellAction { public: - CastEntanglingRootsCcAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "entangling roots on cc") {} + CastEntanglingRootsCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "entangling roots") {} Value* GetTargetValue() override; - bool Execute(Event event) override; }; class CastHibernateAction : public CastSpellAction @@ -207,12 +211,18 @@ class CastHibernateAction : public CastSpellAction CastHibernateAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hibernate") {} }; -class CastHibernateCcAction : public CastSpellAction +class CastHibernateCcAction : public CastCrowdControlSpellAction { public: - CastHibernateCcAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hibernate on cc") {} + CastHibernateCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "hibernate") {} + Value* GetTargetValue() override; +}; + +class CastCycloneCcAction : public CastCrowdControlSpellAction +{ +public: + CastCycloneCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "cyclone") {} Value* GetTargetValue() override; - bool Execute(Event event) override; }; class CastNaturesGraspAction : public CastBuffSpellAction @@ -264,6 +274,16 @@ class CastInnervateAction : public CastSpellAction std::string const GetTargetName() override { return "self target"; } }; +class CastInnervateOnHealerAction : public CastSpellAction +{ +public: + CastInnervateOnHealerAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "innervate") {} + + std::string const GetTargetName() override { return "healer low mana"; } + bool isPossible() override; + std::vector getPrerequisites() override; +}; + class CastTranquilityAction : public CastAoeHealSpellAction { public: @@ -312,13 +332,15 @@ class CastDruidRemoveCurseOnPartyAction : public CurePartyMemberAction class CastInsectSwarmOnAttackerAction : public CastDebuffSpellOnAttackerAction { public: - CastInsectSwarmOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "insect swarm") {} + CastInsectSwarmOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "insect swarm", true, 0.0f) {} + bool isUseful() override { return CastAuraSpellAction::isUseful(); } }; class CastMoonfireOnAttackerAction : public CastDebuffSpellOnAttackerAction { public: - CastMoonfireOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "moonfire") {} + CastMoonfireOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "moonfire", true, 0.0f) {} + bool isUseful() override { return CastAuraSpellAction::isUseful(); } }; class CastEnrageAction : public CastBuffSpellAction @@ -344,4 +366,48 @@ class CastForceOfNatureAction : public CastSpellAction CastForceOfNatureAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "force of nature") {} }; +// Base for blanket HoT actions. Provides GetBlanketTarget() as a member so +// subclasses can use AI_VALUE and the standard context machinery. +class CastBlanketHotAction : public CastSpellAction +{ +public: + CastBlanketHotAction(PlayerbotAI* ai, std::string const& spell) : CastSpellAction(ai, spell) + { + range = botAI->GetRange("heal"); + } + +protected: + Unit* GetBlanketTarget(std::string const& auraName); +}; + +class CastRejuvenationBlanketAction : public CastBlanketHotAction +{ +public: + CastRejuvenationBlanketAction(PlayerbotAI* ai) : CastBlanketHotAction(ai, "rejuvenation") {} + bool isUseful() override; + Unit* GetTarget() override; + std::string const getName() override { return "rejuvenation blanket"; } +}; + +class CastWildGrowthBlanketAction : public CastBlanketHotAction +{ +public: + CastWildGrowthBlanketAction(PlayerbotAI* ai) : CastBlanketHotAction(ai, "wild growth") {} + bool isUseful() override; + Unit* GetTarget() override; + std::string const getName() override { return "wild growth blanket"; } +}; + +class EclipseSolarProcTimeValue : public ManualSetValue +{ +public: + EclipseSolarProcTimeValue(PlayerbotAI* botAI) : ManualSetValue(botAI, 0) {} +}; + +class EclipseLunarProcTimeValue : public ManualSetValue +{ +public: + EclipseLunarProcTimeValue(PlayerbotAI* botAI) : ManualSetValue(botAI, 0) {} +}; + #endif diff --git a/src/Ai/Class/Druid/Action/DruidCatActions.h b/src/Ai/Class/Druid/Action/DruidCatActions.h index f6d8f232039..1fdd9aba7ff 100644 --- a/src/Ai/Class/Druid/Action/DruidCatActions.h +++ b/src/Ai/Class/Druid/Action/DruidCatActions.h @@ -9,12 +9,23 @@ #include "GenericSpellActions.h" #include "ReachTargetActions.h" +constexpr uint32 SPELL_POUNCE_RANK_1 = 9005; +constexpr uint32 SPELL_RAVAGE_RANK_1 = 6785; + class PlayerbotAI; class CastFeralChargeCatAction : public CastReachTargetSpellAction { public: CastFeralChargeCatAction(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, "feral charge - cat", 1.5f) {} + + bool isUseful() override + { + if (botAI->HasAura("prowl", bot)) + return false; + + return CastReachTargetSpellAction::isUseful(); + } }; class CastCowerAction : public CastBuffSpellAction @@ -48,28 +59,47 @@ class CastRakeAction : public CastDebuffSpellAction CastRakeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "rake", true, 6.0f) {} }; -class CastRakeOnMeleeAttackersAction : public CastDebuffSpellOnMeleeAttackerAction -{ -public: - CastRakeOnMeleeAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnMeleeAttackerAction(botAI, "rake", true, 6.0f) {} -}; - class CastClawAction : public CastMeleeSpellAction { public: CastClawAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "claw") {} + + bool isUseful() override + { + // Block Claw once Pounce is learned; Claw remains available as the stealth opener before then. + if (botAI->HasAura("prowl", bot) && bot->HasSpell(SPELL_POUNCE_RANK_1)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastMangleCatAction : public CastMeleeSpellAction { public: CastMangleCatAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "mangle (cat)") {} + + bool isUseful() override + { + if (botAI->HasAura("prowl", bot)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastSwipeCatAction : public CastMeleeSpellAction { public: CastSwipeCatAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "swipe (cat)") {} + + bool isUseful() override + { + if (botAI->HasAura("prowl", bot)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastFerociousBiteAction : public CastMeleeSpellAction @@ -78,6 +108,21 @@ class CastFerociousBiteAction : public CastMeleeSpellAction CastFerociousBiteAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ferocious bite") {} }; +class CastMaimAction : public CastMeleeSpellAction +{ +public: + CastMaimAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "maim") {} + + bool isUseful() override + { + Unit* target = GetTarget(); + if (!target || !target->ToPlayer()) + return false; + + return CastMeleeSpellAction::isUseful(); + } +}; + class CastRipAction : public CastMeleeDebuffSpellAction { public: @@ -88,6 +133,14 @@ class CastShredAction : public CastMeleeSpellAction { public: CastShredAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "shred") {} + + bool isUseful() override + { + if (botAI->HasAura("prowl", bot) && bot->HasSpell(SPELL_RAVAGE_RANK_1)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastProwlAction : public CastBuffSpellAction @@ -106,12 +159,28 @@ class CastRavageAction : public CastMeleeSpellAction { public: CastRavageAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ravage") {} + + bool isUseful() override + { + if (!botAI->HasAura("prowl", bot)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastPounceAction : public CastMeleeSpellAction { public: CastPounceAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "pounce") {} + + bool isUseful() override + { + if (!botAI->HasAura("prowl", bot)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; #endif diff --git a/src/Ai/Class/Druid/DruidAiObjectContext.cpp b/src/Ai/Class/Druid/DruidAiObjectContext.cpp index 4d74d1db357..9a4f243c014 100644 --- a/src/Ai/Class/Druid/DruidAiObjectContext.cpp +++ b/src/Ai/Class/Druid/DruidAiObjectContext.cpp @@ -5,9 +5,9 @@ #include "DruidAiObjectContext.h" -#include "BearTankDruidStrategy.h" -#include "CasterDruidStrategy.h" -#include "CatDpsDruidStrategy.h" +#include "BalanceDruidStrategy.h" +#include "BearDruidStrategy.h" +#include "CatDruidStrategy.h" #include "DruidActions.h" #include "DruidBearActions.h" #include "DruidCatActions.h" @@ -15,9 +15,7 @@ #include "DruidTriggers.h" #include "GenericDruidNonCombatStrategy.h" #include "GenericDruidStrategy.h" -#include "HealDruidStrategy.h" -#include "MeleeDruidStrategy.h" -#include "OffhealDruidCatStrategy.h" +#include "RestoDruidStrategy.h" #include "Playerbots.h" #include "DruidPullStrategy.h" @@ -28,30 +26,31 @@ class DruidStrategyFactoryInternal : public NamedObjectContext { creators["nc"] = &DruidStrategyFactoryInternal::nc; creators["pull"] = &DruidStrategyFactoryInternal::pull; - creators["cat aoe"] = &DruidStrategyFactoryInternal::cat_aoe; - creators["caster aoe"] = &DruidStrategyFactoryInternal::caster_aoe; - creators["caster debuff"] = &DruidStrategyFactoryInternal::caster_debuff; - creators["dps debuff"] = &DruidStrategyFactoryInternal::caster_debuff; + creators["aoe"] = &DruidStrategyFactoryInternal::aoe; creators["cure"] = &DruidStrategyFactoryInternal::cure; - creators["melee"] = &DruidStrategyFactoryInternal::melee; creators["buff"] = &DruidStrategyFactoryInternal::buff; creators["boost"] = &DruidStrategyFactoryInternal::boost; creators["cc"] = &DruidStrategyFactoryInternal::cc; creators["healer dps"] = &DruidStrategyFactoryInternal::healer_dps; + creators["offheal"] = &DruidStrategyFactoryInternal::offheal; + creators["blanketing"] = &DruidStrategyFactoryInternal::blanketing; + creators["tranquility"] = &DruidStrategyFactoryInternal::tranquility; + creators["feral charge"] = &DruidStrategyFactoryInternal::feral_charge; } private: static Strategy* nc(PlayerbotAI* botAI) { return new GenericDruidNonCombatStrategy(botAI); } static Strategy* pull(PlayerbotAI* botAI) { return new DruidPullStrategy(botAI); } - static Strategy* cat_aoe(PlayerbotAI* botAI) { return new CatAoeDruidStrategy(botAI); } - static Strategy* caster_aoe(PlayerbotAI* botAI) { return new CasterDruidAoeStrategy(botAI); } - static Strategy* caster_debuff(PlayerbotAI* botAI) { return new CasterDruidDebuffStrategy(botAI); } + static Strategy* aoe(PlayerbotAI* botAI) { return new DruidAoeStrategy(botAI); } static Strategy* cure(PlayerbotAI* botAI) { return new DruidCureStrategy(botAI); } - static Strategy* melee(PlayerbotAI* botAI) { return new MeleeDruidStrategy(botAI); } static Strategy* buff(PlayerbotAI* botAI) { return new GenericDruidBuffStrategy(botAI); } static Strategy* boost(PlayerbotAI* botAI) { return new DruidBoostStrategy(botAI); } static Strategy* cc(PlayerbotAI* botAI) { return new DruidCcStrategy(botAI); } static Strategy* healer_dps(PlayerbotAI* botAI) { return new DruidHealerDpsStrategy(botAI); } + static Strategy* offheal(PlayerbotAI* botAI) { return new CatOffhealStrategy(botAI); } + static Strategy* blanketing(PlayerbotAI* botAI) { return new DruidBlanketStrategy(botAI); } + static Strategy* tranquility(PlayerbotAI* botAI) { return new DruidTranquilityStrategy(botAI); } + static Strategy* feral_charge(PlayerbotAI* botAI) { return new FeralChargeDruidStrategy(botAI); } }; class DruidDruidStrategyFactoryInternal : public NamedObjectContext @@ -62,18 +61,16 @@ class DruidDruidStrategyFactoryInternal : public NamedObjectContext creators["bear"] = &DruidDruidStrategyFactoryInternal::bear; creators["tank"] = &DruidDruidStrategyFactoryInternal::bear; creators["cat"] = &DruidDruidStrategyFactoryInternal::cat; - creators["caster"] = &DruidDruidStrategyFactoryInternal::caster; + creators["balance"] = &DruidDruidStrategyFactoryInternal::balance; creators["dps"] = &DruidDruidStrategyFactoryInternal::cat; - creators["heal"] = &DruidDruidStrategyFactoryInternal::heal; - creators["offheal"] = &DruidDruidStrategyFactoryInternal::offheal; + creators["resto"] = &DruidDruidStrategyFactoryInternal::heal; } private: - static Strategy* bear(PlayerbotAI* botAI) { return new BearTankDruidStrategy(botAI); } - static Strategy* cat(PlayerbotAI* botAI) { return new CatDpsDruidStrategy(botAI); } - static Strategy* caster(PlayerbotAI* botAI) { return new CasterDruidStrategy(botAI); } - static Strategy* heal(PlayerbotAI* botAI) { return new HealDruidStrategy(botAI); } - static Strategy* offheal(PlayerbotAI* botAI) { return new OffhealDruidCatStrategy(botAI); } + static Strategy* bear(PlayerbotAI* botAI) { return new BearDruidStrategy(botAI); } + static Strategy* cat(PlayerbotAI* botAI) { return new CatDruidStrategy(botAI); } + static Strategy* balance(PlayerbotAI* botAI) { return new BalanceDruidStrategy(botAI); } + static Strategy* heal(PlayerbotAI* botAI) { return new RestoDruidStrategy(botAI); } }; class DruidTriggerFactoryInternal : public NamedObjectContext @@ -81,7 +78,6 @@ class DruidTriggerFactoryInternal : public NamedObjectContext public: DruidTriggerFactoryInternal() { - creators["omen of clarity"] = &DruidTriggerFactoryInternal::omen_of_clarity; creators["clearcasting"] = &DruidTriggerFactoryInternal::clearcasting; creators["thorns"] = &DruidTriggerFactoryInternal::thorns; creators["thorns on party"] = &DruidTriggerFactoryInternal::thorns_on_party; @@ -90,10 +86,12 @@ class DruidTriggerFactoryInternal : public NamedObjectContext creators["faerie fire (feral)"] = &DruidTriggerFactoryInternal::faerie_fire_feral; creators["faerie fire"] = &DruidTriggerFactoryInternal::faerie_fire; creators["insect swarm"] = &DruidTriggerFactoryInternal::insect_swarm; + creators["insect swarm on attacker"] = &DruidTriggerFactoryInternal::insect_swarm_on_attacker; creators["moonfire"] = &DruidTriggerFactoryInternal::moonfire; + creators["moonfire on attacker"] = &DruidTriggerFactoryInternal::moonfire_on_attacker; creators["nature's grasp"] = &DruidTriggerFactoryInternal::natures_grasp; - creators["tiger's fury"] = &DruidTriggerFactoryInternal::tigers_fury; creators["berserk"] = &DruidTriggerFactoryInternal::berserk; + creators["berserk active"] = &DruidTriggerFactoryInternal::berserk_active; creators["savage roar"] = &DruidTriggerFactoryInternal::savage_roar; creators["rake"] = &DruidTriggerFactoryInternal::rake; creators["mark of the wild"] = &DruidTriggerFactoryInternal::mark_of_the_wild; @@ -110,17 +108,34 @@ class DruidTriggerFactoryInternal : public NamedObjectContext creators["eclipse (lunar)"] = &DruidTriggerFactoryInternal::eclipse_lunar; creators["bash on enemy healer"] = &DruidTriggerFactoryInternal::bash_on_enemy_healer; creators["nature's swiftness"] = &DruidTriggerFactoryInternal::natures_swiftness; + creators["nature's swiftness active"] = &DruidTriggerFactoryInternal::natures_swiftness_active; creators["party member remove curse"] = &DruidTriggerFactoryInternal::party_member_remove_curse; - creators["eclipse (solar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_solar_cooldown; - creators["eclipse (lunar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_lunar_cooldown; + creators["mangle (bear)"] = &DruidTriggerFactoryInternal::mangle_bear_trigger; + creators["lacerate"] = &DruidTriggerFactoryInternal::lacerate_trigger; + creators["demoralizing roar"] = &DruidTriggerFactoryInternal::demoralize_roar; creators["mangle (cat)"] = &DruidTriggerFactoryInternal::mangle_cat; creators["ferocious bite time"] = &DruidTriggerFactoryInternal::ferocious_bite_time; + creators["ferocious bite execute"] = &DruidTriggerFactoryInternal::ferocious_bite_execute; creators["hurricane channel check"] = &DruidTriggerFactoryInternal::hurricane_channel_check; creators["no healer dps strategy"] = &DruidTriggerFactoryInternal::no_healer_dps_strategy; + creators["starfall"] = &DruidTriggerFactoryInternal::starfall; + creators["force of nature"] = &DruidTriggerFactoryInternal::force_of_nature; + creators["cyclone"] = &DruidTriggerFactoryInternal::cyclone; + creators["predator's swiftness"] = &DruidTriggerFactoryInternal::predators_swiftness; + creators["predator's swiftness and cyclone"] = &DruidTriggerFactoryInternal::predators_swiftness_and_cyclone; + creators["predator's swiftness and hibernate"] = &DruidTriggerFactoryInternal::predators_swiftness_and_hibernate; + creators["predator's swiftness and entangling roots"] = &DruidTriggerFactoryInternal::predators_swiftness_and_entangling_roots; + creators["predator's swiftness and combat party member dead"] = &DruidTriggerFactoryInternal::predators_swiftness_and_combat_party_member_dead; + creators["clearcasting and medium aoe"] = &DruidTriggerFactoryInternal::clearcasting_and_medium_aoe; + creators["prowl"] = &DruidTriggerFactoryInternal::prowl_trigger; + creators["rejuvenation blanket"] = &DruidTriggerFactoryInternal::rejuvenation_blanket; + creators["wild growth blanket"] = &DruidTriggerFactoryInternal::wild_growth_blanket; + creators["aquatic form"] = &DruidTriggerFactoryInternal::aquatic_form; } private: static Trigger* natures_swiftness(PlayerbotAI* botAI) { return new NaturesSwiftnessTrigger(botAI); } + static Trigger* natures_swiftness_active(PlayerbotAI* botAI) { return new NaturesSwiftnessActiveTrigger(botAI); } static Trigger* clearcasting(PlayerbotAI* botAI) { return new ClearcastingTrigger(botAI); } static Trigger* eclipse_solar(PlayerbotAI* botAI) { return new EclipseSolarTrigger(botAI); } static Trigger* eclipse_lunar(PlayerbotAI* botAI) { return new EclipseLunarTrigger(botAI); } @@ -130,11 +145,13 @@ class DruidTriggerFactoryInternal : public NamedObjectContext static Trigger* bash(PlayerbotAI* botAI) { return new BashInterruptSpellTrigger(botAI); } static Trigger* faerie_fire_feral(PlayerbotAI* botAI) { return new FaerieFireFeralTrigger(botAI); } static Trigger* insect_swarm(PlayerbotAI* botAI) { return new InsectSwarmTrigger(botAI); } + static Trigger* insect_swarm_on_attacker(PlayerbotAI* botAI) { return new InsectSwarmOnAttackerTrigger(botAI); } static Trigger* moonfire(PlayerbotAI* botAI) { return new MoonfireTrigger(botAI); } + static Trigger* moonfire_on_attacker(PlayerbotAI* botAI) { return new MoonfireOnAttackerTrigger(botAI); } static Trigger* faerie_fire(PlayerbotAI* botAI) { return new FaerieFireTrigger(botAI); } static Trigger* natures_grasp(PlayerbotAI* botAI) { return new NaturesGraspTrigger(botAI); } - static Trigger* tigers_fury(PlayerbotAI* botAI) { return new TigersFuryTrigger(botAI); } static Trigger* berserk(PlayerbotAI* botAI) { return new BerserkTrigger(botAI); } + static Trigger* berserk_active(PlayerbotAI* botAI) { return new BerserkActiveTrigger(botAI); } static Trigger* savage_roar(PlayerbotAI* botAI) { return new SavageRoarTrigger(botAI); } static Trigger* rake(PlayerbotAI* botAI) { return new RakeTrigger(botAI); } static Trigger* mark_of_the_wild(PlayerbotAI* botAI) { return new MarkOfTheWildTrigger(botAI); } @@ -148,14 +165,28 @@ class DruidTriggerFactoryInternal : public NamedObjectContext static Trigger* cat_form(PlayerbotAI* botAI) { return new CatFormTrigger(botAI); } static Trigger* tree_form(PlayerbotAI* botAI) { return new TreeFormTrigger(botAI); } static Trigger* bash_on_enemy_healer(PlayerbotAI* botAI) { return new BashInterruptEnemyHealerSpellTrigger(botAI); } - static Trigger* omen_of_clarity(PlayerbotAI* botAI) { return new OmenOfClarityTrigger(botAI); } static Trigger* party_member_remove_curse(PlayerbotAI* ai) { return new DruidPartyMemberRemoveCurseTrigger(ai); } - static Trigger* eclipse_solar_cooldown(PlayerbotAI* ai) { return new EclipseSolarCooldownTrigger(ai); } - static Trigger* eclipse_lunar_cooldown(PlayerbotAI* ai) { return new EclipseLunarCooldownTrigger(ai); } + static Trigger* mangle_bear_trigger(PlayerbotAI* botAI) { return new MangleBearTrigger(botAI); } + static Trigger* lacerate_trigger(PlayerbotAI* botAI) { return new LacerateTrigger(botAI); } + static Trigger* demoralize_roar(PlayerbotAI* botAI) { return new DemoralizeRoarTrigger(botAI); } static Trigger* mangle_cat(PlayerbotAI* ai) { return new MangleCatTrigger(ai); } static Trigger* ferocious_bite_time(PlayerbotAI* ai) { return new FerociousBiteTimeTrigger(ai); } + static Trigger* ferocious_bite_execute(PlayerbotAI* ai) { return new FerociousBiteExecuteTrigger(ai); } static Trigger* hurricane_channel_check(PlayerbotAI* ai) { return new HurricaneChannelCheckTrigger(ai); } static Trigger* no_healer_dps_strategy(PlayerbotAI* ai) { return new NoHealerDpsStrategyTrigger(ai); } + static Trigger* starfall(PlayerbotAI* ai) { return new StarfallTrigger(ai); } + static Trigger* force_of_nature(PlayerbotAI* ai) { return new ForceOfNatureTrigger(ai); } + static Trigger* cyclone(PlayerbotAI* ai) { return new CycloneTrigger(ai); } + static Trigger* predators_swiftness(PlayerbotAI* ai) { return new PredatorsSwiftnessTrigger(ai); } + static Trigger* predators_swiftness_and_cyclone(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "cyclone"); } + static Trigger* predators_swiftness_and_hibernate(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "hibernate"); } + static Trigger* predators_swiftness_and_entangling_roots(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "entangling roots"); } + static Trigger* predators_swiftness_and_combat_party_member_dead(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "combat party member dead"); } + static Trigger* clearcasting_and_medium_aoe(PlayerbotAI* ai) { return new TwoTriggers(ai, "clearcasting", "medium aoe"); } + static Trigger* prowl_trigger(PlayerbotAI* ai) { return new ProwlTrigger(ai); } + static Trigger* rejuvenation_blanket(PlayerbotAI* ai) { return new BuffOnPartyTrigger(ai, "rejuvenation"); } + static Trigger* wild_growth_blanket(PlayerbotAI* ai) { return new BuffOnPartyTrigger(ai, "wild growth"); } + static Trigger* aquatic_form(PlayerbotAI* ai) { return new AquaticFormTrigger(ai); } }; class DruidAiObjectContextInternal : public NamedObjectContext @@ -193,8 +224,8 @@ class DruidAiObjectContextInternal : public NamedObjectContext creators["hibernate"] = &DruidAiObjectContextInternal::hibernate; creators["entangling roots"] = &DruidAiObjectContextInternal::entangling_roots; creators["entangling roots on cc"] = &DruidAiObjectContextInternal::entangling_roots_on_cc; - creators["hibernate"] = &DruidAiObjectContextInternal::hibernate; creators["hibernate on cc"] = &DruidAiObjectContextInternal::hibernate_on_cc; + creators["cyclone on cc"] = &DruidAiObjectContextInternal::cyclone_on_cc; creators["wrath"] = &DruidAiObjectContextInternal::wrath; creators["starfall"] = &DruidAiObjectContextInternal::starfall; creators["insect swarm"] = &DruidAiObjectContextInternal::insect_swarm; @@ -205,9 +236,9 @@ class DruidAiObjectContextInternal : public NamedObjectContext creators["mangle (cat)"] = &DruidAiObjectContextInternal::mangle_cat; creators["swipe (cat)"] = &DruidAiObjectContextInternal::swipe_cat; creators["rake"] = &DruidAiObjectContextInternal::rake; - creators["rake on attacker"] = &DruidAiObjectContextInternal::rake_on_attacker; creators["ferocious bite"] = &DruidAiObjectContextInternal::ferocious_bite; creators["rip"] = &DruidAiObjectContextInternal::rip; + creators["maim"] = &DruidAiObjectContextInternal::maim; creators["cower"] = &DruidAiObjectContextInternal::cower; creators["survival instincts"] = &DruidAiObjectContextInternal::survival_instincts; creators["frenzied regeneration"] = &DruidAiObjectContextInternal::frenzied_regeneration; @@ -237,9 +268,9 @@ class DruidAiObjectContextInternal : public NamedObjectContext creators["lacerate"] = &DruidAiObjectContextInternal::lacerate; creators["hurricane"] = &DruidAiObjectContextInternal::hurricane; creators["innervate"] = &DruidAiObjectContextInternal::innervate; + creators["innervate on healer"] = &DruidAiObjectContextInternal::innervate_on_healer; creators["tranquility"] = &DruidAiObjectContextInternal::tranquility; creators["bash on enemy healer"] = &DruidAiObjectContextInternal::bash_on_enemy_healer; - creators["omen of clarity"] = &DruidAiObjectContextInternal::omen_of_clarity; creators["nature's swiftness"] = &DruidAiObjectContextInternal::natures_swiftness; creators["prowl"] = &DruidAiObjectContextInternal::prowl; creators["dash"] = &DruidAiObjectContextInternal::dash; @@ -254,11 +285,13 @@ class DruidAiObjectContextInternal : public NamedObjectContext creators["moonfire on attacker"] = &DruidAiObjectContextInternal::moonfire_on_attacker; creators["enrage"] = &DruidAiObjectContextInternal::enrage; creators["force of nature"] = &DruidAiObjectContextInternal::force_of_nature; + creators["typhoon"] = &DruidAiObjectContextInternal::typhoon; + creators["rejuvenation blanket"] = &DruidAiObjectContextInternal::rejuvenation_blanket; + creators["wild growth blanket"] = &DruidAiObjectContextInternal::wild_growth_blanket; } private: static Action* natures_swiftness(PlayerbotAI* botAI) { return new CastNaturesSwiftnessAction(botAI); } - static Action* omen_of_clarity(PlayerbotAI* botAI) { return new CastOmenOfClarityAction(botAI); } static Action* tranquility(PlayerbotAI* botAI) { return new CastTranquilityAction(botAI); } static Action* feral_charge_bear(PlayerbotAI* botAI) { return new CastFeralChargeBearAction(botAI); } static Action* feral_charge_cat(PlayerbotAI* botAI) { return new CastFeralChargeCatAction(botAI); } @@ -291,6 +324,7 @@ class DruidAiObjectContextInternal : public NamedObjectContext static Action* entangling_roots(PlayerbotAI* botAI) { return new CastEntanglingRootsAction(botAI); } static Action* hibernate_on_cc(PlayerbotAI* botAI) { return new CastHibernateCcAction(botAI); } static Action* entangling_roots_on_cc(PlayerbotAI* botAI) { return new CastEntanglingRootsCcAction(botAI); } + static Action* cyclone_on_cc(PlayerbotAI* botAI) { return new CastCycloneCcAction(botAI); } static Action* wrath(PlayerbotAI* botAI) { return new CastWrathAction(botAI); } static Action* starfall(PlayerbotAI* botAI) { return new CastStarfallAction(botAI); } static Action* insect_swarm(PlayerbotAI* botAI) { return new CastInsectSwarmAction(botAI); } @@ -301,9 +335,9 @@ class DruidAiObjectContextInternal : public NamedObjectContext static Action* mangle_cat(PlayerbotAI* botAI) { return new CastMangleCatAction(botAI); } static Action* swipe_cat(PlayerbotAI* botAI) { return new CastSwipeCatAction(botAI); } static Action* rake(PlayerbotAI* botAI) { return new CastRakeAction(botAI); } - static Action* rake_on_attacker(PlayerbotAI* botAI) { return new CastRakeOnMeleeAttackersAction(botAI); } static Action* ferocious_bite(PlayerbotAI* botAI) { return new CastFerociousBiteAction(botAI); } static Action* rip(PlayerbotAI* botAI) { return new CastRipAction(botAI); } + static Action* maim(PlayerbotAI* botAI) { return new CastMaimAction(botAI); } static Action* cower(PlayerbotAI* botAI) { return new CastCowerAction(botAI); } static Action* survival_instincts(PlayerbotAI* botAI) { return new CastSurvivalInstinctsAction(botAI); } static Action* frenzied_regeneration(PlayerbotAI* botAI) { return new CastFrenziedRegenerationAction(botAI); } @@ -333,6 +367,7 @@ class DruidAiObjectContextInternal : public NamedObjectContext static Action* lacerate(PlayerbotAI* botAI) { return new CastLacerateAction(botAI); } static Action* hurricane(PlayerbotAI* botAI) { return new CastHurricaneAction(botAI); } static Action* innervate(PlayerbotAI* botAI) { return new CastInnervateAction(botAI); } + static Action* innervate_on_healer(PlayerbotAI* botAI) { return new CastInnervateOnHealerAction(botAI); } static Action* bash_on_enemy_healer(PlayerbotAI* botAI) { return new CastBashOnEnemyHealerAction(botAI); } static Action* ravage(PlayerbotAI* botAI) { return new CastRavageAction(botAI); } static Action* pounce(PlayerbotAI* botAI) { return new CastPounceAction(botAI); } @@ -347,6 +382,9 @@ class DruidAiObjectContextInternal : public NamedObjectContext static Action* moonfire_on_attacker(PlayerbotAI* ai) { return new CastMoonfireOnAttackerAction(ai); } static Action* enrage(PlayerbotAI* ai) { return new CastEnrageAction(ai); } static Action* force_of_nature(PlayerbotAI* ai) { return new CastForceOfNatureAction(ai); } + static Action* typhoon(PlayerbotAI* ai) { return new CastTyphoonAction(ai); } + static Action* rejuvenation_blanket(PlayerbotAI* ai) { return new CastRejuvenationBlanketAction(ai); } + static Action* wild_growth_blanket(PlayerbotAI* ai) { return new CastWildGrowthBlanketAction(ai); } }; SharedNamedObjectContextList DruidAiObjectContext::sharedStrategyContexts; @@ -386,7 +424,22 @@ void DruidAiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextLi triggerContexts.Add(new DruidTriggerFactoryInternal()); } +class DruidValueContextInternal : public NamedObjectContext +{ +public: + DruidValueContextInternal() + { + creators["eclipse solar proc time"] = &DruidValueContextInternal::eclipse_solar_proc_time; + creators["eclipse lunar proc time"] = &DruidValueContextInternal::eclipse_lunar_proc_time; + } + +private: + static UntypedValue* eclipse_solar_proc_time(PlayerbotAI* botAI) { return new EclipseSolarProcTimeValue(botAI); } + static UntypedValue* eclipse_lunar_proc_time(PlayerbotAI* botAI) { return new EclipseLunarProcTimeValue(botAI); } +}; + void DruidAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList& valueContexts) { AiObjectContext::BuildSharedValueContexts(valueContexts); + valueContexts.Add(new DruidValueContextInternal()); } diff --git a/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.cpp similarity index 52% rename from src/Ai/Class/Druid/Strategy/CasterDruidStrategy.cpp rename to src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.cpp index a91f3b54090..f3fbc2218f7 100644 --- a/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.cpp @@ -3,15 +3,15 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "CasterDruidStrategy.h" +#include "BalanceDruidStrategy.h" #include "AiObjectContext.h" #include "FeralDruidStrategy.h" -class CasterDruidStrategyActionNodeFactory : public NamedObjectFactory +class BalanceDruidStrategyActionNodeFactory : public NamedObjectFactory { public: - CasterDruidStrategyActionNodeFactory() + BalanceDruidStrategyActionNodeFactory() { creators["faerie fire"] = &faerie_fire; creators["hibernate"] = &hibernate; @@ -23,6 +23,10 @@ class CasterDruidStrategyActionNodeFactory : public NamedObjectFactory CasterDruidStrategy::getDefaultActions() +std::vector BalanceDruidStrategy::getDefaultActions() { return { - NextAction("starfall", ACTION_HIGH + 1.0f), - NextAction("force of nature", ACTION_DEFAULT + 1.0f), - NextAction("wrath", ACTION_DEFAULT + 0.1f), + NextAction("starfire", 5.4f), + NextAction("wrath", 5.3f), }; } -void CasterDruidStrategy::InitTriggers(std::vector& triggers) +void BalanceDruidStrategy::InitTriggers(std::vector& triggers) { GenericDruidStrategy::InitTriggers(triggers); - triggers.push_back( - new TriggerNode( - "eclipse (lunar) cooldown", - { - NextAction("starfire", ACTION_DEFAULT + 0.2f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "eclipse (solar) cooldown", - { - NextAction("wrath", ACTION_DEFAULT + 0.2f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "insect swarm", - { - NextAction("insect swarm", ACTION_NORMAL + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "moonfire", - { - NextAction("moonfire", ACTION_NORMAL + 4) - } - ) - ); - triggers.push_back( - new TriggerNode( - "eclipse (solar)", - { - NextAction("wrath", ACTION_NORMAL + 6) - } - ) - ); - triggers.push_back( - new TriggerNode( - "eclipse (lunar)", - { - NextAction("starfire", ACTION_NORMAL + 6) - } - ) - ); - triggers.push_back( - new TriggerNode( - "medium mana", - { - NextAction("innervate", ACTION_HIGH + 9) - } - ) - ); - triggers.push_back( - new TriggerNode( - "enemy too close for spell", - { - NextAction("flee", ACTION_MOVE + 9) - } - ) - ); -} + // Debuffs and DoTs + triggers.push_back(new TriggerNode("faerie fire", { NextAction("faerie fire", 29.5f) })); + triggers.push_back(new TriggerNode("insect swarm", { NextAction("insect swarm", 18.0f) })); + triggers.push_back(new TriggerNode("moonfire", { NextAction("moonfire", 17.5f) })); -void CasterDruidAoeStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back( - new TriggerNode( - "hurricane channel check", - { - NextAction("cancel channel", ACTION_HIGH + 2) - } - ) - ); - triggers.push_back( - new TriggerNode( - "medium aoe", - { - NextAction("hurricane", ACTION_HIGH + 1) - } - ) - ); - triggers.push_back( - new TriggerNode( - "light aoe", - { - NextAction("insect swarm on attacker", ACTION_NORMAL + 3), - NextAction("moonfire on attacker", ACTION_NORMAL + 3) - } - ) - ); -} + // Eclipse procs + triggers.push_back(new TriggerNode("eclipse (solar)", { NextAction("wrath", 20.0f) })); + triggers.push_back(new TriggerNode("eclipse (lunar)", { NextAction("starfire", 20.0f) })); -void CasterDruidDebuffStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back( - new TriggerNode( - "faerie fire", - { - NextAction("faerie fire", ACTION_HIGH) - } - ) - ); + // Utility/Defensive + triggers.push_back(new TriggerNode("medium mana", { NextAction("innervate", 29.0f) })); + triggers.push_back(new TriggerNode("enemy too close for spell", { NextAction("flee", 39.0f) })); } diff --git a/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.h b/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.h new file mode 100644 index 00000000000..d01db490108 --- /dev/null +++ b/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_BALANCEDRUIDSTRATEGY_H +#define _PLAYERBOT_BALANCEDRUIDSTRATEGY_H + +#include "GenericDruidStrategy.h" + +class PlayerbotAI; + +class BalanceDruidStrategy : public GenericDruidStrategy +{ +public: + BalanceDruidStrategy(PlayerbotAI* botAI); + +public: + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "balance"; } + std::vector getDefaultActions() override; + uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; } +}; + +#endif diff --git a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/BearDruidStrategy.cpp similarity index 59% rename from src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp rename to src/Ai/Class/Druid/Strategy/BearDruidStrategy.cpp index 13af635c3d3..418f057cd67 100644 --- a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/BearDruidStrategy.cpp @@ -3,19 +3,17 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "BearTankDruidStrategy.h" +#include "BearDruidStrategy.h" #include "Playerbots.h" -class BearTankDruidStrategyActionNodeFactory : public NamedObjectFactory +class BearDruidStrategyActionNodeFactory : public NamedObjectFactory { public: - BearTankDruidStrategyActionNodeFactory() + BearDruidStrategyActionNodeFactory() { - creators["melee"] = &melee; creators["feral charge - bear"] = &feral_charge_bear; creators["swipe (bear)"] = &swipe_bear; - creators["faerie fire (feral)"] = &faerie_fire_feral; creators["bear form"] = &bear_form; creators["dire bear form"] = &dire_bear_form; creators["mangle (bear)"] = &mangle_bear; @@ -28,16 +26,6 @@ class BearTankDruidStrategyActionNodeFactory : public NamedObjectFactory BearTankDruidStrategy::getDefaultActions() +std::vector BearDruidStrategy::getDefaultActions() { return { - NextAction("mangle (bear)", ACTION_DEFAULT + 0.5f), - NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.4f), - NextAction("lacerate", ACTION_DEFAULT + 0.3f), - NextAction("maul", ACTION_DEFAULT + 0.2f), - NextAction("enrage", ACTION_DEFAULT + 0.1f), - NextAction("melee", ACTION_DEFAULT) + NextAction("maul", 5.2f), + NextAction("enrage", 5.1f), + NextAction("melee", 5.0f) }; } -void BearTankDruidStrategy::InitTriggers(std::vector& triggers) +void BearDruidStrategy::InitTriggers(std::vector& triggers) { FeralDruidStrategy::InitTriggers(triggers); - triggers.push_back( - new TriggerNode( - "enemy out of melee", - { - NextAction("feral charge - bear", ACTION_NORMAL + 8) - } - ) - ); triggers.push_back( new TriggerNode( "bear form", - { - NextAction("dire bear form", ACTION_HIGH + 8) - } + { NextAction("dire bear form", 28.0f) } ) ); triggers.push_back( new TriggerNode( - "low health", - { - NextAction("frenzied regeneration", ACTION_HIGH + 7) - } - ) - ); - triggers.push_back( - new TriggerNode( - "faerie fire (feral)", - { - NextAction("faerie fire (feral)", ACTION_HIGH + 7) - } - ) - ); - triggers.push_back(new TriggerNode("high aoe", {NextAction("challenging roar", ACTION_HIGH + 8)})); - triggers.push_back( - new TriggerNode( - "lose aggro", - { - NextAction("growl", ACTION_HIGH + 8) - } + "medium health", + { NextAction("frenzied regeneration", 27.0f) } ) ); + triggers.push_back(new TriggerNode( + "mangle (bear)", { NextAction("mangle (bear)", 17.5f) } + )); + triggers.push_back(new TriggerNode( + "faerie fire (feral)", { NextAction("faerie fire (feral)", 17.0f) } + )); + triggers.push_back(new TriggerNode( + "lacerate", { NextAction("lacerate", 16.0f) } + )); + triggers.push_back(new TriggerNode( + "demoralizing roar", { NextAction("demoralizing roar", 15.5f) } + )); + triggers.push_back(new TriggerNode("high aoe", { NextAction("challenging roar", 26.5f) })); + triggers.push_back(new TriggerNode("lose aggro", + { + NextAction("growl", 26.0f), + NextAction("faerie fire (feral)", 25.5f) + } + )); + triggers.push_back(new TriggerNode("berserk active", { NextAction("mangle (bear)", 25.0f) })); triggers.push_back( new TriggerNode( "medium aoe", { - NextAction("demoralizing roar", ACTION_HIGH + 6), - NextAction("swipe (bear)", ACTION_HIGH + 6) + NextAction("demoralizing roar", 24.5f), + NextAction("swipe (bear)", 24.0f) } ) ); triggers.push_back( new TriggerNode( "light aoe", - { - NextAction("swipe (bear)", ACTION_HIGH + 5) - } + { NextAction("swipe (bear)", 24.0f) } ) ); triggers.push_back( new TriggerNode( "bash", - { - NextAction("bash", ACTION_INTERRUPT + 2) - } + { NextAction("bash", 42.0f) } ) ); triggers.push_back( new TriggerNode( "bash on enemy healer", - { - NextAction("bash on enemy healer", ACTION_INTERRUPT + 1) - } + { NextAction("bash on enemy healer", 41.0f) } ) ); } diff --git a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.h b/src/Ai/Class/Druid/Strategy/BearDruidStrategy.h similarity index 75% rename from src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.h rename to src/Ai/Class/Druid/Strategy/BearDruidStrategy.h index 2019bd0eba8..1e47fd08afb 100644 --- a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.h +++ b/src/Ai/Class/Druid/Strategy/BearDruidStrategy.h @@ -3,17 +3,17 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#ifndef _PLAYERBOT_BEARTANKDRUIDSTRATEGY_H -#define _PLAYERBOT_BEARTANKDRUIDSTRATEGY_H +#ifndef _PLAYERBOT_BEARDRUIDSTRATEGY_H +#define _PLAYERBOT_BEARDRUIDSTRATEGY_H #include "FeralDruidStrategy.h" class PlayerbotAI; -class BearTankDruidStrategy : public FeralDruidStrategy +class BearDruidStrategy : public FeralDruidStrategy { public: - BearTankDruidStrategy(PlayerbotAI* botAI); + BearDruidStrategy(PlayerbotAI* botAI); void InitTriggers(std::vector& triggers) override; std::string const getName() override { return "bear"; } diff --git a/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.h b/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.h deleted file mode 100644 index 45bc78dba80..00000000000 --- a/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it - * and/or modify it under version 3 of the License, or (at your option), any later version. - */ - -#ifndef _PLAYERBOT_CASTERDRUIDSTRATEGY_H -#define _PLAYERBOT_CASTERDRUIDSTRATEGY_H - -#include "GenericDruidStrategy.h" - -class PlayerbotAI; - -class CasterDruidStrategy : public GenericDruidStrategy -{ -public: - CasterDruidStrategy(PlayerbotAI* botAI); - -public: - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "caster"; } - std::vector getDefaultActions() override; - uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; } -}; - -class CasterDruidAoeStrategy : public CombatStrategy -{ -public: - CasterDruidAoeStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} - -public: - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "caster aoe"; } -}; - -class CasterDruidDebuffStrategy : public CombatStrategy -{ -public: - CasterDruidDebuffStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} - -public: - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "caster debuff"; } -}; - -#endif diff --git a/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.cpp deleted file mode 100644 index f7a76b0fc8c..00000000000 --- a/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.cpp +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it - * and/or modify it under version 3 of the License, or (at your option), any later version. - */ - -#include "CatDpsDruidStrategy.h" - -#include "AiObjectContext.h" - -class CatDpsDruidStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - CatDpsDruidStrategyActionNodeFactory() - { - creators["faerie fire (feral)"] = &faerie_fire_feral; - creators["melee"] = &melee; - creators["feral charge - cat"] = &feral_charge_cat; - creators["cat form"] = &cat_form; - creators["claw"] = &claw; - creators["mangle (cat)"] = &mangle_cat; - creators["rake"] = &rake; - creators["ferocious bite"] = &ferocious_bite; - creators["rip"] = &rip; - creators["pounce"] = &pounce; - creators["ravage"] = &ravage; - } - -private: - static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "faerie fire (feral)", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "melee", - /*P*/ { NextAction("feral charge - cat") }, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "feral charge - cat", - /*P*/ {}, - /*A*/ { NextAction("reach melee") }, - /*C*/ {} - ); - } - - static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "cat form", - /*P*/ { NextAction("caster form") }, - /*A*/ { NextAction("bear form") }, - /*C*/ {} - ); - } - - static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "claw", - /*P*/ {}, - /*A*/ { NextAction("melee") }, - /*C*/ {} - ); - } - - static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "mangle (cat)", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rake", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "ferocious bite", - /*P*/ {}, - /*A*/ { NextAction("rip") }, - /*C*/ {} - ); - } - - static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rip", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "pounce", - /*P*/ {}, - /*A*/ { NextAction("ravage") }, - /*C*/ {} - ); - } - - static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "ravage", - /*P*/ {}, - /*A*/ { NextAction("shred") }, - /*C*/ {} - ); - } -}; - -CatDpsDruidStrategy::CatDpsDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI) -{ - actionNodeFactories.Add(new CatDpsDruidStrategyActionNodeFactory()); -} - -std::vector CatDpsDruidStrategy::getDefaultActions() -{ - return { - NextAction("tiger's fury", ACTION_DEFAULT + 0.1f) - }; -} - -void CatDpsDruidStrategy::InitTriggers(std::vector& triggers) -{ - FeralDruidStrategy::InitTriggers(triggers); - - // Default priority - triggers.push_back( - new TriggerNode( - "almost full energy available", - { - NextAction("shred", ACTION_DEFAULT + 0.4f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points not full", - { - NextAction("shred", ACTION_DEFAULT + 0.4f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "almost full energy available", - { - NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points not full and high energy", - { - NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "almost full energy available", - { - NextAction("claw", ACTION_DEFAULT + 0.2f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points not full and high energy", - { - NextAction("claw", ACTION_DEFAULT + 0.2f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "faerie fire (feral)", - { - NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f) - } - ) - ); - - // Main spell - triggers.push_back( - new TriggerNode( - "cat form", { - NextAction("cat form", ACTION_HIGH + 8) - } - ) - ); - triggers.push_back( - new TriggerNode( - "savage roar", { - NextAction("savage roar", ACTION_HIGH + 7) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points 5 available", - { - NextAction("rip", ACTION_HIGH + 6) - } - ) - ); - triggers.push_back( - new TriggerNode( - "ferocious bite time", - { - NextAction("ferocious bite", ACTION_HIGH + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "target with combo points almost dead", - { - NextAction("ferocious bite", ACTION_HIGH + 4) - } - ) - ); - triggers.push_back( - new TriggerNode( - "mangle (cat)", - { - NextAction("mangle (cat)", ACTION_HIGH + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "rake", - { - NextAction("rake", ACTION_HIGH + 2) - } - ) - ); - triggers.push_back( - new TriggerNode( - "medium threat", - { - NextAction("cower", ACTION_HIGH + 1) - } - ) - ); - - // AOE - triggers.push_back( - new TriggerNode( - "medium aoe", - { - NextAction("swipe (cat)", ACTION_HIGH + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "light aoe", - { - NextAction("rake on attacker", ACTION_HIGH + 2) - } - ) - ); - // Reach target - triggers.push_back( - new TriggerNode( - "enemy out of melee", - { - NextAction("feral charge - cat", ACTION_HIGH + 9) - } - ) - ); - triggers.push_back( - new TriggerNode( - "enemy out of melee", - { - NextAction("dash", ACTION_HIGH + 8) - } - ) - ); -} - -void CatAoeDruidStrategy::InitTriggers(std::vector& /*triggers*/) {} diff --git a/src/Ai/Class/Druid/Strategy/CatDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/CatDruidStrategy.cpp new file mode 100644 index 00000000000..df6ba4a927c --- /dev/null +++ b/src/Ai/Class/Druid/Strategy/CatDruidStrategy.cpp @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "CatDruidStrategy.h" + +#include "AiObjectContext.h" + +class CatDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + CatDruidStrategyActionNodeFactory() + { + creators["faerie fire (feral)"] = &faerie_fire_feral; + creators["melee"] = &melee; + creators["feral charge - cat"] = &feral_charge_cat; + creators["cat form"] = &cat_form; + creators["claw"] = &claw; + creators["mangle (cat)"] = &mangle_cat; + creators["rake"] = &rake; + creators["ferocious bite"] = &ferocious_bite; + creators["rip"] = &rip; + creators["pounce"] = &pounce; + creators["ravage"] = &ravage; + creators["prowl"] = &prowl; + } + +private: + static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "faerie fire (feral)", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "melee", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "feral charge - cat", + /*P*/ {}, + /*A*/ { NextAction("reach melee") }, + /*C*/ {} + ); + } + + static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "cat form", + /*P*/ { NextAction("caster form") }, + /*A*/ { NextAction("bear form") }, + /*C*/ {} + ); + } + + static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "claw", + /*P*/ {}, + /*A*/ { NextAction("melee") }, + /*C*/ {} + ); + } + + static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "mangle (cat)", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "rake", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "ferocious bite", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "rip", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "ravage", + /*P*/ {}, + /*A*/ { NextAction("pounce") }, + /*C*/ {} + ); + } + + static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "pounce", + /*P*/ {}, + /*A*/ { NextAction("shred") }, + /*C*/ {} + ); + } + + static ActionNode* prowl([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "prowl", + /*P*/ { NextAction("cat form") }, + /*A*/ {}, + /*C*/ {} + ); + } + +}; + +CatDruidStrategy::CatDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI) +{ + actionNodeFactories.Add(new CatDruidStrategyActionNodeFactory()); +} + +std::vector CatDruidStrategy::getDefaultActions() +{ + return { + NextAction("melee", ACTION_DEFAULT) + }; +} + +void CatDruidStrategy::InitTriggers(std::vector& triggers) +{ + FeralDruidStrategy::InitTriggers(triggers); + + triggers.push_back( + new TriggerNode( + "healer low mana", { + NextAction("innervate on healer", 35.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "prowl", { + NextAction("prowl", 29.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "enemy out of melee", { + NextAction("dash", 28.0f) + } + ) + ); + + triggers.push_back( + new TriggerNode( + "cat form", { + NextAction("cat form", 28.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "low energy", { + NextAction("tiger's fury", 27.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "savage roar", { + NextAction("savage roar", 26.0f) + } + ) + ); + + triggers.push_back( + new TriggerNode( + "combo points 5 available", { + NextAction("rip", 23.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points 5 available", { + NextAction("maim", 23.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "ferocious bite execute", { + NextAction("ferocious bite", 24.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "clearcasting", { + NextAction("shred", 24.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "ferocious bite time", { + NextAction("ferocious bite", 22.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "mangle (cat)", { + NextAction("mangle (cat)", 22.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "rake", { + NextAction("rake", 21.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "medium threat", { + NextAction("cower", 21.0f) + } + ) + ); + + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("ravage", 5.6f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full", { + NextAction("ravage", 5.6f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("pounce", 5.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full", { + NextAction("pounce", 5.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("shred", 5.4f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full", { + NextAction("shred", 5.4f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("mangle (cat)", 5.3f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full and high energy", { + NextAction("mangle (cat)", 5.3f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("claw", 5.2f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full and high energy", { + NextAction("claw", 5.2f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "faerie fire (feral)", { + NextAction("faerie fire (feral)", 5.0f) + } + ) + ); +} + +// ============================================================ +// CatOffhealStrategy +// Additive overlay — only the healing triggers. Designed to be +// stacked on top of "cat" so the bot stays in cat form for DPS +// but shifts out to heal when the party needs it. +// ============================================================ + +class CatOffhealStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + CatOffhealStrategyActionNodeFactory() + { + creators["healing touch on party"] = &healing_touch_on_party; + creators["regrowth on party"] = ®rowth_on_party; + creators["rejuvenation on party"] = &rejuvenation_on_party; + } + +private: + // P: shift to caster form before casting C: shift back to cat form afterwards + static ActionNode* healing_touch_on_party([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "healing touch on party", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ { NextAction("cat form") } + ); + } + + static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "regrowth on party", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ { NextAction("cat form") } + ); + } + + static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "rejuvenation on party", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ { NextAction("cat form") } + ); + } +}; + +CatOffhealStrategy::CatOffhealStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) +{ + actionNodeFactories.Add(new CatOffhealStrategyActionNodeFactory()); +} + +void CatOffhealStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back( + new TriggerNode( + "party member critical health", + { + NextAction("regrowth on party", 36.0f), + NextAction("healing touch on party", 35.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "party member low health", + { + NextAction("healing touch on party", 25.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "party member medium health", + { + NextAction("rejuvenation on party", 18.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "party member to heal out of spell range", + { + NextAction("reach party member to heal", 93.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "low mana", + { + NextAction("innervate", 24.0f) + } + ) + ); +} diff --git a/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.h b/src/Ai/Class/Druid/Strategy/CatDruidStrategy.h similarity index 59% rename from src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.h rename to src/Ai/Class/Druid/Strategy/CatDruidStrategy.h index 312e94d0f6b..51d80cd7718 100644 --- a/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.h +++ b/src/Ai/Class/Druid/Strategy/CatDruidStrategy.h @@ -3,17 +3,17 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#ifndef _PLAYERBOT_CATDPSDRUIDSTRATEGY_H -#define _PLAYERBOT_CATDPSDRUIDSTRATEGY_H +#ifndef _PLAYERBOT_CATDRUIDSTRATEGY_H +#define _PLAYERBOT_CATDRUIDSTRATEGY_H #include "FeralDruidStrategy.h" class PlayerbotAI; -class CatDpsDruidStrategy : public FeralDruidStrategy +class CatDruidStrategy : public FeralDruidStrategy { public: - CatDpsDruidStrategy(PlayerbotAI* botAI); + CatDruidStrategy(PlayerbotAI* botAI); public: void InitTriggers(std::vector& triggers) override; @@ -22,14 +22,16 @@ class CatDpsDruidStrategy : public FeralDruidStrategy uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; } }; -class CatAoeDruidStrategy : public CombatStrategy +// Optional additive strategy. Layers emergency heals on top of the "cat" strategy. +// Enable : co +offheal +// Disable: co -offheal +class CatOffhealStrategy : public CombatStrategy { public: - CatAoeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} + CatOffhealStrategy(PlayerbotAI* botAI); -public: void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "cat aoe"; } + std::string const getName() override { return "offheal"; } }; #endif diff --git a/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.cpp index 894c05bff68..aec438890ee 100644 --- a/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.cpp @@ -14,12 +14,10 @@ class FeralDruidStrategyActionNodeFactory : public NamedObjectFactory& triggers) GenericDruidStrategy::InitTriggers(triggers); triggers.push_back(new TriggerNode( - "enemy out of melee", { NextAction("reach melee", ACTION_HIGH + 1) })); + "enemy out of melee", { NextAction("reach melee", 21.0f) })); triggers.push_back(new TriggerNode( - "critical health", { NextAction("survival instincts", ACTION_EMERGENCY + 1) })); - triggers.push_back(new TriggerNode( - "omen of clarity", { NextAction("omen of clarity", ACTION_HIGH + 9) })); + "low health", { NextAction("survival instincts", 91.0f) })); triggers.push_back(new TriggerNode("player has flag", - { NextAction("dash", ACTION_EMERGENCY + 2) })); + { NextAction("dash", 92.0f) })); triggers.push_back(new TriggerNode("enemy flagcarrier near", - { NextAction("dash", ACTION_EMERGENCY + 2) })); - triggers.push_back( - new TriggerNode("berserk", { NextAction("berserk", ACTION_HIGH + 6) })); + { NextAction("dash", 92.0f) })); +} + +void FeralChargeDruidStrategy::InitTriggers(std::vector& triggers) +{ + Player* bot = botAI->GetBot(); + + if (bot->HasSpell(SPELL_CAT_FORM) && !bot->HasAura(AURA_THICK_HIDE)) + triggers.push_back(new TriggerNode( + "enemy out of melee", { NextAction("feral charge - cat", 29.0f) })); + else + triggers.push_back(new TriggerNode( + "enemy out of melee", { NextAction("feral charge - bear", 18.0f) })); } diff --git a/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.h b/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.h index abf01c694ca..ebff73de357 100644 --- a/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.h +++ b/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.h @@ -3,11 +3,14 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#ifndef _PLAYERBOT_FERALRUIDSTRATEGY_H -#define _PLAYERBOT_FERALRUIDSTRATEGY_H +#ifndef _PLAYERBOT_FERALDRUIDSTRATEGY_H +#define _PLAYERBOT_FERALDRUIDSTRATEGY_H #include "GenericDruidStrategy.h" +constexpr uint32 SPELL_CAT_FORM = 768; +constexpr uint32 AURA_THICK_HIDE = 16931; + class PlayerbotAI; class ShapeshiftDruidStrategyActionNodeFactory : public NamedObjectFactory @@ -83,4 +86,18 @@ class FeralDruidStrategy : public GenericDruidStrategy uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; } }; +// Optional strategy — enabled by default for cat and bear. +// Registers the "enemy out of melee" → Feral Charge trigger, spec-gated at +// init time so cats get Feral Charge (Cat) and bears get Feral Charge (Bear). +// Disable with: co -feral charge +// Re-enable with: co +feral charge +class FeralChargeDruidStrategy : public CombatStrategy +{ +public: + FeralChargeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "feral charge"; } +}; + #endif diff --git a/src/Ai/Class/Druid/Strategy/GenericDruidNonCombatStrategy.cpp b/src/Ai/Class/Druid/Strategy/GenericDruidNonCombatStrategy.cpp index 7d1a5f7ca12..0977540f630 100644 --- a/src/Ai/Class/Druid/Strategy/GenericDruidNonCombatStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/GenericDruidNonCombatStrategy.cpp @@ -17,12 +17,13 @@ class GenericDruidNonCombatStrategyActionNodeFactory : public NamedObjectFactory creators["thorns on party"] = þs_on_party; creators["mark of the wild"] = &mark_of_the_wild; creators["mark of the wild on party"] = &mark_of_the_wild_on_party; - // creators["innervate"] = &innervate; creators["regrowth_on_party"] = ®rowth_on_party; creators["rejuvenation on party"] = &rejuvenation_on_party; creators["remove curse on party"] = &remove_curse_on_party; creators["abolish poison on party"] = &abolish_poison_on_party; creators["revive"] = &revive; + creators["prowl"] = &prowl; + creators["aquatic form"] = &aquatic_form; } private: @@ -92,6 +93,23 @@ class GenericDruidNonCombatStrategyActionNodeFactory : public NamedObjectFactory /*A*/ {}, /*C*/ {}); } + + static ActionNode* prowl([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("prowl", + /*P*/ { NextAction("cat form") }, + /*A*/ {}, + /*C*/ {}); + } + + static ActionNode* aquatic_form([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("aquatic form", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ {}); + } + }; GenericDruidNonCombatStrategy::GenericDruidNonCombatStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) @@ -165,11 +183,16 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector& trig NextAction("remove curse on party", ACTION_DISPEL + 7), })); + triggers.push_back(new TriggerNode("aquatic form", { NextAction("aquatic form", 10.0f) })); + int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot()); - if (specTab == 0 || specTab == 2) // Balance or Restoration + if (specTab == DRUID_TAB_BALANCE || specTab == DRUID_TAB_RESTORATION) triggers.push_back(new TriggerNode("often", { NextAction("apply oil", 1.0f) })); - if (specTab == 1) // Feral + if (specTab == DRUID_TAB_FERAL) + { triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) })); + triggers.push_back(new TriggerNode("prowl", { NextAction("prowl", ACTION_INTERRUPT) })); + } } diff --git a/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.cpp index 36b90a146a2..d5e7b9f4035 100644 --- a/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.cpp @@ -5,6 +5,8 @@ #include "GenericDruidStrategy.h" +#include "AiFactory.h" +#include "FeralDruidStrategy.h" #include "Playerbots.h" class GenericDruidStrategyActionNodeFactory : public NamedObjectFactory @@ -20,6 +22,8 @@ class GenericDruidStrategyActionNodeFactory : public NamedObjectFactory& triggers) CombatStrategy::InitTriggers(triggers); triggers.push_back( - new TriggerNode("low health", { NextAction("barkskin", ACTION_HIGH + 7) })); + new TriggerNode("almost full health", { NextAction("barkskin", 40.0f) })); + + Player* bot = botAI->GetBot(); + int tab = AiFactory::GetPlayerSpecTab(bot); + + if (tab == DRUID_TAB_FERAL) + { + if (!bot->HasAura(16931) /*thick hide — bear spec*/) + { + triggers.push_back(new TriggerNode("predator's swiftness and combat party member dead", + { NextAction("rebirth", 29.0f) })); + triggers.push_back(new TriggerNode("combat party member dead", + { NextAction("rebirth", 28.5f) })); + } + } + else + { + triggers.push_back(new TriggerNode("combat party member dead", + { NextAction("rebirth", 29.0f) })); + } - triggers.push_back(new TriggerNode("combat party member dead", - { NextAction("rebirth", ACTION_HIGH + 9) })); triggers.push_back(new TriggerNode("being attacked", - { NextAction("nature's grasp", ACTION_HIGH + 1) })); - triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) })); + { NextAction("nature's grasp", 39.0f) })); } void DruidCureStrategy::InitTriggers(std::vector& triggers) { triggers.push_back( new TriggerNode("party member cure poison", - { NextAction("abolish poison on party", ACTION_DISPEL + 1) })); + { NextAction("abolish poison on party", 51.0f) })); triggers.push_back( new TriggerNode("party member remove curse", - { NextAction("remove curse on party", ACTION_DISPEL + 7) })); + { NextAction("remove curse on party", 57.0f) })); } void DruidBoostStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode( - "nature's swiftness", { NextAction("nature's swiftness", ACTION_HIGH + 9) })); + Player* bot = botAI->GetBot(); + int tab = AiFactory::GetPlayerSpecTab(bot); + + if (tab == DRUID_TAB_BALANCE) + { + triggers.push_back(new TriggerNode("force of nature", { NextAction("force of nature", 29.0f) })); + triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) })); + } + + if (tab == DRUID_TAB_FERAL) + { + triggers.push_back(new TriggerNode("berserk", { NextAction("berserk", 27.5f) })); + } } void DruidCcStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode( - "entangling roots", { NextAction("entangling roots on cc", ACTION_HIGH + 2) })); - triggers.push_back(new TriggerNode( - "entangling roots kite", { NextAction("entangling roots", ACTION_HIGH + 2) })); - triggers.push_back(new TriggerNode( - "hibernate", { NextAction("hibernate on cc", ACTION_HIGH + 3) })); + Player* bot = botAI->GetBot(); + int tab = AiFactory::GetPlayerSpecTab(bot); + + if (tab == DRUID_TAB_BALANCE || tab == DRUID_TAB_RESTORATION) + { + triggers.push_back(new TriggerNode( + "cyclone", { NextAction("cyclone on cc", 42.0f) })); + triggers.push_back(new TriggerNode( + "hibernate", { NextAction("hibernate on cc", 41.0f) })); + triggers.push_back(new TriggerNode( + "entangling roots", { NextAction("entangling roots on cc", 40.0f) })); + } + if (tab == DRUID_TAB_FERAL) + { + if (bot->HasSpell(SPELL_CAT_FORM) && !bot->HasAura(AURA_THICK_HIDE)) + { + triggers.push_back(new TriggerNode( + "predator's swiftness and cyclone", { NextAction("cyclone on cc", 42.0f) })); + triggers.push_back(new TriggerNode( + "predator's swiftness and hibernate", { NextAction("hibernate on cc", 41.0f) })); + triggers.push_back(new TriggerNode( + "predator's swiftness and entangling roots", { NextAction("entangling roots on cc", 40.0f) })); + } + else + { + triggers.push_back(new TriggerNode( + "cyclone", { NextAction("cyclone on cc", 42.0f) })); + triggers.push_back(new TriggerNode( + "hibernate", { NextAction("hibernate on cc", 41.0f) })); + triggers.push_back(new TriggerNode( + "entangling roots", { NextAction("entangling roots on cc", 40.0f) })); + } + } } void DruidHealerDpsStrategy::InitTriggers(std::vector& triggers) @@ -149,10 +223,39 @@ void DruidHealerDpsStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("healer should attack", { - NextAction("cancel tree form", ACTION_DEFAULT + 0.4f), - NextAction("moonfire", ACTION_DEFAULT + 0.3f), - NextAction("wrath", ACTION_DEFAULT + 0.2f), - NextAction("starfire", ACTION_DEFAULT + 0.1f), -})); + NextAction("cancel tree form", 5.4f), + NextAction("moonfire", 5.3f), + NextAction("wrath", 5.2f), + NextAction("starfire", 5.1f), + })); +} +void DruidAoeStrategy::InitTriggers(std::vector& triggers) +{ + Player* bot = botAI->GetBot(); + int tab = AiFactory::GetPlayerSpecTab(bot); + + if (tab == DRUID_TAB_BALANCE) + { + triggers.push_back(new TriggerNode("hurricane channel check", { NextAction("cancel channel", 22.0f) })); + triggers.push_back(new TriggerNode("starfall", { NextAction("starfall", 28.5f) })); + triggers.push_back(new TriggerNode("medium aoe", { NextAction("hurricane", 23.0f) })); + triggers.push_back(new TriggerNode("enemy within melee", { NextAction("typhoon", 40.0f) })); + triggers.push_back(new TriggerNode("insect swarm on attacker", { NextAction("insect swarm on attacker", 5.2f) })); + triggers.push_back(new TriggerNode("moonfire on attacker", { NextAction("moonfire on attacker", 5.1f) })); + } + + if (tab == DRUID_TAB_RESTORATION) + { + triggers.push_back(new TriggerNode("hurricane channel check", { NextAction("cancel channel", 22.0f) })); + triggers.push_back(new TriggerNode("medium aoe", { NextAction("hurricane", 23.0f) })); + triggers.push_back(new TriggerNode("insect swarm on attacker", { NextAction("insect swarm on attacker", 5.2f) })); + triggers.push_back(new TriggerNode("moonfire on attacker", { NextAction("moonfire on attacker", 5.1f) })); + } + + if (tab == DRUID_TAB_FERAL && bot->HasSpell(SPELL_CAT_FORM) && !bot->HasAura(AURA_THICK_HIDE)) + { + triggers.push_back(new TriggerNode("clearcasting and medium aoe", { NextAction("swipe (cat)", 25.5f) })); + triggers.push_back(new TriggerNode("medium aoe", { NextAction("swipe (cat)", 25.0f) })); + } } diff --git a/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.h b/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.h index 2f1c2e78743..1cb88f82ab3 100644 --- a/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.h +++ b/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.h @@ -55,4 +55,13 @@ class DruidHealerDpsStrategy : public Strategy std::string const getName() override { return "healer dps"; } }; +class DruidAoeStrategy : public Strategy +{ +public: + DruidAoeStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "aoe"; } +}; + #endif diff --git a/src/Ai/Class/Druid/Strategy/HealDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/HealDruidStrategy.cpp deleted file mode 100644 index 0710e0fdc1e..00000000000 --- a/src/Ai/Class/Druid/Strategy/HealDruidStrategy.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it - * and/or modify it under version 3 of the License, or (at your option), any later version. - */ - -#include "HealDruidStrategy.h" - -#include "Playerbots.h" - -class HealDruidStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - HealDruidStrategyActionNodeFactory() { - creators["nourish on party"] = &nourtish_on_party; - } - -private: - static ActionNode* nourtish_on_party([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode("nourish on party", - /*P*/ {}, - /*A*/ { NextAction("healing touch on party") }, - /*C*/ {}); - } -}; - -HealDruidStrategy::HealDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI) -{ - actionNodeFactories.Add(new HealDruidStrategyActionNodeFactory()); -} - -void HealDruidStrategy::InitTriggers(std::vector& triggers) -{ - GenericDruidStrategy::InitTriggers(triggers); - - // no healer dps strategy - triggers.push_back(new TriggerNode("no healer dps strategy", - { NextAction("tree form", ACTION_DEFAULT) })); - - triggers.push_back(new TriggerNode( - "party member to heal out of spell range", - { NextAction("reach party member to heal", ACTION_CRITICAL_HEAL + 9) })); - - // CRITICAL - triggers.push_back( - new TriggerNode("party member critical health", - { - NextAction("tree form", ACTION_CRITICAL_HEAL + 4.1f), - NextAction("swiftmend on party", ACTION_CRITICAL_HEAL + 4), - NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 3), - NextAction("wild growth on party", ACTION_CRITICAL_HEAL + 2), - NextAction("nourish on party", ACTION_CRITICAL_HEAL + 1), - })); - - triggers.push_back( - new TriggerNode("party member critical health", - { NextAction("nature's swiftness", ACTION_CRITICAL_HEAL + 4) })); - - triggers.push_back(new TriggerNode("clearcasting", - { NextAction("lifebloom on main tank", ACTION_CRITICAL_HEAL - 1) })); - - triggers.push_back(new TriggerNode( - "group heal setting", - { - NextAction("tree form", ACTION_MEDIUM_HEAL + 2.3f), - NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 2.2f), - NextAction("rejuvenation on not full", ACTION_MEDIUM_HEAL + 2.1f), - })); - - triggers.push_back( - new TriggerNode("medium group heal setting", - { - NextAction("tree form", ACTION_CRITICAL_HEAL + 0.6f), - NextAction("tranquility", ACTION_CRITICAL_HEAL + 0.5f) })); - - // LOW - triggers.push_back( - new TriggerNode("party member low health", - { NextAction("tree form", ACTION_MEDIUM_HEAL + 1.5f), - NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 1.4f), - NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1.3f), - NextAction("swiftmend on party", ACTION_MEDIUM_HEAL + 1.2), - NextAction("nourish on party", ACTION_MEDIUM_HEAL + 1.1f), - })); - - // MEDIUM - triggers.push_back( - new TriggerNode("party member medium health", - { - NextAction("tree form", ACTION_MEDIUM_HEAL + 0.5f), - NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 0.4f), - NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 0.3f), - NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 0.2f), - NextAction("nourish on party", ACTION_MEDIUM_HEAL + 0.1f) })); - - // almost full - triggers.push_back( - new TriggerNode("party member almost full health", - { NextAction("wild growth on party", ACTION_LIGHT_HEAL + 0.3f), - NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 0.2f), - NextAction("regrowth on party", ACTION_LIGHT_HEAL + 0.1f) })); - - triggers.push_back( - new TriggerNode("medium mana", { NextAction("innervate", ACTION_HIGH + 5) })); - - triggers.push_back(new TriggerNode("enemy too close for spell", - { NextAction("flee", ACTION_MOVE + 9) })); -} diff --git a/src/Ai/Class/Druid/Strategy/HealDruidStrategy.h b/src/Ai/Class/Druid/Strategy/HealDruidStrategy.h deleted file mode 100644 index 36ce402715d..00000000000 --- a/src/Ai/Class/Druid/Strategy/HealDruidStrategy.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it - * and/or modify it under version 3 of the License, or (at your option), any later version. - */ - -#ifndef _PLAYERBOT_HEALDRUIDSTRATEGY_H -#define _PLAYERBOT_HEALDRUIDSTRATEGY_H - -#include "GenericDruidStrategy.h" -#include "Strategy.h" - -class PlayerbotAI; - -class HealDruidStrategy : public GenericDruidStrategy -{ -public: - HealDruidStrategy(PlayerbotAI* botAI); - - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "heal"; } - uint32 GetType() const override { return STRATEGY_TYPE_RANGED | STRATEGY_TYPE_HEAL; } -}; - -#endif diff --git a/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.cpp deleted file mode 100644 index 5dc0f85d914..00000000000 --- a/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it - * and/or modify it under version 3 of the License, or (at your option), any later version. - */ - -#include "MeleeDruidStrategy.h" - -#include "Playerbots.h" - -MeleeDruidStrategy::MeleeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} - -std::vector MeleeDruidStrategy::getDefaultActions() -{ - return { - NextAction("faerie fire", ACTION_DEFAULT + 0.1f), - NextAction("melee", ACTION_DEFAULT) - }; -} - -void MeleeDruidStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back( - new TriggerNode( - "omen of clarity", - { - NextAction("omen of clarity", ACTION_HIGH + 9) - } - ) - ); - - CombatStrategy::InitTriggers(triggers); -} diff --git a/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.h b/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.h deleted file mode 100644 index 67bbbbfed10..00000000000 --- a/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it - * and/or modify it under version 3 of the License, or (at your option), any later version. - */ - -#ifndef _PLAYERBOT_MELEEDRUIDSTRATEGY_H -#define _PLAYERBOT_MELEEDRUIDSTRATEGY_H - -#include "CombatStrategy.h" - -class MeleeDruidStrategy : public CombatStrategy -{ -public: - MeleeDruidStrategy(PlayerbotAI* botAI); - - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "melee"; } - std::vector getDefaultActions() override; -}; - -#endif diff --git a/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.cpp b/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.cpp deleted file mode 100644 index fb7893651c0..00000000000 --- a/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it - * and/or modify it under version 3 of the License, or (at your option), any later version. - */ - - #include "OffhealDruidCatStrategy.h" - - #include "Playerbots.h" - #include "Strategy.h" - - class OffhealDruidCatStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - OffhealDruidCatStrategyActionNodeFactory() - { - creators["cat form"] = &cat_form; - creators["mangle (cat)"] = &mangle_cat; - creators["shred"] = &shred; - creators["rake"] = &rake; - creators["rip"] = &rip; - creators["ferocious bite"] = &ferocious_bite; - creators["savage roar"] = &savage_roar; - creators["faerie fire (feral)"] = &faerie_fire_feral; - creators["healing touch on party"] = &healing_touch_on_party; - creators["regrowth on party"] = ®rowth_on_party; - creators["rejuvenation on party"] = &rejuvenation_on_party; - } - -private: - static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "cat form", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "mangle (cat)", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* shred([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "shred", - /*P*/ {}, - /*A*/ { NextAction("claw") }, - /*C*/ {} - ); - } - - static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rake", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rip", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "ferocious bite", - /*P*/ {}, - /*A*/ { NextAction("rip") }, - /*C*/ {} - ); - } - - static ActionNode* savage_roar([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "savage roar", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "faerie fire (feral)", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* healing_touch_on_party([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "healing touch on party", - /*P*/ { NextAction("caster form") }, - /*A*/ {}, - /*C*/ { NextAction("cat form") } - ); - } - - static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "regrowth on party", - /*P*/ { NextAction("caster form") }, - /*A*/ {}, - /*C*/ { NextAction("cat form") } - ); - } - - static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rejuvenation on party", - /*P*/ { NextAction("caster form") }, - /*A*/ {}, - /*C*/ { NextAction("cat form") } - ); - } -}; - -OffhealDruidCatStrategy::OffhealDruidCatStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI) -{ - actionNodeFactories.Add(new OffhealDruidCatStrategyActionNodeFactory()); -} - -std::vector OffhealDruidCatStrategy::getDefaultActions() -{ - return { - NextAction("mangle (cat)", ACTION_DEFAULT + 0.5f), - NextAction("shred", ACTION_DEFAULT + 0.4f), - NextAction("rake", ACTION_DEFAULT + 0.3f), - NextAction("melee", ACTION_DEFAULT), - NextAction("cat form", ACTION_DEFAULT - 0.1f) - }; -} - -void OffhealDruidCatStrategy::InitTriggers(std::vector& triggers) -{ - FeralDruidStrategy::InitTriggers(triggers); - - triggers.push_back( - new TriggerNode( - "cat form", - { - NextAction("cat form", ACTION_HIGH + 8) - } - ) - ); - triggers.push_back( - new TriggerNode( - "savage roar", - { - NextAction("savage roar", ACTION_HIGH + 7) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points 5 available", - { - NextAction("rip", ACTION_HIGH + 6) - } - ) - ); - triggers.push_back( - new TriggerNode( - "ferocious bite time", - { - NextAction("ferocious bite", ACTION_HIGH + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "target with combo points almost dead", - { - NextAction("ferocious bite", ACTION_HIGH + 4) - } - ) - ); - triggers.push_back( - new TriggerNode( - "mangle (cat)", - { - NextAction("mangle (cat)", ACTION_HIGH + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "rake", - { - NextAction("rake", ACTION_HIGH + 2) - } - ) - ); - triggers.push_back( - new TriggerNode( - "almost full energy available", - { - NextAction("shred", ACTION_DEFAULT + 0.4f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points not full", - { - NextAction("shred", ACTION_DEFAULT + 0.4f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "faerie fire (feral)", - { - NextAction("faerie fire (feral)", ACTION_NORMAL) - } - ) - ); - triggers.push_back( - new TriggerNode( - "enemy out of melee", - { - NextAction("feral charge - cat", ACTION_HIGH + 9), - NextAction("dash", ACTION_HIGH + 8) - } - ) - ); - triggers.push_back( - new TriggerNode( - "medium aoe", - { - NextAction("swipe (cat)", ACTION_HIGH + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "tiger's fury", - { - NextAction("tiger's fury", ACTION_NORMAL + 1) - } - ) - ); - triggers.push_back( - new TriggerNode( - "party member critical health", - { - NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 6), - NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "party member low health", - { - NextAction("healing touch on party", ACTION_MEDIUM_HEAL + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "party member medium health", - { - NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 8) - } - ) - ); - triggers.push_back( - new TriggerNode( - "party member to heal out of spell range", - { - NextAction("reach party member to heal", ACTION_EMERGENCY + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "low mana", - { - NextAction("innervate", ACTION_HIGH + 4) - } - ) - ); -} diff --git a/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.h b/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.h deleted file mode 100644 index 83775ef98c0..00000000000 --- a/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it - * and/or modify it under version 3 of the License, or (at your option), any later version. - */ - - #ifndef _PLAYERBOT_OFFHEALDRUIDCATSTRATEGY_H - #define _PLAYERBOT_OFFHEALDRUIDCATSTRATEGY_H - - #include "FeralDruidStrategy.h" - - class PlayerbotAI; - - class OffhealDruidCatStrategy : public FeralDruidStrategy - { - public: - OffhealDruidCatStrategy(PlayerbotAI* botAI); - - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "offheal"; } - std::vector getDefaultActions() override; - uint32 GetType() const override - { - return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_HEAL | STRATEGY_TYPE_MELEE; - } - }; - - #endif diff --git a/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.cpp new file mode 100644 index 00000000000..5f086ee507b --- /dev/null +++ b/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "RestoDruidStrategy.h" + +#include "Playerbots.h" + +class RestoDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + RestoDruidStrategyActionNodeFactory() { + creators["nourish on party"] = &nourish_on_party; + } + +private: + static ActionNode* nourish_on_party([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("nourish on party", + /*P*/ {}, + /*A*/ {}, + /*C*/ {}); + } +}; + +RestoDruidStrategy::RestoDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI) +{ + actionNodeFactories.Add(new RestoDruidStrategyActionNodeFactory()); +} + +void RestoDruidStrategy::InitTriggers(std::vector& triggers) +{ + GenericDruidStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode("no healer dps strategy", + { NextAction("tree form", 5.0f) })); + + triggers.push_back(new TriggerNode( + "party member to heal out of spell range", + { NextAction("reach party member to heal", 39.0f) })); + + triggers.push_back( + new TriggerNode("party member critical health", + { + NextAction("tree form", 34.1f), + NextAction("swiftmend on party", 34.0f), + NextAction("wild growth on party", 33.0f), + NextAction("nourish on party", 32.0f), + NextAction("regrowth on party", 31.0f), + NextAction("healing touch on party", 30.0f), + })); + + triggers.push_back( + new TriggerNode("party member critical health", + { NextAction("nature's swiftness", 58.0f) })); + + triggers.push_back(new TriggerNode( + "nature's swiftness active", + { NextAction("healing touch on party", 55.0f) })); + + triggers.push_back(new TriggerNode("clearcasting", + { NextAction("lifebloom on main tank", 13.0f) })); + + // LOW + triggers.push_back( + new TriggerNode("party member low health", + { + NextAction("tree form", 21.5f), + NextAction("swiftmend on party", 21.4f), + NextAction("wild growth on party", 21.3f), + NextAction("nourish on party", 21.2f), + NextAction("regrowth on party", 21.1f), + NextAction("healing touch on party", 21.0f), + })); + + // MEDIUM + triggers.push_back( + new TriggerNode("party member medium health", + { + NextAction("tree form", 20.5f), + NextAction("swiftmend on party", 20.4f), + NextAction("wild growth on party", 20.3f), + NextAction("nourish on party", 20.2f), + NextAction("regrowth on party", 20.1f), + NextAction("healing touch on party", 20.0f), + })); + + // ALMOST FULL + triggers.push_back( + new TriggerNode("party member almost full health", + { + NextAction("wild growth on party", 10.3f), + NextAction("rejuvenation on party", 10.2f), + NextAction("regrowth on party", 10.1f), + })); + + triggers.push_back( + new TriggerNode("medium mana", { NextAction("innervate", 25.0f) })); + + triggers.push_back(new TriggerNode("enemy too close for spell", + { NextAction("flee", 39.0f) })); +} + +void DruidTranquilityStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back(new TriggerNode("medium group heal setting", + { NextAction("tree form", 30.6f), NextAction("tranquility", 30.5f) })); +} + +void DruidBlanketStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back(new TriggerNode( + "wild growth blanket", + { NextAction("tree form", 8.1f), NextAction("wild growth blanket", 8.0f) })); + + triggers.push_back(new TriggerNode( + "rejuvenation blanket", + { NextAction("tree form", 6.1f), NextAction("rejuvenation blanket", 6.0f) })); +} diff --git a/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.h b/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.h new file mode 100644 index 00000000000..afc3a93bc0b --- /dev/null +++ b/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_RESTODRUIDSTRATEGY_H +#define _PLAYERBOT_RESTODRUIDSTRATEGY_H + +#include "GenericDruidStrategy.h" +#include "Strategy.h" + +class PlayerbotAI; + +class RestoDruidStrategy : public GenericDruidStrategy +{ +public: + RestoDruidStrategy(PlayerbotAI* botAI); + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "resto"; } + uint32 GetType() const override { return STRATEGY_TYPE_RANGED | STRATEGY_TYPE_HEAL; } +}; + +class DruidBlanketStrategy : public Strategy +{ +public: + DruidBlanketStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "blanketing"; } +}; + +class DruidTranquilityStrategy : public Strategy +{ +public: + DruidTranquilityStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "tranquility"; } +}; + +#endif diff --git a/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp b/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp index 6cf8552a3af..5a8dbdfa4d7 100644 --- a/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp +++ b/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp @@ -4,8 +4,10 @@ */ #include "DruidTriggers.h" +#include "DynamicObject.h" #include "Player.h" #include "Playerbots.h" +#include "ServerFacade.h" bool MarkOfTheWildOnPartyTrigger::IsActive() { @@ -35,6 +37,45 @@ bool TreeFormTrigger::IsActive() { return !botAI->HasAura(33891, bot); } bool CatFormTrigger::IsActive() { return !botAI->HasAura("cat form", bot); } +bool AquaticFormTrigger::IsActive() +{ + return !bot->IsInCombat() && !botAI->HasAura("aquatic form", bot) && + bot->GetLiquidData().Status == LIQUID_MAP_UNDER_WATER; +} + +bool ProwlTrigger::IsActive() +{ + if (botAI->HasAura("prowl", bot) || bot->IsInCombat()) + return false; + + uint32 prowlId = botAI->GetAiObjectContext()->GetValue("spell id", "prowl")->Get(); + if (!prowlId || !bot->HasSpell(prowlId) || bot->HasSpellCooldown(prowlId)) + return false; + + float distance = 30.f; + + Unit* target = AI_VALUE(Unit*, "enemy player target"); + if (target && !target->IsInWorld()) + return false; + if (!target) + target = AI_VALUE(Unit*, "grind target"); + if (!target) + target = AI_VALUE(Unit*, "dps target"); + if (!target) + return false; + + if (target && target->GetVictim()) + distance -= 10; + if (target->isMoving() && target->GetVictim()) + distance -= 10; + if (bot->InBattleground()) + distance += 15; + if (bot->InArena()) + distance += 15; + + return target && ServerFacade::instance().GetDistance2d(bot, target) < distance; +} + const std::set HurricaneChannelCheckTrigger::HURRICANE_SPELL_IDS = { 16914, // Hurricane Rank 1 17401, // Hurricane Rank 2 @@ -45,19 +86,38 @@ const std::set HurricaneChannelCheckTrigger::HURRICANE_SPELL_IDS = { bool HurricaneChannelCheckTrigger::IsActive() { - Player* bot = botAI->GetBot(); - - // Check if the bot is channeling a spell if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) { - // Only trigger if the spell being channeled is Hurricane - if (HURRICANE_SPELL_IDS.count(spell->m_spellInfo->Id)) + if (!HURRICANE_SPELL_IDS.count(spell->m_spellInfo->Id)) + return false; + + // Find this bot's own Hurricane DynamicObject + DynamicObject* dynObj = nullptr; + for (uint32 spellId : HURRICANE_SPELL_IDS) { - uint8 attackerCount = AI_VALUE(uint8, "attacker count"); - return attackerCount < minEnemies; + dynObj = bot->GetDynObject(spellId); + if (dynObj) + break; } + + if (!dynObj) + return false; + + // Count attackers actually inside the Hurricane AoE + float radius = dynObj->GetRadius(); + GuidVector attackers = AI_VALUE(GuidVector, "attackers"); + uint32 count = 0; + for (ObjectGuid const& guid : attackers) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + if (unit->GetDistance(dynObj->GetPosition()) <= radius) + count++; + } + + return count < minEnemies; } - // Not channeling Hurricane return false; } diff --git a/src/Ai/Class/Druid/Trigger/DruidTriggers.h b/src/Ai/Class/Druid/Trigger/DruidTriggers.h index 01daaeba65a..1f389c947a8 100644 --- a/src/Ai/Class/Druid/Trigger/DruidTriggers.h +++ b/src/Ai/Class/Druid/Trigger/DruidTriggers.h @@ -8,6 +8,7 @@ #include "CureTriggers.h" #include "GenericTriggers.h" +#include "RtiTriggers.h" #include "Player.h" #include "PlayerbotAI.h" #include "Playerbots.h" @@ -15,6 +16,8 @@ #include "Trigger.h" #include +constexpr uint32 AURA_OMEN_OF_CLARITY = 16864; + class PlayerbotAI; class MarkOfTheWildOnPartyTrigger : public BuffOnPartyTrigger @@ -55,22 +58,30 @@ class ThornsTrigger : public BuffTrigger bool IsActive() override; }; -class OmenOfClarityTrigger : public BuffTrigger +class ClearcastingTrigger : public HasAuraTrigger { public: - OmenOfClarityTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "omen of clarity") {} + ClearcastingTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "clearcasting") {} }; -class ClearcastingTrigger : public HasAuraTrigger +class PredatorsSwiftnessTrigger : public HasAuraTrigger { public: - ClearcastingTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "clearcasting") {} + PredatorsSwiftnessTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "predator's swiftness") {} +}; + +class NaturesSwiftnessActiveTrigger : public HasAuraTrigger +{ +public: + NaturesSwiftnessActiveTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "nature's swiftness") {} + bool IsActive() override { return botAI->HasAura("nature's swiftness", bot); } }; class RakeTrigger : public DebuffTrigger { public: RakeTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "rake", 1, true) {} + bool IsActive() override { return !botAI->HasAura("prowl", bot) && DebuffTrigger::IsActive(); } }; class InsectSwarmTrigger : public DebuffTrigger @@ -79,12 +90,26 @@ class InsectSwarmTrigger : public DebuffTrigger InsectSwarmTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "insect swarm", 1, true) {} }; +class InsectSwarmOnAttackerTrigger : public DebuffOnAttackerTrigger +{ +public: + InsectSwarmOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "insect swarm", true) {} + bool IsActive() override { return BuffTrigger::IsActive(); } +}; + class MoonfireTrigger : public DebuffTrigger { public: MoonfireTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "moonfire", 1, true) {} }; +class MoonfireOnAttackerTrigger : public DebuffOnAttackerTrigger +{ +public: + MoonfireOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "moonfire", true) {} + bool IsActive() override { return BuffTrigger::IsActive(); } +}; + class FaerieFireTrigger : public DebuffTrigger { public: @@ -95,6 +120,35 @@ class FaerieFireFeralTrigger : public DebuffTrigger { public: FaerieFireFeralTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "faerie fire (feral)") {} + + bool IsActive() override + { + if (!bot->IsInCombat()) + return false; + + // Bear: every cast generates immediate threat/damage for free — spam it + if (botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr)) + { + Unit* target = GetTarget(); + return target && target->IsAlive() && target->IsInWorld(); + } + + if (!botAI->HasAura("cat form", bot)) + return false; + + if (botAI->HasAura("prowl", bot)) + return false; + + // Cat with Omen of Clarity: spam to fish for Clearcasting procs + if (bot->HasAura(AURA_OMEN_OF_CLARITY)) + { + Unit* target = GetTarget(); + return target && target->IsAlive() && target->IsInWorld(); + } + + // Cat without Omen of Clarity: apply as a normal debuff, don't reapply + return DebuffTrigger::IsActive(); + } }; class BashInterruptSpellTrigger : public InterruptSpellTrigger @@ -103,16 +157,16 @@ class BashInterruptSpellTrigger : public InterruptSpellTrigger BashInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "bash") {} }; -class TigersFuryTrigger : public BuffTrigger +class BerserkTrigger : public BoostTrigger { public: - TigersFuryTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "tiger's fury") {} + BerserkTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "berserk") {} }; -class BerserkTrigger : public BoostTrigger +class BerserkActiveTrigger : public HasAuraTrigger { public: - BerserkTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "berserk") {} + BerserkActiveTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "berserk") {} }; class SavageRoarTrigger : public BuffTrigger @@ -127,10 +181,10 @@ class NaturesGraspTrigger : public BuffTrigger NaturesGraspTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "nature's grasp") {} }; -class EntanglingRootsTrigger : public HasCcTargetTrigger +class EntanglingRootsTrigger : public RtiCcTrigger { public: - EntanglingRootsTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "entangling roots") {} + EntanglingRootsTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "entangling roots") {} }; class EntanglingRootsKiteTrigger : public DebuffTrigger @@ -141,10 +195,16 @@ class EntanglingRootsKiteTrigger : public DebuffTrigger bool IsActive() override; }; -class HibernateTrigger : public HasCcTargetTrigger +class HibernateTrigger : public RtiCcTrigger +{ +public: + HibernateTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "hibernate") {} +}; + +class CycloneTrigger : public RtiCcTrigger { public: - HibernateTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "hibernate") {} + CycloneTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "cyclone") {} }; class CurePoisonTrigger : public NeedCureTrigger @@ -185,6 +245,14 @@ class CatFormTrigger : public BuffTrigger bool IsActive() override; }; +class AquaticFormTrigger : public Trigger +{ +public: + AquaticFormTrigger(PlayerbotAI* botAI) : Trigger(botAI, "aquatic form") {} + + bool IsActive() override; +}; + class EclipseSolarTrigger : public HasAuraTrigger { public: @@ -218,18 +286,69 @@ class DruidPartyMemberRemoveCurseTrigger : public PartyMemberNeedCureTrigger } }; -class EclipseSolarCooldownTrigger : public SpellCooldownTrigger +class StarfallTrigger : public SpellNoCooldownTrigger +{ +public: + StarfallTrigger(PlayerbotAI* botAI) : SpellNoCooldownTrigger(botAI, "starfall") {} +}; + +class ForceOfNatureTrigger : public BoostTrigger +{ +public: + ForceOfNatureTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "force of nature") {} +}; + +class MangleBearTrigger : public DebuffTrigger +{ +public: + MangleBearTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "mangle (bear)") {} + + bool IsActive() override + { + if (!bot->IsInCombat() || !botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr)) + return false; + Unit* target = GetTarget(); + return target && target->IsAlive() && target->IsInWorld(); + } +}; + +class LacerateTrigger : public DebuffTrigger { public: - EclipseSolarCooldownTrigger(PlayerbotAI* ai) : SpellCooldownTrigger(ai, "eclipse (solar)") {} - bool IsActive() override { return bot->HasSpellCooldown(48517); } + LacerateTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "lacerate") {} + + bool IsActive() override + { + if (!bot->IsInCombat() || !botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr)) + return false; + + Unit* target = GetTarget(); + if (!target || !target->IsAlive() || !target->IsInWorld()) + return false; + + Aura* lacerate = botAI->GetAura("lacerate", target, false, false); + if (!lacerate) + return true; + + if (lacerate->GetStackAmount() < 5) + return true; + + return lacerate->GetDuration() <= 6000; + } }; -class EclipseLunarCooldownTrigger : public SpellCooldownTrigger +class DemoralizeRoarTrigger : public DebuffTrigger { public: - EclipseLunarCooldownTrigger(PlayerbotAI* ai) : SpellCooldownTrigger(ai, "eclipse (lunar)") {} - bool IsActive() override { return bot->HasSpellCooldown(48518); } + DemoralizeRoarTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "demoralizing roar") {} + + bool IsActive() override + { + return DebuffTrigger::IsActive() + && !botAI->HasAura("curse of weakness", GetTarget(), false, false) + && !botAI->HasAura("demoralizing shout", GetTarget(), false, false) + && !botAI->HasAura("vindication", GetTarget(), false, false); + } }; class MangleCatTrigger : public DebuffTrigger @@ -238,6 +357,8 @@ class MangleCatTrigger : public DebuffTrigger MangleCatTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "mangle (cat)", 1, false, 0.0f) {} bool IsActive() override { + if (botAI->HasAura("prowl", bot)) + return false; return DebuffTrigger::IsActive() && !botAI->HasAura("mangle (bear)", GetTarget(), false, false, -1, true) && !botAI->HasAura("trauma", GetTarget(), false, false, -1, true); } @@ -271,10 +392,36 @@ class FerociousBiteTimeTrigger : public Trigger } }; +class FerociousBiteExecuteTrigger : public Trigger +{ +public: + FerociousBiteExecuteTrigger(PlayerbotAI* ai) : Trigger(ai, "ferocious bite execute") {} + bool IsActive() override + { + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target || !target->IsAlive()) + return false; + + if (!AI_VALUE2(uint32, "spell id", "ferocious bite")) + return false; + + if (AI_VALUE2(uint8, "combo", "current target") < 1) + return false; + + if (target->GetHealthPct() >= 25.0f) + return false; + + if (target->GetHealth() >= 20000) + return false; + + return true; + } +}; + class HurricaneChannelCheckTrigger : public Trigger { public: - HurricaneChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2) + HurricaneChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 3) : Trigger(botAI, "hurricane channel check"), minEnemies(minEnemies) { } @@ -297,4 +444,12 @@ class NoHealerDpsStrategyTrigger : public Trigger } }; +class ProwlTrigger : public Trigger +{ +public: + ProwlTrigger(PlayerbotAI* botAI) : Trigger(botAI, "prowl") {} + + bool IsActive() override; +}; + #endif diff --git a/src/Ai/Class/Warlock/Action/WarlockActions.cpp b/src/Ai/Class/Warlock/Action/WarlockActions.cpp index 93798b702f6..5e23f1af5ac 100644 --- a/src/Ai/Class/Warlock/Action/WarlockActions.cpp +++ b/src/Ai/Class/Warlock/Action/WarlockActions.cpp @@ -27,6 +27,9 @@ bool CastDrainSoulAction::isUseful() { return AI_VALUE2(uint32, "item count", "s // Checks if the bot's health is above a certain threshold, and if so, allows casting Life Tap bool CastLifeTapAction::isUseful() { return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.lowHealth; } +Value* CastBanishOnCcAction::GetTargetValue() { return context->GetValue("rti cc target"); } +Value* CastFearOnCcAction::GetTargetValue() { return context->GetValue("rti cc target"); } + // Checks if the target marked with the moon icon can be banished bool CastBanishOnCcAction::isPossible() { diff --git a/src/Ai/Class/Warlock/Action/WarlockActions.h b/src/Ai/Class/Warlock/Action/WarlockActions.h index 09f34637031..8fca0fdc402 100644 --- a/src/Ai/Class/Warlock/Action/WarlockActions.h +++ b/src/Ai/Class/Warlock/Action/WarlockActions.h @@ -170,6 +170,7 @@ class CastBanishOnCcAction : public CastCrowdControlSpellAction { public: CastBanishOnCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "banish") {} + Value* GetTargetValue() override; bool isPossible() override; }; @@ -177,6 +178,7 @@ class CastFearOnCcAction : public CastCrowdControlSpellAction { public: CastFearOnCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "fear") {} + Value* GetTargetValue() override; bool isPossible() override; }; diff --git a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp b/src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp index 1df531e7fd7..14e7a52e585 100644 --- a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp +++ b/src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp @@ -50,22 +50,6 @@ bool TooManySoulShardsTrigger::IsActive() { return GetSoulShardCount(botAI->GetB bool OutOfSoulstoneTrigger::IsActive() { return GetSoulstoneCount(botAI->GetBot()) == 0; } -// Checks if the target marked with the moon icon can be banished -bool BanishTrigger::IsActive() -{ - Unit* ccTarget = context->GetValue("cc target", "banish")->Get(); - Unit* moonTarget = context->GetValue("rti cc target")->Get(); - return ccTarget && moonTarget && ccTarget == moonTarget && HasCcTargetTrigger::IsActive(); -} - -// Checks if the target marked with the moon icon can be feared -bool FearTrigger::IsActive() -{ - Unit* ccTarget = context->GetValue("cc target", "fear")->Get(); - Unit* moonTarget = context->GetValue("rti cc target")->Get(); - return ccTarget && moonTarget && ccTarget == moonTarget && HasCcTargetTrigger::IsActive(); -} - bool DemonArmorTrigger::IsActive() { Unit* target = GetTarget(); diff --git a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.h b/src/Ai/Class/Warlock/Trigger/WarlockTriggers.h index d66fe537e1f..15991e270b3 100644 --- a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.h +++ b/src/Ai/Class/Warlock/Trigger/WarlockTriggers.h @@ -8,6 +8,7 @@ #include "GenericTriggers.h" #include "PlayerbotAI.h" +#include "RtiTriggers.h" #include "Playerbots.h" #include "CureTriggers.h" #include "Trigger.h" @@ -137,18 +138,16 @@ class WrongPetTrigger : public Trigger // CC and Pet Triggers -class BanishTrigger : public HasCcTargetTrigger +class BanishTrigger : public RtiCcTrigger { public: - BanishTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "banish") {} - bool IsActive() override; + BanishTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "banish") {} }; -class FearTrigger : public HasCcTargetTrigger +class FearTrigger : public RtiCcTrigger { public: - FearTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "fear") {} - bool IsActive() override; + FearTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "fear") {} }; class SpellLockInterruptSpellTrigger : public InterruptSpellTrigger diff --git a/src/Bot/Factory/AiFactory.cpp b/src/Bot/Factory/AiFactory.cpp index c95180538e7..f1fd2491f47 100644 --- a/src/Bot/Factory/AiFactory.cpp +++ b/src/Bot/Factory/AiFactory.cpp @@ -338,15 +338,17 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa break; case CLASS_DRUID: if (tab == DRUID_TAB_BALANCE) - engine->addStrategiesNoInit("caster", "cure", "caster aoe", "caster debuff", "dps assist", nullptr); + { + engine->addStrategiesNoInit("balance", "cure", "aoe", "cc", "dps assist", nullptr); + } else if (tab == DRUID_TAB_RESTORATION) - engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr); - else // if (tab == DRUID_TAB_FERAL) + engine->addStrategiesNoInit("resto", "cure", "dps assist", "blanketing", "tranquility", nullptr); + else { if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/) - engine->addStrategiesNoInit("cat", "dps assist", nullptr); + engine->addStrategiesNoInit("cat", "aoe", "cc", "dps assist", "feral charge", nullptr); else - engine->addStrategiesNoInit("bear", "tank assist", "pull", "pull back", nullptr); + engine->addStrategiesNoInit("bear", "tank assist", "pull", "pull back", "feral charge", nullptr); } break; case CLASS_HUNTER: @@ -420,8 +422,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa { if (tab == DRUID_TAB_RESTORATION) { - engine->addStrategiesNoInit("caster", "caster aoe", nullptr); - engine->addStrategy("caster debuff", false); + engine->addStrategiesNoInit("aoe", nullptr); } break; } From 82a92f6296e624c96c92c661227ba79a923cfcab Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Tue, 26 May 2026 09:01:56 -0700 Subject: [PATCH 42/63] Modify Illidan distbeyondtrap --- src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp b/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp index 0d47dbf0720..cd6c06892f0 100644 --- a/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp +++ b/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp @@ -1941,7 +1941,7 @@ bool IllidanStormrageMainTankRepositionBossAction::MoveToShadowTrap(GameObject* if (distToTrap <= 0.0f) return false; - constexpr float distBeyondTrap = 4.0f; + constexpr float distBeyondTrap = 6.0f; const float dx = trapX - bot->GetPositionX(); const float dy = trapY - bot->GetPositionY(); From 9bba4b78dd3b38d49e18e826061246fecb20b542 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 30 May 2026 01:08:21 -0500 Subject: [PATCH 43/63] Overhaul party buff/greater blessing system (#2358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Pull Request Description These changes I originally made for myself because as a person who really likes to raid with bots, I felt like the current group buff system is fundamentally broken, and I needed something more consistent and optimal. I debated a lot whether to PR this because it's such an extensive overhaul that was almost entirely reliant on AI, and I know that wishmaster still has a PR open regarding the greater blessings. I decided to after a couple of conversations so at least people can look at it and see if it's something that they want. The tl;dr version is that this PR overhauls buff handling in two related areas: 1. It adds a dedicated greater blessing assignment system. 2. It generalizes party/raid reagent-buff handling for Paladins, Druids, Mages, and Priests. Under this PR, greater blessings are determined by assignments for the current group, and those assignments are determined based on: 1. a hardcoded priority list of blessings for each spec; 2. the number of Paladins in the group; and 3. whether any Paladins have talents for Blessing of Sanctuary, Improved Blessing of Might, or Improved Blessing of Wisdom. Assignment determinations are cached in a value to avoid constant reevaluation. The exact priority list is: - All casters: Kings, Wisdom, Sanctuary, Might - Physical-only DPS (Rogues, Warriors, DKs): Might, Kings, Sanctuary, N/A - Hybrid DPS (Enh, Ret, Hunters, Cats): Might, Kings, Wisdom, Sanctuary - Druid tanks: Kings, Might, Sanctuary, Wisdom - Warrior and DK tanks: Kings, Might, Sanctuary, N/A - Paladin tank: Sanctuary, Might, Wisdom, Kings Note that Sanctuary is preferred over Kings for Paladin tanks because of the mana regen component but deprioritized for other tanks because Kings provides Agility. The extra 3% damage reduction from Sanctuary does not stack with Disc Priests’ Renewed Hope, which will have 100% uptime. For group buffs, logic is centralized so that class triggers use the same gating and upgrade rules for Gift of the Wild, Arcane Brilliance, Prayer of Fortitude, Prayer of Spirit, and Prayer of Shadow Protection. Also, Shadow Protection is now a default strategy for Priests (rshadow, which existed before but wasn’t added by default). I’ve added a config setting for the greater blessing system and adjusted the current config setting for group buffs. In each case, you can pick whether to disable the feature entirely, use it in all groups, or use it only in raid groups. The default is raid only for greater blessings and all groups for group buffs. Note that for group buffs, even if the config is enabled, they will be used only if at least 3 group/raid members on the same map are missing the buff family. This is mainly to stop group buff spamming during wipe recovery as bots are revived one-by-one. I renamed the Paladin buff strategies to align them with the actual blessing names: - `bhealth` -> `bsanc` - `bmana` -> `bwisdom` - `bdps` -> `bmight` - `bstats` -> `bkings` This is an intentional breaking change for saved strategy strings. Bots will need a one-time strategy reset after update. I removed bots telling you when they are out of reagents for greater blessings. If people like that though, I can add it back. A small cleanup is also included in TankPaladinStrategy: Holy Shield was subject to three overlapping health triggers with the same priority; I removed the two lower health thresholds which have no purpose. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. I’m going to let the AI answer this one. > The minimum logic is: > - a shared config-gated check for whether group/raid buff variants are allowed > - a shared way to treat single and group variants as equivalent aura families > - a shared upgrade path from single-target buff to group buff when the group variant is appropriate > - a Paladin-only cached assignment model that decides which blessing family each Paladin should cover for the current group > - trigger/action wiring that only attempts casts when a group member is actually missing the assigned buff > > This avoids scattering separate per-class heuristics across many triggers and actions. - Describe the **processing cost** when this logic executes across many bots. Processing cost should be minimal but non-zero. The general party buff changes are limited to existing buff trigger paths and mostly replace duplicated checks with shared helpers. They do not add expensive default per-tick behavior outside those existing trigger evaluations. The Paladin greater blessing logic does add extra decision-making, but it is limited to Paladins, gated by config and group eligibility, subject to a delayed trigger evaluation of only once per 4s, and cached per group assignment set instead of recomputing the full assignment model on every action attempt. This PR also increases the throttle duration for group buff triggers to limit performance impact; I’m open to adjustments to these durations: - Mark of the Wild triggers were increased from 4s to 8s - Arcane Intellect triggers were increased from 4s to 8s - Priest buff triggers were increased to 8s (previously, Fortitude was 6s, Spirit was 4s, and Shadow Protection had no throttle) - There is now a 5s delay on buffing (greater blessings and group buffs) after bots log in—I was getting bots spamming buffs as soon as they logged in even when it was not necessary I’ve tested with pmon, and the impact is minimal—these are very cheap triggers even compared to standard bot rotational ability triggers. ## How to Test the Changes 1. Try different config settings to confirm that they work to enable/disable greater blessings/group buffs in the configured scenarios 2. For greater blessing changes: - test with one Paladin in a party/raid - test with multiple Paladins in a party/raid - confirm the Paladins divide blessing coverage instead of repeatedly overwriting each other - include at least one Paladin with Improved Blessing of Might and make sure it casts Might over Paladins without the talent; check the same with a Paladin with Improved Blessing of Wisdom - do not include a Paladin that knows Sanctuary, confirm any Paladin tank receives Kings instead (you’ll need a low-level Paladin for this since Sanctuary is a prot talent) - confirm bots cast blessings only when a member is actually missing the relevant blessing family - confirm there is a 5s delay on buffing when bots log in 3. For group buff changes: - confirm there is a 5s delay on buffing when bots log in - confirm that single buffs are used when there aren’t at least three unbuffed members in the same map, even if group buffs are enabled in the config 4. For all buffs, test with reagents missing to confirm fallback to single-target buffs and single blessings 5. Confirm the Paladin buff strategy names are changed after resetting AI ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [x] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) Discussed above in processing costs. - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Yes—that is the purpose of this PR, to change default buffing behavior. - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [x] Yes (**explain below**) Yes, but I think it’s inevitable to add complexity to get greater blessings to function consistently, given the challenges brought by their mechanic of applying across each class. ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) I used GPT-5.4 extensively for this overhaul. It’s much more complicated than I could handle on my own. I’ve done a lot of testing and have reviewed the code and provided plenty of revisions, but I cannot say I can perfectly explain each addition and how it works, not even close. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- conf/playerbots.conf.dist | 36 +- src/Ai/Base/Actions/GenericSpellActions.cpp | 140 +-- src/Ai/Base/Actions/GenericSpellActions.h | 56 +- src/Ai/Base/Trigger/GenericTriggers.cpp | 154 +-- src/Ai/Base/Trigger/GenericTriggers.h | 93 +- src/Ai/Base/Util/GenericBuffUtils.cpp | 230 ++-- src/Ai/Base/Util/GenericBuffUtils.h | 51 +- src/Ai/Class/Druid/Action/DruidActions.h | 8 +- src/Ai/Class/Druid/Trigger/DruidTriggers.cpp | 5 - src/Ai/Class/Druid/Trigger/DruidTriggers.h | 6 +- src/Ai/Class/Mage/Action/MageActions.h | 8 +- src/Ai/Class/Mage/Trigger/MageTriggers.cpp | 5 - src/Ai/Class/Mage/Trigger/MageTriggers.h | 5 +- .../Class/Paladin/Action/PaladinActions.cpp | 438 ++++--- src/Ai/Class/Paladin/Action/PaladinActions.h | 24 +- .../Action/PaladinGreaterBlessingAction.cpp | 1116 +++++++++++++++++ .../Action/PaladinGreaterBlessingAction.h | 267 ++++ .../Class/Paladin/PaladinAiObjectContext.cpp | 73 +- .../GenericPaladinNonCombatStrategy.cpp | 3 + .../Paladin/Strategy/PaladinBuffStrategies.h | 8 +- .../Paladin/Strategy/TankPaladinStrategy.cpp | 34 +- .../Class/Paladin/Trigger/PaladinTriggers.cpp | 39 +- .../Class/Paladin/Trigger/PaladinTriggers.h | 70 +- src/Ai/Class/Paladin/Util/PaladinHelper.h | 2 + src/Ai/Class/Priest/Action/PriestActions.h | 48 +- src/Ai/Class/Priest/PriestAiObjectContext.cpp | 11 - .../Strategy/PriestNonCombatStrategy.cpp | 6 - ...PriestNonCombatStrategyActionNodeFactory.h | 16 - .../Class/Priest/Trigger/PriestTriggers.cpp | 36 +- src/Ai/Class/Priest/Trigger/PriestTriggers.h | 44 +- src/Bot/Factory/AiFactory.cpp | 10 +- src/Bot/PlayerbotAI.cpp | 23 - src/Bot/PlayerbotAI.h | 1 - src/PlayerbotAIConfig.cpp | 28 +- src/PlayerbotAIConfig.h | 15 +- 35 files changed, 2283 insertions(+), 826 deletions(-) create mode 100644 src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.cpp create mode 100644 src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.h diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 6ea92b2d686..81f1b47b2d1 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -22,7 +22,6 @@ # THRESHOLDS # QUESTS # COMBAT -# GREATER BUFFS STRATEGIES # CHEATS # SPELLS # FLIGHTPATH @@ -478,6 +477,23 @@ AiPlayerbot.AutoSaveMana = 1 # Default: 60 (60%) AiPlayerbot.SaveManaThreshold = 60 +# Enable Paladin bots to use greater blessings, with the blessing used being based on the +# number of Paladins in the raid/group and the spec of the recipient. Priorities for each +# spec are hardcoded in GreaterBlessingActions.h. +# 0 = disabled +# 1 = enabled in raid groups only +# 2 = enabled in all groups +# Default: 1 (raid only) +AiPlayerbot.AutoGreaterBlessings = 1 + +# Enable bots to use group reagent buffs: Gift of the Wild, Arcane Brilliance, +# Prayer of Fortitude, Prayer of Spirit, and Prayer of Shadow Protection. +# 0 = disabled +# 1 = enabled in raid groups only +# 2 = enabled in all groups +# Default: 2 (all groups) +AiPlayerbot.AutoPartyBuffs = 2 + # Bots can flee from enemies AiPlayerbot.FleeingEnabled = 1 @@ -486,24 +502,6 @@ AiPlayerbot.FleeingEnabled = 1 # #################################################################################################### -#################################################################################################### -# GREATER BUFFS STRATEGIES -# -# - -# Min group size to use Greater buffs (Paladin, Mage, Druid) -# Default: 3 -AiPlayerbot.MinBotsForGreaterBuff = 3 - -# Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff -# Default: 30 -AiPlayerbot.RPWarningCooldown = 30 - -# -# -# -#################################################################################################### - #################################################################################################### # CHEATS # diff --git a/src/Ai/Base/Actions/GenericSpellActions.cpp b/src/Ai/Base/Actions/GenericSpellActions.cpp index 587862a29fe..d4b54f16fd3 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.cpp +++ b/src/Ai/Base/Actions/GenericSpellActions.cpp @@ -24,9 +24,7 @@ using ai::buff::MakeAuraQualifierForBuff; using ai::spell::HasSpellOrCategoryCooldown; CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell) - : Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell) -{ -} + : Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell) {} bool CastSpellAction::Execute(Event /*event*/) { @@ -53,18 +51,12 @@ bool CastSpellAction::Execute(Event /*event*/) wstrToLower(wnamepart); - if (!Utf8FitTo(spell, wnamepart)) - continue; - - if (spellInfo->Effects[0].Effect != SPELL_EFFECT_CREATE_ITEM) + if (!Utf8FitTo(spell, wnamepart) || spellInfo->Effects[0].Effect != SPELL_EFFECT_CREATE_ITEM) continue; uint32 itemId = spellInfo->Effects[0].ItemType; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); - if (!proto) - continue; - - if (bot->CanUseItem(proto) != EQUIP_ERR_OK) + if (!proto || bot->CanUseItem(proto) != EQUIP_ERR_OK) continue; if (spellInfo->Id > castId) @@ -92,10 +84,7 @@ bool CastSpellAction::isUseful() } Unit* spellTarget = GetTarget(); - if (!spellTarget) - return false; - - if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId()) + if (!spellTarget || !spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId()) return false; // float combatReach = bot->GetCombatReach() + target->GetCombatReach(); @@ -143,10 +132,7 @@ CastMeleeSpellAction::CastMeleeSpellAction( bool CastMeleeSpellAction::isUseful() { Unit* target = GetTarget(); - if (!target) - return false; - - if (!bot->IsWithinMeleeRange(target)) + if (!target || !bot->IsWithinMeleeRange(target)) return false; return CastSpellAction::isUseful(); @@ -162,10 +148,7 @@ CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction( bool CastMeleeDebuffSpellAction::isUseful() { Unit* target = GetTarget(); - if (!target) - return false; - - if (!bot->IsWithinMeleeRange(target)) + if (!target || !bot->IsWithinMeleeRange(target)) return false; return CastDebuffSpellAction::isUseful(); @@ -175,14 +158,55 @@ bool CastAuraSpellAction::isUseful() { if (!GetTarget() || !CastSpellAction::isUseful()) return false; + Aura* aura = botAI->GetAura(spell, GetTarget(), isOwner, checkDuration); - if (!aura) + if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration)) return true; - if (beforeDuration && aura->GetDuration() < beforeDuration) + + return false; +} + +bool CastBuffSpellAction::isUseful() +{ + Unit* target = GetTarget(); + if (!target || !CastSpellAction::isUseful()) + return false; + + Aura* aura = botAI->GetAura(spell, target, isOwner, checkDuration); + return !aura || (beforeDuration && aura->GetDuration() < beforeDuration); +} + +bool CastBuffSpellAction::Execute(Event /*event*/) +{ + return botAI->CastSpell(spell, GetTarget()); +} + +bool GroupBuffSpellAction::isUseful() +{ + Unit* target = GetTarget(); + if (!target || !CastSpellAction::isUseful()) + return false; + + if (ai::buff::IsGroupVariantEnabled(bot, spell)) + { + std::string const groupVariant = ai::buff::GroupVariantFor(spell); + if (!groupVariant.empty() && botAI->HasAura(groupVariant, target, false, isOwner, -1, checkDuration)) + return false; + } + + Aura* aura = botAI->GetAura(spell, target, isOwner, checkDuration); + if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration)) return true; + return false; } +bool GroupBuffSpellAction::Execute(Event /*event*/) +{ + std::string const castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, spell); + return botAI->CastSpell(castName, GetTarget()); +} + CastEnchantItemMainHandAction::CastEnchantItemMainHandAction( PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {} @@ -248,24 +272,15 @@ Value* CurePartyMemberAction::GetTargetValue() return context->GetValue("party member to dispel", dispelType); } -// Make Bots Paladin, druid, mage use the greater buff rank spell -// TODO Priest doen't verify il he have components Value* BuffOnPartyAction::GetTargetValue() { - return context->GetValue("party member without aura", MakeAuraQualifierForBuff(spell)); + return context->GetValue("party member without aura", spell); } -bool BuffOnPartyAction::Execute(Event /*event*/) +Value* GroupBuffOnPartyAction::GetTargetValue() { - std::string castName = spell; // default = mono - - auto SendGroupRP = ai::chat::MakeGroupAnnouncer(bot); - castName = ai::buff::UpgradeToGroupIfAppropriate( - bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP); - - return botAI->CastSpell(castName, GetTarget()); + return context->GetValue("party member without aura", MakeAuraQualifierForBuff(spell)); } -// End greater buff fix CastShootAction::CastShootAction( PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0) @@ -365,50 +380,32 @@ bool CastVehicleSpellAction::Execute(Event /*event*/) bool CastEveryManForHimselfAction::isPossible() { uint32 spellId = AI_VALUE2(uint32, "spell id", spell); - if (!spellId) - return false; - - if (!bot->HasSpell(spellId)) - return false; - - if (HasSpellOrCategoryCooldown(bot, spellId)) - return false; - - return true; + return spellId && bot->HasSpell(spellId) && !HasSpellOrCategoryCooldown(bot, spellId); } bool CastEveryManForHimselfAction::isUseful() { return (bot->HasAuraType(SPELL_AURA_MOD_STUN) || - bot->HasAuraType(SPELL_AURA_MOD_FEAR) || - bot->HasAuraType(SPELL_AURA_MOD_ROOT) || - bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || - bot->HasAuraType(SPELL_AURA_MOD_CHARM)) - && CastSpellAction::isUseful(); + bot->HasAuraType(SPELL_AURA_MOD_FEAR) || + bot->HasAuraType(SPELL_AURA_MOD_ROOT) || + bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || + bot->HasAuraType(SPELL_AURA_MOD_CHARM)) + && CastSpellAction::isUseful(); } bool CastWillOfTheForsakenAction::isPossible() { uint32 spellId = AI_VALUE2(uint32, "spell id", spell); - if (!spellId) - return false; - - if (!bot->HasSpell(spellId)) - return false; - - if (HasSpellOrCategoryCooldown(bot, spellId)) - return false; - - return true; + return spellId && bot->HasSpell(spellId) && !HasSpellOrCategoryCooldown(bot, spellId); } bool CastWillOfTheForsakenAction::isUseful() { return (bot->HasAuraType(SPELL_AURA_MOD_FEAR) || - bot->HasAuraType(SPELL_AURA_MOD_CHARM) || - bot->HasAuraType(SPELL_AURA_AOE_CHARM) || - bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP)) - && CastSpellAction::isUseful(); + bot->HasAuraType(SPELL_AURA_MOD_CHARM) || + bot->HasAuraType(SPELL_AURA_AOE_CHARM) || + bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP)) + && CastSpellAction::isUseful(); } bool UseTrinketAction::Execute(Event /*event*/) @@ -427,10 +424,7 @@ bool UseTrinketAction::Execute(Event /*event*/) bool UseTrinketAction::UseTrinket(Item* item) { - if (bot->CanUseItem(item) != EQUIP_ERR_OK) - return false; - - if (bot->IsNonMeleeSpellCast(true)) + if (bot->CanUseItem(item) != EQUIP_ERR_OK || bot->IsNonMeleeSpellCast(true)) return false; uint8 bagIndex = item->GetBagSlot(); @@ -477,14 +471,13 @@ bool UseTrinketAction::UseTrinket(Item* item) if (spellProcFlag != 0) return false; if (!botAI->CanCastSpell(spellId, bot, false)) - { return false; - } break; } } if (!spellId) return false; + WorldPacket packet(CMSG_USE_ITEM); packet << bagIndex << slot << cast_count << spellId << item_guid << glyphIndex << castFlags; @@ -500,9 +493,8 @@ bool CastDebuffSpellAction::isUseful() { Unit* target = GetTarget(); if (!target || !target->IsAlive() || !target->IsInWorld()) - { return false; - } + return CastAuraSpellAction::isUseful() && (target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime; } diff --git a/src/Ai/Base/Actions/GenericSpellActions.h b/src/Ai/Base/Actions/GenericSpellActions.h index b87bd0a1fcc..5c52b7fd939 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.h +++ b/src/Ai/Base/Actions/GenericSpellActions.h @@ -69,9 +69,7 @@ class CastDebuffSpellAction : public CastAuraSpellAction { public: CastDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false, float needLifeTime = 8.0f) - : CastAuraSpellAction(botAI, spell, isOwner), needLifeTime(needLifeTime) - { - } + : CastAuraSpellAction(botAI, spell, isOwner), needLifeTime(needLifeTime) {} bool isUseful() override; private: @@ -90,9 +88,7 @@ class CastDebuffSpellOnAttackerAction : public CastDebuffSpellAction public: CastDebuffSpellOnAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true, float needLifeTime = 8.0f) - : CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime) - { - } + : CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime) {} Value* GetTargetValue() override; std::string const getName() override { return spell + " on attacker"; } @@ -104,9 +100,7 @@ class CastDebuffSpellOnMeleeAttackerAction : public CastDebuffSpellAction public: CastDebuffSpellOnMeleeAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true, float needLifeTime = 8.0f) - : CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime) - { - } + : CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime) {} Value* GetTargetValue() override; std::string const getName() override { return spell + " on attacker"; } @@ -119,6 +113,19 @@ class CastBuffSpellAction : public CastAuraSpellAction CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false, uint32 beforeDuration = 0); std::string const GetTargetName() override { return "self target"; } + bool isUseful() override; + bool Execute(Event event) override; +}; + +class GroupBuffSpellAction : public CastBuffSpellAction +{ +public: + GroupBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false, + uint32 beforeDuration = 0) + : CastBuffSpellAction(botAI, spell, checkIsOwner, beforeDuration) {} + + bool isUseful() override; + bool Execute(Event event) override; }; class CastEnchantItemMainHandAction : public CastSpellAction @@ -151,8 +158,6 @@ class CastHealingSpellAction : public CastAuraSpellAction // Yunfan: Mana efficiency tell the bot how to save mana. The higher the better. HealingManaEfficiency manaEfficiency; uint8 estAmount; - - // protected: }; class CastAoeHealSpellAction : public CastHealingSpellAction @@ -192,9 +197,7 @@ class HealPartyMemberAction : public CastHealingSpellAction, public PartyMemberA public: HealPartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f, HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM, bool isOwner = true) - : CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency, isOwner), PartyMemberActionNameSupport(spell) - { - } + : CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency, isOwner), PartyMemberActionNameSupport(spell) {} std::string const GetTargetName() override { return "party member to heal"; } std::string const getName() override { return PartyMemberActionNameSupport::getName(); } @@ -219,9 +222,7 @@ class CurePartyMemberAction : public CastSpellAction, public PartyMemberActionNa { public: CurePartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint32 dispelType) - : CastSpellAction(botAI, spell), PartyMemberActionNameSupport(spell), dispelType(dispelType) - { - } + : CastSpellAction(botAI, spell), PartyMemberActionNameSupport(spell), dispelType(dispelType) {} Value* GetTargetValue() override; std::string const getName() override { return PartyMemberActionNameSupport::getName(); } @@ -230,18 +231,25 @@ class CurePartyMemberAction : public CastSpellAction, public PartyMemberActionNa uint32 dispelType; }; -// Make Bots Paladin, druid, mage use the greater buff rank spell class BuffOnPartyAction : public CastBuffSpellAction, public PartyMemberActionNameSupport { public: BuffOnPartyAction(PlayerbotAI* botAI, std::string const spell) - : CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) { } + : CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) {} + + Value* GetTargetValue() override; + std::string const getName() override { return PartyMemberActionNameSupport::getName(); } +}; + +class GroupBuffOnPartyAction : public GroupBuffSpellAction, public PartyMemberActionNameSupport +{ +public: + GroupBuffOnPartyAction(PlayerbotAI* botAI, std::string const spell) + : GroupBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) {} Value* GetTargetValue() override; - bool Execute(Event event) override; std::string const getName() override { return PartyMemberActionNameSupport::getName(); } }; -// End Fix class CastShootAction : public CastSpellAction { @@ -323,6 +331,7 @@ class UseTrinketAction : public Action public: UseTrinketAction(PlayerbotAI* botAI) : Action(botAI, "use trinket") {} bool Execute(Event event) override; + protected: bool UseTrinket(Item* trinket); }; @@ -461,12 +470,11 @@ class BuffOnMainTankAction : public CastBuffSpellAction, public MainTankActionNa { public: BuffOnMainTankAction(PlayerbotAI* ai, std::string spell, bool checkIsOwner = false) - : CastBuffSpellAction(ai, spell, checkIsOwner), MainTankActionNameSupport(spell) - { - } + : CastBuffSpellAction(ai, spell, checkIsOwner), MainTankActionNameSupport(spell) {} public: virtual Value* GetTargetValue(); virtual std::string const getName() { return MainTankActionNameSupport::getName(); } }; + #endif diff --git a/src/Ai/Base/Trigger/GenericTriggers.cpp b/src/Ai/Base/Trigger/GenericTriggers.cpp index 2035e1c29ca..aac920b5b65 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.cpp +++ b/src/Ai/Base/Trigger/GenericTriggers.cpp @@ -7,6 +7,7 @@ #include +#include "GenericBuffUtils.h" #include "CreatureAI.h" #include "ItemVisitors.h" #include "LastSpellCastValue.h" @@ -41,52 +42,50 @@ bool LowEnergyTrigger::IsActive() bool NoPetTrigger::IsActive() { - return (bot->GetMinionGUID().IsEmpty()) && (!AI_VALUE(Unit*, "pet target")) && (!bot->GetGuardianPet()) && - (!bot->GetFirstControlled()) && (!AI_VALUE2(bool, "mounted", "self target")); + return bot->GetMinionGUID().IsEmpty() && !AI_VALUE(Unit*, "pet target") && !bot->GetGuardianPet() && + !bot->GetFirstControlled() && !AI_VALUE2(bool, "mounted", "self target"); } bool HasPetTrigger::IsActive() { - return (AI_VALUE(Unit*, "pet target")) && !AI_VALUE2(bool, "mounted", "self target"); - ; + return AI_VALUE(Unit*, "pet target") && !AI_VALUE2(bool, "mounted", "self target"); } bool PetAttackTrigger::IsActive() { Guardian* pet = bot->GetGuardianPet(); if (!pet) - { return false; - } + Unit* target = AI_VALUE(Unit*, "current target"); if (!target) - { return false; - } + if (pet->GetVictim() == target && pet->GetCharmInfo()->IsCommandAttack()) - { return false; - } + if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat()) - { return false; - } + return true; } bool HighManaTrigger::IsActive() { - return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.highMana; + return AI_VALUE2(bool, "has mana", "self target") && + AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.highMana; } bool AlmostFullManaTrigger::IsActive() { - return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > 85; + return AI_VALUE2(bool, "has mana", "self target") && + AI_VALUE2(uint8, "mana", "self target") > 85; } bool EnoughManaTrigger::IsActive() { - return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.highMana; + return AI_VALUE2(bool, "has mana", "self target") && + AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.highMana; } bool RageAvailable::IsActive() { return AI_VALUE2(uint8, "rage", "self target") >= amount; } @@ -101,9 +100,8 @@ bool TargetWithComboPointsLowerHealTrigger::IsActive() { Unit* target = AI_VALUE(Unit*, "current target"); if (!target || !target->IsAlive() || !target->IsInWorld()) - { return false; - } + return ComboPointsAvailableTrigger::IsActive() && (target->GetHealth() / AI_VALUE(float, "estimated group dps")) <= lifeTime; } @@ -164,19 +162,27 @@ bool BuffTrigger::IsActive() Unit* target = GetTarget(); if (!target) return false; - if (!SpellTrigger::IsActive()) - return false; + Aura* aura = botAI->GetAura(spell, target, checkIsOwner, checkDuration); - if (!aura) - return true; - if (beforeDuration && aura->GetDuration() < beforeDuration) + if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration)) return true; + return false; } Value* BuffOnPartyTrigger::GetTargetValue() { - return context->GetValue("party member without aura", spell); + return context->GetValue( + "party member without aura", ai::buff::MakeAuraQualifierForBuff(spell)); +} + +bool BuffOnPartyTrigger::IsActive() +{ + Unit* target = GetTarget(); + if (ai::buff::ShouldDeferPartyBuffEvaluationForRecentLogin(bot, target, spell)) + return false; + + return BuffTrigger::IsActive(); } bool ProtectPartyMemberTrigger::IsActive() { return AI_VALUE(Unit*, "party member to protect"); } @@ -209,13 +215,14 @@ bool MediumThreatTrigger::IsActive() { if (!AI_VALUE(Unit*, "main tank")) return false; + return MyAttackerCountTrigger::IsActive(); } bool LowTankThreatTrigger::IsActive() { - Unit* mt = AI_VALUE(Unit*, "main tank"); - if (!mt) + Unit* mainTank = AI_VALUE(Unit*, "main tank"); + if (!mainTank) return false; Unit* current_target = AI_VALUE(Unit*, "current target"); @@ -224,7 +231,7 @@ bool LowTankThreatTrigger::IsActive() ThreatManager& mgr = current_target->GetThreatMgr(); float threat = mgr.GetThreat(bot); - float tankThreat = mgr.GetThreat(mt); + float tankThreat = mgr.GetThreat(mainTank); return tankThreat == 0.0f || threat > tankThreat * 0.5f; } @@ -232,9 +239,8 @@ bool AoeTrigger::IsActive() { Unit* current_target = AI_VALUE(Unit*, "current target"); if (!current_target) - { return false; - } + GuidVector attackers = context->GetValue("attackers")->Get(); int attackers_count = 0; for (ObjectGuid const guid : attackers) @@ -242,10 +248,9 @@ bool AoeTrigger::IsActive() Unit* unit = botAI->GetUnit(guid); if (!unit || !unit->IsAlive()) continue; + if (unit->GetDistance(current_target->GetPosition()) <= range) - { attackers_count++; - } } return attackers_count >= amount; } @@ -274,20 +279,19 @@ bool DebuffTrigger::IsActive() { Unit* target = GetTarget(); if (!target || !target->IsAlive() || !target->IsInWorld()) - { return false; - } - return BuffTrigger::IsActive() && (target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime; + + return BuffTrigger::IsActive() && + (target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime; } bool DebuffOnBossTrigger::IsActive() { if (!DebuffTrigger::IsActive()) - { return false; - } - Creature* c = GetTarget()->ToCreature(); - return c && ((c->IsDungeonBoss()) || (c->isWorldBoss())); + + Creature* creature = GetTarget()->ToCreature(); + return creature && (creature->IsDungeonBoss() || creature->isWorldBoss()); } bool SpellTrigger::IsActive() { return GetTarget(); } @@ -317,9 +321,7 @@ bool SpellCooldownTrigger::IsActive() } RandomTrigger::RandomTrigger(PlayerbotAI* botAI, std::string const name, int32 probability) - : Trigger(botAI, name), probability(probability), lastCheck(getMSTime()) -{ -} + : Trigger(botAI, name), probability(probability), lastCheck(getMSTime()) {} bool RandomTrigger::IsActive() { @@ -330,6 +332,7 @@ bool RandomTrigger::IsActive() int32 k = (int32)(probability / sPlayerbotAIConfig.randomChangeMultiplier); if (k < 1) k = 1; + return (rand() % k) == 0; } @@ -368,9 +371,11 @@ bool BoostTrigger::IsActive() { if (!BuffTrigger::IsActive()) return false; + Unit* target = AI_VALUE(Unit*, "current target"); if (target && target->ToPlayer()) return true; + return AI_VALUE(uint8, "balance") <= balance; } @@ -379,20 +384,19 @@ bool GenericBoostTrigger::IsActive() Unit* target = AI_VALUE(Unit*, "current target"); if (target && target->ToPlayer()) return true; + return AI_VALUE(uint8, "balance") <= balance; } bool HealerShouldAttackTrigger::IsActive() { - // nobody can help me if (botAI->GetNearGroupMemberCount(sPlayerbotAIConfig.sightDistance) <= 1) return true; if (AI_VALUE2(uint8, "health", "party member to heal") < sPlayerbotAIConfig.almostFullHealth) return false; - // special check for resto druid (dont remove tree of life frequently) - if (bot->GetAura(33891)) + if (bot->GetAura(33891)) // Tree of Life { LastSpellCast& lastSpell = botAI->GetAiObjectContext()->GetValue("last spell cast")->Get(); if (lastSpell.timer + 5 > time(nullptr)) @@ -401,7 +405,6 @@ bool HealerShouldAttackTrigger::IsActive() int manaThreshold; int balance = AI_VALUE(uint8, "balance"); - // higher threshold in higher pressure if (balance <= 50) manaThreshold = 85; else if (balance <= 100) @@ -425,13 +428,7 @@ bool InterruptSpellTrigger::IsActive() bool DeflectSpellTrigger::IsActive() { Unit* target = GetTarget(); - if (!target) - return false; - - if (!target->IsNonMeleeSpellCast(true)) - return false; - - if (target->GetTarget() != bot->GetGUID()) + if (!target || !target->IsNonMeleeSpellCast(true) || target->GetTarget() != bot->GetGUID()) return false; uint32 spellid = context->GetValue("spell id", spell)->Get(); @@ -462,6 +459,7 @@ bool DeflectSpellTrigger::IsActive() return true; } } + return false; } @@ -495,17 +493,16 @@ bool FearSleepSapTrigger::IsActive() bool HasAuraStackTrigger::IsActive() { - Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack); - // sLog->outMessage("playerbot", LOG_LEVEL_DEBUG, "HasAuraStackTrigger::IsActive %s %d", getName(), aura ? - // aura->GetStackAmount() : -1); - return aura; + return botAI->GetAura(getName(), GetTarget(), false, true, stack); } bool TimerTrigger::IsActive() { - if (time(nullptr) != lastCheck) + time_t now = time(nullptr); + + if (now != lastCheck) { - lastCheck = time(nullptr); + lastCheck = now; return true; } @@ -552,9 +549,8 @@ bool IsBehindTargetTrigger::IsActive() bool IsNotBehindTargetTrigger::IsActive() { if (botAI->HasStrategy("stay", botAI->GetState())) - { return false; - } + Unit* target = AI_VALUE(Unit*, "current target"); return target && !AI_VALUE2(bool, "behind", "current target"); } @@ -562,9 +558,8 @@ bool IsNotBehindTargetTrigger::IsActive() bool IsNotFacingTargetTrigger::IsActive() { if (botAI->HasStrategy("stay", botAI->GetState())) - { return false; - } + return !AI_VALUE2(bool, "facing", "current target"); } @@ -581,12 +576,14 @@ bool NoPossibleTargetsTrigger::IsActive() return !targets.size(); } -bool PossibleAddsTrigger::IsActive() { return AI_VALUE(bool, "possible adds") && !AI_VALUE(ObjectGuid, "pull target"); } +bool PossibleAddsTrigger::IsActive() +{ + return AI_VALUE(bool, "possible adds") && !AI_VALUE(ObjectGuid, "pull target"); +} bool NotDpsTargetActiveTrigger::IsActive() { Unit* target = AI_VALUE(Unit*, "current target"); - // do not switch if enemy target if (target && target->IsAlive()) { Unit* enemy = AI_VALUE(Unit*, "enemy player target"); @@ -604,7 +601,6 @@ bool NotDpsAoeTargetActiveTrigger::IsActive() Unit* target = AI_VALUE(Unit*, "current target"); Unit* enemy = AI_VALUE(Unit*, "enemy player target"); - // do not switch if enemy target if (target && target == enemy && target->IsAlive()) return false; @@ -638,7 +634,10 @@ Value* InterruptEnemyHealerTrigger::GetTargetValue() return context->GetValue("enemy healer target", spell); } -bool RandomBotUpdateTrigger::IsActive() { return RandomTrigger::IsActive() && AI_VALUE(bool, "random bot update"); } +bool RandomBotUpdateTrigger::IsActive() +{ + return RandomTrigger::IsActive() && AI_VALUE(bool, "random bot update"); +} bool NoNonBotPlayersAroundTrigger::IsActive() { @@ -718,43 +717,24 @@ bool AmmoCountTrigger::IsActive() bool NewPetTrigger::IsActive() { - // Get the bot player object from the AI - Player* bot = botAI->GetBot(); - if (!bot) - return false; - - // Try to get the current pet; initialize guardian and GUID to null/empty - Pet* pet = bot->GetPet(); - Guardian* guardian = nullptr; ObjectGuid currentPetGuid = ObjectGuid::Empty; - // If bot has a pet, get its GUID - if (pet) - { + if (Pet* pet = bot->GetPet()) currentPetGuid = pet->GetGUID(); - } - else - { - // If no pet, try to get a guardian pet and its GUID - guardian = bot->GetGuardianPet(); - if (guardian) - currentPetGuid = guardian->GetGUID(); - } + else if (Guardian* guardian = bot->GetGuardianPet()) + currentPetGuid = guardian->GetGUID(); - // If the current pet or guardian GUID has changed (including becoming empty), reset the trigger state if (currentPetGuid != lastPetGuid) { triggered = false; lastPetGuid = currentPetGuid; } - // If there's a valid current pet/guardian (non-empty GUID) and we haven't triggered yet, activate trigger if (currentPetGuid != ObjectGuid::Empty && !triggered) { triggered = true; return true; } - // Otherwise, do not activate return false; } diff --git a/src/Ai/Base/Trigger/GenericTriggers.h b/src/Ai/Base/Trigger/GenericTriggers.h index a3931c24619..3e662eb3bed 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.h +++ b/src/Ai/Base/Trigger/GenericTriggers.h @@ -20,9 +20,7 @@ class StatAvailable : public Trigger { public: StatAvailable(PlayerbotAI* botAI, int32 amount, std::string const name = "stat available") - : Trigger(botAI, name), amount(amount) - { - } + : Trigger(botAI, name), amount(amount) {} protected: int32 amount; @@ -118,8 +116,8 @@ class ComboPointsAvailableTrigger : public StatAvailable class TargetWithComboPointsLowerHealTrigger : public ComboPointsAvailableTrigger { public: - TargetWithComboPointsLowerHealTrigger(PlayerbotAI* ai, int32 combo_point = 5, float lifeTime = 8.0f) - : ComboPointsAvailableTrigger(ai, combo_point), lifeTime(lifeTime) + TargetWithComboPointsLowerHealTrigger(PlayerbotAI* botAI, int32 combo_point = 5, float lifeTime = 8.0f) + : ComboPointsAvailableTrigger(botAI, combo_point), lifeTime(lifeTime) { } bool IsActive() override; @@ -196,7 +194,6 @@ class SpellCooldownTrigger : public SpellTrigger bool IsActive() override; }; -// TODO: check other targets class InterruptSpellTrigger : public SpellTrigger { public: @@ -217,9 +214,7 @@ class AttackerCountTrigger : public Trigger { public: AttackerCountTrigger(PlayerbotAI* botAI, int32 amount, float distance = sPlayerbotAIConfig.sightDistance) - : Trigger(botAI), amount(amount), distance(distance) - { - } + : Trigger(botAI), amount(amount), distance(distance) {} bool IsActive() override; std::string const getName() override { return "attacker count"; } @@ -269,9 +264,7 @@ class AoeTrigger : public AttackerCountTrigger { public: AoeTrigger(PlayerbotAI* botAI, int32 amount = 3, float range = 15.0f) - : AttackerCountTrigger(botAI, amount), range(range) - { - } + : AttackerCountTrigger(botAI, amount), range(range) {} bool IsActive() override; std::string const getName() override { return "aoe"; } @@ -317,7 +310,8 @@ class HighAoeTrigger : public AoeTrigger class BuffTrigger : public SpellTrigger { public: - BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false, bool checkDuration = false, uint32 beforeDuration = 0) + BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, + bool checkIsOwner = false, bool checkDuration = false, uint32 beforeDuration = 0) : SpellTrigger(botAI, spell, checkInterval) { this->checkIsOwner = checkIsOwner; @@ -339,11 +333,10 @@ class BuffOnPartyTrigger : public BuffTrigger { public: BuffOnPartyTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1) - : BuffTrigger(botAI, spell, checkInterval) - { - } + : BuffTrigger(botAI, spell, checkInterval) {} Value* GetTargetValue() override; + bool IsActive() override; std::string const getName() override { return spell + " on party"; } }; @@ -393,9 +386,7 @@ class DebuffTrigger : public BuffTrigger public: DebuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false, float needLifeTime = 8.0f, uint32 beforeDuration = 0) - : BuffTrigger(botAI, spell, checkInterval, checkIsOwner, false, beforeDuration), needLifeTime(needLifeTime) - { - } + : BuffTrigger(botAI, spell, checkInterval, checkIsOwner, false, beforeDuration), needLifeTime(needLifeTime) {} std::string const GetTargetName() override { return "current target"; } bool IsActive() override; @@ -408,9 +399,7 @@ class DebuffOnBossTrigger : public DebuffTrigger { public: DebuffOnBossTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false) - : DebuffTrigger(botAI, spell, checkInterval, checkIsOwner) - { - } + : DebuffTrigger(botAI, spell, checkInterval, checkIsOwner) {} bool IsActive() override; }; @@ -419,9 +408,7 @@ class DebuffOnAttackerTrigger : public DebuffTrigger public: DebuffOnAttackerTrigger(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = true, float needLifeTime = 8.0f) - : DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime) - { - } + : DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime) {} Value* GetTargetValue() override; std::string const getName() override { return spell + " on attacker"; } @@ -432,9 +419,7 @@ class DebuffOnMeleeAttackerTrigger : public DebuffTrigger public: DebuffOnMeleeAttackerTrigger(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = true, float needLifeTime = 8.0f) - : DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime) - { - } + : DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime) {} Value* GetTargetValue() override; std::string const getName() override { return spell + " on attacker"; } @@ -444,9 +429,7 @@ class BoostTrigger : public BuffTrigger { public: BoostTrigger(PlayerbotAI* botAI, std::string const spell, float balance = 50.f) - : BuffTrigger(botAI, spell, 1), balance(balance) - { - } + : BuffTrigger(botAI, spell, 1), balance(balance) {} bool IsActive() override; @@ -458,9 +441,7 @@ class GenericBoostTrigger : public Trigger { public: GenericBoostTrigger(PlayerbotAI* botAI, float balance = 50.f) - : Trigger(botAI, "generic boost", 1), balance(balance) - { - } + : Trigger(botAI, "generic boost", 1), balance(balance) {} bool IsActive() override; @@ -472,9 +453,7 @@ class HealerShouldAttackTrigger : public Trigger { public: HealerShouldAttackTrigger(PlayerbotAI* botAI) - : Trigger(botAI, "healer should attack", 1) - { - } + : Trigger(botAI, "healer should attack", 1) {} bool IsActive() override; }; @@ -580,7 +559,7 @@ class NoPetTrigger : public Trigger class HasPetTrigger : public Trigger { public: - HasPetTrigger(PlayerbotAI* ai) : Trigger(ai, "has pet", 5 * 1000) {} + HasPetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "has pet", 5 * 1000) {} virtual bool IsActive() override; }; @@ -588,7 +567,7 @@ class HasPetTrigger : public Trigger class PetAttackTrigger : public Trigger { public: - PetAttackTrigger(PlayerbotAI* ai) : Trigger(ai, "pet attack") {} + PetAttackTrigger(PlayerbotAI* botAI) : Trigger(botAI, "pet attack") {} virtual bool IsActive() override; }; @@ -597,9 +576,7 @@ class ItemCountTrigger : public Trigger { public: ItemCountTrigger(PlayerbotAI* botAI, std::string const item, int32 count, int32 interval = 30 * 1000) - : Trigger(botAI, item, interval), item(item), count(count) - { - } + : Trigger(botAI, item, interval), item(item), count(count) {} bool IsActive() override; std::string const getName() override { return "item count"; } @@ -613,9 +590,7 @@ class AmmoCountTrigger : public ItemCountTrigger { public: AmmoCountTrigger(PlayerbotAI* botAI, std::string const item, uint32 count = 1, int32 interval = 30 * 1000) - : ItemCountTrigger(botAI, item, count, interval) - { - } + : ItemCountTrigger(botAI, item, count, interval) {} bool IsActive() override; }; @@ -623,9 +598,7 @@ class HasAuraTrigger : public Trigger { public: HasAuraTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1) - : Trigger(botAI, spell, checkInterval) - { - } + : Trigger(botAI, spell, checkInterval) {} std::string const GetTargetName() override { return "self target"; } bool IsActive() override; @@ -634,10 +607,8 @@ class HasAuraTrigger : public Trigger class HasAuraStackTrigger : public Trigger { public: - HasAuraStackTrigger(PlayerbotAI* ai, std::string spell, int stack, int checkInterval = 1) - : Trigger(ai, spell, checkInterval), stack(stack) - { - } + HasAuraStackTrigger(PlayerbotAI* botAI, std::string spell, int stack, int checkInterval = 1) + : Trigger(botAI, spell, checkInterval), stack(stack) {} std::string const GetTargetName() override { return "self target"; } bool IsActive() override; @@ -858,9 +829,7 @@ class StayTimeTrigger : public Trigger { public: StayTimeTrigger(PlayerbotAI* botAI, uint32 delay, std::string const name) - : Trigger(botAI, name, 5 * 1000), delay(delay) - { - } + : Trigger(botAI, name, 5 * 1000), delay(delay) {} bool IsActive() override; @@ -877,7 +846,7 @@ class SitTrigger : public StayTimeTrigger class ReturnToStayPositionTrigger : public Trigger { public: - ReturnToStayPositionTrigger(PlayerbotAI* ai) : Trigger(ai, "return to stay position", 2) {} + ReturnToStayPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "return to stay position", 2) {} virtual bool IsActive() override; }; @@ -892,9 +861,7 @@ class GiveItemTrigger : public Trigger { public: GiveItemTrigger(PlayerbotAI* botAI, std::string const name, std::string const item) - : Trigger(botAI, name, 2 * 1000), item(item) - { - } + : Trigger(botAI, name, 2 * 1000), item(item) {} bool IsActive() override; @@ -962,9 +929,7 @@ class BuffOnMainTankTrigger : public BuffTrigger { public: BuffOnMainTankTrigger(PlayerbotAI* botAI, std::string spell, bool checkIsOwner = false, int checkInterval = 1) - : BuffTrigger(botAI, spell, checkInterval, checkIsOwner) - { - } + : BuffTrigger(botAI, spell, checkInterval, checkIsOwner) {} public: virtual Value* GetTargetValue(); @@ -973,7 +938,7 @@ class BuffOnMainTankTrigger : public BuffTrigger class SelfResurrectTrigger : public Trigger { public: - SelfResurrectTrigger(PlayerbotAI* ai) : Trigger(ai, "can self resurrect") {} + SelfResurrectTrigger(PlayerbotAI* botAI) : Trigger(botAI, "can self resurrect") {} bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); } }; @@ -981,7 +946,7 @@ class SelfResurrectTrigger : public Trigger class NewPetTrigger : public Trigger { public: - NewPetTrigger(PlayerbotAI* ai) : Trigger(ai, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {} + NewPetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {} bool IsActive() override; diff --git a/src/Ai/Base/Util/GenericBuffUtils.cpp b/src/Ai/Base/Util/GenericBuffUtils.cpp index 22d56c0ad8c..233d98f4381 100644 --- a/src/Ai/Base/Util/GenericBuffUtils.cpp +++ b/src/Ai/Base/Util/GenericBuffUtils.cpp @@ -4,23 +4,89 @@ */ #include "GenericBuffUtils.h" -#include "PlayerbotAIConfig.h" -#include +#include "AiObjectContext.h" -#include "Player.h" +#include "GameTime.h" #include "Group.h" -#include "SpellMgr.h" -#include "Chat.h" +#include "Player.h" #include "PlayerbotAI.h" -#include "ServerFacade.h" -#include "AiObjectContext.h" +#include "PlayerbotAIConfig.h" +#include "SpellMgr.h" +#include "Unit.h" #include "Value.h" -#include "Config.h" -#include "PlayerbotTextMgr.h" namespace ai::buff { + namespace + { + // Prevents bots from immediately casting already-present buffs upon logging in + constexpr uint32 POST_LOGIN_BUFF_GRACE_MS = 5 * IN_MILLISECONDS; + + bool IsWithinPostLoginBuffGrace(Player* player) + { + if (!player) + return false; + + return getMSTimeDiff( + player->GetInGameTime(), GameTime::GetGameTimeMS().count()) < POST_LOGIN_BUFF_GRACE_MS; + } + } + + static bool HasEnoughSameMapMissingPlayersForGroupVariant( + Player* bot, PlayerbotAI* botAI, std::string const& baseName, + std::string const& groupName, uint32 requiredCount = 3) + { + Group* group = bot->GetGroup(); + if (!group) + return false; + + uint32 missingCount = 0; + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->GetSource(); + if (!member || !member->IsInWorld() || !member->IsAlive() || + member->GetMap() != bot->GetMap()) + { + continue; + } + + if (botAI->HasAura(baseName, member) || botAI->HasAura(groupName, member)) + continue; + + if (++missingCount >= requiredCount) + return true; + } + + return false; + } + + static bool IsEligibleGroupForPartyBuffs(Group const* group) + { + if (!group) + return false; + + switch (sPlayerbotAIConfig.autoPartyBuffs) + { + case AutoPartyBuffMode::RAID_ONLY: + return group->isRaidGroup(); + case AutoPartyBuffMode::GROUP_OR_RAID: + return true; + case AutoPartyBuffMode::DISABLED: + return false; + } + + return false; + } + + bool IsGroupVariantEnabled(Player* bot, std::string const& name) + { + if (!IsEligibleGroupForPartyBuffs(bot->GetGroup())) + return false; + + return !GroupVariantFor(name).empty(); + } + std::string MakeAuraQualifierForBuff(std::string const& name) { // Paladin @@ -34,27 +100,89 @@ namespace ai::buff if (name == "arcane intellect") return "arcane intellect,arcane brilliance"; // Priest if (name == "power word: fortitude") return "power word: fortitude,prayer of fortitude"; + if (name == "divine spirit") return "divine spirit,prayer of spirit"; + if (name == "shadow protection") return "shadow protection,prayer of shadow protection"; return name; } std::string GroupVariantFor(std::string const& name) { - // Paladin - if (name == "blessing of kings") return "greater blessing of kings"; - if (name == "blessing of might") return "greater blessing of might"; - if (name == "blessing of wisdom") return "greater blessing of wisdom"; - if (name == "blessing of sanctuary") return "greater blessing of sanctuary"; // Druid if (name == "mark of the wild") return "gift of the wild"; // Mage if (name == "arcane intellect") return "arcane brilliance"; // Priest if (name == "power word: fortitude") return "prayer of fortitude"; + if (name == "divine spirit") return "prayer of spirit"; + if (name == "shadow protection") return "prayer of shadow protection"; + // Paladin blessings are intentionally not included here because they are + // coordinated by the auto greater blessing system instead. return std::string(); } + bool NeedsPostLoginBuffGrace(std::string const& name) + { + static char const* const trackedBuffs[] = { + "mark of the wild", + "arcane intellect", + "power word: fortitude", + "prayer of fortitude", + "divine spirit", + "prayer of spirit", + "shadow protection", + "prayer of shadow protection", + "blessing of kings", + "blessing of might", + "blessing of wisdom", + "blessing of sanctuary" + }; + + for (char const* trackedBuff : trackedBuffs) + { + if (name.find(trackedBuff) != std::string::npos) + return true; + } + + return false; + } + + bool ShouldDeferPartyBuffEvaluationForRecentLogin( + Player* bot, Unit* target, std::string const& spell) + { + if (!NeedsPostLoginBuffGrace(spell)) + return false; + + if (IsWithinPostLoginBuffGrace(bot)) + return true; + + Player* playerTarget = target ? target->ToPlayer() : nullptr; + return IsWithinPostLoginBuffGrace(playerTarget); + } + + bool ShouldDeferGreaterBlessingAssignmentForRecentLogin(Player* bot) + { + if (IsWithinPostLoginBuffGrace(bot)) + return true; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->GetSource(); + if (!member || !member->IsInWorld()) + continue; + + if (IsWithinPostLoginBuffGrace(member)) + return true; + } + + return false; + } + bool HasRequiredReagents(Player* bot, uint32 spellId) { if (!spellId) @@ -72,75 +200,33 @@ namespace ai::buff return false; } } - // No reagent required return true; } return false; } std::string UpgradeToGroupIfAppropriate( - Player* bot, - PlayerbotAI* botAI, - std::string const& baseName, - bool announceOnMissing, - std::function announce) + Player* bot, PlayerbotAI* botAI, std::string const& baseName) { - std::string castName = baseName; - Group* g = bot->GetGroup(); - if (!g || g->GetMembersCount() < static_cast(sPlayerbotAIConfig.minBotsForGreaterBuff)) - return castName; // Group too small: stay in solo mode + if (!IsGroupVariantEnabled(bot, baseName)) + return baseName; - if (std::string const groupName = GroupVariantFor(baseName); !groupName.empty()) - { - uint32 const groupVariantSpellId = botAI->GetAiObjectContext() - ->GetValue("spell id", groupName)->Get(); + std::string const groupName = GroupVariantFor(baseName); + if (groupName.empty()) + return baseName; - // We check usefulness on the **basic** buff (not the greater version), - // because "spell cast useful" may return false for the greater variant. - bool const usefulBase = botAI->GetAiObjectContext() - ->GetValue("spell cast useful", baseName)->Get(); + // Prefer singles until at least three living, in-world group members on the bot's map + // are missing both the single-target buff and its group variant. + if (!HasEnoughSameMapMissingPlayersForGroupVariant(bot, botAI, baseName, groupName)) + return baseName; - if (groupVariantSpellId && HasRequiredReagents(bot, groupVariantSpellId)) - { - // Learned + reagents OK -> switch to greater - return groupName; - } + uint32 const groupSpellId = botAI->GetAiObjectContext() + ->GetValue("spell id", groupName)->Get(); - // Missing reagents -> announce if (a) greater is known, (b) base buff is useful, - // (c) announce was requested, (d) a callback is provided. - if (announceOnMissing && groupVariantSpellId && usefulBase && announce) - { - static std::map, time_t> s_lastWarn; // par bot & par buff - time_t now = std::time(nullptr); - uint32 botLow = static_cast(bot->GetGUID().GetCounter()); - time_t& last = s_lastWarn[ std::make_pair(botLow, groupName) ]; - if (!last || now - last >= sPlayerbotAIConfig.rpWarningCooldown) // Configurable anti-spam - { - // DB Key choice in regard of the buff - std::string key; - if (groupName.find("greater blessing") != std::string::npos) - key = "rp_missing_reagent_greater_blessing"; - else if (groupName == "gift of the wild") - key = "rp_missing_reagent_gift_of_the_wild"; - else if (groupName == "arcane brilliance") - key = "rp_missing_reagent_arcane_brilliance"; - else - key = "rp_missing_reagent_generic"; - - // Placeholders - std::map placeholders; - placeholders["%group_spell"] = groupName; - placeholders["%base_spell"] = baseName; - - std::string announceText = PlayerbotTextMgr::instance().GetBotTextOrDefault(key, - "Out of components for %group_spell. Using %base_spell!", placeholders); - - announce(announceText); - last = now; - } - } - } - return castName; + if (groupSpellId && HasRequiredReagents(bot, groupSpellId)) + return groupName; + + return baseName; } } diff --git a/src/Ai/Base/Util/GenericBuffUtils.h b/src/Ai/Base/Util/GenericBuffUtils.h index 37bad58a6d9..9f93bf10816 100644 --- a/src/Ai/Base/Util/GenericBuffUtils.h +++ b/src/Ai/Base/Util/GenericBuffUtils.h @@ -6,63 +6,40 @@ #pragma once #include -#include #include "Common.h" -#include "Group.h" -#include "Chat.h" -#include "Language.h" class Player; class PlayerbotAI; +class Unit; namespace ai::buff { -// Build an aura qualifier "single + greater" to avoid double-buffing +bool IsGroupVariantEnabled(Player* bot, std::string const& name); + std::string MakeAuraQualifierForBuff(std::string const& name); -// Returns the group spell name for a given single-target buff. -// If no group equivalent exists, returns "". std::string GroupVariantFor(std::string const& name); -// Checks if the bot has the required reagents to cast a spell (by its spellId). -// Returns false if the spellId is invalid. +bool NeedsPostLoginBuffGrace(std::string const& name); + +bool ShouldDeferPartyBuffEvaluationForRecentLogin( + Player* bot, + Unit* target, + std::string const& spell); + +bool ShouldDeferGreaterBlessingAssignmentForRecentLogin(Player* bot); + bool HasRequiredReagents(Player* bot, uint32 spellId); -// Applies the "switch to group buff" policy if: the bot is in a group of size x+, -// the group variant is known/useful, and reagents are available. Otherwise, returns baseName. -// If announceOnMissing == true and reagents are missing, calls the 'announce' callback -// (if provided) to notify the party/raid. std::string UpgradeToGroupIfAppropriate( Player* bot, PlayerbotAI* botAI, - std::string const& baseName, - bool announceOnMissing = false, - std::function announce = {} - ); + std::string const& baseName); + } namespace ai::spell { bool HasSpellOrCategoryCooldown(Player* bot, uint32 spellId); } - -namespace ai::chat { - inline std::function MakeGroupAnnouncer(Player* me) - { - return [me](std::string const& msg) - { - if (Group* g = me->GetGroup()) - { - WorldPacket data; - ChatMsg type = g->isRaidGroup() ? CHAT_MSG_RAID : CHAT_MSG_PARTY; - ChatHandler::BuildChatPacket(data, type, LANG_UNIVERSAL, me, /*receiver=*/nullptr, msg.c_str()); - g->BroadcastPacket(&data, true, -1, me->GetGUID()); - } - else - { - me->Say(msg, LANG_UNIVERSAL); - } - }; - } -} diff --git a/src/Ai/Class/Druid/Action/DruidActions.h b/src/Ai/Class/Druid/Action/DruidActions.h index e386ff05d00..bfe5cfc7ff9 100644 --- a/src/Ai/Class/Druid/Action/DruidActions.h +++ b/src/Ai/Class/Druid/Action/DruidActions.h @@ -87,16 +87,16 @@ class CastRebirthAction : public ResurrectPartyMemberAction bool isUseful() override; }; -class CastMarkOfTheWildAction : public CastBuffSpellAction +class CastMarkOfTheWildAction : public GroupBuffSpellAction { public: - CastMarkOfTheWildAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mark of the wild") {} + CastMarkOfTheWildAction(PlayerbotAI* botAI) : GroupBuffSpellAction(botAI, "mark of the wild") {} }; -class CastMarkOfTheWildOnPartyAction : public BuffOnPartyAction +class CastMarkOfTheWildOnPartyAction : public GroupBuffOnPartyAction { public: - CastMarkOfTheWildOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "mark of the wild") {} + CastMarkOfTheWildOnPartyAction(PlayerbotAI* botAI) : GroupBuffOnPartyAction(botAI, "mark of the wild") {} }; class CastSurvivalInstinctsAction : public CastBuffSpellAction diff --git a/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp b/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp index 5a8dbdfa4d7..03b5accc953 100644 --- a/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp +++ b/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp @@ -9,11 +9,6 @@ #include "Playerbots.h" #include "ServerFacade.h" -bool MarkOfTheWildOnPartyTrigger::IsActive() -{ - return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("gift of the wild", GetTarget()); -} - bool MarkOfTheWildTrigger::IsActive() { return BuffTrigger::IsActive() && !botAI->HasAura("gift of the wild", GetTarget()); diff --git a/src/Ai/Class/Druid/Trigger/DruidTriggers.h b/src/Ai/Class/Druid/Trigger/DruidTriggers.h index 1f389c947a8..99002468515 100644 --- a/src/Ai/Class/Druid/Trigger/DruidTriggers.h +++ b/src/Ai/Class/Druid/Trigger/DruidTriggers.h @@ -23,15 +23,13 @@ class PlayerbotAI; class MarkOfTheWildOnPartyTrigger : public BuffOnPartyTrigger { public: - MarkOfTheWildOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "mark of the wild", 2 * 2000) {} - - bool IsActive() override; + MarkOfTheWildOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "mark of the wild", 4 * 2000) {} }; class MarkOfTheWildTrigger : public BuffTrigger { public: - MarkOfTheWildTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "mark of the wild", 2 * 2000) {} + MarkOfTheWildTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "mark of the wild", 4 * 2000) {} bool IsActive() override; }; diff --git a/src/Ai/Class/Mage/Action/MageActions.h b/src/Ai/Class/Mage/Action/MageActions.h index 4c7f76a68fa..c394a379d10 100644 --- a/src/Ai/Class/Mage/Action/MageActions.h +++ b/src/Ai/Class/Mage/Action/MageActions.h @@ -40,16 +40,16 @@ class CastFrostArmorAction : public CastBuffSpellAction CastFrostArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "frost armor") {} }; -class CastArcaneIntellectAction : public CastBuffSpellAction +class CastArcaneIntellectAction : public GroupBuffSpellAction { public: - CastArcaneIntellectAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "arcane intellect") {} + CastArcaneIntellectAction(PlayerbotAI* botAI) : GroupBuffSpellAction(botAI, "arcane intellect") {} }; -class CastArcaneIntellectOnPartyAction : public BuffOnPartyAction +class CastArcaneIntellectOnPartyAction : public GroupBuffOnPartyAction { public: - CastArcaneIntellectOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "arcane intellect") {} + CastArcaneIntellectOnPartyAction(PlayerbotAI* botAI) : GroupBuffOnPartyAction(botAI, "arcane intellect") {} }; class CastFocusMagicOnPartyAction : public CastSpellAction diff --git a/src/Ai/Class/Mage/Trigger/MageTriggers.cpp b/src/Ai/Class/Mage/Trigger/MageTriggers.cpp index 7e7ba9ab732..34babc81e9a 100644 --- a/src/Ai/Class/Mage/Trigger/MageTriggers.cpp +++ b/src/Ai/Class/Mage/Trigger/MageTriggers.cpp @@ -31,11 +31,6 @@ bool NoManaGemTrigger::IsActive() return true; } -bool ArcaneIntellectOnPartyTrigger::IsActive() -{ - return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("arcane brilliance", GetTarget()); -} - bool ArcaneIntellectTrigger::IsActive() { return BuffTrigger::IsActive() && !botAI->HasAura("arcane brilliance", GetTarget()); diff --git a/src/Ai/Class/Mage/Trigger/MageTriggers.h b/src/Ai/Class/Mage/Trigger/MageTriggers.h index 566b6b61eed..58afab0f8aa 100644 --- a/src/Ai/Class/Mage/Trigger/MageTriggers.h +++ b/src/Ai/Class/Mage/Trigger/MageTriggers.h @@ -19,14 +19,13 @@ class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger { public: ArcaneIntellectOnPartyTrigger(PlayerbotAI* botAI) - : BuffOnPartyTrigger(botAI, "arcane intellect", 2 * 2000) {} - bool IsActive() override; + : BuffOnPartyTrigger(botAI, "arcane intellect", 4 * 2000) {} }; class ArcaneIntellectTrigger : public BuffTrigger { public: - ArcaneIntellectTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane intellect", 2 * 2000) {} + ArcaneIntellectTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane intellect", 4 * 2000) {} bool IsActive() override; }; diff --git a/src/Ai/Class/Paladin/Action/PaladinActions.cpp b/src/Ai/Class/Paladin/Action/PaladinActions.cpp index 38e17af1e6e..b7044ee3b52 100644 --- a/src/Ai/Class/Paladin/Action/PaladinActions.cpp +++ b/src/Ai/Class/Paladin/Action/PaladinActions.cpp @@ -7,24 +7,100 @@ #include "AiFactory.h" #include "Event.h" +#include "GenericBuffUtils.h" +#include "PaladinGreaterBlessingAction.h" #include "PaladinHelper.h" -#include "PlayerbotAI.h" #include "Playerbots.h" #include "SharedDefines.h" -#include "../../../../../src/server/scripts/Spells/spell_generic.cpp" -#include "Ai/Base/Util/GenericBuffUtils.h" -#include "Group.h" -#include "ObjectAccessor.h" -using ai::buff::MakeAuraQualifierForBuff; +static bool IsBlessingTargetCandidate(Player* bot, Player* player) +{ + if (!player || !player->IsAlive() || player->GetMapId() != bot->GetMapId()) + return false; + + if (player->IsGameMaster()) + return false; + + return bot->GetDistance(player) < sPlayerbotAIConfig.spellDistance * 2 && + bot->IsWithinLOS(player->GetPositionX(), player->GetPositionY(), + player->GetPositionZ()); +} + +static bool HasBlessingAura( + PlayerbotAI* botAI, Unit* target, std::initializer_list auraNames) +{ + for (char const* auraName : auraNames) + { + if (botAI->HasAura(auraName, target)) + return true; + } + + return false; +} + +static bool IsGreaterBlessingMode(Player* bot) +{ + return ai::gbless::IsEligibleGroupForAutoBlessings(bot->GetGroup()); +} -// Helper : detect tank role on the target (player bot or not) return true if spec is tank or if the bot have tank strategies (bear/tank/tank face). -static inline bool IsTankRole(Player* p) +template +static Unit* FindBlessingTarget( + Player* bot, PlayerbotAI* botAI, Predicate&& predicate) { - if (!p) return false; - if (p->HasTankSpec()) + std::vector masters; + std::vector healers; + std::vector tanks; + std::vector others; + + Player* master = botAI->GetMaster(); + auto addPlayer = [&](Player* player) + { + if (!IsBlessingTargetCandidate(bot, player)) + return; + + if (player == master) + masters.push_back(player); + else if (botAI->IsHeal(player)) + healers.push_back(player); + else if (botAI->IsTank(player)) + tanks.push_back(player); + else + others.push_back(player); + }; + + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + addPlayer(ref->GetSource()); + } + else + { + addPlayer(bot); + } + + std::vector*> orderedLists = { + &masters, &healers, &tanks, &others }; + for (std::vector* players : orderedLists) + { + for (Player* player : *players) + { + if (predicate(player)) + return player; + } + } + + return nullptr; +} + +static inline bool IsTankRole(Player* player) +{ + if (!player) + return false; + + if (player->HasTankSpec()) return true; - if (PlayerbotAI* otherAI = GET_PLAYERBOT_AI(p)) + + if (PlayerbotAI* otherAI = GET_PLAYERBOT_AI(player)) { if (otherAI->HasStrategy("tank", BOT_STATE_NON_COMBAT) || otherAI->HasStrategy("tank", BOT_STATE_COMBAT) || @@ -34,33 +110,36 @@ static inline bool IsTankRole(Player* p) otherAI->HasStrategy("bear", BOT_STATE_COMBAT)) return true; } + return false; } -// Added for solo paladin patch : determine if he's the only paladin on party static inline bool IsOnlyPaladinInGroup(Player* bot) { - if (!bot) return false; - Group* g = bot->GetGroup(); - if (!g) return true; // solo - uint32 pals = 0u; - for (GroupReference* r = g->GetFirstMember(); r; r = r->next()) + if (!bot) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return true; + + uint32 paladins = 0u; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - Player* p = r->GetSource(); - if (!p || !p->IsInWorld()) continue; - if (p->getClass() == CLASS_PALADIN) ++pals; + Player* player = ref->GetSource(); + if (!player || !player->IsInWorld()) continue; + if (player->getClass() == CLASS_PALADIN) ++paladins; } - return pals == 1u; + + return paladins == 1u; } inline std::string const GetActualBlessingOfMight(Unit* target) { if (!target->ToPlayer()) - { return "blessing of might"; - } - int tab = AiFactory::GetPlayerSpecTab(target->ToPlayer()); + uint8 tab = AiFactory::GetPlayerSpecTab(target->ToPlayer()); switch (target->getClass()) { case CLASS_MAGE: @@ -70,21 +149,15 @@ inline std::string const GetActualBlessingOfMight(Unit* target) break; case CLASS_SHAMAN: if (tab == SHAMAN_TAB_ELEMENTAL || tab == SHAMAN_TAB_RESTORATION) - { return "blessing of wisdom"; - } break; case CLASS_DRUID: if (tab == DRUID_TAB_RESTORATION || tab == DRUID_TAB_BALANCE) - { return "blessing of wisdom"; - } break; case CLASS_PALADIN: if (tab == PALADIN_TAB_HOLY) - { return "blessing of wisdom"; - } break; } @@ -94,10 +167,9 @@ inline std::string const GetActualBlessingOfMight(Unit* target) inline std::string const GetActualBlessingOfWisdom(Unit* target) { if (!target->ToPlayer()) - { return "blessing of might"; - } - int tab = AiFactory::GetPlayerSpecTab(target->ToPlayer()); + + uint8 tab = AiFactory::GetPlayerSpecTab(target->ToPlayer()); switch (target->getClass()) { case CLASS_WARRIOR: @@ -108,21 +180,15 @@ inline std::string const GetActualBlessingOfWisdom(Unit* target) break; case CLASS_SHAMAN: if (tab == SHAMAN_TAB_ENHANCEMENT) - { return "blessing of might"; - } break; case CLASS_DRUID: if (tab == DRUID_TAB_FERAL) - { return "blessing of might"; - } break; case CLASS_PALADIN: if (tab == PALADIN_TAB_PROTECTION || tab == PALADIN_TAB_RETRIBUTION) - { return "blessing of might"; - } break; } @@ -131,32 +197,41 @@ inline std::string const GetActualBlessingOfWisdom(Unit* target) inline std::string const GetActualBlessingOfSanctuary(Unit* target, Player* bot) { - if (!bot->HasSpell(SPELL_BLESSING_OF_SANCTUARY)) + if (!bot->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY)) return ""; - Player* tp = target->ToPlayer(); - if (!tp) + Player* targetPlayer = target->ToPlayer(); + if (!targetPlayer) return ""; - if (auto* ai = GET_PLAYERBOT_AI(bot)) + if (auto* botAI = GET_PLAYERBOT_AI(bot)) { - if (Unit* mt = ai->GetAiObjectContext()->GetValue("main tank")->Get()) + if (Unit* mainTank = + botAI->GetAiObjectContext()->GetValue("main tank")->Get()) { - if (mt == target) + if (mainTank == target) return "blessing of sanctuary"; } } - if (tp->HasTankSpec()) + if (targetPlayer->HasTankSpec()) return "blessing of sanctuary"; return ""; } -Value* CastBlessingOnPartyAction::GetTargetValue() +Unit* CastBlessingOfMightOnPartyAction::GetTarget() { + if (IsGreaterBlessingMode(bot)) + return nullptr; - return context->GetValue("party member without aura", MakeAuraQualifierForBuff(spell)); + return FindBlessingTarget(bot, botAI, [&](Player* player) + { + return !HasBlessingAura(botAI, player, + { "blessing of might", "greater blessing of might", + "blessing of wisdom", "greater blessing of wisdom", + "blessing of sanctuary", "greater blessing of sanctuary" }); + }); } bool CastBlessingOfMightAction::Execute(Event /*event*/) @@ -166,9 +241,6 @@ bool CastBlessingOfMightAction::Execute(Event /*event*/) return false; std::string castName = GetActualBlessingOfMight(target); - auto RP = ai::chat::MakeGroupAnnouncer(bot); - - castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP); return botAI->CastSpell(castName, target); } @@ -176,20 +248,22 @@ Value* CastBlessingOfMightOnPartyAction::GetTargetValue() { return context->GetValue( "party member without aura", - "blessing of might,greater blessing of might,blessing of wisdom,greater blessing of wisdom,blessing of sanctuary,greater blessing of sanctuary" + "blessing of might,greater blessing of might,blessing of wisdom," + "greater blessing of wisdom,blessing of sanctuary," + "greater blessing of sanctuary" ); } bool CastBlessingOfMightOnPartyAction::Execute(Event /*event*/) { + if (IsGreaterBlessingMode(bot)) + return false; + Unit* target = GetTarget(); if (!target) return false; std::string castName = GetActualBlessingOfMight(target); - auto RP = ai::chat::MakeGroupAnnouncer(bot); - - castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP); return botAI->CastSpell(castName, target); } @@ -200,45 +274,58 @@ bool CastBlessingOfWisdomAction::Execute(Event /*event*/) return false; std::string castName = GetActualBlessingOfWisdom(target); - auto RP = ai::chat::MakeGroupAnnouncer(bot); - - castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP); return botAI->CastSpell(castName, target); } +Unit* CastBlessingOfWisdomOnPartyAction::GetTarget() +{ + if (IsGreaterBlessingMode(bot)) + return nullptr; + + return FindBlessingTarget(bot, botAI, [&](Player* player) + { + if (botAI->HasStrategy("bwisdom", BOT_STATE_NON_COMBAT) && IsTankRole(player)) + return false; + + return !HasBlessingAura(botAI, player, + { "blessing of might", "greater blessing of might", + "blessing of wisdom", "greater blessing of wisdom", + "blessing of sanctuary", "greater blessing of sanctuary" }); + }); +} + Value* CastBlessingOfWisdomOnPartyAction::GetTargetValue() { return context->GetValue( "party member without aura", - "blessing of wisdom,greater blessing of wisdom,blessing of might,greater blessing of might,blessing of sanctuary,greater blessing of sanctuary" + "blessing of wisdom,greater blessing of wisdom,blessing of might,greater blessing of might," + "blessing of sanctuary,greater blessing of sanctuary" ); } bool CastBlessingOfWisdomOnPartyAction::Execute(Event /*event*/) { + if (IsGreaterBlessingMode(bot)) + return false; + Unit* target = GetTarget(); if (!target) return false; Player* targetPlayer = target->ToPlayer(); - if (Group* g = bot->GetGroup()) - if (targetPlayer && !g->IsMember(targetPlayer->GetGUID())) + if (Group* group = bot->GetGroup()) + if (targetPlayer && !group->IsMember(targetPlayer->GetGUID())) return false; - if (botAI->HasStrategy("bmana", BOT_STATE_NON_COMBAT) && + if (botAI->HasStrategy("bwisdom", BOT_STATE_NON_COMBAT) && targetPlayer && IsTankRole(targetPlayer)) - { - LOG_DEBUG("playerbots", "[Wisdom/bmana] Skip tank {} (Kings only)", target->GetName()); return false; - } std::string castName = GetActualBlessingOfWisdom(target); if (castName.empty()) return false; - auto RP = ai::chat::MakeGroupAnnouncer(bot); - castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP); return botAI->CastSpell(castName, target); } @@ -252,32 +339,31 @@ Value* CastBlessingOfSanctuaryOnPartyAction::GetTargetValue() bool CastBlessingOfSanctuaryOnPartyAction::Execute(Event /*event*/) { - if (!bot->HasSpell(SPELL_BLESSING_OF_SANCTUARY)) + if (IsGreaterBlessingMode(bot)) + return false; + + if (!bot->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY)) return false; Unit* target = GetTarget(); if (!target) - { - // Fallback: GetTarget() can be null if no one needs a buff. - // Keep a valid pointer for the checks/logs that follow. target = bot; - } Player* targetPlayer = target ? target->ToPlayer() : nullptr; - // Small helpers to check relevant auras - const auto HasKingsAura = [&](Unit* u) -> bool { - return botAI->HasAura("blessing of kings", u) || botAI->HasAura("greater blessing of kings", u); + const auto HasKingsAura = [&](Unit* unit) -> bool { + return botAI->HasAura("blessing of kings", unit) || + botAI->HasAura("greater blessing of kings", unit); }; - const auto HasSanctAura = [&](Unit* u) -> bool { - return botAI->HasAura("blessing of sanctuary", u) || botAI->HasAura("greater blessing of sanctuary", u); + const auto HasSanctAura = [&](Unit* unit) -> bool { + return botAI->HasAura("blessing of sanctuary", unit) || + botAI->HasAura("greater blessing of sanctuary", unit); }; - if (Group* g = bot->GetGroup()) + if (Group* group = bot->GetGroup()) { - if (targetPlayer && !g->IsMember(targetPlayer->GetGUID())) + if (targetPlayer && !group->IsMember(targetPlayer->GetGUID())) { - LOG_DEBUG("playerbots", "[Sanct] Initial target not in group, ignoring"); target = bot; targetPlayer = bot->ToPlayer(); } @@ -288,9 +374,6 @@ bool CastBlessingOfSanctuaryOnPartyAction::Execute(Event /*event*/) bool selfHasSanct = HasSanctAura(self); bool needSelf = IsTankRole(self) && !selfHasSanct; - LOG_DEBUG("playerbots", "[Sanct] {} isTank={} selfHasSanct={} needSelf={}", - bot->GetName(), IsTankRole(self), selfHasSanct, needSelf); - if (needSelf) { target = self; @@ -298,7 +381,6 @@ bool CastBlessingOfSanctuaryOnPartyAction::Execute(Event /*event*/) } } - // Try to re-target a valid tank in group if needed bool targetOk = false; if (targetPlayer) { @@ -308,20 +390,20 @@ bool CastBlessingOfSanctuaryOnPartyAction::Execute(Event /*event*/) if (!targetOk) { - if (Group* g = bot->GetGroup()) + if (Group* group = bot->GetGroup()) { - for (GroupReference* gref = g->GetFirstMember(); gref; gref = gref->next()) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - Player* p = gref->GetSource(); - if (!p) continue; - if (!p->IsInWorld() || !p->IsAlive()) continue; - if (!IsTankRole(p)) continue; + Player* player = ref->GetSource(); + if (!player) continue; + if (!player->IsInWorld() || !player->IsAlive()) continue; + if (!IsTankRole(player)) continue; - bool hasSanct = HasSanctAura(p); + bool hasSanct = HasSanctAura(player); if (!hasSanct) { - target = p; // prioritize this tank - targetPlayer = p; + target = player; + targetPlayer = player; targetOk = true; break; } @@ -329,150 +411,147 @@ bool CastBlessingOfSanctuaryOnPartyAction::Execute(Event /*event*/) } } - { - bool hasKings = HasKingsAura(target); - bool hasSanct = HasSanctAura(target); - bool knowSanct = bot->HasSpell(SPELL_BLESSING_OF_SANCTUARY); - LOG_DEBUG("playerbots", "[Sanct] Final target={} hasKings={} hasSanct={} knowSanct={}", - target->GetName(), hasKings, hasSanct, knowSanct); - } - - std::string castName = GetActualBlessingOfSanctuary(target, bot); - // If internal logic didn't recognize the tank (e.g., bear druid), force single-target Sanctuary - if (castName.empty()) + if (GetActualBlessingOfSanctuary(target, bot).empty()) { if (targetPlayer) { if (IsTankRole(targetPlayer)) - castName = "blessing of sanctuary"; // force single-target + return botAI->CastSpell("blessing of sanctuary", target); else return false; } else return false; } - if (targetPlayer && !IsTankRole(targetPlayer)) - { - auto RP = ai::chat::MakeGroupAnnouncer(bot); - castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP); - } - else - { - castName = "blessing of sanctuary"; - } - bool ok = botAI->CastSpell(castName, target); - LOG_DEBUG("playerbots", "[Sanct] Cast {} on {} result={}", castName, target->GetName(), ok); - return ok; + return botAI->CastSpell("blessing of sanctuary", target); +} + +Unit* CastBlessingOfSanctuaryOnPartyAction::GetTarget() +{ + if (IsGreaterBlessingMode(bot)) + return nullptr; + + if (!bot->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY)) + return nullptr; + + return FindBlessingTarget(bot, botAI, [&](Player* player) + { + return IsTankRole(player) && + !HasBlessingAura(botAI, player, + { "blessing of sanctuary", "greater blessing of sanctuary" }); + }); } Value* CastBlessingOfKingsOnPartyAction::GetTargetValue() { return context->GetValue( "party member without aura", - "blessing of kings,greater blessing of kings,blessing of sanctuary,greater blessing of sanctuary" + "blessing of kings,greater blessing of kings," + "blessing of sanctuary,greater blessing of sanctuary" ); } +Unit* CastBlessingOfKingsOnPartyAction::GetTarget() +{ + if (IsGreaterBlessingMode(bot)) + return nullptr; + + const bool hasBwisdom = botAI->HasStrategy("bwisdom", BOT_STATE_NON_COMBAT); + const bool hasBkings = botAI->HasStrategy("bkings", BOT_STATE_NON_COMBAT); + const bool onlyPaladinInGroup = IsOnlyPaladinInGroup(bot); + + return FindBlessingTarget(bot, botAI, [&](Player* player) + { + const bool isTank = IsTankRole(player); + const bool hasKingsOrSanct = HasBlessingAura(botAI, player, + { "blessing of kings", "greater blessing of kings", + "blessing of sanctuary", "greater blessing of sanctuary" }); + + if (hasKingsOrSanct) + return false; + + if (hasBwisdom) + return isTank; + + if (hasBkings) + { + if (isTank) + return false; + + if (onlyPaladinInGroup && player == bot) + return false; + } + + return true; + }); +} + bool CastBlessingOfKingsOnPartyAction::Execute(Event /*event*/) { + if (IsGreaterBlessingMode(bot)) + return false; + Unit* target = GetTarget(); if (!target) return false; - Group* g = bot->GetGroup(); - if (!g) + Group* group = bot->GetGroup(); + if (!group) return false; - // Added for patch solo paladin, never buff itself to not remove his sanctuary buff - if (botAI->HasStrategy("bstats", BOT_STATE_NON_COMBAT) && IsOnlyPaladinInGroup(bot)) + if (botAI->HasStrategy("bkings", BOT_STATE_NON_COMBAT) && + IsOnlyPaladinInGroup(bot)) { if (target->GetGUID() == bot->GetGUID()) - { - LOG_DEBUG("playerbots", "[Kings/bstats-solo] Skip self to keep Sanctuary on {}", bot->GetName()); return false; - } } - // End solo paladin patch Player* targetPlayer = target->ToPlayer(); - if (targetPlayer && !g->IsMember(targetPlayer->GetGUID())) + if (targetPlayer && !group->IsMember(targetPlayer->GetGUID())) return false; - const bool hasBmana = botAI->HasStrategy("bmana", BOT_STATE_NON_COMBAT); - const bool hasBstats = botAI->HasStrategy("bstats", BOT_STATE_NON_COMBAT); + const bool hasBwisdom = botAI->HasStrategy("bwisdom", BOT_STATE_NON_COMBAT); + const bool hasBkings = botAI->HasStrategy("bkings", BOT_STATE_NON_COMBAT); - if (hasBmana) - { - if (!targetPlayer || !IsTankRole(targetPlayer)) - { - LOG_DEBUG("playerbots", "[Kings/bmana] Skip non-tank {}", target->GetName()); - return false; - } - } + if (hasBwisdom && (!targetPlayer || !IsTankRole(targetPlayer))) + return false; if (targetPlayer) { const bool isTank = IsTankRole(targetPlayer); const bool hasSanctFromMe = - target->HasAura(SPELL_BLESSING_OF_SANCTUARY, bot->GetGUID()) || - target->HasAura(SPELL_GREATER_BLESSING_OF_SANCTUARY, bot->GetGUID()); + target->HasAura(ai::paladin::SPELL_BLESSING_OF_SANCTUARY, bot->GetGUID()) || + target->HasAura(ai::paladin::SPELL_GREATER_BLESSING_OF_SANCTUARY, bot->GetGUID()); const bool hasSanctAny = botAI->HasAura("blessing of sanctuary", target) || botAI->HasAura("greater blessing of sanctuary", target); if (isTank && hasSanctFromMe) - { - LOG_DEBUG("playerbots", "[Kings] Skip: {} has my Sanctuary and is a tank", target->GetName()); return false; - } - if (hasBstats && isTank && hasSanctAny) - { - LOG_DEBUG("playerbots", "[Kings] Skip (bstats): {} already has Sanctuary and is a tank", target->GetName()); + if (hasBkings && isTank && hasSanctAny) return false; - } } - std::string castName = "blessing of kings"; - - bool allowGreater = true; - - if (hasBmana) - allowGreater = false; - - if (allowGreater && hasBstats && targetPlayer) - { - switch (targetPlayer->getClass()) - { - case CLASS_WARRIOR: - case CLASS_PALADIN: - case CLASS_DRUID: - case CLASS_DEATH_KNIGHT: - allowGreater = false; - break; - default: - break; - } - } - - if (allowGreater) - { - auto RP = ai::chat::MakeGroupAnnouncer(bot); - castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP); - } - - return botAI->CastSpell(castName, target); + return botAI->CastSpell("blessing of kings", target); } -bool CastSealSpellAction::isUseful() { return AI_VALUE2(bool, "combat", "self target"); } +bool CastSealSpellAction::isUseful() +{ + return AI_VALUE2(bool, "combat", "self target"); +} -Value* CastTurnUndeadAction::GetTargetValue() { return context->GetValue("cc target", getName()); } +Value* CastTurnUndeadAction::GetTargetValue() +{ + return context->GetValue("cc target", getName()); +} Unit* CastHandOfFreedomOnPartyAction::GetTarget() { bool const selfImpaired = botAI->IsMovementImpaired(bot); - bool const hasSelfHand = selfImpaired && ai::paladin::HasAnyPaladinHandFromCaster(bot, bot); + bool const hasSelfHand = + selfImpaired && ai::paladin::HasAnyPaladinHandFromCaster(bot, bot); if (!bot->GetGroup()) { @@ -499,7 +578,8 @@ bool CastHandOfFreedomOnPartyAction::isUseful() if (!target) return false; - return CastBuffSpellAction::isUseful() && !ai::paladin::HasAnyPaladinHandFromCaster(target, bot); + return CastBuffSpellAction::isUseful() && + !ai::paladin::HasAnyPaladinHandFromCaster(target, bot); } Unit* CastRighteousDefenseAction::GetTarget() diff --git a/src/Ai/Class/Paladin/Action/PaladinActions.h b/src/Ai/Class/Paladin/Action/PaladinActions.h index 75b0637a4bc..ef59225e6c6 100644 --- a/src/Ai/Class/Paladin/Action/PaladinActions.h +++ b/src/Ai/Class/Paladin/Action/PaladinActions.h @@ -8,10 +8,6 @@ #include "AiObject.h" #include "GenericSpellActions.h" -#include "SharedDefines.h" - -class PlayerbotAI; -class Unit; // seals BUFF_ACTION(CastSealOfRighteousnessAction, "seal of righteousness"); @@ -88,24 +84,13 @@ class CastBlessingOfMightAction : public CastBuffSpellAction bool Execute(Event event) override; }; -class CastBlessingOnPartyAction : public BuffOnPartyAction -{ -public: - CastBlessingOnPartyAction(PlayerbotAI* botAI, std::string const name) - : BuffOnPartyAction(botAI, name), name(name) {} - - Value* GetTargetValue() override; - -private: - std::string name; -}; - class CastBlessingOfMightOnPartyAction : public BuffOnPartyAction { public: CastBlessingOfMightOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of might") {} std::string const getName() override { return "blessing of might on party"; } + Unit* GetTarget() override; Value* GetTargetValue() override; bool Execute(Event event) override; }; @@ -124,6 +109,7 @@ class CastBlessingOfWisdomOnPartyAction : public BuffOnPartyAction CastBlessingOfWisdomOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of wisdom") {} std::string const getName() override { return "blessing of wisdom on party"; } + Unit* GetTarget() override; Value* GetTargetValue() override; bool Execute(Event event) override; }; @@ -134,12 +120,13 @@ class CastBlessingOfKingsAction : public CastBuffSpellAction CastBlessingOfKingsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "blessing of kings") {} }; -class CastBlessingOfKingsOnPartyAction : public CastBlessingOnPartyAction +class CastBlessingOfKingsOnPartyAction : public BuffOnPartyAction { public: - CastBlessingOfKingsOnPartyAction(PlayerbotAI* botAI) : CastBlessingOnPartyAction(botAI, "blessing of kings") {} + CastBlessingOfKingsOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of kings") {} std::string const getName() override { return "blessing of kings on party"; } + Unit* GetTarget() override; Value* GetTargetValue() override; // added for Sanctuary priority bool Execute(Event event) override; // added for 2 paladins logic }; @@ -156,6 +143,7 @@ class CastBlessingOfSanctuaryOnPartyAction : public BuffOnPartyAction CastBlessingOfSanctuaryOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of sanctuary") {} std::string const getName() override { return "blessing of sanctuary on party"; } + Unit* GetTarget() override; Value* GetTargetValue() override; bool Execute(Event event) override; }; diff --git a/src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.cpp b/src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.cpp new file mode 100644 index 00000000000..a123b29fa5c --- /dev/null +++ b/src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.cpp @@ -0,0 +1,1116 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "PaladinGreaterBlessingAction.h" + +#include "AiObjectContext.h" +#include "AiFactory.h" +#include "Event.h" +#include "GenericBuffUtils.h" +#include "PaladinHelper.h" +#include "Playerbots.h" +#include "SharedDefines.h" +#include "SpellAuraEffects.h" +#include "Value.h" + +#include +#include + +namespace ai::gbless +{ +namespace +{ + constexpr uint32 GREATER_BLESSING_ASSIGNMENT_CACHE_MS = 4 * 1000; + constexpr uint32 GREATER_BLESSING_PENDING_ASSIGNMENT_CACHE_MS = 100; + constexpr uint8 MAX_BLESSING_SLOTS = 4; + constexpr uint8 MAX_CLASS_ID = 12; + + constexpr size_t BaseBlessingCategoryCount = MAX_BLESSING_SLOTS; + + constexpr size_t BaseBlessingIndex(BaseBlessingCategory category) + { + return static_cast(static_cast(category) - static_cast(BASE_MIGHT)); + } + + bool UsesRoleBucket(uint8 classId) + { + switch (classId) + { + case CLASS_WARRIOR: + case CLASS_DEATH_KNIGHT: + case CLASS_SHAMAN: + case CLASS_PALADIN: + case CLASS_DRUID: + return true; + default: + return false; + } + } + + bool IsSameBucket( + CachedBlessingBucketAssignment const& left, + CachedBlessingBucketAssignment const& right) + { + return left.classId == right.classId && + left.byRole == right.byRole && + (!left.byRole || left.role == right.role); + } + + int TalentScore(Player* player) + { + if (!player) + return 0; + + int score = 0; + if (player->HasAura(SPELL_IMPROVED_MIGHT_R1) || + player->HasAura(SPELL_IMPROVED_MIGHT_R2)) + { + score += 2; + } + if (player->HasAura(SPELL_IMPROVED_WISDOM_R1) || + player->HasAura(SPELL_IMPROVED_WISDOM_R2)) + { + score += 1; + } + + return score; + } + + int TalentMatchScore(Player* player, BaseBlessingCategory category) + { + if (!player) + return std::numeric_limits::min() / 4; + + if (category == BASE_SANCTUARY) + { + if (!player->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY)) + return std::numeric_limits::min() / 4; + + return 2; + } + + if (category == BASE_MIGHT && + (player->HasAura(SPELL_IMPROVED_MIGHT_R1) || + player->HasAura(SPELL_IMPROVED_MIGHT_R2))) + { + return 1; + } + if (category == BASE_WISDOM && + (player->HasAura(SPELL_IMPROVED_WISDOM_R1) || + player->HasAura(SPELL_IMPROVED_WISDOM_R2))) + { + return 1; + } + + return 0; + } + + struct DesiredBlessingSet + { + std::array ordered = {}; + std::array wants = {}; + uint8 count = 0; + }; + + struct PresentBucket + { + uint8 classId = 0; + RoleProfile role = ROLE_CASTER; + bool byRole = false; + uint8 memberCount = 0; + DesiredBlessingSet desired; + }; + + DesiredBlessingSet BuildDesiredBlessingSet( + RoleProfile role, uint8 paladinCount, bool anySanctuaryAvailable) + { + DesiredBlessingSet desired; + + auto const& priority = BASE_BLESSING_PRIORITIES[role]; + uint8 requestedCount = std::min(paladinCount, MAX_BLESSING_SLOTS); + + for (uint8 index = 0; + index < MAX_BLESSING_SLOTS && desired.count < requestedCount; + ++index) + { + BaseBlessingCategory category = priority.priorities[index]; + if (category == BASE_NONE) + continue; + + if (category == BASE_SANCTUARY && !anySanctuaryAvailable) + category = BASE_KINGS; + + if (category == BASE_NONE || desired.wants[BaseBlessingIndex(category)]) + continue; + + desired.ordered[desired.count++] = category; + desired.wants[BaseBlessingIndex(category)] = true; + } + + return desired; + } + + std::vector OrderedCommonBases( + std::vector const& classBuckets, + std::array const& commonBases) + { + std::vector ordered; + + for (PresentBucket const* bucket : classBuckets) + { + for (uint8 index = 0; index < bucket->desired.count; ++index) + { + BaseBlessingCategory category = bucket->desired.ordered[index]; + if (!commonBases[BaseBlessingIndex(category)]) + continue; + + if (std::find(ordered.begin(), ordered.end(), category) == ordered.end()) + ordered.push_back(category); + } + } + + return ordered; + } + + bool ComputeBestOwners( + std::vector const& categories, + std::vector const& botPaladins, + std::vector const& candidatePaladins, + std::vector& outOwners, + int* outScore = nullptr) + { + outOwners.clear(); + if (categories.empty()) + { + if (outScore) + *outScore = 0; + return true; + } + + std::vector currentOwners(categories.size(), -1); + std::vector bestOwners(categories.size(), -1); + std::vector used(candidatePaladins.size(), false); + int bestScore = std::numeric_limits::min(); + int bestTalentCost = std::numeric_limits::max(); + bool found = false; + + auto search = [&](auto&& self, size_t position, int score, int talentCost) -> void + { + if (position >= categories.size()) + { + if (!found || score > bestScore || + (score == bestScore && talentCost < bestTalentCost) || + (score == bestScore && + talentCost == bestTalentCost && + std::lexicographical_compare(currentOwners.begin(), currentOwners.end(), + bestOwners.begin(), bestOwners.end()))) + { + found = true; + bestScore = score; + bestTalentCost = talentCost; + bestOwners = currentOwners; + } + return; + } + + for (size_t candidateIndex = 0; + candidateIndex < candidatePaladins.size(); ++candidateIndex) + { + if (used[candidateIndex]) + continue; + + int paladinIndex = candidatePaladins[candidateIndex]; + int matchScore = TalentMatchScore(botPaladins[paladinIndex], categories[position]); + if (matchScore <= std::numeric_limits::min() / 8) + continue; + + used[candidateIndex] = true; + currentOwners[position] = paladinIndex; + self(self, position + 1, score + matchScore, + talentCost + TalentScore(botPaladins[paladinIndex])); + currentOwners[position] = -1; + used[candidateIndex] = false; + } + }; + + search(search, 0, 0, 0); + + if (!found) + return false; + + outOwners = std::move(bestOwners); + if (outScore) + *outScore = bestScore; + return true; + } + + struct ClassPlanPreference + { + std::array slotCoverage = {}; + std::array matchedSlotCoverage = {}; + uint8 commonCount = 0; + int ownerScore = std::numeric_limits::min(); + int commonTalentCost = std::numeric_limits::max(); + uint8 selectedMask = 0; + std::vector classWideOwners; + bool valid = false; + }; + + uint8 OrderedIndexForCategory(PresentBucket const* bucket, BaseBlessingCategory category) + { + for (uint8 index = 0; index < bucket->desired.count; ++index) + { + if (bucket->desired.ordered[index] == category) + return index; + } + + return MAX_BLESSING_SLOTS; + } + + bool IsBetterClassPlanPreference( + ClassPlanPreference const& candidate, ClassPlanPreference const& best) + { + if (!best.valid) + return true; + + for (uint8 index = 0; index < MAX_BLESSING_SLOTS; ++index) + { + if (candidate.slotCoverage[index] != best.slotCoverage[index]) + return candidate.slotCoverage[index] > best.slotCoverage[index]; + } + + for (uint8 index = 0; index < MAX_BLESSING_SLOTS; ++index) + { + if (candidate.matchedSlotCoverage[index] != best.matchedSlotCoverage[index]) + return candidate.matchedSlotCoverage[index] > best.matchedSlotCoverage[index]; + } + + if (candidate.commonCount != best.commonCount) + return candidate.commonCount > best.commonCount; + + if (candidate.ownerScore != best.ownerScore) + return candidate.ownerScore > best.ownerScore; + + if (candidate.commonTalentCost != best.commonTalentCost) + return candidate.commonTalentCost < best.commonTalentCost; + + if (std::lexicographical_compare(candidate.classWideOwners.begin(), + candidate.classWideOwners.end(), + best.classWideOwners.begin(), + best.classWideOwners.end())) + return true; + + return candidate.selectedMask < best.selectedMask; + } + + bool ComputeOwnersForClassPlan( + std::vector const& classWideBases, + std::vector> const& exclusiveBasesByBucket, + std::vector const& botPaladins, + std::vector const& allPaladins, + std::vector& outClassWideOwners, + std::vector>& outExclusiveOwnersByBucket, + int& outOwnerScore, + int& outCommonTalentCost) + { + outClassWideOwners.clear(); + outExclusiveOwnersByBucket.clear(); + outOwnerScore = std::numeric_limits::min(); + outCommonTalentCost = std::numeric_limits::max(); + + std::vector currentClassWideOwners(classWideBases.size(), -1); + std::vector bestClassWideOwners(classWideBases.size(), -1); + std::vector used(allPaladins.size(), false); + std::vector> bestExclusiveOwnersByBucket(exclusiveBasesByBucket.size()); + bool found = false; + + auto search = [&](auto&& self, size_t position, int commonScore, int commonTalentCost) -> void + { + if (position >= classWideBases.size()) + { + std::vector availablePaladins; + availablePaladins.reserve(allPaladins.size()); + for (size_t candidateIndex = 0; candidateIndex < allPaladins.size(); ++candidateIndex) + { + if (!used[candidateIndex]) + availablePaladins.push_back(allPaladins[candidateIndex]); + } + + int totalOwnerScore = commonScore; + std::vector> exclusiveOwnersByBucket(exclusiveBasesByBucket.size()); + for (size_t bucketIndex = 0; bucketIndex < exclusiveBasesByBucket.size(); ++bucketIndex) + { + int exclusiveScore = 0; + if (!ComputeBestOwners( + exclusiveBasesByBucket[bucketIndex], botPaladins, availablePaladins, + exclusiveOwnersByBucket[bucketIndex], &exclusiveScore)) + { + return; + } + + totalOwnerScore += exclusiveScore; + } + + if (!found || totalOwnerScore > outOwnerScore || + (totalOwnerScore == outOwnerScore && commonTalentCost < outCommonTalentCost) || + (totalOwnerScore == outOwnerScore && + commonTalentCost == outCommonTalentCost && + std::lexicographical_compare(currentClassWideOwners.begin(), currentClassWideOwners.end(), + bestClassWideOwners.begin(), bestClassWideOwners.end()))) + { + found = true; + outOwnerScore = totalOwnerScore; + outCommonTalentCost = commonTalentCost; + bestClassWideOwners = currentClassWideOwners; + bestExclusiveOwnersByBucket = std::move(exclusiveOwnersByBucket); + } + + return; + } + + for (size_t candidateIndex = 0; candidateIndex < allPaladins.size(); ++candidateIndex) + { + if (used[candidateIndex]) + continue; + + int const paladinIndex = allPaladins[candidateIndex]; + int const matchScore = TalentMatchScore(botPaladins[paladinIndex], classWideBases[position]); + if (matchScore <= std::numeric_limits::min() / 8) + continue; + + used[candidateIndex] = true; + currentClassWideOwners[position] = paladinIndex; + self(self, position + 1, commonScore + matchScore, + commonTalentCost + TalentScore(botPaladins[paladinIndex])); + currentClassWideOwners[position] = -1; + used[candidateIndex] = false; + } + }; + + search(search, 0, 0, 0); + + if (!found) + return false; + + outClassWideOwners = std::move(bestClassWideOwners); + outExclusiveOwnersByBucket = std::move(bestExclusiveOwnersByBucket); + return true; + } + + bool ComputeBestClassAssignments( + std::vector const& classBuckets, + std::vector const& botPaladins, + std::vector const& allPaladins, + std::vector& outClassWideOwners, + std::vector>& outExclusiveOwnersByBucket, + std::vector& outClassWideBases, + std::vector>& outExclusiveBasesByBucket) + { + outClassWideOwners.clear(); + outExclusiveOwnersByBucket.clear(); + outClassWideBases.clear(); + outExclusiveBasesByBucket.clear(); + + uint8 const categoryMaskLimit = + static_cast(1u << (static_cast(BASE_SANCTUARY) + 1u)); + uint8 commonUnionMask = 0; + ClassPlanPreference bestPreference; + std::vector bestClassWideOwners; + std::vector> bestExclusiveOwnersByBucket; + std::vector bestClassWideBases; + std::vector> bestExclusiveBasesByBucket; + + for (uint8 baseValue = BASE_MIGHT; baseValue <= BASE_SANCTUARY; ++baseValue) + { + BaseBlessingCategory category = static_cast(baseValue); + if (std::all_of(classBuckets.begin(), classBuckets.end(), + [&](PresentBucket const* bucket) + { + return bucket->desired.wants[BaseBlessingIndex(category)]; + })) + { + commonUnionMask |= static_cast(1u << static_cast(category)); + } + } + + for (uint8 promotedMask = 0; promotedMask < categoryMaskLimit; ++promotedMask) + { + if ((promotedMask & commonUnionMask) != promotedMask) + continue; + + ClassPlanPreference candidatePreference; + candidatePreference.selectedMask = promotedMask; + + std::vector> exclusiveBasesByBucket(classBuckets.size()); + + for (size_t bucketIndex = 0; bucketIndex < classBuckets.size(); ++bucketIndex) + { + PresentBucket const* bucket = classBuckets[bucketIndex]; + for (uint8 index = 0; index < bucket->desired.count; ++index) + { + BaseBlessingCategory category = bucket->desired.ordered[index]; + candidatePreference.slotCoverage[index] += bucket->memberCount; + + if (!(promotedMask & static_cast(1u << static_cast(category)))) + exclusiveBasesByBucket[bucketIndex].push_back(category); + } + } + + std::array promotedCommonBases = {}; + for (uint8 baseValue = BASE_MIGHT; baseValue <= BASE_SANCTUARY; ++baseValue) + { + BaseBlessingCategory category = static_cast(baseValue); + promotedCommonBases[BaseBlessingIndex(category)] = + (promotedMask & static_cast(1u << static_cast(category))) != 0; + } + + std::vector classWideBases = + OrderedCommonBases(classBuckets, promotedCommonBases); + + std::vector classWideOwners; + std::vector> exclusiveOwnersByBucket; + int ownerScore = 0; + int commonTalentCost = 0; + if (!ComputeOwnersForClassPlan( + classWideBases, exclusiveBasesByBucket, botPaladins, allPaladins, + classWideOwners, exclusiveOwnersByBucket, ownerScore, commonTalentCost)) + { + continue; + } + + candidatePreference.commonCount = static_cast(classWideBases.size()); + candidatePreference.ownerScore = ownerScore; + candidatePreference.commonTalentCost = commonTalentCost; + candidatePreference.classWideOwners = classWideOwners; + + for (size_t index = 0; index < classWideBases.size(); ++index) + { + Player* owner = botPaladins[classWideOwners[index]]; + BaseBlessingCategory category = classWideBases[index]; + if (TalentMatchScore(owner, category) <= 0) + continue; + + for (PresentBucket const* bucket : classBuckets) + { + uint8 orderedIndex = OrderedIndexForCategory(bucket, category); + if (orderedIndex >= MAX_BLESSING_SLOTS) + continue; + + candidatePreference.matchedSlotCoverage[orderedIndex] += bucket->memberCount; + } + } + + for (size_t bucketIndex = 0; bucketIndex < classBuckets.size(); ++bucketIndex) + { + PresentBucket const* bucket = classBuckets[bucketIndex]; + auto const& exclusiveBases = exclusiveBasesByBucket[bucketIndex]; + auto const& exclusiveOwners = exclusiveOwnersByBucket[bucketIndex]; + + for (size_t index = 0; index < exclusiveBases.size(); ++index) + { + Player* owner = botPaladins[exclusiveOwners[index]]; + BaseBlessingCategory category = exclusiveBases[index]; + if (TalentMatchScore(owner, category) <= 0) + continue; + + uint8 orderedIndex = OrderedIndexForCategory(bucket, category); + if (orderedIndex >= MAX_BLESSING_SLOTS) + continue; + + candidatePreference.matchedSlotCoverage[orderedIndex] += bucket->memberCount; + } + } + + candidatePreference.valid = true; + + if (!IsBetterClassPlanPreference(candidatePreference, bestPreference)) + continue; + + bestPreference = candidatePreference; + bestClassWideOwners = std::move(classWideOwners); + bestExclusiveOwnersByBucket = std::move(exclusiveOwnersByBucket); + bestClassWideBases = std::move(classWideBases); + bestExclusiveBasesByBucket = std::move(exclusiveBasesByBucket); + } + + if (!bestPreference.valid) + return false; + + outClassWideOwners = std::move(bestClassWideOwners); + outExclusiveOwnersByBucket = std::move(bestExclusiveOwnersByBucket); + outClassWideBases = std::move(bestClassWideBases); + outExclusiveBasesByBucket = std::move(bestExclusiveBasesByBucket); + return true; + } + + void AddUniqueAssignment( + std::vector& assignments, + CachedBlessingBucketAssignment const& assignment) + { + auto existing = std::find_if(assignments.begin(), assignments.end(), + [&](CachedBlessingBucketAssignment const& cachedAssignment) + { + return cachedAssignment.blessing == assignment.blessing && + IsSameBucket(cachedAssignment, assignment); + }); + + if (existing == assignments.end()) + assignments.push_back(assignment); + } + + bool ComputeGreaterBlessingAssignments( + PlayerbotAI* botAI, std::vector& outAssignments) + { + Player* bot = botAI->GetBot(); + Group* group = bot->GetGroup(); + if (!IsEligibleGroupForAutoBlessings(group)) + return false; + + std::vector botPaladins; + struct RaidMember + { + Player* player; + RoleProfile role; + }; + std::vector raidMembers; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* player = ref->GetSource(); + if (!player || !player->IsInWorld()) + continue; + + if (player->getClass() == CLASS_PALADIN && GET_PLAYERBOT_AI(player)) + botPaladins.push_back(player); + + // Important: keep dead members in the assignment model so revive does + // not temporarily delete an entire blessing bucket from the cache. + raidMembers.push_back({player, ResolveRoleProfile(player)}); + } + + if (botPaladins.empty()) + return false; + + bool anySanctuaryAvailable = false; + for (Player* paladin : botPaladins) + { + if (paladin && paladin->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY)) + { + anySanctuaryAvailable = true; + break; + } + } + + std::sort(botPaladins.begin(), botPaladins.end(), + [](Player* a, Player* b) + { + int sa = TalentScore(a); + int sb = TalentScore(b); + if (sa != sb) + return sa > sb; + return a->GetGUID() < b->GetGUID(); + }); + + uint8 activePaladinCount = + std::min(static_cast(botPaladins.size()), MAX_BLESSING_SLOTS); + + int mySlot = -1; + for (size_t i = 0; i < botPaladins.size(); ++i) + { + if (botPaladins[i]->GetGUID() == bot->GetGUID()) + { + mySlot = static_cast(i); + break; + } + } + if (mySlot < 0 || mySlot >= activePaladinCount) + return false; + + std::vector buckets; + buckets.reserve(raidMembers.size()); + + for (auto const& member : raidMembers) + { + uint8 classId = member.player->getClass(); + if (classId >= MAX_CLASS_ID) + continue; + + PresentBucket bucket; + bucket.classId = classId; + bucket.role = member.role; + bucket.byRole = UsesRoleBucket(classId); + bucket.memberCount = 1; + bucket.desired = BuildDesiredBlessingSet( + member.role, activePaladinCount, anySanctuaryAvailable); + + if (!bucket.byRole) + bucket.role = ROLE_CASTER; + + if (!bucket.desired.count) + continue; + + auto existing = std::find_if(buckets.begin(), buckets.end(), + [&](PresentBucket const& other) + { + return other.classId == bucket.classId && + other.byRole == bucket.byRole && + (!bucket.byRole || other.role == bucket.role); + }); + + if (existing == buckets.end()) + buckets.push_back(bucket); + else + ++existing->memberCount; + } + + if (buckets.empty()) + return false; + + std::vector allPaladins; + allPaladins.reserve(activePaladinCount); + for (uint8 paladinIndex = 0; paladinIndex < activePaladinCount; ++paladinIndex) + allPaladins.push_back(paladinIndex); + + outAssignments.clear(); + + for (uint8 classId = 0; classId < MAX_CLASS_ID; ++classId) + { + std::vector classBuckets; + for (PresentBucket const& bucket : buckets) + { + if (bucket.classId == classId) + classBuckets.push_back(&bucket); + } + + if (classBuckets.empty()) + continue; + + std::vector classWideOwners; + std::vector> exclusiveOwnersByBucket; + std::vector classWideBases; + std::vector> exclusiveBasesByBucket; + if (!ComputeBestClassAssignments( + classBuckets, botPaladins, allPaladins, + classWideOwners, exclusiveOwnersByBucket, classWideBases, + exclusiveBasesByBucket)) + return false; + + for (size_t index = 0; index < classWideBases.size(); ++index) + { + if (classWideOwners[index] != mySlot) + continue; + + CachedBlessingBucketAssignment assignment; + assignment.classId = classId; + assignment.role = classBuckets.front()->role; + assignment.byRole = false; + assignment.blessing = ToGreaterVariant(classWideBases[index]); + AddUniqueAssignment(outAssignments, assignment); + } + + for (size_t bucketIndex = 0; bucketIndex < classBuckets.size(); ++bucketIndex) + { + PresentBucket const* bucket = classBuckets[bucketIndex]; + auto const& exclusiveBases = exclusiveBasesByBucket[bucketIndex]; + auto const& exclusiveOwners = exclusiveOwnersByBucket[bucketIndex]; + + for (size_t index = 0; index < exclusiveBases.size(); ++index) + { + if (exclusiveOwners[index] != mySlot) + continue; + + CachedBlessingBucketAssignment assignment; + assignment.classId = bucket->classId; + assignment.role = bucket->role; + assignment.byRole = true; + assignment.blessing = ToSingleVariant(exclusiveBases[index]); + AddUniqueAssignment(outAssignments, assignment); + } + } + } + + return !outAssignments.empty(); + } + + class GreaterBlessingAssignmentsValue : public CalculatedValue + { + public: + GreaterBlessingAssignmentsValue(PlayerbotAI* botAI) + : CalculatedValue( + botAI, "greater blessing assignments", GREATER_BLESSING_ASSIGNMENT_CACHE_MS) {} + + protected: + CachedBlessingAssignments Calculate() override + { + CachedBlessingAssignments cached; + + Player* bot = botAI->GetBot(); + Group* group = bot->GetGroup(); + cached.groupKey = group ? group->GetLeaderGUID().GetCounter() : 0; + + std::vector assignments; + if (!ComputeGreaterBlessingAssignments(botAI, assignments)) + return cached; + + cached.valid = true; + cached.assignments = std::move(assignments); + + return cached; + } + }; + +} + +UntypedValue* greater_blessing_assignments_value(PlayerbotAI* botAI) +{ + return new GreaterBlessingAssignmentsValue(botAI); +} + +bool IsEligibleGroupForAutoBlessings(Group const* group) +{ + if (!group) + return false; + + switch (sPlayerbotAIConfig.autoGreaterBlessings) + { + case AutoPartyBuffMode::RAID_ONLY: + return group->isRaidGroup(); + case AutoPartyBuffMode::GROUP_OR_RAID: + return true; + case AutoPartyBuffMode::DISABLED: + default: + return false; + } +} + +static bool HasMyExactBlessing(PlayerbotAI* botAI, Unit* target, BlessingType type) +{ + std::string name = BlessingSpellName(type); + if (name.empty()) + return false; + + return botAI->HasAura(name.c_str(), target, false, true); +} + +static int32 GetAuraStrength(Aura const* aura, AuraType auraType) +{ + if (!aura) + return 0; + + int32 amount = 0; + for (uint8 effect = 0; effect < MAX_SPELL_EFFECTS; ++effect) + { + AuraEffect* auraEffect = aura->GetEffect(effect); + if (!auraEffect || auraEffect->GetAuraType() != auraType) + continue; + + amount = std::max(amount, auraEffect->GetAmount()); + } + + return amount; +} + +static int32 GetExistingBlessingStrength( + PlayerbotAI* botAI, Unit* target, BaseBlessingCategory category) +{ + if (category != BASE_MIGHT && category != BASE_WISDOM) + return 0; + + AuraType auraType = + category == BASE_MIGHT ? SPELL_AURA_MOD_ATTACK_POWER : SPELL_AURA_MOD_POWER_REGEN; + int32 strongestAmount = 0; + + for (BlessingType type : {ToSingleVariant(category), ToGreaterVariant(category)}) + { + Aura* aura = botAI->GetAura(BlessingSpellName(type), target); + strongestAmount = std::max(strongestAmount, GetAuraStrength(aura, auraType)); + } + + return strongestAmount; +} + +static bool HasSameFamilyBlessing( + PlayerbotAI* botAI, Unit* target, BaseBlessingCategory category) +{ + for (BlessingType type : {ToSingleVariant(category), ToGreaterVariant(category)}) + { + if (botAI->HasAura(BlessingSpellName(type), target)) + return true; + } + + return false; +} + +static int32 GetBlessingCastStrength(Player* caster, BlessingType type, uint32 spellId) +{ + if (!caster || !spellId) + return 0; + + BaseBlessingCategory category = BaseBlessingOf(type); + if (category != BASE_MIGHT && category != BASE_WISDOM) + return 0; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + return 0; + + AuraType auraType = + category == BASE_MIGHT ? SPELL_AURA_MOD_ATTACK_POWER : SPELL_AURA_MOD_POWER_REGEN; + int32 amount = 0; + for (uint8 effect = 0; effect < MAX_SPELL_EFFECTS; ++effect) + { + if (spellInfo->Effects[effect].ApplyAuraName != auraType) + continue; + + amount = std::max(amount, spellInfo->Effects[effect].BasePoints + 1); + } + + if (amount <= 0) + return 0; + + switch (category) + { + case BASE_MIGHT: + if (caster->HasAura(SPELL_IMPROVED_MIGHT_R2)) + return amount * 125 / 100; + if (caster->HasAura(SPELL_IMPROVED_MIGHT_R1)) + return amount * 112 / 100; + break; + case BASE_WISDOM: + if (caster->HasAura(SPELL_IMPROVED_WISDOM_R2)) + return amount * 120 / 100; + if (caster->HasAura(SPELL_IMPROVED_WISDOM_R1)) + return amount * 110 / 100; + break; + default: + break; + } + + return amount; +} + +static bool HasEquivalentOrStrongerSameFamilyBlessing( + PlayerbotAI* botAI, Unit* target, BlessingType castType, uint32 spellId) +{ + BaseBlessingCategory category = BaseBlessingOf(castType); + if (category != BASE_MIGHT && category != BASE_WISDOM) + return HasSameFamilyBlessing(botAI, target, category); + + int32 castStrength = GetBlessingCastStrength(botAI->GetBot(), castType, spellId); + if (castStrength <= 0) + return false; + + return GetExistingBlessingStrength(botAI, target, category) >= castStrength; +} + +static bool MatchesBucket(Player* player, CachedBlessingBucketAssignment const& assignment) +{ + if (!player || player->getClass() != assignment.classId) + return false; + + return !assignment.byRole || ResolveRoleProfile(player) == assignment.role; +} + +static Player* FindMissingBlessingTarget( + PlayerbotAI* botAI, CachedBlessingBucketAssignment const& assignment, + BlessingType castType, uint32 spellId, std::string const& spellName) +{ + Player* bot = botAI->GetBot(); + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* player = ref->GetSource(); + if (!player || !player->IsInWorld() || !player->IsAlive()) + continue; + + if (!(player->IsInSameGroupWith(bot) || player->IsInSameRaidWith(bot))) + continue; + + if (!MatchesBucket(player, assignment) || + HasMyExactBlessing(botAI, player, castType) || + HasEquivalentOrStrongerSameFamilyBlessing(botAI, player, castType, spellId) || + !botAI->CanCastSpell(spellName, player)) + { + continue; + } + + return player; + } + + return nullptr; +} + +static bool GetCachedAssignments( + AiObjectContext* context, Group* group, + std::vector& outAssignments) +{ + uint32 const groupKey = group ? group->GetLeaderGUID().GetCounter() : 0; + + Value* cacheValue = + context->GetValue("greater blessing assignments"); + if (!cacheValue) + return false; + + CachedBlessingAssignments cachedAssignments = cacheValue->Get(); + if (cachedAssignments.groupKey != groupKey) + { + cacheValue->Reset(); + cachedAssignments = cacheValue->Get(); + } + + if (!cachedAssignments.valid || cachedAssignments.groupKey != groupKey) + return false; + + outAssignments = cachedAssignments.assignments; + return true; +} + +static bool FindPendingAssignmentFromAssignments( + PlayerbotAI* botAI, + std::vector const& assignments, + GreaterBlessingPlayerAssignment& outAssignment, + std::string& outSpellName) +{ + Player* bot = botAI->GetBot(); + AiObjectContext* aiContext = botAI->GetAiObjectContext(); + if (!aiContext) + return false; + + for (auto const& assigned : assignments) + { + if (assigned.blessing == BLESSING_NONE) + continue; + + BlessingType castType = assigned.blessing; + std::string spellName = BlessingSpellName(castType); + if (spellName.empty()) + continue; + + if (IsGreaterVariant(castType)) + { + uint32 spellId = aiContext->GetValue("spell id", spellName)->Get(); + if (!spellId || !ai::buff::HasRequiredReagents(bot, spellId)) + { + castType = ToSingleVariant(castType); + spellName = BlessingSpellName(castType); + if (spellName.empty()) + continue; + } + } + + uint32 spellId = aiContext->GetValue("spell id", spellName)->Get(); + if (!spellId) + continue; + + Player* target = FindMissingBlessingTarget( + botAI, assigned, castType, spellId, spellName); + if (!target) + continue; + + outAssignment = {target, assigned.blessing}; + outSpellName = spellName; + return true; + } + + return false; +} + +class GreaterBlessingPendingAssignmentValue : public CalculatedValue +{ +public: + GreaterBlessingPendingAssignmentValue(PlayerbotAI* botAI) + : CalculatedValue( + botAI, "greater blessing pending assignment", + GREATER_BLESSING_PENDING_ASSIGNMENT_CACHE_MS) {} + +protected: + CachedPendingBlessingAssignment Calculate() override + { + CachedPendingBlessingAssignment cached; + + Player* bot = botAI->GetBot(); + Group* group = bot->GetGroup(); + cached.groupKey = group ? group->GetLeaderGUID().GetCounter() : 0; + + std::vector assignments; + if (!GetCachedAssignments(this->context, group, assignments)) + return cached; + + if (!FindPendingAssignmentFromAssignments( + botAI, assignments, cached.assignment, cached.spellName)) + { + return cached; + } + + cached.valid = true; + return cached; + } +}; + +UntypedValue* greater_blessing_pending_assignment_value(PlayerbotAI* botAI) +{ + return new GreaterBlessingPendingAssignmentValue(botAI); +} + +} + +CastGreaterBlessingAssignmentAction::CastGreaterBlessingAssignmentAction( + PlayerbotAI* botAI) : Action(botAI, "cast greater blessing assignment") {} + +bool CastGreaterBlessingAssignmentAction::isUseful() +{ + return ai::gbless::IsEligibleGroupForAutoBlessings(bot->GetGroup()); +} + +bool CastGreaterBlessingAssignmentAction::HasPendingAssignment() +{ + ai::gbless::GreaterBlessingPlayerAssignment assignment; + std::string spellName; + + return FindPendingAssignment(assignment, spellName); +} + +bool CastGreaterBlessingAssignmentAction::Execute(Event /*event*/) +{ + ai::gbless::GreaterBlessingPlayerAssignment assignment; + std::string spellName; + if (!FindPendingAssignment(assignment, spellName)) + return false; + + uint32 finalId = AI_VALUE2(uint32, "spell id", spellName); + if (!finalId) + return false; + + return botAI->CastSpell(spellName, assignment.player); +} + +bool CastGreaterBlessingAssignmentAction::FindPendingAssignment( + ai::gbless::GreaterBlessingPlayerAssignment& outAssignment, std::string& outSpellName) +{ + Group* group = bot->GetGroup(); + uint32 const groupKey = group ? group->GetLeaderGUID().GetCounter() : 0; + + Value* pendingValue = + context->GetValue("greater blessing pending assignment"); + if (!pendingValue) + return false; + + ai::gbless::CachedPendingBlessingAssignment pendingAssignment = pendingValue->Get(); + if (pendingAssignment.groupKey != groupKey) + { + pendingValue->Reset(); + pendingAssignment = pendingValue->Get(); + } + + if (!pendingAssignment.valid || pendingAssignment.groupKey != groupKey) + return false; + + outAssignment = pendingAssignment.assignment; + outSpellName = pendingAssignment.spellName; + return true; +} diff --git a/src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.h b/src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.h new file mode 100644 index 00000000000..fa8c66d4adb --- /dev/null +++ b/src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.h @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_PALADINGREATERBLESSINGACTION_H +#define _PLAYERBOT_PALADINGREATERBLESSINGACTION_H + +#include +#include +#include + +#include "Action.h" +#include "AiFactory.h" +#include "Playerbots.h" +#include "SharedDefines.h" + +class UntypedValue; + +namespace ai::gbless +{ + enum RoleProfile : uint8 + { + ROLE_CASTER = 0, + ROLE_PHYSICAL_DPS = 1, + ROLE_HYBRID_DPS = 2, + ROLE_DRUID_TANK = 3, + ROLE_WARRIOR_DK_TANK = 4, + ROLE_PALADIN_TANK = 5, + + ROLE_PROFILE_COUNT = 6 + }; + + enum BlessingType : uint8 + { + BLESSING_NONE = 0, + BLESSING_MIGHT_SINGLE = 1, + BLESSING_MIGHT_GREATER = 2, + BLESSING_WISDOM_SINGLE = 3, + BLESSING_WISDOM_GREATER = 4, + BLESSING_KINGS_SINGLE = 5, + BLESSING_KINGS_GREATER = 6, + BLESSING_SANCTUARY_SINGLE = 7, + BLESSING_SANCTUARY_GREATER = 8 + }; + + enum BaseBlessingCategory : uint8 + { + BASE_NONE = 0, + BASE_MIGHT = 1, + BASE_WISDOM = 2, + BASE_KINGS = 3, + BASE_SANCTUARY = 4 + }; + + inline constexpr BaseBlessingCategory BaseBlessingOf(BlessingType type) + { + switch (type) + { + case BLESSING_MIGHT_SINGLE: + case BLESSING_MIGHT_GREATER: return BASE_MIGHT; + case BLESSING_WISDOM_SINGLE: + case BLESSING_WISDOM_GREATER: return BASE_WISDOM; + case BLESSING_KINGS_SINGLE: + case BLESSING_KINGS_GREATER: return BASE_KINGS; + case BLESSING_SANCTUARY_SINGLE: + case BLESSING_SANCTUARY_GREATER: return BASE_SANCTUARY; + default: return BASE_NONE; + } + } + + inline constexpr bool IsSingleVariant(BlessingType type) + { + return type == BLESSING_MIGHT_SINGLE || type == BLESSING_WISDOM_SINGLE || + type == BLESSING_KINGS_SINGLE || type == BLESSING_SANCTUARY_SINGLE; + } + + inline constexpr bool IsGreaterVariant(BlessingType type) + { + return type == BLESSING_MIGHT_GREATER || type == BLESSING_WISDOM_GREATER || + type == BLESSING_KINGS_GREATER || type == BLESSING_SANCTUARY_GREATER; + } + + inline constexpr BlessingType ToSingleVariant(BaseBlessingCategory category) + { + switch (category) + { + case BASE_MIGHT: return BLESSING_MIGHT_SINGLE; + case BASE_WISDOM: return BLESSING_WISDOM_SINGLE; + case BASE_KINGS: return BLESSING_KINGS_SINGLE; + case BASE_SANCTUARY: return BLESSING_SANCTUARY_SINGLE; + default: return BLESSING_NONE; + } + } + + inline constexpr BlessingType ToSingleVariant(BlessingType type) + { + return ToSingleVariant(BaseBlessingOf(type)); + } + + inline constexpr BlessingType ToGreaterVariant(BaseBlessingCategory category) + { + switch (category) + { + case BASE_MIGHT: return BLESSING_MIGHT_GREATER; + case BASE_WISDOM: return BLESSING_WISDOM_GREATER; + case BASE_KINGS: return BLESSING_KINGS_GREATER; + case BASE_SANCTUARY: return BLESSING_SANCTUARY_GREATER; + default: return BLESSING_NONE; + } + } + + inline constexpr BlessingType ToGreaterVariant(BlessingType type) + { + return ToGreaterVariant(BaseBlessingOf(type)); + } + + inline std::string BlessingSpellName(BlessingType type) + { + switch (type) + { + case BLESSING_MIGHT_SINGLE: return "blessing of might"; + case BLESSING_MIGHT_GREATER: return "greater blessing of might"; + case BLESSING_WISDOM_SINGLE: return "blessing of wisdom"; + case BLESSING_WISDOM_GREATER: return "greater blessing of wisdom"; + case BLESSING_KINGS_SINGLE: return "blessing of kings"; + case BLESSING_KINGS_GREATER: return "greater blessing of kings"; + case BLESSING_SANCTUARY_SINGLE: return "blessing of sanctuary"; + case BLESSING_SANCTUARY_GREATER: return "greater blessing of sanctuary"; + default: return ""; + } + } + + struct BaseBlessingPriorityEntry + { + BaseBlessingCategory priorities[4]; + }; + + inline constexpr BaseBlessingPriorityEntry BASE_BLESSING_PRIORITIES[ROLE_PROFILE_COUNT] = + { + // All casters + {{ BASE_KINGS, BASE_WISDOM, BASE_SANCTUARY, BASE_MIGHT }}, + // Physical DPS (no mana) + {{ BASE_MIGHT, BASE_KINGS, BASE_SANCTUARY, BASE_NONE }}, + // Hybrid DPS + {{ BASE_MIGHT, BASE_KINGS, BASE_WISDOM, BASE_SANCTUARY }}, + // Druid tanks + {{ BASE_KINGS, BASE_MIGHT, BASE_SANCTUARY, BASE_WISDOM, }}, + // Warrior and DK tanks + {{ BASE_KINGS, BASE_MIGHT, BASE_SANCTUARY, BASE_NONE }}, + // Paladin tanks + {{ BASE_SANCTUARY, BASE_MIGHT, BASE_WISDOM, BASE_KINGS }}, + }; + + constexpr uint32 SPELL_IMPROVED_MIGHT_R1 = 20042; + constexpr uint32 SPELL_IMPROVED_MIGHT_R2 = 20045; + constexpr uint32 SPELL_IMPROVED_WISDOM_R1 = 20244; + constexpr uint32 SPELL_IMPROVED_WISDOM_R2 = 20245; + + inline RoleProfile ResolveRoleProfile(Player* player) + { + if (!player) + return ROLE_CASTER; + + uint8 cls = player->getClass(); + int tab = AiFactory::GetPlayerSpecTab(player); + bool isTank = PlayerbotAI::IsTank(player); + + switch (cls) + { + case CLASS_WARRIOR: + if (isTank) + return ROLE_WARRIOR_DK_TANK; + return ROLE_PHYSICAL_DPS; + + case CLASS_DEATH_KNIGHT: + if (isTank) + return ROLE_WARRIOR_DK_TANK; + return ROLE_PHYSICAL_DPS; + + case CLASS_SHAMAN: + if (tab == SHAMAN_TAB_ENHANCEMENT) + return ROLE_HYBRID_DPS; + return ROLE_CASTER; + + case CLASS_PALADIN: + if (isTank) + return ROLE_PALADIN_TANK; + if (tab == PALADIN_TAB_HOLY) + return ROLE_CASTER; + return ROLE_HYBRID_DPS; + + case CLASS_DRUID: + if (tab == DRUID_TAB_FERAL) + return isTank ? ROLE_DRUID_TANK : ROLE_HYBRID_DPS; + return ROLE_CASTER; + + case CLASS_ROGUE: + return ROLE_PHYSICAL_DPS; + + case CLASS_HUNTER: + return ROLE_HYBRID_DPS; + + case CLASS_MAGE: + return ROLE_CASTER; + + case CLASS_WARLOCK: + return ROLE_CASTER; + + case CLASS_PRIEST: + return ROLE_CASTER; + + default: + return ROLE_CASTER; + } + } + + struct GreaterBlessingPlayerAssignment + { + Player* player = nullptr; + BlessingType blessing = BLESSING_NONE; + }; + + struct CachedBlessingBucketAssignment + { + uint8 classId = 0; + RoleProfile role = ROLE_CASTER; + bool byRole = false; + BlessingType blessing = BLESSING_NONE; + }; + + struct CachedBlessingAssignments + { + uint32 groupKey = 0; + bool valid = false; + std::vector assignments; + }; + + struct CachedPendingBlessingAssignment + { + uint32 groupKey = 0; + bool valid = false; + GreaterBlessingPlayerAssignment assignment; + std::string spellName; + }; + + UntypedValue* greater_blessing_assignments_value(PlayerbotAI* botAI); + UntypedValue* greater_blessing_pending_assignment_value(PlayerbotAI* botAI); + bool IsEligibleGroupForAutoBlessings(Group const* group); +} + +class CastGreaterBlessingAssignmentAction : public Action +{ +public: + CastGreaterBlessingAssignmentAction(PlayerbotAI* botAI); + + bool Execute(Event event) override; + bool isUseful() override; + bool HasPendingAssignment(); + +private: + bool FindPendingAssignment( + ai::gbless::GreaterBlessingPlayerAssignment& outAssignment, + std::string& outSpellName); +}; + +#endif diff --git a/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp b/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp index a58ede3f88c..d15a794533b 100644 --- a/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp +++ b/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp @@ -7,6 +7,7 @@ #include "DpsPaladinStrategy.h" #include "GenericPaladinNonCombatStrategy.h" +#include "PaladinGreaterBlessingAction.h" #include "HealPaladinStrategy.h" #include "NamedObjectContext.h" #include "OffhealRetPaladinStrategy.h" @@ -70,17 +71,17 @@ class PaladinBuffStrategyFactoryInternal : public NamedObjectContext public: PaladinBuffStrategyFactoryInternal() : NamedObjectContext(false, true) { - creators["bhealth"] = &PaladinBuffStrategyFactoryInternal::bhealth; - creators["bmana"] = &PaladinBuffStrategyFactoryInternal::bmana; - creators["bdps"] = &PaladinBuffStrategyFactoryInternal::bdps; - creators["bstats"] = &PaladinBuffStrategyFactoryInternal::bstats; + creators["bsanc"] = &PaladinBuffStrategyFactoryInternal::bsanc; + creators["bwisdom"] = &PaladinBuffStrategyFactoryInternal::bwisdom; + creators["bmight"] = &PaladinBuffStrategyFactoryInternal::bmight; + creators["bkings"] = &PaladinBuffStrategyFactoryInternal::bkings; } private: - static Strategy* bhealth(PlayerbotAI* botAI) { return new PaladinBuffHealthStrategy(botAI); } - static Strategy* bmana(PlayerbotAI* botAI) { return new PaladinBuffManaStrategy(botAI); } - static Strategy* bdps(PlayerbotAI* botAI) { return new PaladinBuffDpsStrategy(botAI); } - static Strategy* bstats(PlayerbotAI* botAI) { return new PaladinBuffStatsStrategy(botAI); } + static Strategy* bsanc(PlayerbotAI* botAI) { return new PaladinBuffHealthStrategy(botAI); } + static Strategy* bwisdom(PlayerbotAI* botAI) { return new PaladinBuffManaStrategy(botAI); } + static Strategy* bmight(PlayerbotAI* botAI) { return new PaladinBuffDpsStrategy(botAI); } + static Strategy* bkings(PlayerbotAI* botAI) { return new PaladinBuffStatsStrategy(botAI); } }; class PaladinCombatStrategyFactoryInternal : public NamedObjectContext @@ -154,6 +155,7 @@ class PaladinTriggerFactoryInternal : public NamedObjectContext creators["blessing of sanctuary on party"] = &PaladinTriggerFactoryInternal::blessing_of_sanctuary_on_party; creators["avenging wrath"] = &PaladinTriggerFactoryInternal::avenging_wrath; + creators["greater blessing needed"] = &PaladinTriggerFactoryInternal::greater_blessing_needed; } private: @@ -211,8 +213,8 @@ class PaladinTriggerFactoryInternal : public NamedObjectContext static Trigger* repentance_on_enemy_healer(PlayerbotAI* botAI) { return new RepentanceOnHealerTrigger(botAI); } static Trigger* repentance_on_snare_target(PlayerbotAI* botAI) { return new RepentanceSnareTrigger(botAI); } static Trigger* repentance_interrupt(PlayerbotAI* botAI) { return new RepentanceInterruptTrigger(botAI); } - static Trigger* beacon_of_light_on_main_tank(PlayerbotAI* ai) { return new BeaconOfLightOnMainTankTrigger(ai); } - static Trigger* sacred_shield_on_main_tank(PlayerbotAI* ai) { return new SacredShieldOnMainTankTrigger(ai); } + static Trigger* beacon_of_light_on_main_tank(PlayerbotAI* botAI) { return new BeaconOfLightOnMainTankTrigger(botAI); } + static Trigger* sacred_shield_on_main_tank(PlayerbotAI* botAI) { return new SacredShieldOnMainTankTrigger(botAI); } static Trigger* hand_of_freedom_on_party(PlayerbotAI* botAI) { return new HandOfFreedomOnPartyTrigger(botAI); } static Trigger* blessing_of_kings_on_party(PlayerbotAI* botAI) { return new BlessingOfKingsOnPartyTrigger(botAI); } @@ -227,6 +229,10 @@ class PaladinTriggerFactoryInternal : public NamedObjectContext } static Trigger* avenging_wrath(PlayerbotAI* botAI) { return new AvengingWrathTrigger(botAI); } + static Trigger* greater_blessing_needed(PlayerbotAI* botAI) + { + return new GreaterBlessingNeededTrigger(botAI); + } }; class PaladinAiObjectContextInternal : public NamedObjectContext @@ -316,6 +322,8 @@ class PaladinAiObjectContextInternal : public NamedObjectContext creators["divine sacrifice"] = &PaladinAiObjectContextInternal::divine_sacrifice; creators["cancel divine sacrifice"] = &PaladinAiObjectContextInternal::cancel_divine_sacrifice; creators["hand of freedom on party"] = &PaladinAiObjectContextInternal::hand_of_freedom_on_party; + creators["cast greater blessing assignment"] = + &PaladinAiObjectContextInternal::cast_greater_blessing_assignment; } private: @@ -414,15 +422,41 @@ class PaladinAiObjectContextInternal : public NamedObjectContext static Action* sanctity_aura(PlayerbotAI* botAI) { return new CastSanctityAuraAction(botAI); } static Action* holy_shock(PlayerbotAI* botAI) { return new CastHolyShockAction(botAI); } static Action* holy_shock_on_party(PlayerbotAI* botAI) { return new CastHolyShockOnPartyAction(botAI); } - static Action* divine_plea(PlayerbotAI* ai) { return new CastDivinePleaAction(ai); } - static Action* shield_of_righteousness(PlayerbotAI* ai) { return new ShieldOfRighteousnessAction(ai); } - static Action* beacon_of_light_on_main_tank(PlayerbotAI* ai) { return new CastBeaconOfLightOnMainTankAction(ai); } - static Action* sacred_shield_on_main_tank(PlayerbotAI* ai) { return new CastSacredShieldOnMainTankAction(ai); } - static Action* avenging_wrath(PlayerbotAI* ai) { return new CastAvengingWrathAction(ai); } - static Action* divine_illumination(PlayerbotAI* ai) { return new CastDivineIlluminationAction(ai); } - static Action* divine_sacrifice(PlayerbotAI* ai) { return new CastDivineSacrificeAction(ai); } - static Action* cancel_divine_sacrifice(PlayerbotAI* ai) { return new CastCancelDivineSacrificeAction(ai); } - static Action* hand_of_freedom_on_party(PlayerbotAI* ai) { return new CastHandOfFreedomOnPartyAction(ai); } + static Action* divine_plea(PlayerbotAI* botAI) { return new CastDivinePleaAction(botAI); } + static Action* shield_of_righteousness(PlayerbotAI* botAI) { return new ShieldOfRighteousnessAction(botAI); } + static Action* beacon_of_light_on_main_tank(PlayerbotAI* botAI) { return new CastBeaconOfLightOnMainTankAction(botAI); } + static Action* sacred_shield_on_main_tank(PlayerbotAI* botAI) { return new CastSacredShieldOnMainTankAction(botAI); } + static Action* avenging_wrath(PlayerbotAI* botAI) { return new CastAvengingWrathAction(botAI); } + static Action* divine_illumination(PlayerbotAI* botAI) { return new CastDivineIlluminationAction(botAI); } + static Action* divine_sacrifice(PlayerbotAI* botAI) { return new CastDivineSacrificeAction(botAI); } + static Action* cancel_divine_sacrifice(PlayerbotAI* botAI) { return new CastCancelDivineSacrificeAction(botAI); } + static Action* hand_of_freedom_on_party(PlayerbotAI* botAI) { return new CastHandOfFreedomOnPartyAction(botAI); } + static Action* cast_greater_blessing_assignment(PlayerbotAI* botAI) + { + return new CastGreaterBlessingAssignmentAction(botAI); + } +}; + +class PaladinValueContextInternal : public NamedObjectContext +{ +public: + PaladinValueContextInternal() + { + creators["greater blessing assignments"] = &PaladinValueContextInternal::greater_blessing_assignments; + creators["greater blessing pending assignment"] = + &PaladinValueContextInternal::greater_blessing_pending_assignment; + } + +private: + static UntypedValue* greater_blessing_assignments(PlayerbotAI* botAI) + { + return ai::gbless::greater_blessing_assignments_value(botAI); + } + + static UntypedValue* greater_blessing_pending_assignment(PlayerbotAI* botAI) + { + return ai::gbless::greater_blessing_pending_assignment_value(botAI); + } }; SharedNamedObjectContextList PaladinAiObjectContext::sharedStrategyContexts; @@ -467,4 +501,5 @@ void PaladinAiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContext void PaladinAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList& valueContexts) { AiObjectContext::BuildSharedValueContexts(valueContexts); + valueContexts.Add(new PaladinValueContextInternal()); } diff --git a/src/Ai/Class/Paladin/Strategy/GenericPaladinNonCombatStrategy.cpp b/src/Ai/Class/Paladin/Strategy/GenericPaladinNonCombatStrategy.cpp index 84f449813e4..5b3e697d61b 100644 --- a/src/Ai/Class/Paladin/Strategy/GenericPaladinNonCombatStrategy.cpp +++ b/src/Ai/Class/Paladin/Strategy/GenericPaladinNonCombatStrategy.cpp @@ -30,4 +30,7 @@ void GenericPaladinNonCombatStrategy::InitTriggers(std::vector& tr triggers.push_back(new TriggerNode("often", { NextAction("apply oil", ACTION_IDLE + 1.0f) })); if (specTab == PALADIN_TAB_PROTECTION || specTab == PALADIN_TAB_RETRIBUTION) triggers.push_back(new TriggerNode("often", { NextAction("apply stone", ACTION_IDLE + 1.0f) })); + + triggers.push_back(new TriggerNode("greater blessing needed", + { NextAction("cast greater blessing assignment", ACTION_NORMAL) })); } diff --git a/src/Ai/Class/Paladin/Strategy/PaladinBuffStrategies.h b/src/Ai/Class/Paladin/Strategy/PaladinBuffStrategies.h index 25f8886a344..e3866d6f66f 100644 --- a/src/Ai/Class/Paladin/Strategy/PaladinBuffStrategies.h +++ b/src/Ai/Class/Paladin/Strategy/PaladinBuffStrategies.h @@ -16,7 +16,7 @@ class PaladinBuffManaStrategy : public Strategy PaladinBuffManaStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "bmana"; } + std::string const getName() override { return "bwisdom"; } }; class PaladinBuffHealthStrategy : public Strategy @@ -25,7 +25,7 @@ class PaladinBuffHealthStrategy : public Strategy PaladinBuffHealthStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "bhealth"; } + std::string const getName() override { return "bsanc"; } }; class PaladinBuffDpsStrategy : public Strategy @@ -34,7 +34,7 @@ class PaladinBuffDpsStrategy : public Strategy PaladinBuffDpsStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "bdps"; } + std::string const getName() override { return "bmight"; } }; class PaladinBuffArmorStrategy : public Strategy @@ -88,7 +88,7 @@ class PaladinBuffStatsStrategy : public Strategy PaladinBuffStatsStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "bstats"; } + std::string const getName() override { return "bkings"; } }; class PaladinShadowResistanceStrategy : public Strategy diff --git a/src/Ai/Class/Paladin/Strategy/TankPaladinStrategy.cpp b/src/Ai/Class/Paladin/Strategy/TankPaladinStrategy.cpp index b76116e043e..39d67e14eaf 100644 --- a/src/Ai/Class/Paladin/Strategy/TankPaladinStrategy.cpp +++ b/src/Ai/Class/Paladin/Strategy/TankPaladinStrategy.cpp @@ -95,13 +95,14 @@ void TankPaladinStrategy::InitTriggers(std::vector& triggers) } ) ); - triggers.push_back(new TriggerNode( - "light aoe", - { - NextAction("avenger's shield", ACTION_HIGH + 5) - } - ) -); + triggers.push_back( + new TriggerNode( + "light aoe", + { + NextAction("avenger's shield", ACTION_HIGH + 5) + } + ) + ); triggers.push_back( new TriggerNode( "medium aoe", @@ -122,13 +123,6 @@ void TankPaladinStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode( "medium health", - { NextAction("holy shield", ACTION_HIGH + 4) - } - ) - ); - triggers.push_back( - new TriggerNode( - "low health", { NextAction("holy shield", ACTION_HIGH + 4) } @@ -136,20 +130,12 @@ void TankPaladinStrategy::InitTriggers(std::vector& triggers) ); triggers.push_back( new TriggerNode( - "critical health", + "avenging wrath", { - NextAction("holy shield", ACTION_HIGH + 4) + NextAction("avenging wrath", ACTION_HIGH + 2) } ) ); - triggers.push_back( - new TriggerNode( - "avenging wrath", - { - NextAction("avenging wrath", ACTION_HIGH + 2) - } - ) -); triggers.push_back( new TriggerNode( "target critical health", diff --git a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp index 46a2d8a9427..79459b6827b 100644 --- a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp +++ b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp @@ -5,10 +5,11 @@ #include "PaladinTriggers.h" +#include "GenericBuffUtils.h" +#include "PaladinGreaterBlessingAction.h" #include "PaladinActions.h" -#include "PlayerbotAIConfig.h" -#include "Playerbots.h" #include "PaladinHelper.h" +#include "Playerbots.h" bool SealTrigger::IsActive() { @@ -28,8 +29,9 @@ bool CrusaderAuraTrigger::IsActive() bool BlessingTrigger::IsActive() { Unit* target = GetTarget(); - return SpellTrigger::IsActive() && !botAI->HasAnyAuraOf(target, "blessing of might", "blessing of wisdom", - "blessing of kings", "blessing of sanctuary", nullptr); + return SpellTrigger::IsActive() && + !botAI->HasAnyAuraOf(target, "blessing of might", "blessing of wisdom", + "blessing of kings", "blessing of sanctuary", nullptr); } bool DivineShieldLowHealthTrigger::IsActive() @@ -62,7 +64,8 @@ bool HandOfFreedomOnPartyTrigger::IsActive() if (!target) return false; - if (target != bot && bot->GetExactDist2dSq(target->GetPositionX(), target->GetPositionY()) > 30.0f * 30.0f) + if (target != bot && + bot->GetExactDist2dSq(target->GetPositionX(), target->GetPositionY()) > 30.0f * 30.0f) return false; if (!botAI->CanCastSpell("hand of freedom", target)) @@ -75,3 +78,29 @@ bool NotSensingUndeadTrigger::IsActive() { return !botAI->HasAura("sense undead", bot); } + +bool GreaterBlessingNeededTrigger::IsActive() +{ + if (!ai::gbless::IsEligibleGroupForAutoBlessings(bot->GetGroup())) + return false; + + if (ai::buff::ShouldDeferGreaterBlessingAssignmentForRecentLogin(bot)) + return false; + + Group* group = bot->GetGroup(); + uint32 const groupKey = group ? group->GetLeaderGUID().GetCounter() : 0; + + Value* pendingValue = + context->GetValue("greater blessing pending assignment"); + if (!pendingValue) + return false; + + ai::gbless::CachedPendingBlessingAssignment pendingAssignment = pendingValue->Get(); + if (pendingAssignment.groupKey != groupKey) + { + pendingValue->Reset(); + pendingAssignment = pendingValue->Get(); + } + + return pendingAssignment.valid && pendingAssignment.groupKey == groupKey; +} diff --git a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h index d11c8024fca..28f4d75cb4e 100644 --- a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h +++ b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h @@ -13,32 +13,6 @@ class PlayerbotAI; -inline std::string const GetActualBlessingOfMight(Unit* target) -{ - switch (target->getClass()) - { - case CLASS_MAGE: - case CLASS_PRIEST: - case CLASS_WARLOCK: - return "blessing of wisdom"; - } - - return "blessing of might"; -} - -inline std::string const GetActualBlessingOfWisdom(Unit* target) -{ - switch (target->getClass()) - { - case CLASS_WARRIOR: - case CLASS_ROGUE: - case CLASS_DEATH_KNIGHT: - return "blessing of might"; - } - - return "blessing of wisdom"; -} - BUFF_TRIGGER(HolyShieldTrigger, "holy shield"); BUFF_TRIGGER(RighteousFuryTrigger, "righteous fury"); @@ -212,42 +186,55 @@ DEBUFF_TRIGGER(AvengerShieldTrigger, "avenger's shield"); class BeaconOfLightOnMainTankTrigger : public BuffOnMainTankTrigger { public: - BeaconOfLightOnMainTankTrigger(PlayerbotAI* ai) - : BuffOnMainTankTrigger(ai, "beacon of light", true) {} + BeaconOfLightOnMainTankTrigger(PlayerbotAI* botAI) + : BuffOnMainTankTrigger(botAI, "beacon of light", true) {} }; class SacredShieldOnMainTankTrigger : public BuffOnMainTankTrigger { public: - SacredShieldOnMainTankTrigger(PlayerbotAI* ai) : BuffOnMainTankTrigger(ai, "sacred shield", false) {} + SacredShieldOnMainTankTrigger(PlayerbotAI* botAI) + : BuffOnMainTankTrigger(botAI, "sacred shield", false) {} }; -class BlessingOfKingsOnPartyTrigger : public BuffOnPartyTrigger +class BlessingOfKingsOnPartyTrigger : public BlessingOnPartyTrigger { public: BlessingOfKingsOnPartyTrigger(PlayerbotAI* botAI) - : BuffOnPartyTrigger(botAI, "blessing of kings", 2 * 2000) {} + : BlessingOnPartyTrigger(botAI) + { + spell = "blessing of kings"; + } }; -class BlessingOfWisdomOnPartyTrigger : public BuffOnPartyTrigger +class BlessingOfWisdomOnPartyTrigger : public BlessingOnPartyTrigger { public: BlessingOfWisdomOnPartyTrigger(PlayerbotAI* botAI) - : BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {} + : BlessingOnPartyTrigger(botAI) + { + spell = "blessing of might,blessing of wisdom"; + } }; -class BlessingOfMightOnPartyTrigger : public BuffOnPartyTrigger +class BlessingOfMightOnPartyTrigger : public BlessingOnPartyTrigger { public: BlessingOfMightOnPartyTrigger(PlayerbotAI* botAI) - : BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {} + : BlessingOnPartyTrigger(botAI) + { + spell = "blessing of might,blessing of wisdom"; + } }; -class BlessingOfSanctuaryOnPartyTrigger : public BuffOnPartyTrigger +class BlessingOfSanctuaryOnPartyTrigger : public BlessingOnPartyTrigger { public: BlessingOfSanctuaryOnPartyTrigger(PlayerbotAI* botAI) - : BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000) {} + : BlessingOnPartyTrigger(botAI) + { + spell = "blessing of sanctuary"; + } }; class HandOfFreedomOnPartyTrigger : public Trigger @@ -266,4 +253,13 @@ class AvengingWrathTrigger : public BoostTrigger AvengingWrathTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "avenging wrath") {} }; +class GreaterBlessingNeededTrigger : public Trigger +{ +public: + GreaterBlessingNeededTrigger(PlayerbotAI* botAI) + : Trigger(botAI, "greater blessing needed", 4) {} + + bool IsActive() override; +}; + #endif diff --git a/src/Ai/Class/Paladin/Util/PaladinHelper.h b/src/Ai/Class/Paladin/Util/PaladinHelper.h index 64b88b73103..8e9a155f7e7 100644 --- a/src/Ai/Class/Paladin/Util/PaladinHelper.h +++ b/src/Ai/Class/Paladin/Util/PaladinHelper.h @@ -18,6 +18,8 @@ static constexpr uint32 SPELL_HAND_OF_PROTECTION = 1022; static constexpr uint32 SPELL_HAND_OF_SALVATION = 1038; static constexpr uint32 SPELL_HAND_OF_FREEDOM = 1044; static constexpr uint32 SPELL_HAND_OF_SACRIFICE = 6940; +static constexpr uint32 SPELL_BLESSING_OF_SANCTUARY = 20911; +static constexpr uint32 SPELL_GREATER_BLESSING_OF_SANCTUARY = 25899; inline bool HasHandFromCaster(Unit* target, Player* caster, std::initializer_list spellIds) { diff --git a/src/Ai/Class/Priest/Action/PriestActions.h b/src/Ai/Class/Priest/Action/PriestActions.h index 4e94f27cca4..0fe0a832f7b 100644 --- a/src/Ai/Class/Priest/Action/PriestActions.h +++ b/src/Ai/Class/Priest/Action/PriestActions.h @@ -13,9 +13,19 @@ class PlayerbotAI; // disc -BUFF_ACTION(CastPowerWordFortitudeAction, "power word: fortitude"); -BUFF_PARTY_ACTION(CastPowerWordFortitudeOnPartyAction, "power word: fortitude"); -BUFF_PARTY_ACTION(CastPrayerOfFortitudeOnPartyAction, "prayer of fortitude"); +class CastPowerWordFortitudeAction : public GroupBuffSpellAction +{ +public: + CastPowerWordFortitudeAction(PlayerbotAI* botAI) + : GroupBuffSpellAction(botAI, "power word: fortitude") {} +}; + +class CastPowerWordFortitudeOnPartyAction : public GroupBuffOnPartyAction +{ +public: + CastPowerWordFortitudeOnPartyAction(PlayerbotAI* botAI) + : GroupBuffOnPartyAction(botAI, "power word: fortitude") {} +}; BUFF_ACTION(CastPowerWordShieldAction, "power word: shield"); BUFF_ACTION(CastInnerFireAction, "inner fire"); @@ -26,9 +36,19 @@ CC_ACTION(CastShackleUndeadAction, "shackle undead"); SPELL_ACTION_U(CastManaBurnAction, "mana burn", AI_VALUE2(uint8, "mana", "self target") < 50 && AI_VALUE2(uint8, "mana", "current target") >= 20); BUFF_ACTION(CastLevitateAction, "levitate"); -BUFF_ACTION(CastDivineSpiritAction, "divine spirit"); -BUFF_PARTY_ACTION(CastDivineSpiritOnPartyAction, "divine spirit"); -BUFF_PARTY_ACTION(CastPrayerOfSpiritOnPartyAction, "prayer of spirit"); +class CastDivineSpiritAction : public GroupBuffSpellAction +{ +public: + CastDivineSpiritAction(PlayerbotAI* botAI) + : GroupBuffSpellAction(botAI, "divine spirit") {} +}; + +class CastDivineSpiritOnPartyAction : public GroupBuffOnPartyAction +{ +public: + CastDivineSpiritOnPartyAction(PlayerbotAI* botAI) + : GroupBuffOnPartyAction(botAI, "divine spirit") {} +}; // disc 2.4.3 SPELL_ACTION(CastMassDispelAction, "mass dispel"); @@ -103,9 +123,19 @@ SPELL_ACTION(CastMindBlastAction, "mind blast"); SPELL_ACTION(CastPsychicScreamAction, "psychic scream"); DEBUFF_ACTION(CastMindSootheAction, "mind soothe"); BUFF_ACTION_U(CastFadeAction, "fade", bot->GetGroup()); -BUFF_ACTION(CastShadowProtectionAction, "shadow protection"); -BUFF_PARTY_ACTION(CastShadowProtectionOnPartyAction, "shadow protection"); -BUFF_PARTY_ACTION(CastPrayerOfShadowProtectionAction, "prayer of shadow protection"); +class CastShadowProtectionAction : public GroupBuffSpellAction +{ +public: + CastShadowProtectionAction(PlayerbotAI* botAI) + : GroupBuffSpellAction(botAI, "shadow protection") {} +}; + +class CastShadowProtectionOnPartyAction : public GroupBuffOnPartyAction +{ +public: + CastShadowProtectionOnPartyAction(PlayerbotAI* botAI) + : GroupBuffOnPartyAction(botAI, "shadow protection") {} +}; // shadow talents SPELL_ACTION(CastMindFlayAction, "mind flay"); diff --git a/src/Ai/Class/Priest/PriestAiObjectContext.cpp b/src/Ai/Class/Priest/PriestAiObjectContext.cpp index 7ffac96ccab..0187f838141 100644 --- a/src/Ai/Class/Priest/PriestAiObjectContext.cpp +++ b/src/Ai/Class/Priest/PriestAiObjectContext.cpp @@ -92,8 +92,6 @@ class PriestTriggerFactoryInternal : public NamedObjectContext creators["shadow protection"] = &PriestTriggerFactoryInternal::shadow_protection; creators["shadow protection on party"] = &PriestTriggerFactoryInternal::shadow_protection_on_party; creators["shackle undead"] = &PriestTriggerFactoryInternal::shackle_undead; - creators["prayer of fortitude on party"] = &PriestTriggerFactoryInternal::prayer_of_fortitude_on_party; - creators["prayer of spirit on party"] = &PriestTriggerFactoryInternal::prayer_of_spirit_on_party; creators["holy fire"] = &PriestTriggerFactoryInternal::holy_fire; creators["touch of weakness"] = &PriestTriggerFactoryInternal::touch_of_weakness; creators["hex of weakness"] = &PriestTriggerFactoryInternal::hex_of_weakness; @@ -136,8 +134,6 @@ class PriestTriggerFactoryInternal : public NamedObjectContext static Trigger* shadow_protection_on_party(PlayerbotAI* botAI) { return new ShadowProtectionOnPartyTrigger(botAI); } static Trigger* shadow_protection(PlayerbotAI* botAI) { return new ShadowProtectionTrigger(botAI); } static Trigger* shackle_undead(PlayerbotAI* botAI) { return new ShackleUndeadTrigger(botAI); } - static Trigger* prayer_of_fortitude_on_party(PlayerbotAI* botAI) { return new PrayerOfFortitudeTrigger(botAI); } - static Trigger* prayer_of_spirit_on_party(PlayerbotAI* botAI) { return new PrayerOfSpiritTrigger(botAI); } static Trigger* feedback(PlayerbotAI* botAI) { return new FeedbackTrigger(botAI); } static Trigger* fear_ward(PlayerbotAI* botAI) { return new FearWardTrigger(botAI); } static Trigger* shadowguard(PlayerbotAI* botAI) { return new ShadowguardTrigger(botAI); } @@ -207,8 +203,6 @@ class PriestAiObjectContextInternal : public NamedObjectContext creators["shadow protection"] = &PriestAiObjectContextInternal::shadow_protection; creators["shadow protection on party"] = &PriestAiObjectContextInternal::shadow_protection_on_party; creators["shackle undead"] = &PriestAiObjectContextInternal::shackle_undead; - creators["prayer of fortitude on party"] = &PriestAiObjectContextInternal::prayer_of_fortitude_on_party; - creators["prayer of spirit on party"] = &PriestAiObjectContextInternal::prayer_of_spirit_on_party; creators["power infusion on party"] = &PriestAiObjectContextInternal::power_infusion_on_party; creators["silence"] = &PriestAiObjectContextInternal::silence; creators["silence on enemy healer"] = &PriestAiObjectContextInternal::silence_on_enemy_healer; @@ -311,11 +305,6 @@ class PriestAiObjectContextInternal : public NamedObjectContext static Action* fade(PlayerbotAI* botAI) { return new CastFadeAction(botAI); } static Action* inner_fire(PlayerbotAI* botAI) { return new CastInnerFireAction(botAI); } static Action* shackle_undead(PlayerbotAI* botAI) { return new CastShackleUndeadAction(botAI); } - static Action* prayer_of_spirit_on_party(PlayerbotAI* botAI) { return new CastPrayerOfSpiritOnPartyAction(botAI); } - static Action* prayer_of_fortitude_on_party(PlayerbotAI* botAI) - { - return new CastPrayerOfFortitudeOnPartyAction(botAI); - } static Action* feedback(PlayerbotAI* botAI) { return new CastFeedbackAction(botAI); } static Action* elunes_grace(PlayerbotAI* botAI) { return new CastElunesGraceAction(botAI); } static Action* starshards(PlayerbotAI* botAI) { return new CastStarshardsAction(botAI); } diff --git a/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategy.cpp b/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategy.cpp index 6447d451976..1caa6f8e0bb 100644 --- a/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategy.cpp +++ b/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategy.cpp @@ -54,12 +54,6 @@ void PriestBuffStrategy::InitTriggers(std::vector& triggers) { NonCombatStrategy::InitTriggers(triggers); - triggers.push_back( - new TriggerNode("prayer of fortitude on party", - { NextAction("prayer of fortitude on party", 12.0f) })); - triggers.push_back( - new TriggerNode("prayer of spirit on party", - { NextAction("prayer of spirit on party", 14.0f) })); triggers.push_back( new TriggerNode("power word: fortitude on party", { NextAction("power word: fortitude on party", 11.0f) })); diff --git a/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategyActionNodeFactory.h b/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategyActionNodeFactory.h index c7d4aba82bd..66a6a44c516 100644 --- a/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategyActionNodeFactory.h +++ b/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategyActionNodeFactory.h @@ -30,8 +30,6 @@ class PriestNonCombatStrategyActionNodeFactory : public NamedObjectFactoryHasAura("power word : fortitude", GetTarget()) && - !botAI->HasAura("prayer of fortitude", GetTarget()); + return BuffTrigger::IsActive() && !botAI->HasAura("prayer of shadow protection", GetTarget()); } bool PowerWordFortitudeTrigger::IsActive() @@ -20,43 +19,12 @@ bool PowerWordFortitudeTrigger::IsActive() !botAI->HasAura("prayer of fortitude", GetTarget()); } -bool DivineSpiritOnPartyTrigger::IsActive() -{ - return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("divine spirit", GetTarget()) && - !botAI->HasAura("prayer of spirit", GetTarget()); -} - bool DivineSpiritTrigger::IsActive() { return BuffTrigger::IsActive() && !botAI->HasAura("divine spirit", GetTarget()) && !botAI->HasAura("prayer of spirit", GetTarget()); } -bool PrayerOfFortitudeTrigger::IsActive() -{ - Unit* target = GetTarget(); - if (!target || !target->IsPlayer()) - return false; - - return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("prayer of fortitude", GetTarget()) && - botAI->GetBot()->IsInSameGroupWith((Player*)GetTarget()) && - botAI->GetBuffedCount((Player*)GetTarget(), "prayer of fortitude") < 4 && - !botAI->GetBuffedCount((Player*)GetTarget(), "power word: fortitude"); -} - -bool PrayerOfSpiritTrigger::IsActive() -{ - Unit* target = GetTarget(); - if (!target || !target->IsPlayer()) - return false; - - return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("prayer of spirit", GetTarget()) && - botAI->GetBot()->IsInSameGroupWith((Player*)GetTarget()) && - // botAI->GetManaPercent() > 50 && - botAI->GetBuffedCount((Player*)GetTarget(), "prayer of spirit") < 4 && - !botAI->GetBuffedCount((Player*)GetTarget(), "divine spirit"); -} - bool InnerFireTrigger::IsActive() { Unit* target = GetTarget(); diff --git a/src/Ai/Class/Priest/Trigger/PriestTriggers.h b/src/Ai/Class/Priest/Trigger/PriestTriggers.h index 5ae91985d31..fc5b1f8ccea 100644 --- a/src/Ai/Class/Priest/Trigger/PriestTriggers.h +++ b/src/Ai/Class/Priest/Trigger/PriestTriggers.h @@ -27,8 +27,6 @@ BUFF_TRIGGER_A(InnerFireTrigger, "inner fire"); BUFF_TRIGGER_A(ShadowformTrigger, "shadowform"); BOOST_TRIGGER(PowerInfusionTrigger, "power infusion"); BUFF_TRIGGER(InnerFocusTrigger, "inner focus"); -BUFF_TRIGGER(ShadowProtectionTrigger, "shadow protection"); -BUFF_PARTY_TRIGGER(ShadowProtectionOnPartyTrigger, "shadow protection"); CC_TRIGGER(ShackleUndeadTrigger, "shackle undead"); INTERRUPT_TRIGGER(SilenceTrigger, "silence"); INTERRUPT_HEALER_TRIGGER(SilenceEnemyHealerTrigger, "silence"); @@ -44,52 +42,50 @@ SNARE_TRIGGER(ChastiseTrigger, "chastise"); BOOST_TRIGGER_A(ShadowfiendTrigger, "shadowfiend"); -class PowerWordFortitudeOnPartyTrigger : public BuffOnPartyTrigger +class ShadowProtectionTrigger : public BuffTrigger { public: - PowerWordFortitudeOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "power word: fortitude", 4 * 2000) - { - } + ShadowProtectionTrigger(PlayerbotAI* botAI) + : BuffTrigger(botAI, "shadow protection", 4 * 2000) {} bool IsActive() override; }; -class PowerWordFortitudeTrigger : public BuffTrigger +class ShadowProtectionOnPartyTrigger : public BuffOnPartyTrigger { public: - PowerWordFortitudeTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "power word: fortitude", 4 * 2000) {} - - bool IsActive() override; + ShadowProtectionOnPartyTrigger(PlayerbotAI* botAI) + : BuffOnPartyTrigger(botAI, "shadow protection", 4 * 2000) {} }; -class DivineSpiritOnPartyTrigger : public BuffOnPartyTrigger +class PowerWordFortitudeOnPartyTrigger : public BuffOnPartyTrigger { public: - DivineSpiritOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "divine spirit", 4 * 2000) {} - - bool IsActive() override; + PowerWordFortitudeOnPartyTrigger(PlayerbotAI* botAI) + : BuffOnPartyTrigger(botAI, "power word: fortitude", 4 * 2000) {} }; -class DivineSpiritTrigger : public BuffTrigger +class PowerWordFortitudeTrigger : public BuffTrigger { public: - DivineSpiritTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "divine spirit", 4 * 2000) {} + PowerWordFortitudeTrigger(PlayerbotAI* botAI) + : BuffTrigger(botAI, "power word: fortitude", 4 * 2000) {} bool IsActive() override; }; -class PrayerOfFortitudeTrigger : public BuffOnPartyTrigger +class DivineSpiritOnPartyTrigger : public BuffOnPartyTrigger { public: - PrayerOfFortitudeTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "prayer of fortitude", 3 * 2000) {} - - bool IsActive() override; + DivineSpiritOnPartyTrigger(PlayerbotAI* botAI) + : BuffOnPartyTrigger(botAI, "divine spirit", 4 * 2000) {} }; -class PrayerOfSpiritTrigger : public BuffOnPartyTrigger +class DivineSpiritTrigger : public BuffTrigger { public: - PrayerOfSpiritTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "prayer of spirit", 2 * 2000) {} + DivineSpiritTrigger(PlayerbotAI* botAI) + : BuffTrigger(botAI, "divine spirit", 4 * 2000) {} bool IsActive() override; }; @@ -106,9 +102,7 @@ class MindSearChannelCheckTrigger : public Trigger { public: MindSearChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2) - : Trigger(botAI, "mind sear channel check"), minEnemies(minEnemies) - { - } + : Trigger(botAI, "mind sear channel check"), minEnemies(minEnemies) {} bool IsActive() override; diff --git a/src/Bot/Factory/AiFactory.cpp b/src/Bot/Factory/AiFactory.cpp index f1fd2491f47..b3abbd8ad99 100644 --- a/src/Bot/Factory/AiFactory.cpp +++ b/src/Bot/Factory/AiFactory.cpp @@ -500,21 +500,21 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const switch (player->getClass()) { case CLASS_PRIEST: - nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr); + nonCombatEngine->addStrategiesNoInit("dps assist", "cure", "rshadow", nullptr); break; case CLASS_PALADIN: if (tab == PALADIN_TAB_PROTECTION) { nonCombatEngine->addStrategiesNoInit("bthreat", "tank assist", "pull", "barmor", nullptr); if (player->GetLevel() >= 20) - nonCombatEngine->addStrategy("bhealth", false); + nonCombatEngine->addStrategy("bsanc", false); else - nonCombatEngine->addStrategy("bdps", false); + nonCombatEngine->addStrategy("bmight", false); } else if (tab == PALADIN_TAB_HOLY) - nonCombatEngine->addStrategiesNoInit("dps assist", "bmana", "bcast", nullptr); + nonCombatEngine->addStrategiesNoInit("dps assist", "bwisdom", "bcast", nullptr); else - nonCombatEngine->addStrategiesNoInit("dps assist", "bdps", "baoe", nullptr); + nonCombatEngine->addStrategiesNoInit("dps assist", "bmight", "baoe", nullptr); nonCombatEngine->addStrategiesNoInit("cure", nullptr); break; diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index e1096b0cce2..8bd1f2c2f35 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -5971,29 +5971,6 @@ void PlayerbotAI::EnchantItemT(uint32 spellid, uint8 slot) LOG_INFO("playerbots", "{}: items was enchanted successfully!", bot->GetName().c_str()); } -uint32 PlayerbotAI::GetBuffedCount(Player* player, std::string const spellname) -{ - uint32 bcount = 0; - - if (Group* group = bot->GetGroup()) - { - for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) - { - Player* member = gref->GetSource(); - if (!member || !member->IsInWorld()) - continue; - - if (!member->IsInSameRaidWith(player)) - continue; - - if (HasAura(spellname, member, true)) - bcount++; - } - } - - return bcount; -} - int32 PlayerbotAI::GetNearGroupMemberCount(float dis) { int count = 1; // yourself diff --git a/src/Bot/PlayerbotAI.h b/src/Bot/PlayerbotAI.h index 01924c46fb0..2e31c9a9e7f 100644 --- a/src/Bot/PlayerbotAI.h +++ b/src/Bot/PlayerbotAI.h @@ -493,7 +493,6 @@ class PlayerbotAI : public PlayerbotAIBase void ImbueItem(Item* item, Unit* target); void ImbueItem(Item* item); void EnchantItemT(uint32 spellid, uint8 slot); - uint32 GetBuffedCount(Player* player, std::string const spellname); int32 GetNearGroupMemberCount(float dis = sPlayerbotAIConfig.sightDistance); virtual bool CanCastSpell(std::string const name, Unit* target, Item* itemTarget = nullptr); diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index af08edf64d3..fd230cb9bd1 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -84,8 +84,6 @@ bool PlayerbotAIConfig::Initialize() sitDelay = sConfigMgr->GetOption("AiPlayerbot.SitDelay", 20000); returnDelay = sConfigMgr->GetOption("AiPlayerbot.ReturnDelay", 2000); lootDelay = sConfigMgr->GetOption("AiPlayerbot.LootDelay", 1000); - minBotsForGreaterBuff = sConfigMgr->GetOption("AiPlayerbot.MinBotsForGreaterBuff", 3); - rpWarningCooldown = sConfigMgr->GetOption("AiPlayerbot.RPWarningCooldown", 30); disabledWithoutRealPlayerLoginDelay = sConfigMgr->GetOption("AiPlayerbot.DisabledWithoutRealPlayerLoginDelay", 30); disabledWithoutRealPlayerLogoutDelay = sConfigMgr->GetOption("AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay", 300); @@ -116,6 +114,32 @@ bool PlayerbotAIConfig::Initialize() highMana = sConfigMgr->GetOption("AiPlayerbot.HighMana", 65); autoSaveMana = sConfigMgr->GetOption("AiPlayerbot.AutoSaveMana", true); saveManaThreshold = sConfigMgr->GetOption("AiPlayerbot.SaveManaThreshold", 60); + switch (sConfigMgr->GetOption("AiPlayerbot.AutoGreaterBlessings", 1)) + { + case 0: + autoGreaterBlessings = AutoPartyBuffMode::DISABLED; + break; + case 2: + autoGreaterBlessings = AutoPartyBuffMode::GROUP_OR_RAID; + break; + case 1: + default: + autoGreaterBlessings = AutoPartyBuffMode::RAID_ONLY; + break; + } + switch (sConfigMgr->GetOption("AiPlayerbot.AutoPartyBuffs", 2)) + { + case 0: + autoPartyBuffs = AutoPartyBuffMode::DISABLED; + break; + case 1: + autoPartyBuffs = AutoPartyBuffMode::RAID_ONLY; + break; + case 2: + default: + autoPartyBuffs = AutoPartyBuffMode::GROUP_OR_RAID; + break; + } autoAvoidAoe = sConfigMgr->GetOption("AiPlayerbot.AutoAvoidAoe", true); maxAoeAvoidRadius = sConfigMgr->GetOption("AiPlayerbot.MaxAoeAvoidRadius", 15.0f); LoadSet>(sConfigMgr->GetOption("AiPlayerbot.AoeAvoidSpellWhitelist", "50759,57491,13810,29946"), diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 715701231da..267042271f8 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -40,6 +40,13 @@ enum class HealingManaEfficiency : uint8 SUPERIOR = 32 }; +enum class AutoPartyBuffMode : uint8 +{ + DISABLED = 0, + RAID_ONLY = 1, + GROUP_OR_RAID = 2 +}; + enum NewRpgStatus : int { //Initial Status @@ -94,6 +101,8 @@ class PlayerbotAIConfig uint32 lowMana, mediumMana, highMana; bool autoSaveMana; uint32 saveManaThreshold; + AutoPartyBuffMode autoGreaterBlessings; + AutoPartyBuffMode autoPartyBuffs; bool autoAvoidAoe; float maxAoeAvoidRadius; std::set aoeAvoidSpellWhitelist; @@ -146,12 +155,6 @@ class PlayerbotAIConfig uint32 disabledWithoutRealPlayerLoginDelay, disabledWithoutRealPlayerLogoutDelay; bool randomBotJoinLfg; - // Buff system - // Min group size to use Greater buffs (Paladin, Mage, Druid). Default: 3 - int32 minBotsForGreaterBuff; - // Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff. Default: 30 - int32 rpWarningCooldown; - // Professions bool enableFishingWithMaster; uint32 classMatchingProfessionChance; From 4a63ee37e29f44010a0c926d33c114816abc2946 Mon Sep 17 00:00:00 2001 From: dillyns <49765217+dillyns@users.noreply.github.com> Date: Sat, 30 May 2026 02:09:28 -0400 Subject: [PATCH 44/63] Shadow Priest Vampiric Embrac (#2410) ## Pull Request Description Shadow priest's Vampiric Embrace was incorrectly set as a debuff, and was in the combat strategy. Fixed it to be a buff, and move it to the noncombat strategy with other buffs ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes Get a shadow priest bot. They should now buff themselves with Vampiric Embrace. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Class/Priest/Action/PriestActions.h | 2 +- src/Ai/Class/Priest/Strategy/PriestNonCombatStrategy.cpp | 2 ++ src/Ai/Class/Priest/Strategy/ShadowPriestStrategy.cpp | 8 -------- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Ai/Class/Priest/Action/PriestActions.h b/src/Ai/Class/Priest/Action/PriestActions.h index 0fe0a832f7b..1a6ded13964 100644 --- a/src/Ai/Class/Priest/Action/PriestActions.h +++ b/src/Ai/Class/Priest/Action/PriestActions.h @@ -139,7 +139,7 @@ class CastShadowProtectionOnPartyAction : public GroupBuffOnPartyAction // shadow talents SPELL_ACTION(CastMindFlayAction, "mind flay"); -DEBUFF_ACTION(CastVampiricEmbraceAction, "vampiric embrace"); +BUFF_ACTION(CastVampiricEmbraceAction, "vampiric embrace"); BUFF_ACTION(CastShadowformAction, "shadowform"); SPELL_ACTION(CastSilenceAction, "silence"); ENEMY_HEALER_ACTION(CastSilenceOnEnemyHealerAction, "silence"); diff --git a/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategy.cpp b/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategy.cpp index 1caa6f8e0bb..bd0bdd11f5a 100644 --- a/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategy.cpp +++ b/src/Ai/Class/Priest/Strategy/PriestNonCombatStrategy.cpp @@ -19,6 +19,8 @@ void PriestNonCombatStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("inner fire",{ NextAction("inner fire", 10.0f) })); + triggers.push_back( + new TriggerNode("vampiric embrace", { NextAction("vampiric embrace", 16.0f) })); triggers.push_back(new TriggerNode( "party member dead",{ NextAction("remove shadowform", ACTION_CRITICAL_HEAL + 11), NextAction("resurrection", ACTION_CRITICAL_HEAL + 10) })); diff --git a/src/Ai/Class/Priest/Strategy/ShadowPriestStrategy.cpp b/src/Ai/Class/Priest/Strategy/ShadowPriestStrategy.cpp index 1308f7225d2..2200707c7a8 100644 --- a/src/Ai/Class/Priest/Strategy/ShadowPriestStrategy.cpp +++ b/src/Ai/Class/Priest/Strategy/ShadowPriestStrategy.cpp @@ -51,14 +51,6 @@ void ShadowPriestStrategy::InitTriggers(std::vector& triggers) } ) ); - triggers.push_back( - new TriggerNode( - "vampiric embrace", - { - NextAction("vampiric embrace", 16.0f) - } - ) - ); triggers.push_back( new TriggerNode( "silence", From 240bb2dfcabda44496f1c993841367203635d93b Mon Sep 17 00:00:00 2001 From: Mat Date: Sat, 30 May 2026 15:48:07 +0200 Subject: [PATCH 45/63] Autogear suffixes (#2415) ## Pull Request Description Suffix scoring was already wired up in playerbots, bots could evaluate suffix items they owned, but factory init scored candidates with randomPropertyId = 0 and equipped via EquipNewItem which doesn't roll suffixes, so suffix items always looked like junk and never got picked. Added a cached item_enchantment_template pool in RandomItemMgr, a PickBestRandomPropertyId helper that picks the best suffix per bot class/spec, and added it on the equipped item via SetItemRandomProperties. Tested levels 20-80, suffixes match spec. Closes #2370. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. For items with RandomProperty != 0 or RandomSuffix != 0, run the item_enchantment_template pool once, score each candidate suffix against the bot's existing class/spec stat weights, keep the highest, and stamp that id on the item right after EquipNewItem. Items without a suffix pool skip the helper entirely. Without this, every suffix template scores as base stats only (often near zero) and never gets picked during factory init. - Describe the **processing cost** when this logic executes across many bots. Startup: one SELECT entry, ench FROM item_enchantment_template (few hundred rows), parsed into an unordered_map> once. No SQL afterwards. Per bot during init: PickBestRandomPropertyId only runs on candidates that actually have a suffix pool. Each call is a hash lookup plus a loop over 5-15 enchantment ids, each doing DBC lookups already used by the scoring code. Cost is mostly by the existing CalculateRandomProperty work, not new logic. Scales linearly with bot count, same as the rest of factory init. No new per-tick work. ## How to Test the Changes 1. Generate a fresh random bot at level 20-79 and use autogear (cloth, leather, mail, plate, lots of suffix gear at that range). 2. Inspect the bot's gear. Items with names like "of the Eagle", "of the Monkey", "of Healing", etc... should be equipped, with the suffix stats visible on the tooltip. 3. Generate a level 80 bot at high gear-score limit. Confirm it still equips raid epics normally (epics have no suffix pool, so this path is untouched). ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Bots now consider random-suffix items during factory init and equip them with the best suffix for their class/spec. Before, suffix items were effectively invisible to init autogear because they scored as base stats only. This is the intended fix for #2370. - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [x] Yes (**explain below**) Added one new public method on StatsWeightCalculator (PickBestRandomPropertyId) and one new cache in RandomItemMgr (LoadEnchantmentPool + GetEnchantmentPool). The candidate list in InitEquipment changed from vector to vector> to carry the chosen suffix id alongside the item id. Logic mirrors existing patterns in the same files (DBC lookups, SQL-backed caches, signed randomPropertyId encoding), no new abstractions, no new wrappers. ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) Used AI to help me find the bug. It mapped the existing scoring pipeline and pointed out that PlayerbotFactory::InitEquipment was scoring candidates with randomPropertyId = 0 and equipping via EquipNewItem (which doesn't roll suffixes), so the scoring side was already built, just never fed real suffix ids during init. I reviewed and tested all the code in-game across levels 20-80 and multiple classes/specs. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- src/Ai/Base/Actions/EquipAction.cpp | 8 +++-- src/Bot/Factory/PlayerbotFactory.cpp | 48 ++++++++++++++++++++------ src/Mgr/Item/RandomItemMgr.cpp | 34 ++++++++++++++++++ src/Mgr/Item/RandomItemMgr.h | 5 +++ src/Mgr/Item/StatsWeightCalculator.cpp | 48 ++++++++++++++++++++++++++ src/Mgr/Item/StatsWeightCalculator.h | 1 + 6 files changed, 131 insertions(+), 13 deletions(-) diff --git a/src/Ai/Base/Actions/EquipAction.cpp b/src/Ai/Base/Actions/EquipAction.cpp index a4086f3719c..4cd11de3c8e 100644 --- a/src/Ai/Base/Actions/EquipAction.cpp +++ b/src/Ai/Base/Actions/EquipAction.cpp @@ -154,9 +154,11 @@ void EquipAction::EquipItem(Item* item) calculator.SetOverflowPenalty(false); // Calculate item scores once and store them - float newItemScore = calculator.CalculateItem(itemId); - float mainHandScore = mainHandItem ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId) : 0.0f; - float offHandScore = offHandItem ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId) : 0.0f; + float newItemScore = calculator.CalculateItem(itemId, item->GetItemRandomPropertyId()); + float mainHandScore = mainHandItem + ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId, mainHandItem->GetItemRandomPropertyId()) : 0.0f; + float offHandScore = offHandItem + ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId, offHandItem->GetItemRandomPropertyId()) : 0.0f; // Determine where this weapon can go bool canGoMain = (invType == INVTYPE_WEAPON || diff --git a/src/Bot/Factory/PlayerbotFactory.cpp b/src/Bot/Factory/PlayerbotFactory.cpp index 161410c3f61..a1adfdeaf35 100644 --- a/src/Bot/Factory/PlayerbotFactory.cpp +++ b/src/Bot/Factory/PlayerbotFactory.cpp @@ -2081,7 +2081,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) return; } - std::unordered_map> items; + std::unordered_map>> items; // int tab = AiFactory::GetPlayerSpecTab(bot); uint32 blevel = bot->GetLevel(); @@ -2228,13 +2228,17 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) if (slot == EQUIPMENT_SLOT_OFFHAND && bot->getClass() == CLASS_ROGUE && proto->Class != ITEM_CLASS_WEAPON) continue; - items[slot].push_back(itemId); + + int32 bestRandomProp = 0; + if (proto->RandomProperty || proto->RandomSuffix) + bestRandomProp = calculator.PickBestRandomPropertyId(itemId); + items[slot].push_back({itemId, bestRandomProp}); } } } } while (items[slot].size() < 25 && desiredQuality-- > ITEM_QUALITY_POOR); - std::vector& ids = items[slot]; + std::vector>& ids = items[slot]; if (ids.empty()) { continue; @@ -2242,13 +2246,15 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) float bestScoreForSlot = -1; uint32 bestItemForSlot = 0; + int32 bestRandomPropForSlot = 0; for (int index = 0; index < ids.size(); index++) { - uint32 newItemId = ids[index]; + uint32 newItemId = ids[index].first; + int32 newItemProp = ids[index].second; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(newItemId); - float cur_score = calculator.CalculateItem(newItemId, 0, slot); + float cur_score = calculator.CalculateItem(newItemId, newItemProp, slot); if (cur_score > 0.0f && proto && proto->Class == ITEM_CLASS_ARMOR && sPlayerbotAIConfig.preferClassArmorType) { @@ -2267,6 +2273,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) continue; bestScoreForSlot = cur_score; bestItemForSlot = newItemId; + bestRandomPropForSlot = newItemProp; } } @@ -2304,7 +2311,16 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) if (oldItem) continue; - bot->EquipNewItem(dest, bestItemForSlot, true); + if (Item* equipped = bot->EquipNewItem(dest, bestItemForSlot, true)) + { + if (bestRandomPropForSlot != 0) + { + uint8 equipSlot = equipped->GetSlot(); + bot->_ApplyItemMods(equipped, equipSlot, false); + equipped->SetItemRandomProperties(bestRandomPropForSlot); + bot->_ApplyItemMods(equipped, equipSlot, true); + } + } bot->AutoUnequipOffhandIfNeed(); // if (newItem) // { @@ -2345,19 +2361,21 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) if (Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); - std::vector& ids = items[slot]; + std::vector>& ids = items[slot]; if (ids.empty()) continue; float bestScoreForSlot = -1; uint32 bestItemForSlot = 0; + int32 bestRandomPropForSlot = 0; for (int index = 0; index < ids.size(); index++) { - uint32 newItemId = ids[index]; + uint32 newItemId = ids[index].first; + int32 newItemProp = ids[index].second; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(newItemId); - float cur_score = calculator.CalculateItem(newItemId, 0, slot); + float cur_score = calculator.CalculateItem(newItemId, newItemProp, slot); if (cur_score > 0.0f && proto && proto->Class == ITEM_CLASS_ARMOR && sPlayerbotAIConfig.preferClassArmorType) { @@ -2376,6 +2394,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) continue; bestScoreForSlot = cur_score; bestItemForSlot = newItemId; + bestRandomPropForSlot = newItemProp; } } @@ -2386,7 +2405,16 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) if (!CanEquipUnseenItem(slot, dest, bestItemForSlot)) continue; - bot->EquipNewItem(dest, bestItemForSlot, true); + if (Item* equipped = bot->EquipNewItem(dest, bestItemForSlot, true)) + { + if (bestRandomPropForSlot != 0) + { + uint8 equipSlot = equipped->GetSlot(); + bot->_ApplyItemMods(equipped, equipSlot, false); + equipped->SetItemRandomProperties(bestRandomPropForSlot); + bot->_ApplyItemMods(equipped, equipSlot, true); + } + } bot->AutoUnequipOffhandIfNeed(); } } diff --git a/src/Mgr/Item/RandomItemMgr.cpp b/src/Mgr/Item/RandomItemMgr.cpp index e08f2c38567..c5708064443 100644 --- a/src/Mgr/Item/RandomItemMgr.cpp +++ b/src/Mgr/Item/RandomItemMgr.cpp @@ -160,6 +160,7 @@ void RandomItemMgr::Init() BuildPotionCache(); BuildFoodCache(); BuildTradeCache(); + LoadEnchantmentPool(); } void RandomItemMgr::InitAfterAhBot() @@ -452,6 +453,39 @@ std::vector RandomItemMgr::GetCachedEquipments(uint32 requiredLevel, uin return equipCacheNew[requiredLevel][inventoryType]; } +void RandomItemMgr::LoadEnchantmentPool() +{ + enchPoolCache.clear(); + + QueryResult result = WorldDatabase.Query("SELECT entry, ench FROM item_enchantment_template"); + if (!result) + { + LOG_WARN("playerbots", "item_enchantment_template empty; bot autogear cannot evaluate random suffixes"); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + uint32 entry = fields[0].Get(); + uint32 ench = fields[1].Get(); + enchPoolCache[entry].push_back(ench); + ++count; + } while (result->NextRow()); + + LOG_INFO("playerbots", "Loaded {} item enchantment pool rows for bot autogear", count); +} + +std::vector const& RandomItemMgr::GetEnchantmentPool(uint32 entry) const +{ + static std::vector const empty; + auto it = enchPoolCache.find(entry); + if (it == enchPoolCache.end()) + return empty; + return it->second; +} + bool RandomItemMgr::ShouldEquipArmorForSpec(uint8 playerclass, uint8 spec, ItemTemplate const* proto) { if (proto->InventoryType == INVTYPE_TABARD) diff --git a/src/Mgr/Item/RandomItemMgr.h b/src/Mgr/Item/RandomItemMgr.h index 54e2e635167..bc19d75daf7 100644 --- a/src/Mgr/Item/RandomItemMgr.h +++ b/src/Mgr/Item/RandomItemMgr.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -172,9 +173,11 @@ class RandomItemMgr static bool IsUsedBySkill(ItemTemplate const* proto, uint32 skillId); bool IsTestItem(uint32 itemId) { return itemForTest.find(itemId) != itemForTest.end(); } std::vector GetCachedEquipments(uint32 requiredLevel, uint32 inventoryType); + std::vector const& GetEnchantmentPool(uint32 entry) const; private: void BuildRandomItemCache(); + void LoadEnchantmentPool(); void BuildEquipCache(); void BuildEquipCacheNew(); void BuildItemInfoCache(); @@ -217,6 +220,8 @@ class RandomItemMgr static std::set itemCache; // equipCacheNew[RequiredLevel][InventoryType] std::map>> equipCacheNew; + // enchPoolCache[item_enchantment_template.entry] -> list of enchantment ids + std::unordered_map> enchPoolCache; }; #define sRandomItemMgr RandomItemMgr::instance() diff --git a/src/Mgr/Item/StatsWeightCalculator.cpp b/src/Mgr/Item/StatsWeightCalculator.cpp index b232b1e6b64..2e11f0a38cf 100644 --- a/src/Mgr/Item/StatsWeightCalculator.cpp +++ b/src/Mgr/Item/StatsWeightCalculator.cpp @@ -14,6 +14,7 @@ #include "ObjectMgr.h" #include "PlayerbotAI.h" #include "PlayerbotFactory.h" +#include "RandomItemMgr.h" #include "SharedDefines.h" #include "SpellAuraDefines.h" #include "SpellMgr.h" @@ -190,6 +191,53 @@ void StatsWeightCalculator::CalculateRandomProperty(int32 randomPropertyId, uint } } +int32 StatsWeightCalculator::PickBestRandomPropertyId(uint32 itemId) +{ + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); + if (!proto) + return 0; + + bool isSuffix = false; + uint32 poolEntry = proto->RandomProperty; + if (!poolEntry) + { + poolEntry = proto->RandomSuffix; + isSuffix = true; + } + if (!poolEntry) + return 0; + + std::vector const& pool = sRandomItemMgr.GetEnchantmentPool(poolEntry); + if (pool.empty()) + return 0; + + Reset(); + GenerateWeights(player_); + + int32 bestId = 0; + float bestScore = 0.0f; + for (uint32 enchId : pool) + { + int32 candidate = isSuffix ? -static_cast(enchId) : static_cast(enchId); + + collector_->Reset(); + CalculateRandomProperty(candidate, itemId); + + float score = 0.0f; + for (uint32 i = 0; i < STATS_TYPE_MAX; ++i) + score += stats_weights_[i] * collector_->stats[i]; + + if (bestId == 0 || score > bestScore) + { + bestId = candidate; + bestScore = score; + } + } + + collector_->Reset(); + return bestId; +} + void StatsWeightCalculator::GenerateWeights(Player* player) { GenerateBasicWeights(player); diff --git a/src/Mgr/Item/StatsWeightCalculator.h b/src/Mgr/Item/StatsWeightCalculator.h index d97dafbb34a..1049adcb49f 100644 --- a/src/Mgr/Item/StatsWeightCalculator.h +++ b/src/Mgr/Item/StatsWeightCalculator.h @@ -30,6 +30,7 @@ class StatsWeightCalculator void Reset(); float CalculateItem(uint32 itemId, int32 randomPropertyId = 0, int32 slot = -1); float CalculateEnchant(uint32 enchantId); + int32 PickBestRandomPropertyId(uint32 itemId); void SetOverflowPenalty(bool apply) { enable_overflow_penalty_ = apply; } void SetItemSetBonus(bool apply) { enable_item_set_bonus_ = apply; } From 0afaf753c6ff9e1dd8d3563a8d49c8a388332ab9 Mon Sep 17 00:00:00 2001 From: NoxMax <50133316+NoxMax@users.noreply.github.com> Date: Sat, 30 May 2026 07:48:16 -0600 Subject: [PATCH 46/63] Clarifies BG bracket auto-join comments and configs (#2417) ## Pull Request Description playerbots.conf.dist: 1. Updated and clarified comments. At best they were misleading, at worst they were wrong. 2. Reordered brackets configs top to bottom, from Warsong to IOC. 3. No config values have actually been changed. PlayerbotAIConfig.cpp: 1. Made sure the config values actually match the .dist file. 2. Reordered brackets configs top to bottom, from Warsong to IOC. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers Bracket ranges are from `pvpdifficulty_dbc` --- conf/playerbots.conf.dist | 59 ++++++++++++++++++++------------------- src/PlayerbotAIConfig.cpp | 20 ++++++------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 81f1b47b2d1..23885bee335 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1290,44 +1290,47 @@ AiPlayerbot.RandomBotAutoJoinBG = 0 # This section controls the level brackets and automatic bot participation in battlegrounds and arenas. # # Brackets: -# - Specify the level ranges for bots to auto-join: -# - Warsong Gulch (WS): 0 = 10-19, 1 = 20-29, 2 = 30-39, ..., 7 = 80 (Default: 7) -# - Rated Arena: 0 = 10-14, 1 = 15-19, ..., 14 = 80-84 (Default: 14) -# - Multiple brackets can be specified as a comma-separated list (e.g., "0,2,5"). +# - Specify the level ranges for bots to auto-join (bracket IDs are per-BG and start at 0 from each BG's minimum level): +# - Warsong Gulch (WS): 0=10-19, 1=20-29, 2=30-39, 3=40-49, 4=50-59, 5=60-69, 6=70-79, 7=80 (Default: 7) +# - Arathi Basin (AB): 0=20-29, 1=30-39, 2=40-49, 3=50-59, 4=60-69, 5=70-79, 6=80 (Default: 6) +# - Alterac Valley (AV): 0=51-60, 1=61-70, 2=71-79, 3=80 (Default: 3) +# - Eye of the Storm (EY): 0=61-69, 1=70-79, 2=80 (Default: 2) +# - Isle of Conquest (IC): 0=71-79, 1=80 (Default: 1) +# - Multiple BG brackets can be specified as a comma-separated list (e.g., "0,2,5"). Not applicable for Rated Arena. +# - Rated Arena: 0=10-14, 1=15-19, 2=20-24, ..., 13=75-79, 14=80-84 (Default: 14) # # Counts: -# - Specify the number of battlegrounds to auto-fill per bracket. -# - For battlegrounds, 'Count" is the number of bots per bracket. For example: -# - Warsong Gulch Count = 1 adds 20 bots (10 per team). +# - Specify the number of battles to auto-fill per bracket. +# - For battlegrounds, 'Count" is the number of battles per bracket. For example: +# - RandomBotAutoJoinWSBrackets = 6,7 +# - RandomBotAutoJoinBGWSCount = 1 +# - These configs would create two battles, one for bracket 6 and one for bracket 7. # - Ensure there are enough eligible bots to meet the specified counts. -# -# Arena Considerations: -# - Rated arena brackets default to level 80-84 (bracket 14). -# - Custom code changes are required for lower-level arena brackets to function properly. -# + # Battleground bracket range possibilities: -# AiPlayerbot.RandomBotAutoJoinICBrackets = 0,1 -# AiPlayerbot.RandomBotAutoJoinEYBrackets = 0,1,2 -# AiPlayerbot.RandomBotAutoJoinAVBrackets = 0,1,2,3 -# AiPlayerbot.RandomBotAutoJoinABBrackets = 0,1,2,3,4,5,6 # AiPlayerbot.RandomBotAutoJoinWSBrackets = 0,1,2,3,4,5,6,7 - -AiPlayerbot.RandomBotAutoJoinICBrackets = 1 -AiPlayerbot.RandomBotAutoJoinEYBrackets = 2 -AiPlayerbot.RandomBotAutoJoinAVBrackets = 3 -AiPlayerbot.RandomBotAutoJoinABBrackets = 6 +# AiPlayerbot.RandomBotAutoJoinABBrackets = 0,1,2,3,4,5,6 +# AiPlayerbot.RandomBotAutoJoinAVBrackets = 0,1,2,3 +# AiPlayerbot.RandomBotAutoJoinEYBrackets = 0,1,2 +# AiPlayerbot.RandomBotAutoJoinICBrackets = 0,1 AiPlayerbot.RandomBotAutoJoinWSBrackets = 7 +AiPlayerbot.RandomBotAutoJoinABBrackets = 6 +AiPlayerbot.RandomBotAutoJoinAVBrackets = 3 +AiPlayerbot.RandomBotAutoJoinEYBrackets = 2 +AiPlayerbot.RandomBotAutoJoinICBrackets = 1 -# Battlegrounds count (per bracket!): -AiPlayerbot.RandomBotAutoJoinBGICCount = 0 -AiPlayerbot.RandomBotAutoJoinBGEYCount = 1 -AiPlayerbot.RandomBotAutoJoinBGAVCount = 0 -AiPlayerbot.RandomBotAutoJoinBGABCount = 1 +# Battlegrounds battles count (per bracket): AiPlayerbot.RandomBotAutoJoinBGWSCount = 1 +AiPlayerbot.RandomBotAutoJoinBGABCount = 1 +AiPlayerbot.RandomBotAutoJoinBGAVCount = 0 +AiPlayerbot.RandomBotAutoJoinBGEYCount = 1 +AiPlayerbot.RandomBotAutoJoinBGICCount = 0 -# Arena configuration: +# Arena bracket (single bracket only): +# Custom code changes are required for lower-level arena brackets to function properly. AiPlayerbot.RandomBotAutoJoinArenaBracket = 14 +# Arena battles count: AiPlayerbot.RandomBotAutoJoinBGRatedArena2v2Count = 0 AiPlayerbot.RandomBotAutoJoinBGRatedArena3v3Count = 0 AiPlayerbot.RandomBotAutoJoinBGRatedArena5v5Count = 0 @@ -2463,4 +2466,4 @@ AiPlayerbot.GuildTaskKillTaskDistance = 200 AiPlayerbot.TargetPosRecalcDistance = 0.1 # Allow bots to be summoned near innkeepers -AiPlayerbot.SummonAtInnkeepersEnabled = 1 \ No newline at end of file +AiPlayerbot.SummonAtInnkeepersEnabled = 1 diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index fd230cb9bd1..baaae8c3fa2 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -365,19 +365,19 @@ bool PlayerbotAIConfig::Initialize() randomBotJoinBG = sConfigMgr->GetOption("AiPlayerbot.RandomBotJoinBG", true); randomBotAutoJoinBG = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBG", false); - randomBotAutoJoinArenaBracket = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinArenaBracket", 7); + randomBotAutoJoinArenaBracket = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinArenaBracket", 14); - randomBotAutoJoinICBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinICBrackets", "0,1"); - randomBotAutoJoinEYBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinEYBrackets", "0,1,2"); - randomBotAutoJoinAVBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinAVBrackets", "0,1,2,3"); - randomBotAutoJoinABBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinABBrackets", "0,1,2,3,4,5,6"); - randomBotAutoJoinWSBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinWSBrackets", "0,1,2,3,4,5,6,7"); + randomBotAutoJoinWSBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinWSBrackets", "7"); + randomBotAutoJoinABBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinABBrackets", "6"); + randomBotAutoJoinAVBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinAVBrackets", "3"); + randomBotAutoJoinEYBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinEYBrackets", "2"); + randomBotAutoJoinICBrackets = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinICBrackets", "1"); - randomBotAutoJoinBGICCount = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGICCount", 0); - randomBotAutoJoinBGEYCount = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGEYCount", 0); + randomBotAutoJoinBGWSCount = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGWSCount", 1); + randomBotAutoJoinBGABCount = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGABCount", 1); randomBotAutoJoinBGAVCount = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGAVCount", 0); - randomBotAutoJoinBGABCount = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGABCount", 0); - randomBotAutoJoinBGWSCount = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGWSCount", 0); + randomBotAutoJoinBGEYCount = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGEYCount", 1); + randomBotAutoJoinBGICCount = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGICCount", 0); randomBotAutoJoinBGRatedArena2v2Count = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBGRatedArena2v2Count", 0); From a1f9ff4542b0ff0bb4ff74ef088a718b8efc19dd Mon Sep 17 00:00:00 2001 From: Mat Date: Sat, 30 May 2026 15:48:29 +0200 Subject: [PATCH 47/63] fix bot leader handling (#2426) ## Pull Request Description This pr fixes bots "freaking out" after leader change and bots will promote player to party leader after give leader command closes #2420 #2424 ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes 1. Join rdf or make group 2. Promote bot to leader (rnd or alt), if bot was not leader 3. Type /p give leader 4. Bot will promote you to leader and it will follow you instead of freaking out ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) 1. Bots now reset their AI on every SMSG_GROUP_SET_LEADER packet, so old leader strategies get cleared after a leader change. 2. Random bots auto-bind their master to a real-player group member during reset botAI, so commands like give leader and follow logic that depend on HasActivePlayerMaster() start working for them. 3. On OnBotLogin, bots no longer steal leadership from a real player, they only force the leader change if the current leader is a bot or offline. - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [x] Yes (**explain below**) Adds two small guards: an "is current leader a real player" check on login, and a "find first real-player member" loop inside ResetAiAction. Both reuse existing patterns (IsRealPlayer(), the OnPlayerLogin master-assign loop). ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Actions/ResetAiAction.cpp | 15 +++++++++++++++ .../Base/Strategy/WorldPacketHandlerStrategy.cpp | 2 +- src/Bot/PlayerbotMgr.cpp | 6 ------ src/Script/WorldThr/PlayerbotOperations.h | 1 + 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Ai/Base/Actions/ResetAiAction.cpp b/src/Ai/Base/Actions/ResetAiAction.cpp index 19017c91617..290d0ae1152 100644 --- a/src/Ai/Base/Actions/ResetAiAction.cpp +++ b/src/Ai/Base/Actions/ResetAiAction.cpp @@ -44,6 +44,21 @@ bool ResetAiAction::Execute(Event event) } } } + if (Player* master = botAI->GetMaster()) + { + Group* botGroup = bot->GetGroup(); + Group* masterGroup = master->GetGroup(); + if (botGroup && (!masterGroup || masterGroup != botGroup)) + botAI->SetMaster(nullptr); + } + if (sRandomPlayerbotMgr.IsRandomBot(bot) && !bot->InBattleground()) + { + if (bot->GetGroup() && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster()))) + { + if (Player* newMaster = botAI->FindNewMaster()) + botAI->SetMaster(newMaster); + } + } PlayerbotRepository::instance().Reset(botAI); botAI->ResetStrategies(false); botAI->TellMaster("AI was reset to defaults"); diff --git a/src/Ai/Base/Strategy/WorldPacketHandlerStrategy.cpp b/src/Ai/Base/Strategy/WorldPacketHandlerStrategy.cpp index ad31c2c3902..5f2d2532d49 100644 --- a/src/Ai/Base/Strategy/WorldPacketHandlerStrategy.cpp +++ b/src/Ai/Base/Strategy/WorldPacketHandlerStrategy.cpp @@ -16,7 +16,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector& trigger triggers.push_back( new TriggerNode("uninvite guid", { NextAction("uninvite", relevance) })); triggers.push_back( - new TriggerNode("group set leader", { /*NextAction("leader", relevance),*/ })); + new TriggerNode("group set leader", { NextAction("reset botAI", relevance) })); triggers.push_back(new TriggerNode( "not enough money", { NextAction("tell not enough money", relevance) })); triggers.push_back( diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index 0634bbf781f..a6d4a3b7982 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -481,12 +481,6 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) } Player* master = botAI->GetMaster(); - if (master) - { - ObjectGuid masterGuid = master->GetGUID(); - if (master->GetGroup() && !master->GetGroup()->IsLeader(masterGuid)) - master->GetGroup()->ChangeLeader(masterGuid); - } Group* group = bot->GetGroup(); if (group) diff --git a/src/Script/WorldThr/PlayerbotOperations.h b/src/Script/WorldThr/PlayerbotOperations.h index ee6443cd8a1..cc62e712717 100644 --- a/src/Script/WorldThr/PlayerbotOperations.h +++ b/src/Script/WorldThr/PlayerbotOperations.h @@ -242,6 +242,7 @@ class GroupSetLeaderOperation : public PlayerbotOperation } group->ChangeLeader(newLeader->GetGUID()); + group->SendUpdate(); LOG_DEBUG("playerbots", "GroupSetLeaderOperation: Changed leader to {}", newLeader->GetName()); return true; } From 28ec9b34b8a84fee71aac6f0cd4eae82dde2780f Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sat, 30 May 2026 06:52:54 -0700 Subject: [PATCH 48/63] add conf option for disabling send mail (#2411) ## Pull Request Description Fixes exploit in multiplayer servers where players can ask bots to mail them items. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. Conf. - Describe the **processing cost** when this logic executes across many bots. Cheap bool checkl ## How to Test the Changes Use send mail command to try to get a bot to send you mail. Should follow bool setting. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) CLaude Make do. simple enough one even AI can do it. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- conf/playerbots.conf.dist | 5 +++++ ..._00_ai_playerbot_send_mail_disabled_text.sql | 13 +++++++++++++ src/Ai/Base/Actions/SendMailAction.cpp | 17 ++++++++--------- src/PlayerbotAIConfig.cpp | 2 ++ src/PlayerbotAIConfig.h | 1 + 5 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 data/sql/playerbots/updates/2026_05_22_00_ai_playerbot_send_mail_disabled_text.sql diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 23885bee335..886cc8cd55f 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -750,6 +750,11 @@ AiPlayerbot.EnableRandomBotTrading = 1 # Configure message prefixes which will be excluded in analysis in trade action to open trade window AiPlayerbot.TradeActionExcludedPrefixes = "RPLL_H_,DBMv4,{звезда} Questie,{rt1} Questie" +# Allow bots to send mail. When disabled, bots will not mail items or money in +# response to chat commands ("send mail"). +# Default: 1 (enabled) +AiPlayerbot.BotSendMailEnabled = 1 + # # # diff --git a/data/sql/playerbots/updates/2026_05_22_00_ai_playerbot_send_mail_disabled_text.sql b/data/sql/playerbots/updates/2026_05_22_00_ai_playerbot_send_mail_disabled_text.sql new file mode 100644 index 00000000000..9ed05e8b52e --- /dev/null +++ b/data/sql/playerbots/updates/2026_05_22_00_ai_playerbot_send_mail_disabled_text.sql @@ -0,0 +1,13 @@ +DELETE FROM ai_playerbot_texts WHERE name IN ( + 'send_mail_disabled' +); + +DELETE FROM ai_playerbot_texts_chance WHERE name IN ( + 'send_mail_disabled' +); + +INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES +(1899, 'send_mail_disabled', 'I cannot send mail', 0, 0, '우편을 보낼 수 없습니다', 'Je ne peux pas envoyer de courrier', 'Ich kann keine Post senden', '我不能寄送邮件', '我不能寄送郵件', 'No puedo enviar correo', 'No puedo enviar correo', 'Я не могу отправить почту'); + +INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES +('send_mail_disabled', 100); diff --git a/src/Ai/Base/Actions/SendMailAction.cpp b/src/Ai/Base/Actions/SendMailAction.cpp index 549e6b71610..b0966856b7e 100644 --- a/src/Ai/Base/Actions/SendMailAction.cpp +++ b/src/Ai/Base/Actions/SendMailAction.cpp @@ -34,24 +34,23 @@ bool SendMailAction::Execute(Event event) Player* receiver = GetMaster(); Player* tellTo = receiver; - std::vector ss = split(text, ' '); - if (ss.size() > 1) - { - if (Player* p = ObjectAccessor::FindPlayer(ObjectGuid(uint64(ss[ss.size() - 1].c_str())))) - receiver = p; - } - if (!receiver) receiver = event.getOwner(); if (!receiver || receiver == bot) - { return false; - } if (!tellTo) tellTo = receiver; + if (!sPlayerbotAIConfig.botSendMailEnabled) + { + bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( + "send_mail_disabled", "I cannot send mail", {}), + LANG_UNIVERSAL, tellTo); + return false; + } + if (!mailboxFound && !randomBot) { bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index baaae8c3fa2..06e88a67bd2 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -559,6 +559,8 @@ bool PlayerbotAIConfig::Initialize() randomBotGuildSizeMax = sConfigMgr->GetOption("AiPlayerbot.RandomBotGuildSizeMax", 15); deleteRandomBotGuilds = sConfigMgr->GetOption("AiPlayerbot.DeleteRandomBotGuilds", false); + botSendMailEnabled = sConfigMgr->GetOption("AiPlayerbot.BotSendMailEnabled", true); + guildTaskEnabled = sConfigMgr->GetOption("AiPlayerbot.EnableGuildTasks", false); minGuildTaskChangeTime = sConfigMgr->GetOption("AiPlayerbot.MinGuildTaskChangeTime", 3 * 24 * 3600); maxGuildTaskChangeTime = sConfigMgr->GetOption("AiPlayerbot.MaxGuildTaskChangeTime", 4 * 24 * 3600); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 267042271f8..9dc1dcba198 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -298,6 +298,7 @@ class PlayerbotAIConfig float periodicOnlineOfflineRatio; bool gearscorecheck; bool randomBotPreQuests; + bool botSendMailEnabled; bool guildTaskEnabled; uint32 minGuildTaskChangeTime, maxGuildTaskChangeTime; From a3ca438bef24e3fae4089a679b47bcd0880b9f40 Mon Sep 17 00:00:00 2001 From: Lichborne Date: Sat, 30 May 2026 10:03:22 -0400 Subject: [PATCH 49/63] Make .playerbots bot commands case-insensitive (#2419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Pull Request Description \`.playerbots bot add jared\` fails with "Character 'jared' not found" even when \`Jared\` exists, because the lookup is case-sensitive. Same issue hits \`remove\`, \`init=<...>\`, \`refresh\`, \`addaccount\`, and any other subcommand that takes a character name. Fix at the parser layer in \`HandlePlayerbotCommand\`: every subcommand that takes a charname flows through one shared name-lookup step. Normalize the typed name to canonical form (first letter upper, rest lower) at that step so \`jared\`, \`JARED\`, \`JaReD\` all resolve to \`Jared\`. The \`addaccount\` path still tries the raw token as an account name first (account names are case-insensitive on the auth side), then falls back to a normalized character-name lookup. No subcommand's behavior changes — only the name lookup that precedes them. Subcommands with no charname (\`initself\`, \`list\`, \`reload\`, \`tweak\`, \`self\`, \`lookup\`, \`addclass\`) are unaffected. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. One \`normalizePlayerName(s)\` call on each comma-separated token before the existing \`GetCharacterGuidByName(s)\` lookup. Uses the helper this module already calls in four other places. - Describe the **processing cost** when this logic executes across many bots. Runs only when a player types a \`.playerbots bot ...\` command, on the typed tokens. Not in any bot-tick or per-bot path. Cost does not scale with bot count. ## How to Test the Changes \`\`\` .playerbots bot add Jared → adds Jared .playerbots bot add jared → adds Jared .playerbots bot add JARED → adds Jared .playerbots bot add JaReD → adds Jared .playerbots bot remove Jared → removes Jared .playerbots bot remove jared → removes Jared .playerbots bot init=auto jared → re-rolls Jared's gear .playerbots bot levelup jared → re-rolls Jared at his current level .playerbots bot refresh jared → refreshes Jared (HP/mana restored) .playerbots bot refresh=raid jared → unbinds Jared from saved raid IDs .playerbots bot add jAred,saLLy,BOB → adds all three .playerbots bot add nonexistent → still reports not found .playerbots bot initself → still works (untouched path) \`\`\` All cases verified in-game. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - [x] No, not at all - [ ] Minimal impact (**explain below**) - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - [x] No - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - [x] No - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - [ ] No - [x] Yes (**explain below**) Used Claude to help trace the call graph (confirming which subcommands are affected and which are untouched) and to draft the patch. Reviewed and tested in-game before pushing. ## Final Checklist - [x] Stability is not compromised. - [x] Performance impact is understood, tested, and acceptable. - [x] Added logic complexity is justified and explained. - [x] Any new bot dialogue lines are translated. - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers Diff is small (~25 lines in one file). One minor cosmetic side effect: "Character 'X' not found" errors now echo the normalized form (\`Bogus\` instead of \`bogus\`). --------- Co-authored-by: Lichborne-AC Co-authored-by: Claude Opus 4.7 (1M context) --- src/Bot/PlayerbotMgr.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index a6d4a3b7982..d38e7720efe 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -22,6 +22,7 @@ #include "GuildMgr.h" #include "ObjectAccessor.h" #include "ObjectGuid.h" +#include "ObjectMgr.h" #include "PlayerbotAIConfig.h" #include "PlayerbotRepository.h" #include "PlayerbotFactory.h" @@ -1240,7 +1241,7 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg std::vector chars = split(charnameStr, ','); for (std::vector::iterator i = chars.begin(); i != chars.end(); i++) { - std::string const s = *i; + std::string s = *i; if (!strcmp(cmd, "addaccount")) { @@ -1249,7 +1250,13 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg if (!accountId) { // If not found, try to get account ID from character name - ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s); + std::string charName = s; + if (!normalizePlayerName(charName)) + { + messages.push_back("Neither account nor character '" + s + "' found"); + continue; + } + ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(charName); if (!charGuid) { messages.push_back("Neither account nor character '" + s + "' found"); @@ -1277,6 +1284,11 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg else { // For regular add command, only add the specific character + if (!normalizePlayerName(s)) + { + messages.push_back("Character '" + *i + "' not found"); + continue; + } ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s); if (!charGuid) { From 1bbed177c8ba061f23847720a5837ec8c6115bbf Mon Sep 17 00:00:00 2001 From: dillyns <49765217+dillyns@users.noreply.github.com> Date: Sat, 30 May 2026 10:14:38 -0400 Subject: [PATCH 50/63] Add Nefarian Fear Ward action and trigger, along with Wild Magic trigger (#2412) ## Pull Request Description For Nefarian in BWL, this does the following: Mages - Ice block if the mage class call "Wild Magic" happens Priests - Buff the current target of Nefarian with Fear Ward ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes Go to Nefarian (id 11583) or his phase 1 form (id 10162) in BWL with a mage and a priest bot. During phase 2, the priest bot should cast fear ward on whoever nefarian is targeting. Wait for a mage class call. Once it happens the mage bot should cast ice block (if its available) to remove the Wild Magic debuff. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) AI assistance was used to explore how dungeon/raid strats are implemented and to explore examples. All code has been reviewed ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- .../BlackwingLair/Action/RaidBwlActions.cpp | 13 ++++++++++++ .../BlackwingLair/Action/RaidBwlActions.h | 7 +++++++ .../Raid/BlackwingLair/RaidBwlActionContext.h | 2 ++ .../BlackwingLair/RaidBwlTriggerContext.h | 4 ++++ .../Strategy/RaidBwlStrategy.cpp | 6 ++++++ .../BlackwingLair/Trigger/RaidBwlTriggers.cpp | 21 +++++++++++++++++++ .../BlackwingLair/Trigger/RaidBwlTriggers.h | 16 ++++++++++++++ .../Raid/BlackwingLair/Util/RaidBwlHelpers.h | 5 ++++- 8 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp b/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp index 2d018fc91e6..7a12c3b8794 100644 --- a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp +++ b/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp @@ -38,3 +38,16 @@ bool BwlUseHourglassSandAction::Execute(Event /*event*/) { return botAI->CastSpell(SPELL_HOURGLASS_SAND, bot); } + +bool BwlNefarianFearWardAction::Execute(Event /*event*/) +{ + Unit* nefarian = AI_VALUE2(Unit*, "find target", "nefarian"); + if (!nefarian) + return false; + + Unit* victim = nefarian->GetVictim(); + if (!victim) + return false; + + return botAI->CastSpell("fear ward", victim); +} diff --git a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h b/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h index 27037414aa1..28b2f667cf9 100644 --- a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h +++ b/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h @@ -29,4 +29,11 @@ class BwlUseHourglassSandAction : public Action bool Execute(Event event) override; }; +class BwlNefarianFearWardAction : public Action +{ +public: + BwlNefarianFearWardAction(PlayerbotAI* botAI) : Action(botAI, "bwl nefarian fear ward") {} + bool Execute(Event event) override; +}; + #endif diff --git a/src/Ai/Raid/BlackwingLair/RaidBwlActionContext.h b/src/Ai/Raid/BlackwingLair/RaidBwlActionContext.h index 7b73f410ffe..4e46b1ca5e2 100644 --- a/src/Ai/Raid/BlackwingLair/RaidBwlActionContext.h +++ b/src/Ai/Raid/BlackwingLair/RaidBwlActionContext.h @@ -13,12 +13,14 @@ class RaidBwlActionContext : public NamedObjectContext creators["bwl check onyxia scale cloak"] = &RaidBwlActionContext::bwl_check_onyxia_scale_cloak; creators["bwl turn off suppression device"] = &RaidBwlActionContext::bwl_turn_off_suppression_device; creators["bwl use hourglass sand"] = &RaidBwlActionContext::bwl_use_hourglass_sand; + creators["bwl nefarian fear ward"] = &RaidBwlActionContext::bwl_nefarian_fear_ward; } private: static Action* bwl_check_onyxia_scale_cloak(PlayerbotAI* botAI) { return new BwlOnyxiaScaleCloakAuraCheckAction(botAI); } static Action* bwl_turn_off_suppression_device(PlayerbotAI* botAI) { return new BwlTurnOffSuppressionDeviceAction(botAI); } static Action* bwl_use_hourglass_sand(PlayerbotAI* botAI) { return new BwlUseHourglassSandAction(botAI); } + static Action* bwl_nefarian_fear_ward(PlayerbotAI* botAI) { return new BwlNefarianFearWardAction(botAI); } }; #endif diff --git a/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h b/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h index de2ce005867..a2de3fd5ac1 100644 --- a/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h +++ b/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h @@ -11,11 +11,15 @@ class RaidBwlTriggerContext : public NamedObjectContext { creators["bwl suppression device"] = &RaidBwlTriggerContext::bwl_suppression_device; creators["bwl affliction bronze"] = &RaidBwlTriggerContext::bwl_affliction_bronze; + creators["bwl wild magic"] = &RaidBwlTriggerContext::bwl_wild_magic; + creators["bwl nefarian fear ward"] = &RaidBwlTriggerContext::bwl_nefarian_fear_ward; } private: static Trigger* bwl_suppression_device(PlayerbotAI* ai) { return new BwlSuppressionDeviceTrigger(ai); } static Trigger* bwl_affliction_bronze(PlayerbotAI* ai) { return new BwlAfflictionBronzeTrigger(ai); } + static Trigger* bwl_wild_magic(PlayerbotAI* ai) { return new BwlWildMagicTrigger(ai); } + static Trigger* bwl_nefarian_fear_ward(PlayerbotAI* ai) { return new BwlNefarianFearWardTrigger(ai); } }; #endif diff --git a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp b/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp index ec36b2cde64..76d0da5ef63 100644 --- a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp +++ b/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp @@ -10,4 +10,10 @@ void RaidBwlStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("bwl affliction bronze", { NextAction("bwl use hourglass sand", ACTION_RAID) })); + + triggers.push_back(new TriggerNode("bwl wild magic", { + NextAction("ice block", ACTION_RAID) })); + + triggers.push_back(new TriggerNode("bwl nefarian fear ward", { + NextAction("bwl nefarian fear ward", ACTION_RAID) })); } diff --git a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp b/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp index 4b4bee1adf2..500c81a92ad 100644 --- a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp +++ b/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp @@ -27,3 +27,24 @@ bool BwlAfflictionBronzeTrigger::IsActive() { return bot->HasAura(SPELL_BROOD_AFFLICTION_BRONZE); } + +bool BwlWildMagicTrigger::IsActive() +{ + return bot->getClass() == CLASS_MAGE && bot->HasAura(SPELL_WILD_MAGIC); +} + +bool BwlNefarianFearWardTrigger::IsActive() +{ + if (bot->getClass() != CLASS_PRIEST) + return false; + + Unit* nefarian = AI_VALUE2(Unit*, "find target", "nefarian"); + if (!nefarian || !nefarian->IsInCombat()) + return false; + + Unit* victim = nefarian->GetVictim(); + if (!victim) + return false; + + return !botAI->HasAura("fear ward", victim); +} diff --git a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h b/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h index 0aa29000720..8222cf592e1 100644 --- a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h +++ b/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h @@ -21,4 +21,20 @@ class BwlAfflictionBronzeTrigger : public Trigger bool IsActive() override; }; +// Nefarian + +class BwlWildMagicTrigger : public Trigger +{ +public: + BwlWildMagicTrigger(PlayerbotAI* botAI) : Trigger(botAI, "bwl wild magic") {} + bool IsActive() override; +}; + +class BwlNefarianFearWardTrigger : public Trigger +{ +public: + BwlNefarianFearWardTrigger(PlayerbotAI* botAI) : Trigger(botAI, "bwl nefarian fear ward") {} + bool IsActive() override; +}; + #endif diff --git a/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h b/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h index 3333d826d9d..c76f0e8927b 100644 --- a/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h +++ b/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h @@ -12,7 +12,10 @@ namespace BlackwingLairHelpers // Chromaggus SPELL_BROOD_AFFLICTION_BRONZE = 23170, - SPELL_HOURGLASS_SAND = 23645 + SPELL_HOURGLASS_SAND = 23645, + + // Nefarian + SPELL_WILD_MAGIC = 23410 }; enum BlackwingLairGameObjects From 32d10080a423ffdccbcc7652cb579602ca68a07f Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 30 May 2026 13:12:34 -0500 Subject: [PATCH 51/63] Improve bot trinket usage and fix related bugs (#2425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Pull Request Description This PR makes three changes to UseTrinketAction: 1. It adds health and mana gating (based on existing config thresholds) for bots to activate mana recovery, mana efficiency, and defensive trinkets. The thresholds are mediumMana (default 40%) for mana recovery trinkets, highMana (default 65%) for mana efficiency trinkets, and lowHealth (default 45%) for defensive trinkets. 2. It removes the old overinclusive procflag/specproc gate introduced by PR 1385, which prevents bots from using dozens of valid trinkets, including some extremely powerful ones (such as Skull of Gul’dan, the iconic TBC expansion BiS trinket for all casters), and replaces it with a narrower exclusion that still addresses the original issue. - Regarding PR 1385, focusing on liyunfan’s post specifically, I interpet the issue to be relating to trinkets where the item data is screwed up such that a passive effect was implemented as an on-use spell. I had AI do a scan, and it seems that issue impacts only 2 trinkets in the game, Oracle Talisman of Ablution and Frenzyheart Insignia of Fury, and specifically only the versions of those items that are not legitimately obtainable (unclear why they exist at all). I’ve excluded those trinkets from bots via the config now, and this PR maintains the guards against those trinkets regardless but does so in a narrower fashion. PR 1385's approach of using ProcFlags != 0 (i.e., excluding all on-use trinkets with non-zero ProcFlags) works only if proc metadata can be used to distinguish between active/passive trinkets, and that’s not even close to being the case. AI came up with 44 false positives, including many significant trinkets beyond Skull of Gul’dan such as the ZG Hakkar quest trinkets, Badge of the Swarmguard, Petrified Scarab, Scarab Brooch, Eye of the Dead, Essence of the Martyr, Abacus of Violent Odds, Ribbon of Sacrifice, and Pendant of the Violet Eye (and I’m not mentioning WotLK trinkets only because I don’t know anything about what is relevant for that expansion). 3. It fixes an issue where bots were not respecting trinket cooldowns in some cases. This resulted because trinkets with shared cooldown categories (i.e., those that are not stackable) would substitute the individual trinket cooldowns with shared category cooldowns, which let bots spam usages of trinkets, by tracking per-item and per-category trinket cooldowns locally. The root of this is based in AC; I don't know if it should be considered a bug or not, but regardless it doesn't impact players, presumably because cooldowns are enforced on the client side. - Here’s an illustration to explain the issue in practice. Skull of Gul’dan has a 240s cooldown and Shifting Naaru Sliver has a 180s cooldown, and they share a cooldown category so their usages cannot be stacked. The shared cooldown matches the length of the on-use effect (so 20s for Skull and 15s for Sliver). The below is what can happen without this PR (and is what I observed in testing). - t = 0s: Shifting Naaru Sliver used, writes its 90s personal cooldown and 15s shared category cooldown. - t = 15s: Skull of Gul’dan used, writes its 120s personal cooldown and 20s shared category cooldown. Skull’s shared category cooldown overwrites Sliver’s spell-cooldown entry, giving Sliver 20s left on its personal cooldown instead of 75s. - t = 35s: Sliver incorrectly appears ready and can be used again (55s sooner than should be possible). Then Sliver’s shared category cooldown in turn overwrites Skull’s longer personal cooldown. - t = 50s: Skull incorrectly appears ready and can be used again (85s sooner than should be possible). - Repeat. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. The logic runs through the existing trinket use code. Specifically: - Helpers are used to classify on-use trinket effects into mana restoration, mana efficiency, and defensive/tank categories using spell-effect checks - Existing configured mana and health thresholds are applied to those trinkets - The old procflag gate is replaced with a one-time cached set of mixed ON_USE/ON_EQUIP trinket spell ids - Two small cooldown maps per bot are tracked in UseTrinketAction: one for item cooldowns and one for shared category cooldowns ## How to Test the Changes I did all of these things, but verification is always good. Overall, I think it is worth running with this PR merged into test-staging if/when that happens and keeping an eye on overall performance. 1. Equip a bot with a trinket referenced above, such as Skull of Gul'dan, and confirm it is now used in combat. 2. Test mana-based classes with mana recovery and mana-efficiency trinkets and confirm they are used only when below the applicable configured mana threshold. An easy one to check is Glimmering Naaru Sliver because it is a channel. 3. Test defensive on-use trinkets and confirm they are used only when health is below the configured low-health threshold. Something like Shadowmoon Insignia, which increases maximum health, is pretty obvious. 4. Confirm that the error versions of Oracle/Frenzyheart don’t stack auras on bots (i.e., the bug addressed in PR 1385 has not returned). You can do this by having a bot kill mobs and check .listauras, though I checked through logging in the code because auras are noisy as hell. 5. Equip a bot with two trinkets that have shared cooldowns. I used Skull of Gul’dan and Shifting Naaru Sliver. Go fight a mostly tank-and-spank boss, such as Gruul. Use an add-on like Skada that tracks buffs. You should see that before this PR, bots will use the trinkets multiple times in one cooldown period, and after, they observe the actual cooldowns. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [x] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) Any additional impact is confined to UseTrinketAction. The exclusion of the busted trinkets uses a cache that is built once per server process followed by constant-time lookups. The per-bot trinket cooldown maps also use constant-time lookups and store only a few timestamp entries per bot. - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [x] Yes (**explain below**) Sort of--trinkets previously didn't have any consideration for effects with respect to usage so that is new. I think it is necessary though to have half-decent bot trinket usage, and there could be further refinement for how bots decide to use trinkets based on this structure. ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) GPT-5.4 was used for investigation and root-cause analysis and assistance with drafting. All resulting code and the PR rationale was validated through in-game testing. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers Bots also suck at using DPS trinkets properly, but I don't think there's a simple way to address that unlike with mana recovery or defensive trinkets. So that's to consider another day. --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> --- conf/playerbots.conf.dist | 5 +- src/Ai/Base/Actions/GenericSpellActions.cpp | 214 ++++++++++++++++++-- src/Ai/Base/Actions/GenericSpellActions.h | 4 + src/PlayerbotAIConfig.cpp | 2 +- 4 files changed, 208 insertions(+), 17 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 886cc8cd55f..c12049d6612 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -865,8 +865,9 @@ AiPlayerbot.LimitGearExpansion = 1 AiPlayerbot.RandomGearLoweringChance = 0 # Unobtainable or unusable items (comma-separated list of item IDs) -# Default: Chilton Wand (12468), Totem of the Earthen Ring (46978) -AiPlayerbot.UnobtainableItems = 12468,46978 +# Defaults: Chilton Wand (12468), Frenzyheart Insignia of Fury test/on-use row (44869), +# Oracle Talisman of Ablution test/on-use row (44870), Totem of the Earthen Ring (46978) +AiPlayerbot.UnobtainableItems = 12468,44869,44870,46978 # Randombots check player's gearscore level and deny the group invitation if it's too low # Default: 0 (disabled) diff --git a/src/Ai/Base/Actions/GenericSpellActions.cpp b/src/Ai/Base/Actions/GenericSpellActions.cpp index d4b54f16fd3..657fda7cd48 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.cpp +++ b/src/Ai/Base/Actions/GenericSpellActions.cpp @@ -6,6 +6,7 @@ #include "GenericSpellActions.h" #include +#include #include "Event.h" #include "ItemTemplate.h" @@ -23,6 +24,116 @@ using ai::buff::MakeAuraQualifierForBuff; using ai::spell::HasSpellOrCategoryCooldown; +namespace +{ + std::unordered_set const& GetMixedTriggerTrinketSpellIds() + { + static std::unordered_set const mixedTriggerSpellIds = []() + { + std::unordered_set onUseSpellIds; + std::unordered_set onEquipSpellIds; + std::unordered_set mixedSpellIds; + + auto const* itemTemplates = sObjectMgr->GetItemTemplateStore(); + if (!itemTemplates) + return mixedSpellIds; + + auto const markSpellId = [&](int32 spellId, uint8 spellTrigger) + { + if (spellId <= 0) + return; + + if (spellTrigger == ITEM_SPELLTRIGGER_ON_USE) + { + if (onEquipSpellIds.find(spellId) != onEquipSpellIds.end()) + mixedSpellIds.insert(spellId); + + onUseSpellIds.insert(spellId); + } + else if (spellTrigger == ITEM_SPELLTRIGGER_ON_EQUIP) + { + if (onUseSpellIds.find(spellId) != onUseSpellIds.end()) + mixedSpellIds.insert(spellId); + + onEquipSpellIds.insert(spellId); + } + }; + + for (auto const& itr : *itemTemplates) + { + ItemTemplate const& proto = itr.second; + if (proto.InventoryType != INVTYPE_TRINKET) + continue; + + for (uint8 spellIndex = 0; spellIndex < MAX_ITEM_PROTO_SPELLS; ++spellIndex) + { + auto const& spellData = proto.Spells[spellIndex]; + markSpellId(spellData.SpellId, spellData.SpellTrigger); + } + } + + return mixedSpellIds; + }(); + + return mixedTriggerSpellIds; + } + + bool IsManaRestoreEffect(SpellEffectInfo const& effectInfo) + { + return (effectInfo.Effect == SPELL_EFFECT_ENERGIZE && + effectInfo.MiscValue == POWER_MANA) || + (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA && + effectInfo.ApplyAuraName == SPELL_AURA_PERIODIC_ENERGIZE && + effectInfo.MiscValue == POWER_MANA); + } + + bool IsManaEfficiencyEffect(SpellEffectInfo const& effectInfo) + { + return effectInfo.Effect == SPELL_EFFECT_APPLY_AURA && + (((effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN || + effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN_PERCENT) && + effectInfo.MiscValue == POWER_MANA) || + effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_COST_SCHOOL || + effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT || + effectInfo.ApplyAuraName == SPELL_AURA_MOD_MANA_REGEN_INTERRUPT); + } + + bool IsDefensiveTankEffect(SpellEffectInfo const& effectInfo) + { + if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) + return false; + + uint32 const tankRatingsMask = + (1u << CR_DEFENSE_SKILL) | + (1u << CR_DODGE) | + (1u << CR_PARRY) | + (1u << CR_BLOCK) | + (1u << CR_HIT_TAKEN_MELEE) | + (1u << CR_HIT_TAKEN_RANGED) | + (1u << CR_HIT_TAKEN_SPELL) | + (1u << CR_CRIT_TAKEN_MELEE) | + (1u << CR_CRIT_TAKEN_RANGED) | + (1u << CR_CRIT_TAKEN_SPELL); + + switch (effectInfo.ApplyAuraName) + { + case SPELL_AURA_MOD_RESISTANCE: + return (effectInfo.MiscValue & SPELL_SCHOOL_MASK_NORMAL) != 0; + case SPELL_AURA_MOD_RATING: + return (effectInfo.MiscValue & tankRatingsMask) != 0; + case SPELL_AURA_MOD_INCREASE_HEALTH: + case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT: + case SPELL_AURA_MOD_PARRY_PERCENT: + case SPELL_AURA_MOD_DODGE_PERCENT: + case SPELL_AURA_MOD_BLOCK_PERCENT: + case SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN: + return true; + default: + return false; + } + } +} + CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell) : Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell) {} @@ -429,52 +540,109 @@ bool UseTrinketAction::UseTrinket(Item* item) uint8 bagIndex = item->GetBagSlot(); uint8 slot = item->GetSlot(); - // uint8 spell_index = 0; //not used, line marked for removal. uint8 cast_count = 1; ObjectGuid item_guid = item->GetGUID(); uint32 glyphIndex = 0; uint8 castFlags = 0; uint32 targetFlag = TARGET_FLAG_NONE; uint32 spellId = 0; + int32 itemSpellCooldown = 0; + uint32 itemSpellCategory = 0; + int32 itemSpellCategoryCooldown = 0; + for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { if (item->GetTemplate()->Spells[i].SpellId > 0 && item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) { spellId = item->GetTemplate()->Spells[i].SpellId; - const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); + itemSpellCooldown = item->GetTemplate()->Spells[i].SpellCooldown; + itemSpellCategory = item->GetTemplate()->Spells[i].SpellCategory; + itemSpellCategoryCooldown = item->GetTemplate()->Spells[i].SpellCategoryCooldown; + uint64 const itemCooldownKey = (static_cast(item->GetEntry()) << 32) | spellId; + uint32 const now = getMSTime(); + + if (itemSpellCooldown > 0) + { + auto const itemCooldownItr = trinketItemCooldownExpiries.find(itemCooldownKey); + if (itemCooldownItr != trinketItemCooldownExpiries.end()) + { + if (itemCooldownItr->second > now) + return false; + + trinketItemCooldownExpiries.erase(itemCooldownItr); + } + } + + if (itemSpellCategory && itemSpellCategoryCooldown > 0) + { + auto const categoryCooldownItr = trinketCategoryCooldownExpiries.find(itemSpellCategory); + if (categoryCooldownItr != trinketCategoryCooldownExpiries.end()) + { + if (categoryCooldownItr->second > now) + return false; + trinketCategoryCooldownExpiries.erase(categoryCooldownItr); + } + } + + const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo || !spellInfo->IsPositive()) return false; bool applyAura = false; + bool restoresMana = false; + bool improvesManaEfficiency = false; + bool defensiveTankEffect = false; for (int i = 0; i < MAX_SPELL_EFFECTS; i++) { const SpellEffectInfo& effectInfo = spellInfo->Effects[i]; if (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA) - { applyAura = true; - break; - } + + restoresMana = restoresMana || IsManaRestoreEffect(effectInfo); + improvesManaEfficiency = improvesManaEfficiency || IsManaEfficiencyEffect(effectInfo); + defensiveTankEffect = defensiveTankEffect || IsDefensiveTankEffect(effectInfo); } - if (!applyAura) + if (!applyAura && !restoresMana) return false; - uint32 spellProcFlag = spellInfo->ProcFlags; + if (restoresMana || improvesManaEfficiency) + { + if (!AI_VALUE2(bool, "has mana", "self target")) + return false; + + uint8 const manaPct = AI_VALUE2(uint8, "mana", "self target"); + if ((restoresMana && manaPct >= sPlayerbotAIConfig.mediumMana) || + manaPct >= sPlayerbotAIConfig.highMana) + { + return false; + } + } + + if (defensiveTankEffect) + { + uint8 const healthPct = AI_VALUE2(uint8, "health", "self target"); + if (healthPct > sPlayerbotAIConfig.lowHealth) + return false; + } - // Handle items with procflag "if you kill a target that grants honor or experience" - // Bots will "learn" the trinket proc, so CanCastSpell() will be true - // e.g. on Item https://www.wowhead.com/wotlk/item=44074/oracle-talisman-of-ablution leading to - // constant casting of the proc spell onto themselfes https://www.wowhead.com/wotlk/spell=59787/oracle-ablutions - // This will lead to multiple hundreds of entries in m_appliedAuras -> Once killing an enemy -> Big diff time spikes - if (spellProcFlag != 0) return false; + auto const& mixedTriggerTrinketSpellIds = GetMixedTriggerTrinketSpellIds(); + // Exclude trinkets that expose the same spell as both ON_EQUIP and ON_USE across + // item templates. Those are equip/proc effects leaking into the active-use path, + // as seen with the error versions of Oracle Talisman of Ablution (44870) and + // Frenzyheart Insignia of Fury (44869). + if (mixedTriggerTrinketSpellIds.find(spellId) != mixedTriggerTrinketSpellIds.end()) + return false; - if (!botAI->CanCastSpell(spellId, bot, false)) + if (!botAI->CanCastSpell(spellId, bot, false, nullptr, item)) return false; + break; } } + if (!spellId) return false; @@ -483,7 +651,25 @@ bool UseTrinketAction::UseTrinket(Item* item) targetFlag = TARGET_FLAG_NONE; packet << targetFlag << bot->GetPackGUID(); + bot->GetSession()->HandleUseItemOpcode(packet); + + uint32 const now = getMSTime(); + uint32 const cooldownDelay = bot->GetSpellCooldownDelay(spellId); + if (cooldownDelay > 0) + { + if (itemSpellCooldown > 0) + { + uint64 const itemCooldownKey = (static_cast(item->GetEntry()) << 32) | spellId; + trinketItemCooldownExpiries[itemCooldownKey] = now + static_cast(itemSpellCooldown); + } + + if (itemSpellCategory && itemSpellCategoryCooldown > 0) + { + trinketCategoryCooldownExpiries[itemSpellCategory] = now + static_cast(itemSpellCategoryCooldown); + } + } + return true; } diff --git a/src/Ai/Base/Actions/GenericSpellActions.h b/src/Ai/Base/Actions/GenericSpellActions.h index 5c52b7fd939..c17d969077c 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.h +++ b/src/Ai/Base/Actions/GenericSpellActions.h @@ -334,6 +334,10 @@ class UseTrinketAction : public Action protected: bool UseTrinket(Item* trinket); + +private: + std::unordered_map trinketItemCooldownExpiries; + std::unordered_map trinketCategoryCooldownExpiries; }; class CastSpellOnEnemyHealerAction : public CastSpellAction diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 06e88a67bd2..c01769eb800 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -215,7 +215,7 @@ bool PlayerbotAIConfig::Initialize() attunementQuests); LoadSet>( - sConfigMgr->GetOption("AiPlayerbot.UnobtainableItems", "12468,46978"), + sConfigMgr->GetOption("AiPlayerbot.UnobtainableItems", "12468,44869,44870,46978"), unobtainableItems); botAutologin = sConfigMgr->GetOption("AiPlayerbot.BotAutologin", false); From ff001afd46eaf2e67045ff14b0603194b17eb0c3 Mon Sep 17 00:00:00 2001 From: Mat Date: Sat, 30 May 2026 20:12:54 +0200 Subject: [PATCH 52/63] Reset instance ID via existing cmd refresh=raid for alt bots (#2422) ## Pull Request Description Alt bots can now use refresh=raid cmd if cfg is set to 1. `AiPlayerbot.ResetInstanceIdForAltBots = 1` ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes Add alt bot, use .playerbots bot refresh=raid and it should say ok after reseting instance. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Alt bots will now reset instance ID if cfg is set to 1. - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [x] Yes (**explain below**) It will check if cfg is set to 1 to enable alt bots to reset instance id. ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> --- conf/playerbots.conf.dist | 7 +++++++ src/Bot/PlayerbotMgr.cpp | 5 ++++- src/PlayerbotAIConfig.cpp | 1 + src/PlayerbotAIConfig.h | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index c12049d6612..324cdc45766 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -187,6 +187,13 @@ AiPlayerbot.SelfBotLevel = 1 # Default: 0 (non-GM player can use any intialization commands) AiPlayerbot.AutoInitOnly = 0 +# Allow .bot refresh=raid to unbind instances on player-created alt +# bots (non-addclass bots). When 0, refresh=raid is denied on alts +# with "non-addclass bot" error. UnbindInstance is DB-light, no lag +# risk from enabling this. +# Default: 0 +AiPlayerbot.ResetInstanceIdForAltBots = 0 + # The upper limit ratio of bot equipment level for init=auto # Default: 1.0 (same with the player) AiPlayerbot.AutoInitEquipLevelLimitRatio = 1.0 diff --git a/src/Bot/PlayerbotMgr.cpp b/src/Bot/PlayerbotMgr.cpp index d38e7720efe..9d9f5688f6b 100644 --- a/src/Bot/PlayerbotMgr.cpp +++ b/src/Bot/PlayerbotMgr.cpp @@ -732,7 +732,10 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje bool addClassBot = sRandomPlayerbotMgr.IsAddclassBot(guid.GetCounter()); if (!addClassBot) - return "ERROR: You can not use this command on non-addclass bot."; + { + if (!(cmd == "refresh=raid" && sPlayerbotAIConfig.resetInstanceIdForAltBots)) + return "ERROR: You can only use this command on addclass bots."; + } if (!admin) { diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index c01769eb800..fb036e3592f 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -593,6 +593,7 @@ bool PlayerbotAIConfig::Initialize() reviveBotWhenSummoned = sConfigMgr->GetOption("AiPlayerbot.ReviveBotWhenSummoned", 1); botRepairWhenSummon = sConfigMgr->GetOption("AiPlayerbot.BotRepairWhenSummon", true); autoInitOnly = sConfigMgr->GetOption("AiPlayerbot.AutoInitOnly", false); + resetInstanceIdForAltBots = sConfigMgr->GetOption("AiPlayerbot.ResetInstanceIdForAltBots", false); autoInitEquipLevelLimitRatio = sConfigMgr->GetOption("AiPlayerbot.AutoInitEquipLevelLimitRatio", 1.0); maxAddedBots = sConfigMgr->GetOption("AiPlayerbot.MaxAddedBots", 40); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 9dc1dcba198..1936d1ca7d1 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -406,6 +406,7 @@ class PlayerbotAIConfig int reviveBotWhenSummoned; bool botRepairWhenSummon; bool autoInitOnly; + bool resetInstanceIdForAltBots; float autoInitEquipLevelLimitRatio; int32 maxAddedBots; int32 addClassCommand; From 92fa97c3aa3d9391b895f525956b0f07678adcaa Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 30 May 2026 13:13:18 -0500 Subject: [PATCH 53/63] Rewrite Equipment-Randomization-Related Configs (#2409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Pull Request Description The main focus on this PR is to clarify and eliminate overlap between three very confusingly described yet important config options: AutoUpgradeEquip, EquipmentPersistence, and IncrementalGearInit. For context, after initialization, randombots generally get randomized each time after logging in and periodically between MinRandomBotRandomizeTime (configurable, default 2 hours) to MaxRandomBotRandomizeTime (configurable, default 14 days). This randomization happens only when the bot is idle. Now let’s look at what those three config options currently say. - AutoUpgradeEquip: Randombots automatically upgrade their equipment on levelup. - EquipmentPersistence: Enable/Disable bot equipment persistence (stop random initialization) after certain level (EquipmentPersistenceLevel). - IncrementalGearInit: If disabled, random bots can only upgrade equipment through looting and quest None of those descriptions are accurate. - AutoUpgradeEquip determines if randombots, upon leveling up, refresh ammo, reagents, food, consumables, potions, and, ONLY IF IncrementalGearInit is also enabled and EquipmentPersistence is disabled, upgrade equipment (yes, three config options required for this one thing). - EquipmentPersistence affects both equipment and talents. - Disabling IncrementalGearInit does not prevent randombots from changing their equipment through the login/periodic randomization process unless EquipmentPersistence is enabled. These config options shouldn’t overlap with or be dependent on each other, and their names and descriptions should reflect what they actually do. Thus, this PR does the following: - AutoUpgradeEquip solely controls whether or not randombots automatically upgrade their gear upon level up. No other config option is involved for this purpose, and AutoUpgradeEquip no longer impacts inventory items. This does mean that it is no longer possible to stop randombots from being given ammo, potions, etc. when they level up. I tend to think that randombots as they currently are cannot fully function otherwise so I have no issue with the loss of this ability, but if there is disagreement, then we need to introduce a new config option. AutoUpgradeEquip is also now set to true in PlayerbotAIConfig to reflect that the default is true in the .dist. - I originally wanted to combine EquipmentPersistence and EquipmentPersistenceLevel into a single config option that also included talents in the name, but EquipmentPersistence is used in the level brackets mod so I don’t want to make a breaking change there. I settled for making EquipmentPersistence enabled by default and also reducing EquipmentPersistenceLevel to 1 by default, in effect entirely disabling periodic/login randomization of randombot talents and gear by default. I only see people complain about these features so I think unless there is some compelling performance or structural reason to the contrary, the default should be that randombots do not continuously randomize their talents and equipment. - IncrementalGearInit is eliminated. There are two real functional impacts: (1) as noted above, randombots cannot be blocked from receiving standard inventory items upon level-up (though the way it is currently being gated does not make sense anyway), and (2) you can no longer have equipment persistence for equipment only but not talents (I cannot imagine anybody would ever want to do that, and I don't think it was even intended given the strange interaction between config options that was needed to even accomplish that before). - For all these settings, the config descriptions are updated to try to be clear about what the player is actually configuring. Beyond that, I made some clean-up changes to the config to fix some typos and try to shorten it in places, and I also deleted a few config options that do not appear in operative code anymore (and any corresponding references in PlayerbotAIConfig). I also deleted some comments and made some minor style changes in AutoMaintenanceOnLevelupAction.cpp. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. The main substance of the PR consists of changes to checks in PlayerbotFactory and AutoMaintenanceOnLevelupAction. ## How to Test the Changes - To test AutoUpgradeEquip, use the gm command .level 1 while targeting an rndbot. - To test EquipmentPersistence, the only way I could find to force an incremental randomization was to use .playerbots rndbot level BotName. This will do an incremental randomization and a level up. However, this level does not go through the AutoMaintenanceOnLevelupAction path so just disregard the +1 level aspect and consider it a pure test of EquipmentPersistence. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) I had GPT-5.4 review my changes and come up with the testing method. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> --- conf/playerbots.conf.dist | 313 ++++++------------ .../AutoMaintenanceOnLevelupAction.cpp | 41 +-- src/Bot/Factory/PlayerbotFactory.cpp | 35 +- src/PlayerbotAIConfig.cpp | 13 +- src/PlayerbotAIConfig.h | 14 +- 5 files changed, 136 insertions(+), 280 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 324cdc45766..a2d2d509c5c 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -37,7 +37,7 @@ # RPG STRATEGY # TELEPORTS # BATTLEGROUND & ARENA & PVP -# RANDOM BOT TIMING AND BEHAVIOR +# RANDOMBOT TIMING AND BEHAVIOR # PREMADE SPECS # INFORMATION # WARRIOR @@ -61,15 +61,13 @@ # MAGE # WARLOCK # DRUID +# RAIDS # PLAYERBOTS SYSTEM SETTINGS # DATABASE & CONNECTIONS # DEBUG # CHAT SETTINGS # LOGS -# DEPRECIATED (TEMPORARY) -# -# -# +# DEPRECATED (TEMPORARY) #################################################################################################### ################################### @@ -122,7 +120,6 @@ AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay = 300 #################################################################################################### # GENERAL # -# # The maximum number of bots that a player can control simultaneously AiPlayerbot.MaxAddedBots = 40 @@ -138,7 +135,7 @@ AiPlayerbot.AddClassAccountPoolSize = 50 # Default: 1 (accept based on level) AiPlayerbot.GroupInvitationPermission = 1 -# Keep alt bots in the party even when the master leaves +# Keep altbots in the party even when the master leaves # 0 = disabled (default behavior) # 1 = enabled (prevents bots from automatically leaving the group) AiPlayerbot.KeepAltsInGroup = 0 @@ -198,14 +195,11 @@ AiPlayerbot.ResetInstanceIdForAltBots = 0 # Default: 1.0 (same with the player) AiPlayerbot.AutoInitEquipLevelLimitRatio = 1.0 -# -# AllowLearnTrainerSpells -# Description: Allow the bot to learn trainers' spells as long as it has the money. -# Default: 1 - (Enabled) -# 0 - (Disabled) +# AllowLearnTrainerSpells +# Description: Allow the bot to learn trainers' spells as long as it has the money. +# Default: 1 - (enabled) AiPlayerbot.AllowLearnTrainerSpells = 1 -# # # #################################################################################################### @@ -213,7 +207,6 @@ AiPlayerbot.AllowLearnTrainerSpells = 1 #################################################################################################### # SUMMON OPTIONS # -# # Enable/Disable summoning bots when the master is in combat # Default: 1 (enabled) @@ -235,7 +228,6 @@ AiPlayerbot.ReviveBotWhenSummoned = 1 # Default: 1 (enabled) AiPlayerbot.BotRepairWhenSummon = 1 -# # # #################################################################################################### @@ -243,7 +235,6 @@ AiPlayerbot.BotRepairWhenSummon = 1 #################################################################################################### # MOUNT # -# # Defines at what level a bot will naturally use its 60% ground mount # Note: was level 20 during WotLK, 30 during TBC, 40 during Vanilla @@ -265,7 +256,6 @@ AiPlayerbot.UseFlyMountAtMinLevel = 60 # Default: 70 AiPlayerbot.UseFastFlyMountAtMinLevel = 70 -# # # #################################################################################################### @@ -273,7 +263,6 @@ AiPlayerbot.UseFastFlyMountAtMinLevel = 70 #################################################################################################### # GEAR # -# # Show helmet and cloak on randombots (reset required) AiPlayerbot.RandomBotShowHelmet = 1 @@ -290,7 +279,6 @@ AiPlayerbot.EquipUpgradeThreshold = 1.1 # Two rounds of equipment initialization to create more suitable gear AiPlayerbot.TwoRoundsGearInit = 0 -# # # #################################################################################################### @@ -298,7 +286,6 @@ AiPlayerbot.TwoRoundsGearInit = 0 #################################################################################################### # LOOTING # -# # Bots keep looting when loot system is set to free for all # Default: 0 (disabled) @@ -323,7 +310,16 @@ AiPlayerbot.LootRollRecipe = 0 # Default: 0 (disabled) AiPlayerbot.LootRollDisenchant = 0 -# +# A list of GameObject GUIDs that bots will not interact with. +# List of default GUIDs: +# QuestItems: +# Blood of Heroes = 176213, Defias Gunpowder = 17155, Waterlogged Envelope = 2656, Baelog's Chest = 123329, Half-Buried Bottle = 2560 +# 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, 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 +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,2560 + # # #################################################################################################### @@ -331,7 +327,6 @@ AiPlayerbot.LootRollDisenchant = 0 #################################################################################################### # TIMERS # -# # Max AI iterations per tick AiPlayerbot.IterationsPerTick = 10 @@ -379,7 +374,6 @@ AiPlayerbot.SitDelay = 20000 AiPlayerbot.ReturnDelay = 2000 AiPlayerbot.LootDelay = 1000 -# # # #################################################################################################### @@ -387,7 +381,6 @@ AiPlayerbot.LootDelay = 1000 #################################################################################################### # DISTANCES # -# # Distances are in yards AiPlayerbot.FarDistance = 20.0 @@ -408,7 +401,6 @@ AiPlayerbot.RpgDistance = 200 AiPlayerbot.GrindDistance = 75.0 AiPlayerbot.ReactDistance = 150.0 -# # # #################################################################################################### @@ -416,7 +408,6 @@ AiPlayerbot.ReactDistance = 150.0 #################################################################################################### # THRESHOLDS # -# # Health/Mana levels AiPlayerbot.CriticalHealth = 25 @@ -427,7 +418,6 @@ AiPlayerbot.LowMana = 15 AiPlayerbot.MediumMana = 40 AiPlayerbot.HighMana = 65 -# # # #################################################################################################### @@ -435,7 +425,6 @@ AiPlayerbot.HighMana = 65 #################################################################################################### # QUESTS # -# # Bots pick their quest rewards # yes = picks the most useful item, no = list all rewards, ask = pick useful item and lists if multiple @@ -453,7 +442,6 @@ AiPlayerbot.SyncQuestForPlayer = 0 # Default: 1 (enabled) AiPlayerbot.DropObsoleteQuests = 1 -# # # #################################################################################################### @@ -461,7 +449,6 @@ AiPlayerbot.DropObsoleteQuests = 1 #################################################################################################### # COMBAT # -# # Auto add dungeon/raid strategies when entering the instance if implemented AiPlayerbot.ApplyInstanceStrategies = 1 @@ -505,6 +492,21 @@ AiPlayerbot.AutoPartyBuffs = 2 AiPlayerbot.FleeingEnabled = 1 # +# +#################################################################################################### + +#################################################################################################### +# GREATER BUFFS STRATEGIES +# + +# Min group size to use Greater buffs (Paladin, Mage, Druid) +# Default: 3 +AiPlayerbot.MinBotsForGreaterBuff = 3 + +# Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff +# Default: 30 +AiPlayerbot.RPWarningCooldown = 30 + # # #################################################################################################### @@ -512,7 +514,6 @@ AiPlayerbot.FleeingEnabled = 1 #################################################################################################### # CHEATS # -# # Enable/Disable maintenance command # Learn all available spells and skills, assign talent points, refresh consumables, repair, enchant equipment, socket gems, etc. @@ -520,8 +521,8 @@ AiPlayerbot.FleeingEnabled = 1 # Default: 1 (enabled) AiPlayerbot.MaintenanceCommand = 1 -# Enable/Disable specific maintenance command functionality for alt bots -# Disable to prevent players from giving free bags, spells, skill levels, etc. to their alt bots +# Enable/Disable specific maintenance command functionality for altbots +# Disable to prevent players from giving free bags, spells, skill levels, etc. to their altbots # Default: 1 (enabled) AiPlayerbot.AltMaintenanceAmmo = 1 AiPlayerbot.AltMaintenanceFood = 1 @@ -601,44 +602,35 @@ AiPlayerbot.BotCheats = "food,taxi,raid" # While mod-playerbots does not restore removed attunement requirements, other mods, such as mod-individual-progression, may do so. # This is meant to exclude bots from such requirements. # -# Default: +# Defaults: # Caverns of Time - Part 1 # - 10279, To The Master's Lair # - 10277, The Caverns of Time -# # Caverns of Time - Part 2 (Escape from Durnholde Keep) # - 10282, Old Hillsbrad # - 10283, Taretha's Diversion # - 10284, Escape from Durnholde # - 10285, Return to Andormu -# # Caverns of Time - Part 2 (The Black Morass) # - 10296, The Black Morass # - 10297, The Opening of the Dark Portal # - 10298, Hero of the Brood -# -# Magister's Terrace Attunement +# Magister's Terrace # - 11481, Crisis at the Sunwell # - 11482, Duty Calls # - 11488, Magisters' Terrace # - 11490, The Scryer's Scryer # - 11492, Hard to Kill -# # Serpentshrine Cavern # - 10901, The Cudgel of Kar'desh -# -# The Eye +# Tempest Keep: The Eye # - 10888, Trial of the Naaru: Magtheridon -# -# Mount Hyjal +# Battle for Mount Hyjal (Hyjal Summit) # - 10445, The Vials of Eternity -# # Black Temple # - 10985, A Distraction for Akama -# AiPlayerbot.AttunementQuests = 10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901,10888,10445,10985 -# # # #################################################################################################### @@ -646,7 +638,6 @@ AiPlayerbot.AttunementQuests = 10279,10277,10282,10283,10284,10285,10296,10297,1 #################################################################################################### # FLIGHTPATH # -# # Min random delay before the 1st follower bot clicks the flight-master (ms) AiPlayerbot.BotTaxiDelayMinMs = 350 @@ -660,7 +651,6 @@ AiPlayerbot.BotTaxiGapMs = 200 # Extra small randomness added to each gap so launches don’t look robotic (ms) AiPlayerbot.BotTaxiGapJitterMs = 100 -# # # #################################################################################################### @@ -689,21 +679,19 @@ AiPlayerbot.FishingDistance = 40.0 # Distance from water (in yards) beyond which a bot will remove the 'master fishing' strategy AiPlayerbot.EndFishingWithMaster = 30.0 -# # # #################################################################################################### ####################################### # # -# RANDOMBOT-SPECIFIC SETTINGS # +# RANDOMBOT-SPECIFIC SETTINGS # # # ####################################### #################################################################################################### # GENERAL # -# # Enable/Disable randomly generated password for randombot accounts AiPlayerbot.RandomBotRandomPassword = 0 @@ -745,8 +733,8 @@ AiPlayerbot.RandomBotHordeRatio = 50 AiPlayerbot.DisableDeathKnightLogin = 0 # Enable simulated expansion limitation for talents and glyphs -# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61 -# and 7 rows plus the middle talent of the 8th row for bots from level 61 until level 71 +# If enabled, limits talent trees to 6 rows plus the middle talent of the 7th row for bots until level 61 +# and 8 rows plus the middle talent of the 9th row for bots from level 61 until level 71 # Default: 0 (disabled) AiPlayerbot.LimitTalentsExpansion = 0 @@ -762,7 +750,6 @@ AiPlayerbot.TradeActionExcludedPrefixes = "RPLL_H_,DBMv4,{звезда} Questie, # Default: 1 (enabled) AiPlayerbot.BotSendMailEnabled = 1 -# # # #################################################################################################### @@ -770,13 +757,12 @@ AiPlayerbot.BotSendMailEnabled = 1 #################################################################################################### # LEVELS # -# # Disable randombots being generated with a random level # If disabled, every randombot starts on a specified level (but can still level up by killing mobs and questing) AiPlayerbot.DisableRandomLevels = 0 -# Set randombots' starting level here if "AiPlayerbot.DisableRandomLevels" enabled +# Set randombots' starting level here if "AiPlayerbot.DisableRandomLevels" is enabled AiPlayerbot.RandombotStartingLevel = 1 # Chance randombot has min level on first randomize @@ -795,11 +781,10 @@ AiPlayerbot.RandomBotFixedLevel = 0 # Default: 0 (disabled) AiPlayerbot.DowngradeMaxLevelBot = 0 -# Set XP rate for random bots (Default: 1.0) +# Set XP rate for randombots (Default: 1.0) # Server XP Rate * AiPlayerbot.RandomBotXPRate AiPlayerbot.RandomBotXPRate = 1.0 -# # # #################################################################################################### @@ -807,7 +792,20 @@ AiPlayerbot.RandomBotXPRate = 1.0 #################################################################################################### # GEAR # -# + +# Randombots automatically receive upgraded equipment on levelup +# Default: 1 (enabled) +AiPlayerbot.AutoUpgradeEquip = 1 + +# Enable/disable which (if any) randombots preserve their current equipment and spec during login +# and periodic randomization (i.e., persistence). +# Default: 1 (enabled) +AiPlayerbot.EquipAndSpecPersistence = 1 + +# If EquipAndSpecPersistence is enabled, equipment and talent persistence will affect randombots of this +# level or higher. +# Default: 1 (enable persistence for randombots of at least level 1 (i.e., all levels)) +AiPlayerbot.EquipAndSpecPersistenceLevel = 1 # Equipment quality limitation for randombots (1 = normal, 2 = uncommon, 3 = rare, 4 = epic, 5 = legendary) # This also sets the maximum quality that can be generated by autogear for randombots and altbots @@ -833,25 +831,20 @@ AiPlayerbot.RandomGearScoreLimit = 0 # armor of the preferred type will score 3x higher and be equipped instead. # # ARMOR TYPE PREFERENCES: -# Plate: Warriors, Paladins, Death Knights -# Mail: Hunters, Shamans +# Plate: Warriors, Paladins, Death Knights +# Mail: Hunters, Shamans # Leather: Rogues, Druids -# Cloth: Priests, Mages, Warlocks +# Cloth: Priests, Mages, Warlocks # # Default: 0 (disabled) AiPlayerbot.PreferClassArmorType = 0 - # When enabled, bots prefer spec-appropriate weapons based on speed and weapon type during autogear. # Examples: Arms Warriors favor slow 2H axes/polearms (Axe Specialization), Combat Rogues # favor a slow MH with a fast OH, and Enhancement Shamans favor synchronized slow 1H weapons. # Default: 0 (disabled) AiPlayerbot.PreferredSpecWeapons = 0 -# If disabled, random bots can only upgrade equipment through looting and quests -# Default: 1 (enabled) -AiPlayerbot.IncrementalGearInit = 1 - # Set minimum level of bots that will enchant and socket gems into their equipment with maintenance # If greater than RandomBotMaxlevel, bots will not automatically enchant equipment or socket gems # Default: 60 @@ -880,17 +873,6 @@ AiPlayerbot.UnobtainableItems = 12468,44869,44870,46978 # Default: 0 (disabled) AiPlayerbot.GearScoreCheck = 0 -# Enable/Disable bot equipment persistence (stop random initialization) after certain level (EquipmentPersistenceLevel) -# Default: 0 (disabled) -AiPlayerbot.EquipmentPersistence = 0 - -# Default level if enabled: 80 -AiPlayerbot.EquipmentPersistenceLevel = 80 - -# Randombots automatically upgrade their equipment on levelup -# Default: 1 (enabled) -AiPlayerbot.AutoUpgradeEquip = 1 - # Only set wolf pets for hunters for optimal raid performance (0 = disabled, 1 = enabled only for max-level bots, 2 = enabled) # Default: 0 (disabled) AiPlayerbot.HunterWolfPet = 0 @@ -909,13 +891,14 @@ AiPlayerbot.PetChatCommandDebug = 0 # See the creature_family database table for all pet families by ID (note: ID for spiders is 3) AiPlayerbot.ExcludedHunterPetFamilies = "" -# # # #################################################################################################### + #################################################################################################### # ACTIVITY # + # BotActiveAlone # - Controls how many bots are active when no real players are nearby. # - Think of it as a rough percentage: 10 means approximately 10% of bots will be active. @@ -937,7 +920,6 @@ AiPlayerbot.ExcludedHunterPetFamilies = "" AiPlayerbot.BotActiveAlone = 10 AiPlayerbot.BotActiveAloneDurationSeconds = 30 -# # Force-active rules (1 = on, 0 = off) # These override the percentage above. If any of these conditions is true, the bot stays active. # @@ -979,7 +961,6 @@ AiPlayerbot.botActiveAloneSmartScaleDiffLimitCeiling = 200 AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1 AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80 -# # # #################################################################################################### @@ -987,22 +968,21 @@ AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80 #################################################################################################### # QUESTS # -# -# Quest that will be completed and rewarded for all randombots +# Quests that will be completed and rewarded for all randombots AiPlayerbot.RandomBotQuestIds = "3802,5505,6502,7761,7848,10277,10285,11492,13188,13189,24499,24511,24710,24712" # Randombots will group with nearby randombots to do shared quests +# Note: Currently not functioning properly AiPlayerbot.RandomBotGroupNearby = 0 # Randombots will pick quests on their own and try to complete them # Default: 1 (enabled) AiPlayerbot.AutoDoQuests = 1 -# Quest items to keep in bots' inventories (do not destroy) +# Quest items to keep in bots' inventories (do not automatically destroy) AiPlayerbot.RandomBotQuestItems = "5175,5176,5177,5178,6948,11000,12382,13704,16309" -# # # #################################################################################################### @@ -1010,7 +990,6 @@ AiPlayerbot.RandomBotQuestItems = "5175,5176,5177,5178,6948,11000,12382,13704,16 #################################################################################################### # SPELLS # -# # Randombots automatically learn class quest reward spells on levelup # Default: 0 (disabled) @@ -1024,13 +1003,13 @@ AiPlayerbot.AutoLearnTrainerSpells = 1 # Default: 1 (enabled) AiPlayerbot.AutoPickTalents = 1 -# Spells every randombot will learn automatically and every altbot will learn with maintenance (54197 - cold weather flying) +# Spells every randombot will learn automatically and every altbot will learn with maintenance +# Default: 54197 (Cold Weather Flying) AiPlayerbot.RandomBotSpellIds = "54197" # ID of spell to open lootable chests AiPlayerbot.OpenGoSpell = 6477 -# # # #################################################################################################### @@ -1038,7 +1017,6 @@ AiPlayerbot.OpenGoSpell = 6477 #################################################################################################### # STRATEGIES # -# # Additional randombot strategies # Strategies added here are applied to all randombots, in addition (or subtraction) to spec/role-based default strategies. @@ -1061,7 +1039,6 @@ AiPlayerbot.HealerDPSMapRestriction = 1 # Default: (Dungeon and Raid maps) "33,34,36,43,47,48,70,90,109,129,209,229,230,329,349,389,429,1001,1004,1007,269,540,542,543,545,546,547,552,553,554,555,556,557,558,560,585,574,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" AiPlayerbot.RestrictedHealerDPSMaps = "33,34,36,43,47,48,70,90,109,129,209,229,230,329,349,389,429,1001,1004,1007,269,540,542,543,545,546,547,552,553,554,555,556,557,558,560,585,574,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" -# # # #################################################################################################### @@ -1069,7 +1046,6 @@ AiPlayerbot.RestrictedHealerDPSMaps = "33,34,36,43,47,48,70,90,109,129,209,229,2 #################################################################################################### # RPG STRATEGY # -# # Randombots will behave more like real players (experimental) # This option will override AiPlayerbot.AutoDoQuests, RandomBotTeleLowerLevel, and RandomBotTeleHigherLevel @@ -1231,7 +1207,6 @@ AiPlayerbot.ZoneBracket.3537 = 68,75 AiPlayerbot.ZoneBracket.3711 = 75,80 AiPlayerbot.ZoneBracket.4197 = 79,80 -# # # #################################################################################################### @@ -1239,13 +1214,12 @@ AiPlayerbot.ZoneBracket.4197 = 79,80 #################################################################################################### # TELEPORTS # -# # Map IDs where bots can be teleported to # Defaults: 0 = Eastern Kingdoms, 1 = Kalimdor, 530 = Outland, 571 = Northrend AiPlayerbot.RandomBotMaps = 0,1,530,571 -# Probabilty bots teleport to banker (city) +# Probability bots teleport to banker (city) # Default: 0.25 AiPlayerbot.ProbTeleToBankers = 0.25 @@ -1263,7 +1237,7 @@ AiPlayerbot.TeleToSilvermoonCityWeight = 1 AiPlayerbot.TeleToShattrathCityWeight = 1 AiPlayerbot.TeleToDalaranWeight = 1 -# How far randombots are teleported after death +# How far randombots are teleported after death in yards AiPlayerbot.RandomBotTeleportDistance = 100 # How many levels below the lowest-level creature in a zone, can a bot be @@ -1280,7 +1254,6 @@ AiPlayerbot.RandomBotTeleHigherLevel = 3 # Default: 1 (enabled) AiPlayerbot.AutoTeleportForLevel = 1 -# # # #################################################################################################### @@ -1288,7 +1261,6 @@ AiPlayerbot.AutoTeleportForLevel = 1 #################################################################################################### # BATTLEGROUNDS & ARENAS & PVP # -# # Enable battlegrounds/arenas for randombots AiPlayerbot.RandomBotJoinBG = 1 @@ -1298,7 +1270,8 @@ AiPlayerbot.RandomBotAutoJoinBG = 0 # Required Configuration for RandomBotAutoJoinBG # -# Known issue: When enabling many brackats in combination with multiple instances, it can lead to more instances created by bots than intended (over-queuing). +# Known issue: When enabling many brackets in combination with multiple instances, it can lead to more +# instances created by bots than intended (over-queuing). # # This section controls the level brackets and automatic bot participation in battlegrounds and arenas. # @@ -1366,41 +1339,41 @@ AiPlayerbot.RandomBotArenaTeamMinRating = 1000 # Delete all randombot arena teams AiPlayerbot.DeleteRandomBotArenaTeams = 0 -# PvP Restricted Zones (bots don't pvp) +# PvP Restricted Zones (bots will not initiate PvP or defend themselves if attacked) 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) +# PvP Restricted Areas (bots will not initiate PvP or defend themselves if attacked) AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973,4085,4086,4087,4088" # Improve reaction speeds in battlegrounds and arenas (may cause lag) AiPlayerbot.FastReactInBG = 1 -# # # #################################################################################################### #################################################################################################### -# RANDOM BOT TIMING AND BEHAVIOR -# +# RANDOMBOT TIMING AND BEHAVIOR # -# How often (in seconds) the random bot manager runs its main update loop +# How often (in seconds) the randombot manager runs its main update loop # Default: 20 AiPlayerbot.RandomBotUpdateInterval = 20 -# Minimum and maximum seconds before the manager re-evaluates and adjusts total random bot count +# Minimum and maximum seconds before the manager re-evaluates and adjusts total randombot count # Defaults: 1800 (min), 7200 (max) AiPlayerbot.RandomBotCountChangeMinInterval = 1800 AiPlayerbot.RandomBotCountChangeMaxInterval = 7200 -# Minimum and maximum seconds a random bot will stay online before logging out +# Minimum and maximum seconds a randombot will stay online before logging out # Defaults: 600 (min), 28800 (max) AiPlayerbot.MinRandomBotInWorldTime = 600 AiPlayerbot.MaxRandomBotInWorldTime = 28800 -# Minimum and maximum seconds before a bot is eligible for re-randomization (gear, class, talents, etc.) -# Defaults: 7200 (min), 1209600 (max) +# Some aspects of randombots are periodically randomized while they are logged in and idle, +# including their inventories and, if EquipAndSpecPersistence is disabled, their equipment and specs. +# This config sets the minimum and maximum seconds between such randomization events. +# Defaults: 7200 (min - 2 hours), 1209600 (max - 14 days) AiPlayerbot.MinRandomBotRandomizeTime = 7200 AiPlayerbot.MaxRandomBotRandomizeTime = 1209600 @@ -1422,7 +1395,6 @@ AiPlayerbot.MaxRandomBotTeleportInterval = 18000 # Default: 31104000 AiPlayerbot.PermanentlyInWorldTime = 31104000 -# # # #################################################################################################### @@ -1436,7 +1408,6 @@ AiPlayerbot.PermanentlyInWorldTime = 31104000 #################################################################################################### # INFORMATION # -# # AiPlayerbot.PremadeSpecName.. = #Name of the talent specialisation # AiPlayerbot.PremadeSpecLink... = #Wowhead style link the bot should work towards at given level. @@ -1444,7 +1415,6 @@ AiPlayerbot.PermanentlyInWorldTime = 31104000 # e.g., formulate the link on https://www.wowhead.com/wotlk/talent-calc/warrior/3022032123335100202012013031251-32505010002 # 0 <= specno < 20, 1 <= level <= 80 -# # # #################################################################################################### @@ -1452,7 +1422,6 @@ AiPlayerbot.PermanentlyInWorldTime = 31104000 #################################################################################################### # WARRIOR # -# AiPlayerbot.PremadeSpecName.1.0 = arms pve AiPlayerbot.PremadeSpecGlyph.1.0 = 43418,43395,43423,43399,43397,43421 @@ -1479,7 +1448,6 @@ AiPlayerbot.PremadeSpecGlyph.1.5 = 43425,43397,43415,43396,49084,45792 AiPlayerbot.PremadeSpecLink.1.5.60 = --250031220223012520332113321 AiPlayerbot.PremadeSpecLink.1.5.80 = 0502300123-3-250031220223012521332113321 -# # # #################################################################################################### @@ -1487,7 +1455,6 @@ AiPlayerbot.PremadeSpecLink.1.5.80 = 0502300123-3-250031220223012521332113321 #################################################################################################### # PALADIN # -# AiPlayerbot.PremadeSpecName.2.0 = holy pve AiPlayerbot.PremadeSpecGlyph.2.0 = 41106,43367,45741,43368,43365,41109 @@ -1515,7 +1482,6 @@ AiPlayerbot.PremadeSpecGlyph.2.5 = 41095,43367,41102,43369,43365,45747 AiPlayerbot.PremadeSpecLink.2.5.60 = --05230250203331222133201321 AiPlayerbot.PremadeSpecLink.2.5.80 = -1532013022-05230250203331322133201321 -# # # #################################################################################################### @@ -1523,7 +1489,6 @@ AiPlayerbot.PremadeSpecLink.2.5.80 = -1532013022-05230250203331322133201321 #################################################################################################### # HUNTER # -# AiPlayerbot.PremadeSpecName.3.0 = bm pve AiPlayerbot.PremadeSpecGlyph.3.0 = 42912,43350,42902,43351,43338,42914 @@ -1564,7 +1529,6 @@ AiPlayerbot.PremadeHunterPetLink.1.20 = 21303010300120101002 AiPlayerbot.PremadeHunterPetLink.2.16 = 2100020330000211001 AiPlayerbot.PremadeHunterPetLink.2.20 = 21000203300002110221 -# # # #################################################################################################### @@ -1572,7 +1536,6 @@ AiPlayerbot.PremadeHunterPetLink.2.20 = 21000203300002110221 #################################################################################################### # ROGUE # -# AiPlayerbot.PremadeSpecName.4.0 = as pve AiPlayerbot.PremadeSpecGlyph.4.0 = 45768,43379,45761,43380,43378,45766 @@ -1599,7 +1562,6 @@ AiPlayerbot.PremadeSpecGlyph.4.5 = 42968,43376,45764,43380,43379,42971 AiPlayerbot.PremadeSpecLink.4.5.60 = --5120212030320121330133221251 AiPlayerbot.PremadeSpecLink.4.5.80 = 3023031-3-5120212030320121350135231251 -# # # #################################################################################################### @@ -1607,7 +1569,6 @@ AiPlayerbot.PremadeSpecLink.4.5.80 = 3023031-3-5120212030320121350135231251 #################################################################################################### # PRIEST # -# AiPlayerbot.PremadeSpecName.5.0 = disc pve AiPlayerbot.PremadeSpecGlyph.5.0 = 42408,43371,42400,43374,43342,45756 @@ -1634,7 +1595,6 @@ AiPlayerbot.PremadeSpecGlyph.5.5 = 42407,43371,45753,43370,43374,42408 AiPlayerbot.PremadeSpecLink.5.5.60 = --005323241223112003102311351 AiPlayerbot.PremadeSpecLink.5.5.80 = 50332031003--005323241223112003102311351 -# # # #################################################################################################### @@ -1642,7 +1602,6 @@ AiPlayerbot.PremadeSpecLink.5.5.80 = 50332031003--005323241223112003102311351 #################################################################################################### # DEATH KNIGHT # -# AiPlayerbot.PremadeSpecName.6.0 = blood pve AiPlayerbot.PremadeSpecGlyph.6.0 = 45805,43673,43538,43544,43672,43542 @@ -1673,8 +1632,6 @@ AiPlayerbot.PremadeSpecGlyph.6.6 = 45804,43539,43549,43673,43672,45805 AiPlayerbot.PremadeSpecLink.6.6.60 = --2301323301002152230101203103151 AiPlayerbot.PremadeSpecLink.6.6.80 = -320050410002-2301323301002152230101203133151 - -# # # #################################################################################################### @@ -1682,7 +1639,6 @@ AiPlayerbot.PremadeSpecLink.6.6.80 = -320050410002-23013233010021522301012031331 #################################################################################################### # SHAMAN # -# AiPlayerbot.PremadeSpecName.7.0 = ele pve AiPlayerbot.PremadeSpecGlyph.7.0 = 41536,43385,41532,43386,44923,45776 @@ -1709,8 +1665,6 @@ AiPlayerbot.PremadeSpecGlyph.7.5 = 45778,43388,45775,43725,43344,41535 AiPlayerbot.PremadeSpecLink.7.5.60 = --05032331331013501120321251 AiPlayerbot.PremadeSpecLink.7.5.80 = -023222301004-05032331331013501120331251 - -# # # #################################################################################################### @@ -1718,7 +1672,6 @@ AiPlayerbot.PremadeSpecLink.7.5.80 = -023222301004-05032331331013501120331251 #################################################################################################### # MAGE # -# AiPlayerbot.PremadeSpecName.8.0 = arcane pve AiPlayerbot.PremadeSpecGlyph.8.0 = 42735,43339,44955,43364,43361,42751 @@ -1749,8 +1702,6 @@ AiPlayerbot.PremadeSpecGlyph.8.6 = 42738,43364,45740,43357,43360,42752 AiPlayerbot.PremadeSpecLink.8.6.60 = --3533203210203100232102231151 AiPlayerbot.PremadeSpecLink.8.6.80 = 23032103010203--3533203210203100232102231151 - -# # # #################################################################################################### @@ -1758,7 +1709,6 @@ AiPlayerbot.PremadeSpecLink.8.6.80 = 23032103010203--353320321020310023210223115 #################################################################################################### # WARLOCK # -# AiPlayerbot.PremadeSpecName.9.0 = affli pve AiPlayerbot.PremadeSpecGlyph.9.0 = 45785,43390,50077,43394,43393,45779 @@ -1787,8 +1737,6 @@ AiPlayerbot.PremadeSpecGlyph.9.5 = 42471,43392,42454,43390,43389,45783 AiPlayerbot.PremadeSpecLink.9.5.60 = --05230015220331351005031051 AiPlayerbot.PremadeSpecLink.9.5.80 = -2032003311302-05230015220331351005031051 - -# # # #################################################################################################### @@ -1796,7 +1744,6 @@ AiPlayerbot.PremadeSpecLink.9.5.80 = -2032003311302-05230015220331351005031051 #################################################################################################### # DRUID # -# AiPlayerbot.PremadeSpecName.11.0 = balance pve AiPlayerbot.PremadeSpecGlyph.11.0 = 40916,43331,40921,43335,44922,40919 @@ -1827,8 +1774,6 @@ AiPlayerbot.PremadeSpecGlyph.11.6 = 40913,43331,40906,43335,43674,45623 AiPlayerbot.PremadeSpecLink.11.6.60 = --230033312031500511350013051 AiPlayerbot.PremadeSpecLink.11.6.80 = 05320021--230033312031500531353013251 - -# # # #################################################################################################### @@ -1842,7 +1787,6 @@ AiPlayerbot.PremadeSpecLink.11.6.80 = 05320021--230033312031500531353013251 #################################################################################################### # # -# # Applies automatically refreshing buffs to bots simulating effects of spells, flasks, food, runes, etc. # Requires sending the command "nc +worldbuff" in chat to a bot (or a group of bots) to enable @@ -1853,7 +1797,6 @@ AiPlayerbot.PremadeSpecLink.11.6.80 = 05320021--230033312031500531353013251 AiPlayerbot.WorldBuffMatrix = # WARRIOR ARMS 1:0,1,0,80,80:53760,57358; # WARRIOR FURY 2:0,1,1,80,80:53760,57358; # WARRIOR PROTECTION 3:0,1,2,80,80:53758,57356; # PALADIN HOLY 4:0,2,0,80,80:53749,57332,60347; # PALADIN PROTECTION 5:0,2,1,80,80:53758,57356; # PALADIN RETRIBUTION 6:0,2,2,80,80:53760,57371; # HUNTER BEAST 7:0,3,0,80,80:53760,57325; # HUNTER MARKSMANSHIP 8:0,3,1,80,80:53760,57358; # HUNTER SURVIVAL 9:0,3,2,80,80:53760,57367; # ROGUE ASSASSINATION 10:0,4,0,80,80:53760,57325; # ROGUE COMBAT 11:0,4,1,80,80:53760,57358; # ROGUE SUBTLETY 12:0,4,2,80,80:53760,57367; # PRIEST DISCIPLINE 13:0,5,0,80,80:53755,57327; # PRIEST HOLY 14:0,5,1,80,80:53755,57327; # PRIEST SHADOW 15:0,5,2,80,80:53755,57327; # DEATH KNIGHT BLOOD 16:0,6,0,80,80:53758,57356; # DEATH KNIGHT FROST 17:0,6,1,80,80:53760,57358; # DEATH KNIGHT UNHOLY 18:0,6,2,80,80:53760,57358; # DEATH KNIGHT BLOOD DPS 19:0,6,3,80,80:53760,57371; # SHAMAN ELEMENTAL 20:0,7,0,80,80:53755,57327; # SHAMAN ENHANCEMENT 21:0,7,1,80,80:53760,57325; # SHAMAN RESTORATION 22:0,7,2,80,80:53755,57327; # MAGE ARCANE 23:0,8,0,80,80:53755,57327; # MAGE FIRE 24:0,8,1,80,80:53755,57327; # MAGE FROST 25:0,8,2,80,80:53755,57327; # WARLOCK AFFLICTION 26:0,9,0,80,80:53755,57327; # WARLOCK DEMONOLOGY 27:0,9,1,80,80:53755,57327; # WARLOCK DESTRUCTION 28:0,9,2,80,80:53755,57327; # DRUID BALANCE 29:0,11,0,80,80:53755,57327; # DRUID FERAL BEAR 30:0,11,1,80,80:53749,53763,57367; # DRUID RESTORATION 31:0,11,2,80,80:54212,57334; # DRUID FERAL CAT 32:0,11,3,80,80:53760,57358; # WARRIOR ARMS TBC 33:0,1,0,70,79:28520,33256; # WARRIOR FURY TBC 34:0,1,1,70,79:28520,33256; # WARRIOR PROTECTION TBC 35:0,1,2,70,79:28518,33257; # PALADIN HOLY TBC 36:0,2,0,70,79:28491,39627,33263; # PALADIN PROTECTION TBC 37:0,2,1,70,79:28518,33257; # PALADIN RETRIBUTION TBC 38:0,2,2,70,79:28520,33256; # HUNTER BEAST TBC 39:0,3,0,70,79:28520,33261; # HUNTER MARKSMANSHIP TBC 40:0,3,1,70,79:28520,33261; # HUNTER SURVIVAL TBC 41:0,3,2,70,79:28520,33261; # ROGUE ASSASSINATION TBC 42:0,4,0,70,79:28520,33261; # ROGUE COMBAT TBC 43:0,4,1,70,79:28520,33261; # ROGUE SUBTLETY TBC 44:0,4,2,70,79:28520,33261; # PRIEST DISCIPLINE TBC 45:0,5,0,70,79:28491,39627,33263; # PRIEST HOLY TBC 46:0,5,1,70,79:28491,39627,33263; # PRIEST SHADOW TBC 47:0,5,2,70,79:28540,33263; # SHAMAN ELEMENTAL TBC 48:0,7,0,70,79:28521,33263; # SHAMAN ENHANCEMENT TBC 49:0,7,1,70,79:28520,33261; # SHAMAN RESTORATION TBC 50:0,7,2,70,79:28491,39627,33263; # MAGE ARCANE TBC 51:0,8,0,70,79:28521,33263; # MAGE FIRE TBC 52:0,8,1,70,79:28540,33263; # MAGE FROST TBC 53:0,8,2,70,79:28540,33263; # WARLOCK AFFLICTION TBC 54:0,9,0,70,79:28540,33263; # WARLOCK DEMONOLOGY TBC 55:0,9,1,70,79:28540,33263; # WARLOCK DESTRUCTION TBC 56:0,9,2,70,79:28540,33263; # DRUID BALANCE TBC 57:0,11,0,70,79:28521,33263; # DRUID FERAL BEAR TBC 58:0,11,1,70,79:28518,33257; # DRUID RESTORATION TBC 59:0,11,2,70,79:28491,39627,33263; # DRUID FERAL CAT TBC 60:0,11,3,70,79:28520,33261; # WARRIOR ARMS VANILLA 61:0,1,0,60,69:17538,24799; # WARRIOR FURY VANILLA 62:0,1,1,60,69:17538,24799; # WARRIOR PROTECTION VANILLA 63:0,1,2,60,69:17626,25661; # PALADIN HOLY VANILLA 64:0,2,0,60,69:17627,18194; # PALADIN PROTECTION VANILLA 65:0,2,1,60,69:17626,25661; # PALADIN RETRIBUTION VANILLA 66:0,2,2,60,69:17628,24799; # HUNTER BEAST VANILLA 67:0,3,0,60,69:17538,18192; # HUNTER MARKSMANSHIP VANILLA 68:0,3,1,60,69:17538,18192; # HUNTER SURVIVAL VANILLA 69:0,3,2,60,69:17538,18192; # ROGUE ASSASSINATION VANILLA 70:0,4,0,60,69:17538,18192; # ROGUE COMBAT VANILLA 71:0,4,1,60,69:17538,18192; # ROGUE SUBTLETY VANILLA 72:0,4,2,60,69:17538,18192; # PRIEST DISCIPLINE VANILLA 73:0,5,0,60,69:17628,18194; # PRIEST HOLY VANILLA 74:0,5,1,60,69:17627,18194; # PRIEST SHADOW VANILLA 75:0,5,2,60,69:17628,18194; # SHAMAN ELEMENTAL VANILLA 76:0,7,0,60,69:17628,18194; # SHAMAN ENHANCEMENT VANILLA 77:0,7,1,60,69:17538,24799; # SHAMAN RESTORATION VANILLA 78:0,7,2,60,69:17627,18194; # MAGE ARCANE VANILLA 79:0,8,0,60,69:17628,18194; # MAGE FIRE VANILLA 80:0,8,1,60,69:17628,18194; # MAGE FROST VANILLA 81:0,8,2,60,69:17628,18194; # WARLOCK AFFLICTION VANILLA 82:0,9,0,60,69:17628,25661; # WARLOCK DEMONOLOGY VANILLA 83:0,9,1,60,69:17628,25661; # WARLOCK DESTRUCTION VANILLA 84:0,9,2,60,69:17628,25661; # DRUID BALANCE VANILLA 85:0,11,0,60,69:17628,18194; # DRUID FERAL BEAR VANILLA 86:0,11,1,60,69:17626,25661; # DRUID RESTORATION VANILLA 87:0,11,2,60,69:17627,18194; # DRUID FERAL CAT VANILLA 88:0,11,3,60,69:17538,24799 -# # # #################################################################################################### @@ -1867,12 +1810,10 @@ AiPlayerbot.WorldBuffMatrix = # WARRIOR ARMS 1:0,1,0,80,80:53760,57358; # WARRIO #################################################################################################### # # -# # AiPlayerbot.RandomClassSpecProb.. # The probability to choose the spec # AiPlayerbot.RandomClassSpecIndex.. # The spec index in PremadeSpec -# # # #################################################################################################### @@ -1880,7 +1821,6 @@ AiPlayerbot.WorldBuffMatrix = # WARRIOR ARMS 1:0,1,0,80,80:53760,57358; # WARRIO #################################################################################################### # WARRIOR # -# # arms pve AiPlayerbot.RandomClassSpecProb.1.0 = 20 @@ -1901,7 +1841,6 @@ AiPlayerbot.RandomClassSpecIndex.1.4 = 4 AiPlayerbot.RandomClassSpecProb.1.5 = 0 AiPlayerbot.RandomClassSpecIndex.1.5 = 5 -# # # #################################################################################################### @@ -1909,7 +1848,6 @@ AiPlayerbot.RandomClassSpecIndex.1.5 = 5 #################################################################################################### # PALADIN # -# # holy pve AiPlayerbot.RandomClassSpecProb.2.0 = 30 @@ -1930,7 +1868,6 @@ AiPlayerbot.RandomClassSpecIndex.2.4 = 4 AiPlayerbot.RandomClassSpecProb.2.5 = 0 AiPlayerbot.RandomClassSpecIndex.2.5 = 5 -# # # #################################################################################################### @@ -1938,7 +1875,6 @@ AiPlayerbot.RandomClassSpecIndex.2.5 = 5 #################################################################################################### # HUNTER # -# # bm pve AiPlayerbot.RandomClassSpecProb.3.0 = 33 @@ -1959,7 +1895,6 @@ AiPlayerbot.RandomClassSpecIndex.3.4 = 4 AiPlayerbot.RandomClassSpecProb.3.5 = 0 AiPlayerbot.RandomClassSpecIndex.3.5 = 5 -# # # #################################################################################################### @@ -1967,7 +1902,6 @@ AiPlayerbot.RandomClassSpecIndex.3.5 = 5 #################################################################################################### # ROGUE # -# # as pve AiPlayerbot.RandomClassSpecProb.4.0 = 45 @@ -1988,7 +1922,6 @@ AiPlayerbot.RandomClassSpecIndex.4.4 = 4 AiPlayerbot.RandomClassSpecProb.4.5 = 0 AiPlayerbot.RandomClassSpecIndex.4.5 = 5 -# # # #################################################################################################### @@ -1996,7 +1929,6 @@ AiPlayerbot.RandomClassSpecIndex.4.5 = 5 #################################################################################################### # PRIEST # -# # disc pve AiPlayerbot.RandomClassSpecProb.5.0 = 40 @@ -2017,7 +1949,6 @@ AiPlayerbot.RandomClassSpecIndex.5.4 = 4 AiPlayerbot.RandomClassSpecProb.5.5 = 0 AiPlayerbot.RandomClassSpecIndex.5.5 = 5 -# # # #################################################################################################### @@ -2025,7 +1956,6 @@ AiPlayerbot.RandomClassSpecIndex.5.5 = 5 #################################################################################################### # DEATH KNIGHT # -# # blood pve AiPlayerbot.RandomClassSpecProb.6.0 = 30 @@ -2049,7 +1979,6 @@ AiPlayerbot.RandomClassSpecIndex.6.5 = 5 AiPlayerbot.RandomClassSpecProb.6.6 = 0 AiPlayerbot.RandomClassSpecIndex.6.6 = 6 -# # # #################################################################################################### @@ -2057,7 +1986,6 @@ AiPlayerbot.RandomClassSpecIndex.6.6 = 6 #################################################################################################### # SHAMAN # -# # ele pve AiPlayerbot.RandomClassSpecProb.7.0 = 33 @@ -2078,7 +2006,6 @@ AiPlayerbot.RandomClassSpecIndex.7.4 = 4 AiPlayerbot.RandomClassSpecProb.7.5 = 0 AiPlayerbot.RandomClassSpecIndex.7.5 = 5 -# # # #################################################################################################### @@ -2086,7 +2013,6 @@ AiPlayerbot.RandomClassSpecIndex.7.5 = 5 #################################################################################################### # MAGE # -# # arcane pve AiPlayerbot.RandomClassSpecProb.8.0 = 30 @@ -2110,7 +2036,6 @@ AiPlayerbot.RandomClassSpecIndex.8.5 = 5 AiPlayerbot.RandomClassSpecProb.8.6 = 0 AiPlayerbot.RandomClassSpecIndex.8.6 = 6 -# # # #################################################################################################### @@ -2118,7 +2043,6 @@ AiPlayerbot.RandomClassSpecIndex.8.6 = 6 #################################################################################################### # WARLOCK # -# # affli pve AiPlayerbot.RandomClassSpecProb.9.0 = 33 @@ -2139,7 +2063,6 @@ AiPlayerbot.RandomClassSpecIndex.9.4 = 4 AiPlayerbot.RandomClassSpecProb.9.5 = 0 AiPlayerbot.RandomClassSpecIndex.9.5 = 5 -# # # #################################################################################################### @@ -2147,7 +2070,6 @@ AiPlayerbot.RandomClassSpecIndex.9.5 = 5 #################################################################################################### # DRUID # -# # balance pve AiPlayerbot.RandomClassSpecProb.11.0 = 20 @@ -2171,7 +2093,6 @@ AiPlayerbot.RandomClassSpecIndex.11.5 = 5 AiPlayerbot.RandomClassSpecProb.11.6 = 0 AiPlayerbot.RandomClassSpecIndex.11.6 = 6 -# # # #################################################################################################### @@ -2185,15 +2106,14 @@ AiPlayerbot.RandomClassSpecIndex.11.6 = 6 #################################################################################################### # # -# -# Enable buffs in ICC to make Heroic easier and more casual. Default is 1. +# Enable buffs in ICC to make Heroic easier and more casual. # 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for -# non tank bots, increased threat for tank bots. +# non-tank bots, increased threat for tank bots. # Buffs will be applied on LDW, PP, Sindragosa and Lich King. +# Default: 1 (enabled) AiPlayerbot.EnableICCBuffs = 1 -# # # #################################################################################################### @@ -2207,8 +2127,6 @@ AiPlayerbot.EnableICCBuffs = 1 #################################################################################################### # DATABASE & CONNECTIONS # -# - # PlayerbotsDatabaseInfo # Description: Database connection settings for the playerbots server. # Example: "hostname;port;username;password;database" @@ -2221,7 +2139,6 @@ AiPlayerbot.EnableICCBuffs = 1 PlayerbotsDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_playerbots" -# # PlayerbotsDatabase.WorkerThreads # Description: The amount of worker threads spawned to handle asynchronous (delayed) MySQL # statements. Each worker thread is mirrored with its own connection to the @@ -2230,7 +2147,6 @@ PlayerbotsDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_playerbots" PlayerbotsDatabase.WorkerThreads = 1 -# # PlayerbotsDatabase.SynchThreads # Description: The amount of MySQL connections spawned to handle. # Default: 1 - (PlayerbotDatabase.WorkerThreads) @@ -2248,7 +2164,6 @@ Playerbots.Updates.EnableDatabases = 1 # Command server port, 0 - disabled AiPlayerbot.CommandServerPort = 8888 -# # # #################################################################################################### @@ -2256,7 +2171,6 @@ AiPlayerbot.CommandServerPort = 8888 #################################################################################################### # DEBUG SWITCHES # -# AiPlayerbot.SpellDump = 0 AiPlayerbot.LogInGroupOnly = 1 @@ -2270,7 +2184,6 @@ AiPlayerbot.TellWhenAvoidAoe = 0 # Enable/Disable performance monitor AiPlayerbot.PerfMonEnabled = 0 -# # # #################################################################################################### @@ -2278,7 +2191,6 @@ AiPlayerbot.PerfMonEnabled = 0 #################################################################################################### # CHAT SETTINGS # -# # Prefix for bot chat commands (e.g., follow, stay) AiPlayerbot.CommandPrefix = "" @@ -2313,7 +2225,6 @@ AiPlayerbot.GuildRepliesRate = 100 # Bots without a master will say their lines AiPlayerbot.RandomBotSayWithoutMaster = 0 -# # # #################################################################################################### @@ -2321,17 +2232,15 @@ AiPlayerbot.RandomBotSayWithoutMaster = 0 #################################################################################################### # Broadcast rates # -# # Enable/disable broadcasts globally # Default: 1 (enabled) AiPlayerbot.EnableBroadcasts = 1 -# + # All broadcast chances should be in range 0-30000 # Value of 0 will disable this particular broadcast # Setting value to 30000 does not guarantee the broadcast, as there are some internal randoms as well -# -# Setting channel broadcast chance to 0, will re-route most broadcasts to other available channels +# Setting channel broadcast chance to 0 will re-route most broadcasts to other available channels # Setting all channel broadcasts to 0 will disable most broadcasts AiPlayerbot.BroadcastToGuildGlobalChance = 30000 AiPlayerbot.BroadcastToWorldGlobalChance = 30000 @@ -2341,7 +2250,7 @@ AiPlayerbot.BroadcastToLFGGlobalChance = 30000 AiPlayerbot.BroadcastToLocalDefenseGlobalChance = 30000 AiPlayerbot.BroadcastToWorldDefenseGlobalChance = 30000 AiPlayerbot.BroadcastToGuildRecruitmentGlobalChance = 30000 -# + # Individual settings # Setting one of these to 0 will disable the particular broadcast AiPlayerbot.BroadcastChanceLootingItemPoor = 30 @@ -2351,14 +2260,14 @@ AiPlayerbot.BroadcastChanceLootingItemRare = 20000 AiPlayerbot.BroadcastChanceLootingItemEpic = 30000 AiPlayerbot.BroadcastChanceLootingItemLegendary = 30000 AiPlayerbot.BroadcastChanceLootingItemArtifact = 30000 -# + AiPlayerbot.BroadcastChanceQuestAccepted = 6000 AiPlayerbot.BroadcastChanceQuestUpdateObjectiveCompleted = 300 AiPlayerbot.BroadcastChanceQuestUpdateObjectiveProgress = 300 AiPlayerbot.BroadcastChanceQuestUpdateFailedTimer = 300 AiPlayerbot.BroadcastChanceQuestUpdateComplete = 1000 AiPlayerbot.BroadcastChanceQuestTurnedIn = 10000 -# + AiPlayerbot.BroadcastChanceKillNormal = 30 AiPlayerbot.BroadcastChanceKillElite = 300 AiPlayerbot.BroadcastChanceKillRareelite = 3000 @@ -2367,39 +2276,40 @@ AiPlayerbot.BroadcastChanceKillRare = 10000 AiPlayerbot.BroadcastChanceKillUnknown = 100 AiPlayerbot.BroadcastChanceKillPet = 10 AiPlayerbot.BroadcastChanceKillPlayer = 30 -# + AiPlayerbot.BroadcastChanceLevelupGeneric = 20000 AiPlayerbot.BroadcastChanceLevelupTenX = 30000 AiPlayerbot.BroadcastChanceLevelupMaxLevel = 30000 -# + AiPlayerbot.BroadcastChanceSuggestInstance = 5000 AiPlayerbot.BroadcastChanceSuggestQuest = 10000 AiPlayerbot.BroadcastChanceSuggestGrindMaterials = 5000 AiPlayerbot.BroadcastChanceSuggestGrindReputation = 5000 AiPlayerbot.BroadcastChanceSuggestSell = 300 AiPlayerbot.BroadcastChanceSuggestSomething = 30000 -# + # Bots will say very rude things AiPlayerbot.BroadcastChanceSuggestSomethingToxic = 0 -# + # Specifically for " [item link]" AiPlayerbot.BroadcastChanceSuggestToxicLinks = 0 -# + # Prefix is used as a word in " [item link]" AiPlayerbot.ToxicLinksPrefix = gnomes -# + # Chance to suggest Thunderfury AiPlayerbot.BroadcastChanceSuggestThunderfury = 1 -# + # Does not depend on global chance AiPlayerbot.BroadcastChanceGuildManagement = 30000 + +# # #################################################################################################### #################################################################################################### # LOGS # -# # Custom config to allow logfiles to be created. # Example: AiPlayerbot.AllowedLogFiles = travelNodes.csv,travelPaths.csv,TravelNodeStore.h,bot_movement.csv,bot_location.csv @@ -2407,38 +2317,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,2857,179490,141596,160836,160845,179516,176224,181085,176112,128308,128403,165739,165738,175245,175970,176325,176327,123329,2560 -# -# List of GUID's: -# QuestItems: -# 176213 = Blood of Heroes, 17155 = Defias Gunpowder, 2656 = Waterlogged Envelope, 123329 = Baelogs Chest, 2560 = Half-Buried Bottle -# 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, 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. -# #################################################################################################### ############################################## # Deprecated Settings (yet still in use) # ############################################## -# Log on all randombots on start -AiPlayerbot.RandomBotLoginAtStartup = 1 - # Guild Task system AiPlayerbot.EnableGuildTasks = 0 -# Enable dungeon suggestions in lower case randomly -AiPlayerbot.SuggestDungeonsInLowerCaseRandomly = 0 - # Chance bot chooses RPG (Teleport to random camp for their level) instead of grinding AiPlayerbot.RandomBotRpgChance = 0.20 @@ -2448,10 +2335,6 @@ AiPlayerbot.RandombotsWalkingRPG = 0 # Set randombots movement speed to walking only inside buildings AiPlayerbot.RandombotsWalkingRPG.InDoors = 0 -# Premade spell to avoid (undetected spells) -# spellid-radius, ... -AiPlayerbot.PremadeAvoidAoe = 62234-4 - AiPlayerbot.MinRandomBotsPriceChangeInterval = 7200 AiPlayerbot.MaxRandomBotsPriceChangeInterval = 172800 AiPlayerbot.MinRandomBotChangeStrategyTime = 180 diff --git a/src/Ai/Base/Actions/AutoMaintenanceOnLevelupAction.cpp b/src/Ai/Base/Actions/AutoMaintenanceOnLevelupAction.cpp index 75152d87b32..cfca69cb9c6 100644 --- a/src/Ai/Base/Actions/AutoMaintenanceOnLevelupAction.cpp +++ b/src/Ai/Base/Actions/AutoMaintenanceOnLevelupAction.cpp @@ -12,8 +12,8 @@ bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/) { AutoPickTalents(); AutoLearnSpell(); - AutoUpgradeEquip(); AutoTeleportForLevel(); + AutoUpgradeEquip(); return true; } @@ -21,13 +21,11 @@ bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/) void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel() { if (!sPlayerbotAIConfig.autoTeleportForLevel || !sRandomPlayerbotMgr.IsRandomBot(bot)) - { return; - } + if (botAI->HasRealPlayerMaster()) - { return; - } + sRandomPlayerbotMgr.RandomTeleportForLevel(bot); return; } @@ -89,21 +87,17 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out) { Quest const* quest = i->second; - // only process class-specific quests to learn class-related spells, cuz - // we don't want all these bunch of entries to be handled! - if (!quest->GetRequiredClasses()) - continue; - - // skip quests that are repeatable, too low level, or above bots' level - if (quest->IsRepeatable() || quest->GetMinLevel() < 10 || quest->GetMinLevel() > bot->GetLevel()) + if (!quest->GetRequiredClasses() || quest->IsRepeatable() || quest->GetMinLevel() < 10 || + quest->GetMinLevel() > bot->GetLevel()) + { continue; + } - // skip if bot doesnt satisfy class, race, or skill requirements if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) || !bot->SatisfyQuestSkill(quest, false)) + { continue; - - // use the same logic and impl from Player::learnQuestRewardedSpells + } int32 spellId = quest->GetRewSpellCast(); if (!spellId) @@ -113,31 +107,26 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out) if (!spellInfo) continue; - // xinef: find effect with learn spell and check if we have this spell bool found = false; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell && !bot->HasSpell(spellInfo->Effects[i].TriggerSpell)) { - // pusywizard: don't re-add profession specialties! if (SpellInfo const* triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell)) if (triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL) - break; // pussywizard: break and not cast the spell (found is false) + break; found = true; break; } } - // xinef: we know the spell, continue if (!found) continue; bot->CastSpell(bot, spellId, true); - // Check if RewardDisplaySpell is set to output the proper spell learned - // after processing quests. Output the original RewardSpell otherwise. uint32 rewSpellId = quest->GetRewSpell(); if (rewSpellId) { @@ -167,12 +156,11 @@ std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* s void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip() { - if (!sPlayerbotAIConfig.autoUpgradeEquip || !sRandomPlayerbotMgr.IsRandomBot(bot)) + if (!sRandomPlayerbotMgr.IsRandomBot(bot)) return; PlayerbotFactory factory(bot, bot->GetLevel()); - // Clean up old consumables before adding new ones factory.CleanupConsumables(); factory.InitAmmo(); @@ -181,9 +169,6 @@ void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip() factory.InitConsumables(); factory.InitPotions(); - if (!sPlayerbotAIConfig.equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel) - { - if (sPlayerbotAIConfig.incrementalGearInit) - factory.InitEquipment(true); - } + if (sPlayerbotAIConfig.autoUpgradeEquip) + factory.InitEquipment(true); } diff --git a/src/Bot/Factory/PlayerbotFactory.cpp b/src/Bot/Factory/PlayerbotFactory.cpp index a1adfdeaf35..06e3e840585 100644 --- a/src/Bot/Factory/PlayerbotFactory.cpp +++ b/src/Bot/Factory/PlayerbotFactory.cpp @@ -552,9 +552,8 @@ void PlayerbotFactory::Prepare() void PlayerbotFactory::Randomize(bool incremental) { // if (sPlayerbotAIConfig.disableRandomLevels) - // { // return; - // } + LOG_DEBUG("playerbots", "{} randomizing {} (level {} class = {})...", (incremental ? "Incremental" : "Full"), bot->GetName().c_str(), level, bot->getClass()); // LOG_DEBUG("playerbots", "Preparing to {} randomize...", (incremental ? "incremental" : "full")); @@ -562,16 +561,22 @@ void PlayerbotFactory::Randomize(bool incremental) LOG_DEBUG("playerbots", "Resetting player..."); PerfMonitorOperation* pmo = sPerfMonitor.start(PERF_MON_RNDBOT, "PlayerbotFactory_Reset"); - if (!PlayerbotAIConfig::instance().equipmentPersistence || level < PlayerbotAIConfig::instance().equipmentPersistenceLevel) + if (!sPlayerbotAIConfig.equipAndSpecPersistence || + level < sPlayerbotAIConfig.equipAndSpecPersistenceLevel) + { bot->resetTalents(true); + } if (!incremental) { ClearSkills(); ClearSpells(); ResetQuests(); - if (!PlayerbotAIConfig::instance().equipmentPersistence || level < PlayerbotAIConfig::instance().equipmentPersistenceLevel) + if (!sPlayerbotAIConfig.equipAndSpecPersistence || + level < sPlayerbotAIConfig.equipAndSpecPersistenceLevel) + { ClearAllItems(); + } } ClearInventory(); bot->RemoveAllSpellCooldown(); @@ -622,8 +627,8 @@ void PlayerbotFactory::Randomize(bool incremental) pmo = sPerfMonitor.start(PERF_MON_RNDBOT, "PlayerbotFactory_Talents"); LOG_DEBUG("playerbots", "Initializing talents..."); - if (!incremental || !sPlayerbotAIConfig.equipmentPersistence || - bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel) + if (!incremental || !sPlayerbotAIConfig.equipAndSpecPersistence || + bot->GetLevel() < sPlayerbotAIConfig.equipAndSpecPersistenceLevel) { uint32 specIndex = InitTalentsTree(); sRandomPlayerbotMgr.SetValue(bot->GetGUID().GetCounter(), "specNo", specIndex + 1); @@ -670,11 +675,10 @@ void PlayerbotFactory::Randomize(bool incremental) pmo = sPerfMonitor.start(PERF_MON_RNDBOT, "PlayerbotFactory_Equip"); LOG_DEBUG("playerbots", "Initializing equipmemt..."); - if (!incremental || !sPlayerbotAIConfig.equipmentPersistence || - bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel) + if (!incremental || !sPlayerbotAIConfig.equipAndSpecPersistence || + bot->GetLevel() < sPlayerbotAIConfig.equipAndSpecPersistenceLevel) { - if (sPlayerbotAIConfig.incrementalGearInit || !incremental) - InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig.twoRoundsGearInit); + InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig.twoRoundsGearInit); } // bot->SaveToDB(false, false); if (pmo) @@ -811,7 +815,8 @@ void PlayerbotFactory::Randomize(bool incremental) void PlayerbotFactory::Refresh() { // Prepare(); - // if (!sPlayerbotAIConfig.equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel) + // if (!sPlayerbotAIConfig.equipAndSpecPersistence || + // bot->GetLevel() < sPlayerbotAIConfig.equipAndSpecPersistenceLevel) // { // InitEquipment(true); // } @@ -831,14 +836,13 @@ void PlayerbotFactory::Refresh() InitSpecialSpells(); InitMounts(); InitKeyring(); - if (!sPlayerbotAIConfig.equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel) + if (!sPlayerbotAIConfig.equipAndSpecPersistence || + bot->GetLevel() < sPlayerbotAIConfig.equipAndSpecPersistenceLevel) { InitTalentsTree(true, true, true); } if (bot->GetLevel() >= sPlayerbotAIConfig.minEnchantingBotLevel) - { ApplyEnchantAndGemsNew(); - } bot->DurabilityRepairAll(false, 1.0f, false); if (bot->isDead()) bot->ResurrectPlayer(1.0f, false); @@ -2037,9 +2041,6 @@ void Shuffle(std::vector& items) void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) { - if (incremental && !sPlayerbotAIConfig.incrementalGearInit) - return; - if (level < 5) { // original items diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index fb036e3592f..b67c1e7e364 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -147,7 +147,6 @@ bool PlayerbotAIConfig::Initialize() tellWhenAvoidAoe = sConfigMgr->GetOption("AiPlayerbot.TellWhenAvoidAoe", false); randomGearLoweringChance = sConfigMgr->GetOption("AiPlayerbot.RandomGearLoweringChance", 0.0f); - incrementalGearInit = sConfigMgr->GetOption("AiPlayerbot.IncrementalGearInit", true); randomGearQualityLimit = sConfigMgr->GetOption("AiPlayerbot.RandomGearQualityLimit", 3); randomGearScoreLimit = sConfigMgr->GetOption("AiPlayerbot.RandomGearScoreLimit", 0); preferClassArmorType = sConfigMgr->GetOption("AiPlayerbot.PreferClassArmorType", false); @@ -352,15 +351,10 @@ bool PlayerbotAIConfig::Initialize() // does not depend on global chance broadcastChanceGuildManagement = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceGuildManagement", 30000); - //////////////////////////// toxicLinksRepliesChance = sConfigMgr->GetOption("AiPlayerbot.ToxicLinksRepliesChance", 30); // 0-100 thunderfuryRepliesChance = sConfigMgr->GetOption("AiPlayerbot.ThunderfuryRepliesChance", 40); // 0-100 guildRepliesRate = sConfigMgr->GetOption("AiPlayerbot.GuildRepliesRate", 100); // 0-100 - suggestDungeonsInLowerCaseRandomly = - sConfigMgr->GetOption("AiPlayerbot.SuggestDungeonsInLowerCaseRandomly", false); - - ////////////////////////// !CHAT randomBotJoinBG = sConfigMgr->GetOption("AiPlayerbot.RandomBotJoinBG", true); randomBotAutoJoinBG = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBG", false); @@ -393,7 +387,6 @@ bool PlayerbotAIConfig::Initialize() randomBotMaxLevel = sConfigMgr->GetOption("AiPlayerbot.RandomBotMaxLevel", 80); if (randomBotMaxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) randomBotMaxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); - randomBotLoginAtStartup = sConfigMgr->GetOption("AiPlayerbot.RandomBotLoginAtStartup", true); randomBotTeleLowerLevel = sConfigMgr->GetOption("AiPlayerbot.RandomBotTeleLowerLevel", 1); randomBotTeleHigherLevel = sConfigMgr->GetOption("AiPlayerbot.RandomBotTeleHigherLevel", 3); openGoSpell = sConfigMgr->GetOption("AiPlayerbot.OpenGoSpell", 6477); @@ -583,8 +576,8 @@ bool PlayerbotAIConfig::Initialize() disableRandomLevels = sConfigMgr->GetOption("AiPlayerbot.DisableRandomLevels", false); randomBotRandomPassword = sConfigMgr->GetOption("AiPlayerbot.RandomBotRandomPassword", true); downgradeMaxLevelBot = sConfigMgr->GetOption("AiPlayerbot.DowngradeMaxLevelBot", true); - equipmentPersistence = sConfigMgr->GetOption("AiPlayerbot.EquipmentPersistence", false); - equipmentPersistenceLevel = sConfigMgr->GetOption("AiPlayerbot.EquipmentPersistenceLevel", 80); + equipAndSpecPersistence = sConfigMgr->GetOption("AiPlayerbot.EquipAndSpecPersistence", true); + equipAndSpecPersistenceLevel = sConfigMgr->GetOption("AiPlayerbot.EquipAndSpecPersistenceLevel", 1); groupInvitationPermission = sConfigMgr->GetOption("AiPlayerbot.GroupInvitationPermission", 1); keepAltsInGroup = sConfigMgr->GetOption("AiPlayerbot.KeepAltsInGroup", false); allowSummonInCombat = sConfigMgr->GetOption("AiPlayerbot.AllowSummonInCombat", true); @@ -672,7 +665,7 @@ bool PlayerbotAIConfig::Initialize() dropObsoleteQuests = sConfigMgr->GetOption("AiPlayerbot.DropObsoleteQuests", true); allowLearnTrainerSpells = sConfigMgr->GetOption("AiPlayerbot.AllowLearnTrainerSpells", true); autoPickTalents = sConfigMgr->GetOption("AiPlayerbot.AutoPickTalents", true); - autoUpgradeEquip = sConfigMgr->GetOption("AiPlayerbot.AutoUpgradeEquip", false); + autoUpgradeEquip = sConfigMgr->GetOption("AiPlayerbot.AutoUpgradeEquip", true); hunterWolfPet = sConfigMgr->GetOption("AiPlayerbot.HunterWolfPet", 0); defaultPetStance = sConfigMgr->GetOption("AiPlayerbot.DefaultPetStance", 1); petChatCommandDebug = sConfigMgr->GetOption("AiPlayerbot.PetChatCommandDebug", 0); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 1936d1ca7d1..2d9071ead88 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -3,8 +3,8 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#ifndef _PLAYERBOT_PLAYERbotAICONFIG_H -#define _PLAYERBOT_PLAYERbotAICONFIG_H +#ifndef _PLAYERBOT_PLAYERBOTAICONFIG_H +#define _PLAYERBOT_PLAYERBOTAICONFIG_H #include #include @@ -134,7 +134,6 @@ class PlayerbotAIConfig std::vector randomBotQuestIds; uint32 randomBotTeleportDistance; float randomGearLoweringChance; - bool incrementalGearInit; int32 randomGearQualityLimit; int32 randomGearScoreLimit; bool preferClassArmorType; @@ -227,10 +226,6 @@ class PlayerbotAIConfig uint32 guildRepliesRate; - bool suggestDungeonsInLowerCaseRandomly; - - // -- - bool randomBotJoinBG; bool randomBotAutoJoinBG; @@ -252,7 +247,6 @@ class PlayerbotAIConfig uint32 randomBotAutoJoinBGRatedArena3v3Count; uint32 randomBotAutoJoinBGRatedArena5v5Count; - bool randomBotLoginAtStartup; uint32 randomBotTeleLowerLevel, randomBotTeleHigherLevel; std::map> zoneBrackets; bool logInGroupOnly, logValuesPerTick; @@ -395,8 +389,8 @@ class PlayerbotAIConfig uint32 selfBotLevel; bool downgradeMaxLevelBot; - bool equipmentPersistence; - int32 equipmentPersistenceLevel; + bool equipAndSpecPersistence; + int32 equipAndSpecPersistenceLevel; int32 groupInvitationPermission; bool keepAltsInGroup = false; bool KeepAltsInGroup() const { return keepAltsInGroup; } From 9e251dc39782aad9223552c433faea34b52a9a5b Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sat, 30 May 2026 13:50:54 -0700 Subject: [PATCH 54/63] Fix conf merge error --- conf/playerbots.conf.dist | 16 ---------------- tmpmod-playerbots-wiki | 1 + 2 files changed, 1 insertion(+), 16 deletions(-) create mode 160000 tmpmod-playerbots-wiki diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index a2d2d509c5c..d673cd53e02 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -495,22 +495,6 @@ AiPlayerbot.FleeingEnabled = 1 # #################################################################################################### -#################################################################################################### -# GREATER BUFFS STRATEGIES -# - -# Min group size to use Greater buffs (Paladin, Mage, Druid) -# Default: 3 -AiPlayerbot.MinBotsForGreaterBuff = 3 - -# Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff -# Default: 30 -AiPlayerbot.RPWarningCooldown = 30 - -# -# -#################################################################################################### - #################################################################################################### # CHEATS # diff --git a/tmpmod-playerbots-wiki b/tmpmod-playerbots-wiki new file mode 160000 index 00000000000..c72709d2c60 --- /dev/null +++ b/tmpmod-playerbots-wiki @@ -0,0 +1 @@ +Subproject commit c72709d2c6062ab7c9106a604d21687c2c9ace45 From 180be33d9d655a1f6dd7b426266a2d3ccaabb804 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sat, 30 May 2026 14:09:09 -0700 Subject: [PATCH 55/63] Remove stray folder. --- tmpmod-playerbots-wiki | 1 - 1 file changed, 1 deletion(-) delete mode 160000 tmpmod-playerbots-wiki diff --git a/tmpmod-playerbots-wiki b/tmpmod-playerbots-wiki deleted file mode 160000 index c72709d2c60..00000000000 --- a/tmpmod-playerbots-wiki +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c72709d2c6062ab7c9106a604d21687c2c9ace45 From b430118124effc3239da4c538dc3a37c418b1255 Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 30 May 2026 23:14:54 +0200 Subject: [PATCH 56/63] Sunder Armor fixes (#2427) ## Pull Request Description Arms and Fury using Sunder Armor when there is not Protection Warrior in group ## How to Test the Changes Invite warrior/warriors to group and check how they apply Sunder Armor on dummy target ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Yes, expained in Pull Request Description - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) Analyze orginal behavior ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- .../Class/Warrior/Action/WarriorActions.cpp | 22 +++++++++++++++++++ .../Warrior/Strategy/ArmsWarriorStrategy.cpp | 1 + 2 files changed, 23 insertions(+) diff --git a/src/Ai/Class/Warrior/Action/WarriorActions.cpp b/src/Ai/Class/Warrior/Action/WarriorActions.cpp index 20a42c21997..b30c1a38bbd 100644 --- a/src/Ai/Class/Warrior/Action/WarriorActions.cpp +++ b/src/Ai/Class/Warrior/Action/WarriorActions.cpp @@ -5,6 +5,7 @@ #include "WarriorActions.h" +#include "AiFactory.h" #include "Playerbots.h" bool CastBerserkerRageAction::isPossible() @@ -36,6 +37,27 @@ bool CastBerserkerRageAction::isUseful() bool CastSunderArmorAction::isUseful() { + Group* group = bot->GetGroup(); + if (!group) + return false; + + if (!botAI->IsTank(bot, false)) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || member == bot || !member->IsAlive() || !member->IsInWorld() || + member->GetMapId() != bot->GetMapId()) + { + continue; + } + + if (member->getClass() == CLASS_WARRIOR && + botAI->IsTank(member, false)) + return false; + } + } + Aura* aura = botAI->GetAura("sunder armor", GetTarget(), false, true); return !aura || aura->GetStackAmount() < 5 || aura->GetDuration() <= 6000; } diff --git a/src/Ai/Class/Warrior/Strategy/ArmsWarriorStrategy.cpp b/src/Ai/Class/Warrior/Strategy/ArmsWarriorStrategy.cpp index bbae89d20ef..193aab1d70b 100644 --- a/src/Ai/Class/Warrior/Strategy/ArmsWarriorStrategy.cpp +++ b/src/Ai/Class/Warrior/Strategy/ArmsWarriorStrategy.cpp @@ -112,6 +112,7 @@ std::vector ArmsWarriorStrategy::getDefaultActions() return { NextAction("bladestorm", ACTION_DEFAULT + 0.2f), NextAction("mortal strike", ACTION_DEFAULT + 0.1f), + NextAction("sunder armor", ACTION_DEFAULT + 0.05f), NextAction("melee", ACTION_DEFAULT) }; } From 571735cd576db0bfbbf621c5576c36a88d460e03 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 30 May 2026 22:10:33 -0500 Subject: [PATCH 57/63] Fix crash from missing spellInfo check in TogglePetSpellAutoCastAction (#2431) ## Pull Request Description I had repeated crashes upon login traced to line 89 of TogglePetSpellAutoCastAction, where a spellInfo check is missing. I confirmed that adding the check fixed my crashing. I don't know why there was invalid spellInfo to create the crash, as this missing check is not a new development and I never had a crash before, but clearly this code should be fixed in any case. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Actions/GenericActions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ai/Base/Actions/GenericActions.cpp b/src/Ai/Base/Actions/GenericActions.cpp index 354cb456eff..4371cb40265 100644 --- a/src/Ai/Base/Actions/GenericActions.cpp +++ b/src/Ai/Base/Actions/GenericActions.cpp @@ -86,7 +86,7 @@ bool TogglePetSpellAutoCastAction::Execute(Event /*event*/) uint32 spellId = itr->first; const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); - if (!spellInfo->IsAutocastable()) + if (!spellInfo || !spellInfo->IsAutocastable()) continue; bool shouldApply = true; From 585027fab7ff44cf3e99701de03e979b56a29c91 Mon Sep 17 00:00:00 2001 From: dillyns <49765217+dillyns@users.noreply.github.com> Date: Sun, 31 May 2026 09:41:13 -0400 Subject: [PATCH 58/63] Remove fire totem override from enhancement AOE strategy (#2270) ## Pull Request Description Currently in AOE enchancement shamans are dropping magma totems. They should not be overriding the set fire totem strategy. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes Bring an enhancement shaman into medium aoe scenario (3 enemies) They should no longer replace their fire totem with magma totem. They should only drop the fire totem that is set in their strategies. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## Messages to Translate - Does this change add bot messages to translate? - - [x] No - - [ ] Yes (**list messages in the table**) | Message key | Default message | | --------------- | ------------------ | | | | | | | ## AI Assistance - Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Class/Shaman/Strategy/GenericShamanStrategy.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Ai/Class/Shaman/Strategy/GenericShamanStrategy.cpp b/src/Ai/Class/Shaman/Strategy/GenericShamanStrategy.cpp index e89c16105be..b5064b55457 100644 --- a/src/Ai/Class/Shaman/Strategy/GenericShamanStrategy.cpp +++ b/src/Ai/Class/Shaman/Strategy/GenericShamanStrategy.cpp @@ -161,9 +161,7 @@ void ShamanAoeStrategy::InitTriggers(std::vector& triggers) } else if (tab == SHAMAN_TAB_ENHANCEMENT) { - triggers.push_back(new TriggerNode("medium aoe",{ NextAction("magma totem", 24.0f), - NextAction("fire nova", 23.0f), })); - + triggers.push_back(new TriggerNode("medium aoe",{ NextAction("fire nova", 23.0f), })); triggers.push_back(new TriggerNode("maelstrom weapon 5 and medium aoe", { NextAction("chain lightning", 22.0f), })); triggers.push_back(new TriggerNode("maelstrom weapon 4 and medium aoe", { NextAction("chain lightning", 21.0f), })); triggers.push_back(new TriggerNode("enemy within melee", { NextAction("fire nova", 5.1f), })); From 714bb6bca3c903b9abeaa66df7c89ebd981339e2 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sun, 31 May 2026 09:38:01 -0700 Subject: [PATCH 59/63] Shorten paths (#2396) ## Pull Request Description This is designed to significantly shorten the overall path while maintaining the more detailed structure. I tried to follow the principle of removing any folder that had 1 or 2 files in it, shortenining dungeon/raid names to acronyms, and removing instances when the base name was in the file name (Like Raid) etc. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) Plan and execute ## Final Checklist - - [ ] Stability is not compromised. - - [ ] Performance impact is understood, tested, and acceptable. - - [ ] Added logic complexity is justified and explained. - - [ ] Any new bot dialogue lines are translated. - - [ ] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Class/Dk/{Action => }/DKActions.cpp | 0 src/Ai/Class/Dk/{Action => }/DKActions.h | 0 src/Ai/Class/Dk/{Trigger => }/DKTriggers.cpp | 0 src/Ai/Class/Dk/{Trigger => }/DKTriggers.h | 0 .../Druid/{Trigger => }/DruidTriggers.cpp | 0 .../Class/Druid/{Trigger => }/DruidTriggers.h | 0 .../Hunter/{Action => }/HunterActions.cpp | 0 .../Class/Hunter/{Action => }/HunterActions.h | 0 .../Hunter/{Trigger => }/HunterTriggers.cpp | 0 .../Hunter/{Trigger => }/HunterTriggers.h | 0 .../Class/Mage/{Action => }/MageActions.cpp | 0 src/Ai/Class/Mage/{Action => }/MageActions.h | 0 .../Class/Mage/{Trigger => }/MageTriggers.cpp | 0 .../Class/Mage/{Trigger => }/MageTriggers.h | 0 .../{Action => Actions}/PaladinActions.cpp | 0 .../{Action => Actions}/PaladinActions.h | 0 .../PaladinGreaterBlessingAction.cpp | 0 .../PaladinGreaterBlessingAction.h | 0 .../Class/Paladin/{Util => }/PaladinHelper.h | 0 .../Paladin/{Trigger => }/PaladinTriggers.cpp | 0 .../Paladin/{Trigger => }/PaladinTriggers.h | 0 .../Priest/{Action => }/PriestActions.cpp | 0 .../Class/Priest/{Action => }/PriestActions.h | 0 .../Priest/{Trigger => }/PriestTriggers.cpp | 0 .../Priest/{Trigger => }/PriestTriggers.h | 0 .../Rogue/{Trigger => }/RogueTriggers.cpp | 0 .../Class/Rogue/{Trigger => }/RogueTriggers.h | 0 .../Shaman/{Action => }/ShamanActions.cpp | 0 .../Class/Shaman/{Action => }/ShamanActions.h | 0 .../Shaman/{Trigger => }/ShamanTriggers.cpp | 0 .../Shaman/{Trigger => }/ShamanTriggers.h | 0 .../Warlock/{Action => }/WarlockActions.cpp | 0 .../Warlock/{Action => }/WarlockActions.h | 0 .../Warlock/{Trigger => }/WarlockTriggers.cpp | 0 .../Warlock/{Trigger => }/WarlockTriggers.h | 0 .../Warrior/{Action => }/WarriorActions.cpp | 0 .../Warrior/{Action => }/WarriorActions.h | 0 .../Warrior/{Trigger => }/WarriorTriggers.cpp | 0 .../Warrior/{Trigger => }/WarriorTriggers.h | 0 .../ACActionContext.h} | 2 +- .../ACActions.cpp} | 4 +-- .../ACActions.h} | 2 +- .../ACMultipliers.cpp} | 6 ++-- .../ACMultipliers.h} | 0 .../ACStrategy.cpp} | 6 ++-- .../ACStrategy.h} | 0 .../ACTriggerContext.h} | 2 +- .../ACTriggers.cpp} | 2 +- .../ACTriggers.h} | 0 .../AKActionContext.h} | 2 +- .../AKActions.cpp} | 2 +- .../OldKingdomActions.h => AK/AKActions.h} | 2 +- .../AKMultipliers.cpp} | 6 ++-- .../AKMultipliers.h} | 0 .../AKStrategy.cpp} | 4 +-- .../OldKingdomStrategy.h => AK/AKStrategy.h} | 0 .../AKTriggerContext.h} | 2 +- .../AKTriggers.cpp} | 2 +- .../OldKingdomTriggers.h => AK/AKTriggers.h} | 0 .../ANActionContext.h} | 2 +- .../ANActions.cpp} | 2 +- .../AzjolNerubActions.h => AN/ANActions.h} | 2 +- .../ANMultipliers.cpp} | 6 ++-- .../ANMultipliers.h} | 0 .../ANStrategy.cpp} | 4 +-- .../AzjolNerubStrategy.h => AN/ANStrategy.h} | 0 .../ANTriggerContext.h} | 2 +- .../ANTriggers.cpp} | 2 +- .../AzjolNerubTriggers.h => AN/ANTriggers.h} | 0 .../CoSActionContext.h} | 2 +- .../CoSActions.cpp} | 2 +- .../CoSActions.h} | 2 +- .../CoSMultipliers.cpp} | 6 ++-- .../CoSMultipliers.h} | 0 .../CoSStrategy.cpp} | 4 +-- .../CoSStrategy.h} | 0 .../CoSTriggerContext.h} | 2 +- .../CoSTriggers.cpp} | 2 +- .../CoSTriggers.h} | 0 .../DTKActionContext.h} | 2 +- .../DTKActions.cpp} | 2 +- .../DTKActions.h} | 2 +- .../DTKMultipliers.cpp} | 6 ++-- .../DTKMultipliers.h} | 0 .../DTKStrategy.cpp} | 4 +-- .../DTKStrategy.h} | 0 .../DTKTriggerContext.h} | 2 +- .../DTKTriggers.cpp} | 2 +- .../DTKTriggers.h} | 0 src/Ai/Dungeon/DungeonStrategyContext.h | 32 ++++++++--------- .../FoSActionContext.h} | 2 +- .../FoSActions.cpp} | 2 +- .../FoSActions.h} | 2 +- .../FoSMultipliers.cpp} | 8 ++--- .../FoSMultipliers.h} | 0 .../FoSStrategy.cpp} | 4 +-- .../FoSStrategy.h} | 0 .../FoSTriggerContext.h} | 2 +- .../FoSTriggers.cpp} | 2 +- .../FoSTriggers.h} | 0 .../GDActionContext.h} | 2 +- .../GundrakActions.cpp => GD/GDActions.cpp} | 2 +- .../GundrakActions.h => GD/GDActions.h} | 2 +- .../GDMultipliers.cpp} | 6 ++-- .../GDMultipliers.h} | 0 .../GundrakStrategy.cpp => GD/GDStrategy.cpp} | 4 +-- .../GundrakStrategy.h => GD/GDStrategy.h} | 0 .../GDTriggerContext.h} | 2 +- .../GundrakTriggers.cpp => GD/GDTriggers.cpp} | 2 +- .../GundrakTriggers.h => GD/GDTriggers.h} | 0 src/Ai/Dungeon/HallsOfReflection/TODO | 0 .../HoLActionContext.h} | 2 +- .../HoLActions.cpp} | 2 +- .../HoLActions.h} | 2 +- .../HoLMultipliers.cpp} | 6 ++-- .../HoLMultipliers.h} | 0 .../HoLStrategy.cpp} | 4 +-- .../HoLStrategy.h} | 0 .../HoLTriggerContext.h} | 2 +- .../HoLTriggers.cpp} | 2 +- .../HoLTriggers.h} | 0 .../HoSActionContext.h} | 2 +- .../HoSActions.cpp} | 2 +- .../HoSActions.h} | 2 +- .../HoSMultipliers.cpp} | 6 ++-- .../HoSMultipliers.h} | 0 .../HoSStrategy.cpp} | 4 +-- .../HoSStrategy.h} | 0 .../HoSTriggerContext.h} | 2 +- .../HoSTriggers.cpp} | 2 +- .../HoSTriggers.h} | 0 .../NexActionContext.h} | 2 +- .../NexusActions.cpp => Nex/NexActions.cpp} | 2 +- .../NexusActions.h => Nex/NexActions.h} | 2 +- .../NexMultipliers.cpp} | 6 ++-- .../NexMultipliers.h} | 0 .../NexusStrategy.cpp => Nex/NexStrategy.cpp} | 4 +-- .../NexusStrategy.h => Nex/NexStrategy.h} | 0 .../NexTriggerContext.h} | 2 +- .../NexusTriggers.cpp => Nex/NexTriggers.cpp} | 2 +- .../NexusTriggers.h => Nex/NexTriggers.h} | 0 .../OCActionContext.h} | 2 +- .../OculusActions.cpp => OC/OCActions.cpp} | 4 +-- .../Action/OculusActions.h => OC/OCActions.h} | 0 .../OCMultipliers.cpp} | 6 ++-- .../OCMultipliers.h} | 0 .../OculusStrategy.cpp => OC/OCStrategy.cpp} | 4 +-- .../OculusStrategy.h => OC/OCStrategy.h} | 0 .../OCTriggerContext.h} | 2 +- .../OculusTriggers.cpp => OC/OCTriggers.cpp} | 2 +- .../OculusTriggers.h => OC/OCTriggers.h} | 0 .../PoSActionContext.h} | 2 +- .../PoSActions.cpp} | 2 +- .../PitOfSaronActions.h => PoS/PoSActions.h} | 2 +- .../PoSMultipliers.cpp} | 6 ++-- .../PoSMultipliers.h} | 0 .../PoSStrategy.cpp} | 4 +-- .../PoSStrategy.h} | 0 .../PoSTriggerContext.h} | 2 +- .../PoSTriggers.cpp} | 2 +- .../PoSTriggers.h} | 0 .../TOCActionContext.h} | 2 +- .../TOCActions.cpp} | 2 +- .../TOCActions.h} | 2 +- .../TOCMultipliers.cpp} | 6 ++-- .../TOCMultipliers.h} | 0 .../TOCStrategy.cpp} | 2 +- .../TOCStrategy.h} | 2 +- .../TOCTriggerContext.h} | 2 +- .../TOCTriggers.cpp} | 2 +- .../TOCTriggers.h} | 0 src/Ai/Dungeon/TbcDungeonActionContext.h | 2 +- src/Ai/Dungeon/TbcDungeonTriggerContext.h | 2 +- .../UKActionContext.h} | 2 +- .../UKActions.cpp} | 2 +- .../UtgardeKeepActions.h => UK/UKActions.h} | 2 +- .../UKMultipliers.cpp} | 6 ++-- .../UKMultipliers.h} | 0 .../UKStrategy.cpp} | 4 +-- .../UtgardeKeepStrategy.h => UK/UKStrategy.h} | 0 .../UKTriggerContext.h} | 2 +- .../UKTriggers.cpp} | 2 +- .../UtgardeKeepTriggers.h => UK/UKTriggers.h} | 0 .../UPActionContext.h} | 2 +- .../UPActions.cpp} | 2 +- .../UPActions.h} | 2 +- .../UPMultipliers.cpp} | 6 ++-- .../UPMultipliers.h} | 0 .../UPStrategy.cpp} | 4 +-- .../UPStrategy.h} | 0 .../UPTriggerContext.h} | 2 +- .../UPTriggers.cpp} | 2 +- .../UPTriggers.h} | 0 .../VHActionContext.h} | 2 +- .../VHActions.cpp} | 2 +- .../VioletHoldActions.h => VH/VHActions.h} | 2 +- .../VHMultipliers.cpp} | 6 ++-- .../VHMultipliers.h} | 0 .../VHStrategy.cpp} | 4 +-- .../VioletHoldStrategy.h => VH/VHStrategy.h} | 0 .../VHTriggerContext.h} | 2 +- .../VHTriggers.cpp} | 2 +- .../VioletHoldTriggers.h => VH/VHTriggers.h} | 0 src/Ai/Dungeon/WotlkDungeonActionContext.h | 30 ++++++++-------- src/Ai/Dungeon/WotlkDungeonTriggerContext.h | 30 ++++++++-------- ...q20ActionContext.h => Aq20ActionContext.h} | 2 +- .../RaidAq20Actions.cpp => Aq20Actions.cpp} | 4 +-- .../RaidAq20Actions.h => Aq20Actions.h} | 0 .../RaidAq20Strategy.cpp => Aq20Strategy.cpp} | 2 +- .../RaidAq20Strategy.h => Aq20Strategy.h} | 0 ...0TriggerContext.h => Aq20TriggerContext.h} | 2 +- .../RaidAq20Triggers.cpp => Aq20Triggers.cpp} | 4 +-- .../RaidAq20Triggers.h => Aq20Triggers.h} | 0 .../{Util/RaidAq20Utils.cpp => Aq20Utils.cpp} | 2 +- .../{Util/RaidAq20Utils.h => Aq20Utils.h} | 0 .../BTActionContext.h} | 2 +- .../BTActions.cpp} | 4 +-- .../BTActions.h} | 2 +- .../BTHelpers.cpp} | 2 +- .../BTHelpers.h} | 0 .../BTMultipliers.cpp} | 6 ++-- .../BTMultipliers.h} | 0 .../BTStrategy.cpp} | 4 +-- .../BTStrategy.h} | 0 .../BTTriggerContext.h} | 2 +- .../BTTriggers.cpp} | 6 ++-- .../BTTriggers.h} | 0 .../BWLActionContext.h} | 2 +- .../RaidBwlActions.cpp => BWL/BWLActions.cpp} | 4 +-- .../RaidBwlActions.h => BWL/BWLActions.h} | 0 .../RaidBwlHelpers.cpp => BWL/BWLHelpers.cpp} | 2 +- .../RaidBwlHelpers.h => BWL/BWLHelpers.h} | 0 .../BWLStrategy.cpp} | 2 +- .../RaidBwlStrategy.h => BWL/BWLStrategy.h} | 0 .../BWLTriggerContext.h} | 2 +- .../BWLTriggers.cpp} | 4 +-- .../RaidBwlTriggers.h => BWL/BWLTriggers.h} | 0 .../EoEActionContext.h} | 2 +- .../RaidEoEActions.cpp => EoE/EoEActions.cpp} | 4 +-- .../RaidEoEActions.h => EoE/EoEActions.h} | 0 .../EoEMultipliers.cpp} | 6 ++-- .../EoEMultipliers.h} | 0 .../EoEStrategy.cpp} | 4 +-- .../RaidEoEStrategy.h => EoE/EoEStrategy.h} | 0 .../EoETriggerContext.h} | 2 +- .../EoETriggers.cpp} | 2 +- .../RaidEoETriggers.h => EoE/EoETriggers.h} | 0 .../GruulActionContext.h} | 2 +- .../GruulActions.cpp} | 4 +-- .../GruulActions.h} | 0 .../GruulHelpers.cpp} | 2 +- .../GruulHelpers.h} | 0 .../GruulMultipliers.cpp} | 6 ++-- .../GruulMultipliers.h} | 0 .../GruulStrategy.cpp} | 4 +-- .../GruulStrategy.h} | 0 .../GruulTriggerContext.h} | 2 +- .../GruulTriggers.cpp} | 4 +-- .../GruulTriggers.h} | 0 .../HyjalActionContext.h} | 2 +- .../HyjalActions.cpp} | 4 +-- .../HyjalActions.h} | 0 .../HyjalMultipliers.cpp} | 6 ++-- .../HyjalMultipliers.h} | 0 .../HyjalStrategy.cpp} | 4 +-- .../HyjalStrategy.h} | 0 .../HyjalTriggerContext.h} | 2 +- .../HyjalTriggers.cpp} | 6 ++-- .../HyjalTriggers.h} | 0 .../Util/HyjalHelpers.cpp} | 2 +- .../Util/HyjalHelpers.h} | 0 .../Util/HyjalScripts.cpp} | 2 +- .../KaraActionContext.h} | 2 +- .../KaraActions.cpp} | 4 +-- .../KaraActions.h} | 0 .../KaraHelpers.cpp} | 2 +- .../KaraHelpers.h} | 0 .../KaraMultipliers.cpp} | 6 ++-- .../KaraMultipliers.h} | 0 .../KaraStrategy.cpp} | 4 +-- .../KaraStrategy.h} | 0 .../KaraTriggerContext.h} | 2 +- .../KaraTriggers.cpp} | 6 ++-- .../KaraTriggers.h} | 0 .../MCActionContext.h} | 2 +- .../RaidMcActions.cpp => MC/MCActions.cpp} | 4 +-- .../Action/RaidMcActions.h => MC/MCActions.h} | 0 .../Util/RaidMcHelpers.h => MC/MCHelpers.h} | 0 .../MCMultipliers.cpp} | 6 ++-- .../MCMultipliers.h} | 0 .../RaidMcStrategy.cpp => MC/MCStrategy.cpp} | 4 +-- .../RaidMcStrategy.h => MC/MCStrategy.h} | 0 .../MCTriggerContext.h} | 2 +- .../RaidMcTriggers.cpp => MC/MCTriggers.cpp} | 4 +-- .../RaidMcTriggers.h => MC/MCTriggers.h} | 0 .../MagActionContext.h} | 2 +- .../MagActions.cpp} | 4 +-- .../MagActions.h} | 2 +- .../MagHelpers.cpp} | 2 +- .../MagHelpers.h} | 0 .../MagMultipliers.cpp} | 6 ++-- .../MagMultipliers.h} | 0 .../MagStrategy.cpp} | 4 +-- .../MagStrategy.h} | 0 .../MagTriggerContext.h} | 2 +- .../MagTriggers.cpp} | 4 +-- .../MagTriggers.h} | 0 .../Action/NaxxActions.h} | 2 +- .../Action/NaxxActions_Anubrekhan.cpp} | 2 +- .../Action/NaxxActions_Faerlina.cpp} | 2 +- .../Action/NaxxActions_FourHorsemen.cpp} | 2 +- .../Action/NaxxActions_Gluth.cpp} | 2 +- .../Action/NaxxActions_Gothik.cpp} | 2 +- .../Action/NaxxActions_Grobbulus.cpp} | 2 +- .../Action/NaxxActions_Heigan.cpp} | 4 +-- .../Action/NaxxActions_Kelthuzad.cpp} | 2 +- .../Action/NaxxActions_Loatheb.cpp} | 2 +- .../Action/NaxxActions_Maexxna.cpp} | 2 +- .../Action/NaxxActions_Noth.cpp} | 2 +- .../Action/NaxxActions_Patchwerk.cpp} | 2 +- .../Action/NaxxActions_Razuvious.cpp} | 2 +- .../Action/NaxxActions_Sapphiron.cpp} | 6 ++-- .../Action/NaxxActions_Shared.cpp} | 2 +- .../Action/NaxxActions_Thaddius.cpp} | 4 +-- .../NaxxActionContext.h} | 2 +- .../NaxxBossHelper.h} | 2 +- .../NaxxMultipliers.cpp} | 6 ++-- .../NaxxMultipliers.h} | 2 +- .../NaxxSpellIds.h} | 0 .../NaxxStrategy.cpp} | 4 +-- .../NaxxStrategy.h} | 0 .../NaxxTriggerContext.h} | 2 +- .../NaxxTriggers.cpp} | 4 +-- .../NaxxTriggers.h} | 2 +- .../OSActionContext.h} | 2 +- .../RaidOsActions.cpp => OS/OSActions.cpp} | 4 +-- .../Action/RaidOsActions.h => OS/OSActions.h} | 0 .../OSMultipliers.cpp} | 6 ++-- .../OSMultipliers.h} | 0 .../RaidOsStrategy.cpp => OS/OSStrategy.cpp} | 4 +-- .../RaidOsStrategy.h => OS/OSStrategy.h} | 0 .../OSTriggerContext.h} | 2 +- .../RaidOsTriggers.cpp => OS/OSTriggers.cpp} | 2 +- .../RaidOsTriggers.h => OS/OSTriggers.h} | 0 .../OnyActionContext.h} | 2 +- .../OnyActions.cpp} | 2 +- .../RaidOnyxiaActions.h => Ony/OnyActions.h} | 0 .../OnyStrategy.cpp} | 2 +- .../OnyStrategy.h} | 0 .../OnyTriggerContext.h} | 2 +- .../OnyTriggers.cpp} | 2 +- .../OnyTriggers.h} | 0 src/Ai/Raid/RaidStrategyContext.h | 34 +++++++++--------- .../SSCActionContext.h} | 2 +- .../RaidSSCActions.cpp => SSC/SSCActions.cpp} | 4 +-- .../RaidSSCActions.h => SSC/SSCActions.h} | 0 .../RaidSSCHelpers.cpp => SSC/SSCHelpers.cpp} | 2 +- .../RaidSSCHelpers.h => SSC/SSCHelpers.h} | 0 .../SSCMultipliers.cpp} | 6 ++-- .../SSCMultipliers.h} | 0 .../SSCStrategy.cpp} | 4 +-- .../RaidSSCStrategy.h => SSC/SSCStrategy.h} | 0 .../SSCTriggerContext.h} | 2 +- .../SSCTriggers.cpp} | 6 ++-- .../RaidSSCTriggers.h => SSC/SSCTriggers.h} | 0 .../TKActionContext.h} | 2 +- .../TKActions.cpp} | 6 ++-- .../TKActions.h} | 4 +-- .../TKMultipliers.cpp} | 8 ++--- .../TKMultipliers.h} | 0 .../TKStrategy.cpp} | 4 +-- .../TKStrategy.h} | 0 .../TKTriggerContext.h} | 2 +- .../TKTriggers.cpp} | 8 ++--- .../TKTriggers.h} | 0 .../Util/TKHelpers.cpp} | 4 +-- .../Util/TKHelpers.h} | 0 .../Util/TKKaelthasBossAI.h} | 0 .../Util/TKScripts.cpp} | 2 +- .../UldActionContext.h} | 2 +- .../UldActions.cpp} | 6 ++-- .../RaidUlduarActions.h => Uld/UldActions.h} | 4 +-- .../UldStrategy.cpp} | 2 +- .../UldStrategy.h} | 0 .../UldTriggerContext.h} | 2 +- .../UldTriggers.cpp} | 6 ++-- .../UldTriggers.h} | 2 +- .../Util/UldBossHelper.cpp} | 2 +- .../Util/UldBossHelper.h} | 0 .../Util/UldScripts.h} | 0 .../VoAActionContext.h} | 2 +- .../RaidVoAActions.cpp => VoA/VoAActions.cpp} | 4 +-- .../RaidVoAActions.h => VoA/VoAActions.h} | 0 .../VoAStrategy.cpp} | 2 +- .../RaidVoAStrategy.h => VoA/VoAStrategy.h} | 0 .../VoATriggerContext.h} | 2 +- .../VoATriggers.cpp} | 2 +- .../RaidVoATriggers.h => VoA/VoATriggers.h} | 0 .../ZAActionContext.h} | 2 +- .../ZAActions.cpp} | 4 +-- .../RaidZulAmanActions.h => ZA/ZAActions.h} | 0 .../ZAHelpers.cpp} | 2 +- .../RaidZulAmanHelpers.h => ZA/ZAHelpers.h} | 0 .../ZAMultipliers.cpp} | 6 ++-- .../ZAMultipliers.h} | 0 .../ZAStrategy.cpp} | 4 +-- .../RaidZulAmanStrategy.h => ZA/ZAStrategy.h} | 0 .../ZATriggerContext.h} | 2 +- .../ZATriggers.cpp} | 6 ++-- .../RaidZulAmanTriggers.h => ZA/ZATriggers.h} | 0 src/Bot/Engine/BuildSharedActionContexts.cpp | 36 +++++++++---------- src/Bot/Engine/BuildSharedTriggerContexts.cpp | 36 +++++++++---------- 412 files changed, 480 insertions(+), 480 deletions(-) rename src/Ai/Class/Dk/{Action => }/DKActions.cpp (100%) rename src/Ai/Class/Dk/{Action => }/DKActions.h (100%) rename src/Ai/Class/Dk/{Trigger => }/DKTriggers.cpp (100%) rename src/Ai/Class/Dk/{Trigger => }/DKTriggers.h (100%) rename src/Ai/Class/Druid/{Trigger => }/DruidTriggers.cpp (100%) rename src/Ai/Class/Druid/{Trigger => }/DruidTriggers.h (100%) rename src/Ai/Class/Hunter/{Action => }/HunterActions.cpp (100%) rename src/Ai/Class/Hunter/{Action => }/HunterActions.h (100%) rename src/Ai/Class/Hunter/{Trigger => }/HunterTriggers.cpp (100%) rename src/Ai/Class/Hunter/{Trigger => }/HunterTriggers.h (100%) rename src/Ai/Class/Mage/{Action => }/MageActions.cpp (100%) rename src/Ai/Class/Mage/{Action => }/MageActions.h (100%) rename src/Ai/Class/Mage/{Trigger => }/MageTriggers.cpp (100%) rename src/Ai/Class/Mage/{Trigger => }/MageTriggers.h (100%) rename src/Ai/Class/Paladin/{Action => Actions}/PaladinActions.cpp (100%) rename src/Ai/Class/Paladin/{Action => Actions}/PaladinActions.h (100%) rename src/Ai/Class/Paladin/{Action => Actions}/PaladinGreaterBlessingAction.cpp (100%) rename src/Ai/Class/Paladin/{Action => Actions}/PaladinGreaterBlessingAction.h (100%) rename src/Ai/Class/Paladin/{Util => }/PaladinHelper.h (100%) rename src/Ai/Class/Paladin/{Trigger => }/PaladinTriggers.cpp (100%) rename src/Ai/Class/Paladin/{Trigger => }/PaladinTriggers.h (100%) rename src/Ai/Class/Priest/{Action => }/PriestActions.cpp (100%) rename src/Ai/Class/Priest/{Action => }/PriestActions.h (100%) rename src/Ai/Class/Priest/{Trigger => }/PriestTriggers.cpp (100%) rename src/Ai/Class/Priest/{Trigger => }/PriestTriggers.h (100%) rename src/Ai/Class/Rogue/{Trigger => }/RogueTriggers.cpp (100%) rename src/Ai/Class/Rogue/{Trigger => }/RogueTriggers.h (100%) rename src/Ai/Class/Shaman/{Action => }/ShamanActions.cpp (100%) rename src/Ai/Class/Shaman/{Action => }/ShamanActions.h (100%) rename src/Ai/Class/Shaman/{Trigger => }/ShamanTriggers.cpp (100%) rename src/Ai/Class/Shaman/{Trigger => }/ShamanTriggers.h (100%) rename src/Ai/Class/Warlock/{Action => }/WarlockActions.cpp (100%) rename src/Ai/Class/Warlock/{Action => }/WarlockActions.h (100%) rename src/Ai/Class/Warlock/{Trigger => }/WarlockTriggers.cpp (100%) rename src/Ai/Class/Warlock/{Trigger => }/WarlockTriggers.h (100%) rename src/Ai/Class/Warrior/{Action => }/WarriorActions.cpp (100%) rename src/Ai/Class/Warrior/{Action => }/WarriorActions.h (100%) rename src/Ai/Class/Warrior/{Trigger => }/WarriorTriggers.cpp (100%) rename src/Ai/Class/Warrior/{Trigger => }/WarriorTriggers.h (100%) rename src/Ai/Dungeon/{AuchenaiCrypts/AuchenaiCryptsActionContext.h => AC/ACActionContext.h} (97%) rename src/Ai/Dungeon/{AuchenaiCrypts/Action/AuchenaiCryptsActions.cpp => AC/ACActions.cpp} (98%) rename src/Ai/Dungeon/{AuchenaiCrypts/Action/AuchenaiCryptsActions.h => AC/ACActions.h} (96%) rename src/Ai/Dungeon/{AuchenaiCrypts/Multiplier/AuchenaiCryptsMultipliers.cpp => AC/ACMultipliers.cpp} (92%) rename src/Ai/Dungeon/{AuchenaiCrypts/Multiplier/AuchenaiCryptsMultipliers.h => AC/ACMultipliers.h} (100%) rename src/Ai/Dungeon/{AuchenaiCrypts/Strategy/AuchenaiCryptsStrategy.cpp => AC/ACStrategy.cpp} (86%) rename src/Ai/Dungeon/{AuchenaiCrypts/Strategy/AuchenaiCryptsStrategy.h => AC/ACStrategy.h} (100%) rename src/Ai/Dungeon/{AuchenaiCrypts/AuchenaiCryptsTriggerContext.h => AC/ACTriggerContext.h} (97%) rename src/Ai/Dungeon/{AuchenaiCrypts/Trigger/AuchenaiCryptsTriggers.cpp => AC/ACTriggers.cpp} (96%) rename src/Ai/Dungeon/{AuchenaiCrypts/Trigger/AuchenaiCryptsTriggers.h => AC/ACTriggers.h} (100%) rename src/Ai/Dungeon/{OldKingdom/OldKingdomActionContext.h => AK/AKActionContext.h} (96%) rename src/Ai/Dungeon/{OldKingdom/Action/OldKingdomActions.cpp => AK/AKActions.cpp} (98%) rename src/Ai/Dungeon/{OldKingdom/Action/OldKingdomActions.h => AK/AKActions.h} (96%) rename src/Ai/Dungeon/{OldKingdom/Multiplier/OldKingdomMultipliers.cpp => AK/AKMultipliers.cpp} (94%) rename src/Ai/Dungeon/{OldKingdom/Multiplier/OldKingdomMultipliers.h => AK/AKMultipliers.h} (100%) rename src/Ai/Dungeon/{OldKingdom/Strategy/OldKingdomStrategy.cpp => AK/AKStrategy.cpp} (95%) rename src/Ai/Dungeon/{OldKingdom/Strategy/OldKingdomStrategy.h => AK/AKStrategy.h} (100%) rename src/Ai/Dungeon/{OldKingdom/OldKingdomTriggerContext.h => AK/AKTriggerContext.h} (96%) rename src/Ai/Dungeon/{OldKingdom/Trigger/OldKingdomTriggers.cpp => AK/AKTriggers.cpp} (97%) rename src/Ai/Dungeon/{OldKingdom/Trigger/OldKingdomTriggers.h => AK/AKTriggers.h} (100%) rename src/Ai/Dungeon/{AzjolNerub/AzjolNerubActionContext.h => AN/ANActionContext.h} (96%) rename src/Ai/Dungeon/{AzjolNerub/Action/AzjolNerubActions.cpp => AN/ANActions.cpp} (99%) rename src/Ai/Dungeon/{AzjolNerub/Action/AzjolNerubActions.h => AN/ANActions.h} (96%) rename src/Ai/Dungeon/{AzjolNerub/Multiplier/AzjolNerubMultipliers.cpp => AN/ANMultipliers.cpp} (93%) rename src/Ai/Dungeon/{AzjolNerub/Multiplier/AzjolNerubMultipliers.h => AN/ANMultipliers.h} (100%) rename src/Ai/Dungeon/{AzjolNerub/Strategy/AzjolNerubStrategy.cpp => AN/ANStrategy.cpp} (94%) rename src/Ai/Dungeon/{AzjolNerub/Strategy/AzjolNerubStrategy.h => AN/ANStrategy.h} (100%) rename src/Ai/Dungeon/{AzjolNerub/AzjolNerubTriggerContext.h => AN/ANTriggerContext.h} (97%) rename src/Ai/Dungeon/{AzjolNerub/Trigger/AzjolNerubTriggers.cpp => AN/ANTriggers.cpp} (98%) rename src/Ai/Dungeon/{AzjolNerub/Trigger/AzjolNerubTriggers.h => AN/ANTriggers.h} (100%) rename src/Ai/Dungeon/{CullingOfStratholme/CullingOfStratholmeActionContext.h => CoS/CoSActionContext.h} (94%) rename src/Ai/Dungeon/{CullingOfStratholme/Action/CullingOfStratholmeActions.cpp => CoS/CoSActions.cpp} (97%) rename src/Ai/Dungeon/{CullingOfStratholme/Action/CullingOfStratholmeActions.h => CoS/CoSActions.h} (93%) rename src/Ai/Dungeon/{CullingOfStratholme/Multiplier/CullingOfStratholmeMultipliers.cpp => CoS/CoSMultipliers.cpp} (76%) rename src/Ai/Dungeon/{CullingOfStratholme/Multiplier/CullingOfStratholmeMultipliers.h => CoS/CoSMultipliers.h} (100%) rename src/Ai/Dungeon/{CullingOfStratholme/Strategy/CullingOfStratholmeStrategy.cpp => CoS/CoSStrategy.cpp} (90%) rename src/Ai/Dungeon/{CullingOfStratholme/Strategy/CullingOfStratholmeStrategy.h => CoS/CoSStrategy.h} (100%) rename src/Ai/Dungeon/{CullingOfStratholme/CullingOfStratholmeTriggerContext.h => CoS/CoSTriggerContext.h} (94%) rename src/Ai/Dungeon/{CullingOfStratholme/Trigger/CullingOfStratholmeTriggers.cpp => CoS/CoSTriggers.cpp} (95%) rename src/Ai/Dungeon/{CullingOfStratholme/Trigger/CullingOfStratholmeTriggers.h => CoS/CoSTriggers.h} (100%) rename src/Ai/Dungeon/{DraktharonKeep/DrakTharonKeepActionContext.h => DTK/DTKActionContext.h} (98%) rename src/Ai/Dungeon/{DraktharonKeep/Action/DrakTharonKeepActions.cpp => DTK/DTKActions.cpp} (99%) rename src/Ai/Dungeon/{DraktharonKeep/Action/DrakTharonKeepActions.h => DTK/DTKActions.h} (98%) rename src/Ai/Dungeon/{DraktharonKeep/Multiplier/DrakTharonKeepMultipliers.cpp => DTK/DTKMultipliers.cpp} (93%) rename src/Ai/Dungeon/{DraktharonKeep/Multiplier/DrakTharonKeepMultipliers.h => DTK/DTKMultipliers.h} (100%) rename src/Ai/Dungeon/{DraktharonKeep/Strategy/DrakTharonKeepStrategy.cpp => DTK/DTKStrategy.cpp} (95%) rename src/Ai/Dungeon/{DraktharonKeep/Strategy/DrakTharonKeepStrategy.h => DTK/DTKStrategy.h} (100%) rename src/Ai/Dungeon/{DraktharonKeep/DrakTharonKeepTriggerContext.h => DTK/DTKTriggerContext.h} (97%) rename src/Ai/Dungeon/{DraktharonKeep/Trigger/DrakTharonKeepTriggers.cpp => DTK/DTKTriggers.cpp} (97%) rename src/Ai/Dungeon/{DraktharonKeep/Trigger/DrakTharonKeepTriggers.h => DTK/DTKTriggers.h} (100%) rename src/Ai/Dungeon/{ForgeOfSouls/ForgeOfSoulsActionContext.h => FoS/FoSActionContext.h} (97%) rename src/Ai/Dungeon/{ForgeOfSouls/Action/ForgeOfSoulsActions.cpp => FoS/FoSActions.cpp} (99%) rename src/Ai/Dungeon/{ForgeOfSouls/Action/ForgeOfSoulsActions.h => FoS/FoSActions.h} (97%) rename src/Ai/Dungeon/{ForgeOfSouls/Multiplier/ForgeOfSoulsMultipliers.cpp => FoS/FoSMultipliers.cpp} (88%) rename src/Ai/Dungeon/{ForgeOfSouls/Multiplier/ForgeOfSoulsMultipliers.h => FoS/FoSMultipliers.h} (100%) rename src/Ai/Dungeon/{ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.cpp => FoS/FoSStrategy.cpp} (91%) rename src/Ai/Dungeon/{ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.h => FoS/FoSStrategy.h} (100%) rename src/Ai/Dungeon/{ForgeOfSouls/ForgeOfSoulsTriggerContext.h => FoS/FoSTriggerContext.h} (97%) rename src/Ai/Dungeon/{ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.cpp => FoS/FoSTriggers.cpp} (97%) rename src/Ai/Dungeon/{ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.h => FoS/FoSTriggers.h} (100%) rename src/Ai/Dungeon/{Gundrak/GundrakActionContext.h => GD/GDActionContext.h} (96%) rename src/Ai/Dungeon/{Gundrak/Action/GundrakActions.cpp => GD/GDActions.cpp} (98%) rename src/Ai/Dungeon/{Gundrak/Action/GundrakActions.h => GD/GDActions.h} (96%) rename src/Ai/Dungeon/{Gundrak/Multiplier/GundrakMultipliers.cpp => GD/GDMultipliers.cpp} (94%) rename src/Ai/Dungeon/{Gundrak/Multiplier/GundrakMultipliers.h => GD/GDMultipliers.h} (100%) rename src/Ai/Dungeon/{Gundrak/Strategy/GundrakStrategy.cpp => GD/GDStrategy.cpp} (94%) rename src/Ai/Dungeon/{Gundrak/Strategy/GundrakStrategy.h => GD/GDStrategy.h} (100%) rename src/Ai/Dungeon/{Gundrak/GundrakTriggerContext.h => GD/GDTriggerContext.h} (96%) rename src/Ai/Dungeon/{Gundrak/Trigger/GundrakTriggers.cpp => GD/GDTriggers.cpp} (97%) rename src/Ai/Dungeon/{Gundrak/Trigger/GundrakTriggers.h => GD/GDTriggers.h} (100%) delete mode 100644 src/Ai/Dungeon/HallsOfReflection/TODO rename src/Ai/Dungeon/{HallsOfLightning/HallsOfLightningActionContext.h => HoL/HoLActionContext.h} (98%) rename src/Ai/Dungeon/{HallsOfLightning/Action/HallsOfLightningActions.cpp => HoL/HoLActions.cpp} (99%) rename src/Ai/Dungeon/{HallsOfLightning/Action/HallsOfLightningActions.h => HoL/HoLActions.h} (98%) rename src/Ai/Dungeon/{HallsOfLightning/Multiplier/HallsOfLightningMultipliers.cpp => HoL/HoLMultipliers.cpp} (96%) rename src/Ai/Dungeon/{HallsOfLightning/Multiplier/HallsOfLightningMultipliers.h => HoL/HoLMultipliers.h} (100%) rename src/Ai/Dungeon/{HallsOfLightning/Strategy/HallsOfLightningStrategy.cpp => HoL/HoLStrategy.cpp} (95%) rename src/Ai/Dungeon/{HallsOfLightning/Strategy/HallsOfLightningStrategy.h => HoL/HoLStrategy.h} (100%) rename src/Ai/Dungeon/{HallsOfLightning/HallsOfLightningTriggerContext.h => HoL/HoLTriggerContext.h} (98%) rename src/Ai/Dungeon/{HallsOfLightning/Trigger/HallsOfLightningTriggers.cpp => HoL/HoLTriggers.cpp} (98%) rename src/Ai/Dungeon/{HallsOfLightning/Trigger/HallsOfLightningTriggers.h => HoL/HoLTriggers.h} (100%) rename src/Ai/Dungeon/{HallsOfStone/HallsOfStoneActionContext.h => HoS/HoSActionContext.h} (95%) rename src/Ai/Dungeon/{HallsOfStone/Action/HallsOfStoneActions.cpp => HoS/HoSActions.cpp} (97%) rename src/Ai/Dungeon/{HallsOfStone/Action/HallsOfStoneActions.h => HoS/HoSActions.h} (94%) rename src/Ai/Dungeon/{HallsOfStone/Multiplier/HallsOfStoneMultipliers.cpp => HoS/HoSMultipliers.cpp} (92%) rename src/Ai/Dungeon/{HallsOfStone/Multiplier/HallsOfStoneMultipliers.h => HoS/HoSMultipliers.h} (100%) rename src/Ai/Dungeon/{HallsOfStone/Strategy/HallsOfStoneStrategy.cpp => HoS/HoSStrategy.cpp} (93%) rename src/Ai/Dungeon/{HallsOfStone/Strategy/HallsOfStoneStrategy.h => HoS/HoSStrategy.h} (100%) rename src/Ai/Dungeon/{HallsOfStone/HallsOfStoneTriggerContext.h => HoS/HoSTriggerContext.h} (95%) rename src/Ai/Dungeon/{HallsOfStone/Trigger/HallsOfStoneTriggers.cpp => HoS/HoSTriggers.cpp} (95%) rename src/Ai/Dungeon/{HallsOfStone/Trigger/HallsOfStoneTriggers.h => HoS/HoSTriggers.h} (100%) rename src/Ai/Dungeon/{Nexus/NexusActionContext.h => Nex/NexActionContext.h} (98%) rename src/Ai/Dungeon/{Nexus/Action/NexusActions.cpp => Nex/NexActions.cpp} (99%) rename src/Ai/Dungeon/{Nexus/Action/NexusActions.h => Nex/NexActions.h} (98%) rename src/Ai/Dungeon/{Nexus/Multiplier/NexusMultipliers.cpp => Nex/NexMultipliers.cpp} (97%) rename src/Ai/Dungeon/{Nexus/Multiplier/NexusMultipliers.h => Nex/NexMultipliers.h} (100%) rename src/Ai/Dungeon/{Nexus/Strategy/NexusStrategy.cpp => Nex/NexStrategy.cpp} (97%) rename src/Ai/Dungeon/{Nexus/Strategy/NexusStrategy.h => Nex/NexStrategy.h} (100%) rename src/Ai/Dungeon/{Nexus/NexusTriggerContext.h => Nex/NexTriggerContext.h} (98%) rename src/Ai/Dungeon/{Nexus/Trigger/NexusTriggers.cpp => Nex/NexTriggers.cpp} (99%) rename src/Ai/Dungeon/{Nexus/Trigger/NexusTriggers.h => Nex/NexTriggers.h} (100%) rename src/Ai/Dungeon/{Oculus/OculusActionContext.h => OC/OCActionContext.h} (98%) rename src/Ai/Dungeon/{Oculus/Action/OculusActions.cpp => OC/OCActions.cpp} (99%) rename src/Ai/Dungeon/{Oculus/Action/OculusActions.h => OC/OCActions.h} (100%) rename src/Ai/Dungeon/{Oculus/Multiplier/OculusMultipliers.cpp => OC/OCMultipliers.cpp} (97%) rename src/Ai/Dungeon/{Oculus/Multiplier/OculusMultipliers.h => OC/OCMultipliers.h} (100%) rename src/Ai/Dungeon/{Oculus/Strategy/OculusStrategy.cpp => OC/OCStrategy.cpp} (96%) rename src/Ai/Dungeon/{Oculus/Strategy/OculusStrategy.h => OC/OCStrategy.h} (100%) rename src/Ai/Dungeon/{Oculus/OculusTriggerContext.h => OC/OCTriggerContext.h} (98%) rename src/Ai/Dungeon/{Oculus/Trigger/OculusTriggers.cpp => OC/OCTriggers.cpp} (98%) rename src/Ai/Dungeon/{Oculus/Trigger/OculusTriggers.h => OC/OCTriggers.h} (100%) rename src/Ai/Dungeon/{PitOfSaron/PitOfSaronActionContext.h => PoS/PoSActionContext.h} (95%) rename src/Ai/Dungeon/{PitOfSaron/Action/PitOfSaronActions.cpp => PoS/PoSActions.cpp} (99%) rename src/Ai/Dungeon/{PitOfSaron/Action/PitOfSaronActions.h => PoS/PoSActions.h} (96%) rename src/Ai/Dungeon/{PitOfSaron/Multiplier/PitOfSaronMultipliers.cpp => PoS/PoSMultipliers.cpp} (93%) rename src/Ai/Dungeon/{PitOfSaron/Multiplier/PitOfSaronMultipliers.h => PoS/PoSMultipliers.h} (100%) rename src/Ai/Dungeon/{PitOfSaron/Strategy/PitOfSaronStrategy.cpp => PoS/PoSStrategy.cpp} (87%) rename src/Ai/Dungeon/{PitOfSaron/Strategy/PitOfSaronStrategy.h => PoS/PoSStrategy.h} (100%) rename src/Ai/Dungeon/{PitOfSaron/PitOfSaronTriggerContext.h => PoS/PoSTriggerContext.h} (95%) rename src/Ai/Dungeon/{PitOfSaron/Trigger/PitOfSaronTriggers.cpp => PoS/PoSTriggers.cpp} (92%) rename src/Ai/Dungeon/{PitOfSaron/Trigger/PitOfSaronTriggers.h => PoS/PoSTriggers.h} (100%) rename src/Ai/Dungeon/{TrialOfTheChampion/TrialOfTheChampionActionContext.h => TOC/TOCActionContext.h} (96%) rename src/Ai/Dungeon/{TrialOfTheChampion/Action/TrialOfTheChampionActions.cpp => TOC/TOCActions.cpp} (99%) rename src/Ai/Dungeon/{TrialOfTheChampion/Action/TrialOfTheChampionActions.h => TOC/TOCActions.h} (97%) rename src/Ai/Dungeon/{TrialOfTheChampion/Multiplier/TrialOfTheChampionMultipliers.cpp => TOC/TOCMultipliers.cpp} (59%) rename src/Ai/Dungeon/{TrialOfTheChampion/Multiplier/TrialOfTheChampionMultipliers.h => TOC/TOCMultipliers.h} (100%) rename src/Ai/Dungeon/{TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.cpp => TOC/TOCStrategy.cpp} (94%) rename src/Ai/Dungeon/{TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.h => TOC/TOCStrategy.h} (92%) rename src/Ai/Dungeon/{TrialOfTheChampion/TrialOfTheChampionTriggerContext.h => TOC/TOCTriggerContext.h} (96%) rename src/Ai/Dungeon/{TrialOfTheChampion/Trigger/TrialOfTheChampionTriggers.cpp => TOC/TOCTriggers.cpp} (97%) rename src/Ai/Dungeon/{TrialOfTheChampion/Trigger/TrialOfTheChampionTriggers.h => TOC/TOCTriggers.h} (100%) rename src/Ai/Dungeon/{UtgardeKeep/UtgardeKeepActionContext.h => UK/UKActionContext.h} (98%) rename src/Ai/Dungeon/{UtgardeKeep/Action/UtgardeKeepActions.cpp => UK/UKActions.cpp} (98%) rename src/Ai/Dungeon/{UtgardeKeep/Action/UtgardeKeepActions.h => UK/UKActions.h} (97%) rename src/Ai/Dungeon/{UtgardeKeep/Multiplier/UtgardeKeepMultipliers.cpp => UK/UKMultipliers.cpp} (96%) rename src/Ai/Dungeon/{UtgardeKeep/Multiplier/UtgardeKeepMultipliers.h => UK/UKMultipliers.h} (100%) rename src/Ai/Dungeon/{UtgardeKeep/Strategy/UtgardeKeepStrategy.cpp => UK/UKStrategy.cpp} (96%) rename src/Ai/Dungeon/{UtgardeKeep/Strategy/UtgardeKeepStrategy.h => UK/UKStrategy.h} (100%) rename src/Ai/Dungeon/{UtgardeKeep/UtgardeKeepTriggerContext.h => UK/UKTriggerContext.h} (98%) rename src/Ai/Dungeon/{UtgardeKeep/Trigger/UtgardeKeepTriggers.cpp => UK/UKTriggers.cpp} (98%) rename src/Ai/Dungeon/{UtgardeKeep/Trigger/UtgardeKeepTriggers.h => UK/UKTriggers.h} (100%) rename src/Ai/Dungeon/{UtgardePinnacle/UtgardePinnacleActionContext.h => UP/UPActionContext.h} (96%) rename src/Ai/Dungeon/{UtgardePinnacle/Action/UtgardePinnacleActions.cpp => UP/UPActions.cpp} (98%) rename src/Ai/Dungeon/{UtgardePinnacle/Action/UtgardePinnacleActions.h => UP/UPActions.h} (94%) rename src/Ai/Dungeon/{UtgardePinnacle/Multiplier/UtgardePinnacleMultipliers.cpp => UP/UPMultipliers.cpp} (96%) rename src/Ai/Dungeon/{UtgardePinnacle/Multiplier/UtgardePinnacleMultipliers.h => UP/UPMultipliers.h} (100%) rename src/Ai/Dungeon/{UtgardePinnacle/Strategy/UtgardePinnacleStrategy.cpp => UP/UPStrategy.cpp} (92%) rename src/Ai/Dungeon/{UtgardePinnacle/Strategy/UtgardePinnacleStrategy.h => UP/UPStrategy.h} (100%) rename src/Ai/Dungeon/{UtgardePinnacle/UtgardePinnacleTriggerContext.h => UP/UPTriggerContext.h} (95%) rename src/Ai/Dungeon/{UtgardePinnacle/Trigger/UtgardePinnacleTriggers.cpp => UP/UPTriggers.cpp} (98%) rename src/Ai/Dungeon/{UtgardePinnacle/Trigger/UtgardePinnacleTriggers.h => UP/UPTriggers.h} (100%) rename src/Ai/Dungeon/{VioletHold/VioletHoldActionContext.h => VH/VHActionContext.h} (97%) rename src/Ai/Dungeon/{VioletHold/Action/VioletHoldActions.cpp => VH/VHActions.cpp} (98%) rename src/Ai/Dungeon/{VioletHold/Action/VioletHoldActions.h => VH/VHActions.h} (97%) rename src/Ai/Dungeon/{VioletHold/Multiplier/VioletHoldMultipliers.cpp => VH/VHMultipliers.cpp} (93%) rename src/Ai/Dungeon/{VioletHold/Multiplier/VioletHoldMultipliers.h => VH/VHMultipliers.h} (100%) rename src/Ai/Dungeon/{VioletHold/Strategy/VioletHoldStrategy.cpp => VH/VHStrategy.cpp} (95%) rename src/Ai/Dungeon/{VioletHold/Strategy/VioletHoldStrategy.h => VH/VHStrategy.h} (100%) rename src/Ai/Dungeon/{VioletHold/VioletHoldTriggerContext.h => VH/VHTriggerContext.h} (97%) rename src/Ai/Dungeon/{VioletHold/Trigger/VioletHoldTriggers.cpp => VH/VHTriggers.cpp} (97%) rename src/Ai/Dungeon/{VioletHold/Trigger/VioletHoldTriggers.h => VH/VHTriggers.h} (100%) rename src/Ai/Raid/Aq20/{RaidAq20ActionContext.h => Aq20ActionContext.h} (93%) rename src/Ai/Raid/Aq20/{Action/RaidAq20Actions.cpp => Aq20Actions.cpp} (97%) rename src/Ai/Raid/Aq20/{Action/RaidAq20Actions.h => Aq20Actions.h} (100%) rename src/Ai/Raid/Aq20/{Strategy/RaidAq20Strategy.cpp => Aq20Strategy.cpp} (88%) rename src/Ai/Raid/Aq20/{Strategy/RaidAq20Strategy.h => Aq20Strategy.h} (100%) rename src/Ai/Raid/Aq20/{RaidAq20TriggerContext.h => Aq20TriggerContext.h} (93%) rename src/Ai/Raid/Aq20/{Trigger/RaidAq20Triggers.cpp => Aq20Triggers.cpp} (96%) rename src/Ai/Raid/Aq20/{Trigger/RaidAq20Triggers.h => Aq20Triggers.h} (100%) rename src/Ai/Raid/Aq20/{Util/RaidAq20Utils.cpp => Aq20Utils.cpp} (97%) rename src/Ai/Raid/Aq20/{Util/RaidAq20Utils.h => Aq20Utils.h} (100%) rename src/Ai/Raid/{BlackTemple/RaidBlackTempleActionContext.h => BT/BTActionContext.h} (99%) rename src/Ai/Raid/{BlackTemple/Action/RaidBlackTempleActions.cpp => BT/BTActions.cpp} (99%) rename src/Ai/Raid/{BlackTemple/Action/RaidBlackTempleActions.h => BT/BTActions.h} (99%) rename src/Ai/Raid/{BlackTemple/Util/RaidBlackTempleHelpers.cpp => BT/BTHelpers.cpp} (99%) rename src/Ai/Raid/{BlackTemple/Util/RaidBlackTempleHelpers.h => BT/BTHelpers.h} (100%) rename src/Ai/Raid/{BlackTemple/Multiplier/RaidBlackTempleMultipliers.cpp => BT/BTMultipliers.cpp} (99%) rename src/Ai/Raid/{BlackTemple/Multiplier/RaidBlackTempleMultipliers.h => BT/BTMultipliers.h} (100%) rename src/Ai/Raid/{BlackTemple/Strategy/RaidBlackTempleStrategy.cpp => BT/BTStrategy.cpp} (99%) rename src/Ai/Raid/{BlackTemple/Strategy/RaidBlackTempleStrategy.h => BT/BTStrategy.h} (100%) rename src/Ai/Raid/{BlackTemple/RaidBlackTempleTriggerContext.h => BT/BTTriggerContext.h} (99%) rename src/Ai/Raid/{BlackTemple/Trigger/RaidBlackTempleTriggers.cpp => BT/BTTriggers.cpp} (99%) rename src/Ai/Raid/{BlackTemple/Trigger/RaidBlackTempleTriggers.h => BT/BTTriggers.h} (100%) rename src/Ai/Raid/{BlackwingLair/RaidBwlActionContext.h => BWL/BWLActionContext.h} (97%) rename src/Ai/Raid/{BlackwingLair/Action/RaidBwlActions.cpp => BWL/BWLActions.cpp} (95%) rename src/Ai/Raid/{BlackwingLair/Action/RaidBwlActions.h => BWL/BWLActions.h} (100%) rename src/Ai/Raid/{BlackwingLair/Util/RaidBwlHelpers.cpp => BWL/BWLHelpers.cpp} (91%) rename src/Ai/Raid/{BlackwingLair/Util/RaidBwlHelpers.h => BWL/BWLHelpers.h} (100%) rename src/Ai/Raid/{BlackwingLair/Strategy/RaidBwlStrategy.cpp => BWL/BWLStrategy.cpp} (96%) rename src/Ai/Raid/{BlackwingLair/Strategy/RaidBwlStrategy.h => BWL/BWLStrategy.h} (100%) rename src/Ai/Raid/{BlackwingLair/RaidBwlTriggerContext.h => BWL/BWLTriggerContext.h} (97%) rename src/Ai/Raid/{BlackwingLair/Trigger/RaidBwlTriggers.cpp => BWL/BWLTriggers.cpp} (94%) rename src/Ai/Raid/{BlackwingLair/Trigger/RaidBwlTriggers.h => BWL/BWLTriggers.h} (100%) rename src/Ai/Raid/{EyeOfEternity/RaidEoEActionContext.h => EoE/EoEActionContext.h} (97%) rename src/Ai/Raid/{EyeOfEternity/Action/RaidEoEActions.cpp => EoE/EoEActions.cpp} (99%) rename src/Ai/Raid/{EyeOfEternity/Action/RaidEoEActions.h => EoE/EoEActions.h} (100%) rename src/Ai/Raid/{EyeOfEternity/Multiplier/RaidEoEMultipliers.cpp => EoE/EoEMultipliers.cpp} (95%) rename src/Ai/Raid/{EyeOfEternity/Multiplier/RaidEoEMultipliers.h => EoE/EoEMultipliers.h} (100%) rename src/Ai/Raid/{EyeOfEternity/Strategy/RaidEoEStrategy.cpp => EoE/EoEStrategy.cpp} (91%) rename src/Ai/Raid/{EyeOfEternity/Strategy/RaidEoEStrategy.h => EoE/EoEStrategy.h} (100%) rename src/Ai/Raid/{EyeOfEternity/RaidEoETriggerContext.h => EoE/EoETriggerContext.h} (95%) rename src/Ai/Raid/{EyeOfEternity/Trigger/RaidEoETriggers.cpp => EoE/EoETriggers.cpp} (97%) rename src/Ai/Raid/{EyeOfEternity/Trigger/RaidEoETriggers.h => EoE/EoETriggers.h} (100%) rename src/Ai/Raid/{GruulsLair/RaidGruulsLairActionContext.h => Gruul/GruulActionContext.h} (99%) rename src/Ai/Raid/{GruulsLair/Action/RaidGruulsLairActions.cpp => Gruul/GruulActions.cpp} (99%) rename src/Ai/Raid/{GruulsLair/Action/RaidGruulsLairActions.h => Gruul/GruulActions.h} (100%) rename src/Ai/Raid/{GruulsLair/Util/RaidGruulsLairHelpers.cpp => Gruul/GruulHelpers.cpp} (99%) rename src/Ai/Raid/{GruulsLair/Util/RaidGruulsLairHelpers.h => Gruul/GruulHelpers.h} (100%) rename src/Ai/Raid/{GruulsLair/Multiplier/RaidGruulsLairMultipliers.cpp => Gruul/GruulMultipliers.cpp} (96%) rename src/Ai/Raid/{GruulsLair/Multiplier/RaidGruulsLairMultipliers.h => Gruul/GruulMultipliers.h} (100%) rename src/Ai/Raid/{GruulsLair/Strategy/RaidGruulsLairStrategy.cpp => Gruul/GruulStrategy.cpp} (97%) rename src/Ai/Raid/{GruulsLair/Strategy/RaidGruulsLairStrategy.h => Gruul/GruulStrategy.h} (100%) rename src/Ai/Raid/{GruulsLair/RaidGruulsLairTriggerContext.h => Gruul/GruulTriggerContext.h} (99%) rename src/Ai/Raid/{GruulsLair/Trigger/RaidGruulsLairTriggers.cpp => Gruul/GruulTriggers.cpp} (98%) rename src/Ai/Raid/{GruulsLair/Trigger/RaidGruulsLairTriggers.h => Gruul/GruulTriggers.h} (100%) rename src/Ai/Raid/{HyjalSummit/RaidHyjalSummitActionContext.h => Hyjal/HyjalActionContext.h} (99%) rename src/Ai/Raid/{HyjalSummit/Action/RaidHyjalSummitActions.cpp => Hyjal/HyjalActions.cpp} (99%) rename src/Ai/Raid/{HyjalSummit/Action/RaidHyjalSummitActions.h => Hyjal/HyjalActions.h} (100%) rename src/Ai/Raid/{HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp => Hyjal/HyjalMultipliers.cpp} (98%) rename src/Ai/Raid/{HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h => Hyjal/HyjalMultipliers.h} (100%) rename src/Ai/Raid/{HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp => Hyjal/HyjalStrategy.cpp} (98%) rename src/Ai/Raid/{HyjalSummit/Strategy/RaidHyjalSummitStrategy.h => Hyjal/HyjalStrategy.h} (100%) rename src/Ai/Raid/{HyjalSummit/RaidHyjalSummitTriggerContext.h => Hyjal/HyjalTriggerContext.h} (99%) rename src/Ai/Raid/{HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp => Hyjal/HyjalTriggers.cpp} (98%) rename src/Ai/Raid/{HyjalSummit/Trigger/RaidHyjalSummitTriggers.h => Hyjal/HyjalTriggers.h} (100%) rename src/Ai/Raid/{HyjalSummit/Util/RaidHyjalSummitHelpers.cpp => Hyjal/Util/HyjalHelpers.cpp} (99%) rename src/Ai/Raid/{HyjalSummit/Util/RaidHyjalSummitHelpers.h => Hyjal/Util/HyjalHelpers.h} (100%) rename src/Ai/Raid/{HyjalSummit/Util/RaidHyjalSummitScripts.cpp => Hyjal/Util/HyjalScripts.cpp} (99%) rename src/Ai/Raid/{Karazhan/RaidKarazhanActionContext.h => Kara/KaraActionContext.h} (99%) rename src/Ai/Raid/{Karazhan/Action/RaidKarazhanActions.cpp => Kara/KaraActions.cpp} (99%) rename src/Ai/Raid/{Karazhan/Action/RaidKarazhanActions.h => Kara/KaraActions.h} (100%) rename src/Ai/Raid/{Karazhan/Util/RaidKarazhanHelpers.cpp => Kara/KaraHelpers.cpp} (99%) rename src/Ai/Raid/{Karazhan/Util/RaidKarazhanHelpers.h => Kara/KaraHelpers.h} (100%) rename src/Ai/Raid/{Karazhan/Multiplier/RaidKarazhanMultipliers.cpp => Kara/KaraMultipliers.cpp} (99%) rename src/Ai/Raid/{Karazhan/Multiplier/RaidKarazhanMultipliers.h => Kara/KaraMultipliers.h} (100%) rename src/Ai/Raid/{Karazhan/Strategy/RaidKarazhanStrategy.cpp => Kara/KaraStrategy.cpp} (99%) rename src/Ai/Raid/{Karazhan/Strategy/RaidKarazhanStrategy.h => Kara/KaraStrategy.h} (100%) rename src/Ai/Raid/{Karazhan/RaidKarazhanTriggerContext.h => Kara/KaraTriggerContext.h} (99%) rename src/Ai/Raid/{Karazhan/Trigger/RaidKarazhanTriggers.cpp => Kara/KaraTriggers.cpp} (99%) rename src/Ai/Raid/{Karazhan/Trigger/RaidKarazhanTriggers.h => Kara/KaraTriggers.h} (100%) rename src/Ai/Raid/{MoltenCore/RaidMcActionContext.h => MC/MCActionContext.h} (99%) rename src/Ai/Raid/{MoltenCore/Action/RaidMcActions.cpp => MC/MCActions.cpp} (99%) rename src/Ai/Raid/{MoltenCore/Action/RaidMcActions.h => MC/MCActions.h} (100%) rename src/Ai/Raid/{MoltenCore/Util/RaidMcHelpers.h => MC/MCHelpers.h} (100%) rename src/Ai/Raid/{MoltenCore/Multiplier/RaidMcMultipliers.cpp => MC/MCMultipliers.cpp} (97%) rename src/Ai/Raid/{MoltenCore/Multiplier/RaidMcMultipliers.h => MC/MCMultipliers.h} (100%) rename src/Ai/Raid/{MoltenCore/Strategy/RaidMcStrategy.cpp => MC/MCStrategy.cpp} (98%) rename src/Ai/Raid/{MoltenCore/Strategy/RaidMcStrategy.h => MC/MCStrategy.h} (100%) rename src/Ai/Raid/{MoltenCore/RaidMcTriggerContext.h => MC/MCTriggerContext.h} (99%) rename src/Ai/Raid/{MoltenCore/Trigger/RaidMcTriggers.cpp => MC/MCTriggers.cpp} (95%) rename src/Ai/Raid/{MoltenCore/Trigger/RaidMcTriggers.h => MC/MCTriggers.h} (100%) rename src/Ai/Raid/{Magtheridon/RaidMagtheridonActionContext.h => Mag/MagActionContext.h} (98%) rename src/Ai/Raid/{Magtheridon/Action/RaidMagtheridonActions.cpp => Mag/MagActions.cpp} (99%) rename src/Ai/Raid/{Magtheridon/Action/RaidMagtheridonActions.h => Mag/MagActions.h} (98%) rename src/Ai/Raid/{Magtheridon/Util/RaidMagtheridonHelpers.cpp => Mag/MagHelpers.cpp} (99%) rename src/Ai/Raid/{Magtheridon/Util/RaidMagtheridonHelpers.h => Mag/MagHelpers.h} (100%) rename src/Ai/Raid/{Magtheridon/Multiplier/RaidMagtheridonMultipliers.cpp => Mag/MagMultipliers.cpp} (95%) rename src/Ai/Raid/{Magtheridon/Multiplier/RaidMagtheridonMultipliers.h => Mag/MagMultipliers.h} (100%) rename src/Ai/Raid/{Magtheridon/Strategy/RaidMagtheridonStrategy.cpp => Mag/MagStrategy.cpp} (96%) rename src/Ai/Raid/{Magtheridon/Strategy/RaidMagtheridonStrategy.h => Mag/MagStrategy.h} (100%) rename src/Ai/Raid/{Magtheridon/RaidMagtheridonTriggerContext.h => Mag/MagTriggerContext.h} (98%) rename src/Ai/Raid/{Magtheridon/Trigger/RaidMagtheridonTriggers.cpp => Mag/MagTriggers.cpp} (98%) rename src/Ai/Raid/{Magtheridon/Trigger/RaidMagtheridonTriggers.h => Mag/MagTriggers.h} (100%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions.h => Naxx/Action/NaxxActions.h} (99%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp => Naxx/Action/NaxxActions_Anubrekhan.cpp} (98%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Faerlina.cpp => Naxx/Action/NaxxActions_Faerlina.cpp} (60%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_FourHorsemen.cpp => Naxx/Action/NaxxActions_FourHorsemen.cpp} (98%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Gluth.cpp => Naxx/Action/NaxxActions_Gluth.cpp} (99%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Gothik.cpp => Naxx/Action/NaxxActions_Gothik.cpp} (59%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Grobbulus.cpp => Naxx/Action/NaxxActions_Grobbulus.cpp} (97%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Heigan.cpp => Naxx/Action/NaxxActions_Heigan.cpp} (97%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Kelthuzad.cpp => Naxx/Action/NaxxActions_Kelthuzad.cpp} (99%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Loatheb.cpp => Naxx/Action/NaxxActions_Loatheb.cpp} (98%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Maexxna.cpp => Naxx/Action/NaxxActions_Maexxna.cpp} (59%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Noth.cpp => Naxx/Action/NaxxActions_Noth.cpp} (57%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Patchwerk.cpp => Naxx/Action/NaxxActions_Patchwerk.cpp} (97%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Razuvious.cpp => Naxx/Action/NaxxActions_Razuvious.cpp} (99%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp => Naxx/Action/NaxxActions_Sapphiron.cpp} (97%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Shared.cpp => Naxx/Action/NaxxActions_Shared.cpp} (93%) rename src/Ai/Raid/{Naxxramas/Action/RaidNaxxActions_Thaddius.cpp => Naxx/Action/NaxxActions_Thaddius.cpp} (98%) rename src/Ai/Raid/{Naxxramas/RaidNaxxActionContext.h => Naxx/NaxxActionContext.h} (99%) rename src/Ai/Raid/{Naxxramas/Util/RaidNaxxBossHelper.h => Naxx/NaxxBossHelper.h} (99%) rename src/Ai/Raid/{Naxxramas/Multiplier/RaidNaxxMultipliers.cpp => Naxx/NaxxMultipliers.cpp} (99%) rename src/Ai/Raid/{Naxxramas/Multiplier/RaidNaxxMultipliers.h => Naxx/NaxxMultipliers.h} (98%) rename src/Ai/Raid/{Naxxramas/Util/RaidNaxxSpellIds.h => Naxx/NaxxSpellIds.h} (100%) rename src/Ai/Raid/{Naxxramas/Strategy/RaidNaxxStrategy.cpp => Naxx/NaxxStrategy.cpp} (98%) rename src/Ai/Raid/{Naxxramas/Strategy/RaidNaxxStrategy.h => Naxx/NaxxStrategy.h} (100%) rename src/Ai/Raid/{Naxxramas/RaidNaxxTriggerContext.h => Naxx/NaxxTriggerContext.h} (99%) rename src/Ai/Raid/{Naxxramas/Trigger/RaidNaxxTriggers.cpp => Naxx/NaxxTriggers.cpp} (99%) rename src/Ai/Raid/{Naxxramas/Trigger/RaidNaxxTriggers.h => Naxx/NaxxTriggers.h} (99%) rename src/Ai/Raid/{ObsidianSanctum/RaidOsActionContext.h => OS/OSActionContext.h} (98%) rename src/Ai/Raid/{ObsidianSanctum/Action/RaidOsActions.cpp => OS/OSActions.cpp} (99%) rename src/Ai/Raid/{ObsidianSanctum/Action/RaidOsActions.h => OS/OSActions.h} (100%) rename src/Ai/Raid/{ObsidianSanctum/Multiplier/RaidOsMultipliers.cpp => OS/OSMultipliers.cpp} (94%) rename src/Ai/Raid/{ObsidianSanctum/Multiplier/RaidOsMultipliers.h => OS/OSMultipliers.h} (100%) rename src/Ai/Raid/{ObsidianSanctum/Strategy/RaidOsStrategy.cpp => OS/OSStrategy.cpp} (95%) rename src/Ai/Raid/{ObsidianSanctum/Strategy/RaidOsStrategy.h => OS/OSStrategy.h} (100%) rename src/Ai/Raid/{ObsidianSanctum/RaidOsTriggerContext.h => OS/OSTriggerContext.h} (98%) rename src/Ai/Raid/{ObsidianSanctum/Trigger/RaidOsTriggers.cpp => OS/OSTriggers.cpp} (99%) rename src/Ai/Raid/{ObsidianSanctum/Trigger/RaidOsTriggers.h => OS/OSTriggers.h} (100%) rename src/Ai/Raid/{Onyxia/RaidOnyxiaActionContext.h => Ony/OnyActionContext.h} (97%) rename src/Ai/Raid/{Onyxia/Action/RaidOnyxiaActions.cpp => Ony/OnyActions.cpp} (99%) rename src/Ai/Raid/{Onyxia/Action/RaidOnyxiaActions.h => Ony/OnyActions.h} (100%) rename src/Ai/Raid/{Onyxia/Strategy/RaidOnyxiaStrategy.cpp => Ony/OnyStrategy.cpp} (96%) rename src/Ai/Raid/{Onyxia/Strategy/RaidOnyxiaStrategy.h => Ony/OnyStrategy.h} (100%) rename src/Ai/Raid/{Onyxia/RaidOnyxiaTriggerContext.h => Ony/OnyTriggerContext.h} (97%) rename src/Ai/Raid/{Onyxia/Trigger/RaidOnyxiaTriggers.cpp => Ony/OnyTriggers.cpp} (99%) rename src/Ai/Raid/{Onyxia/Trigger/RaidOnyxiaTriggers.h => Ony/OnyTriggers.h} (100%) rename src/Ai/Raid/{SerpentshrineCavern/RaidSSCActionContext.h => SSC/SSCActionContext.h} (99%) rename src/Ai/Raid/{SerpentshrineCavern/Action/RaidSSCActions.cpp => SSC/SSCActions.cpp} (99%) rename src/Ai/Raid/{SerpentshrineCavern/Action/RaidSSCActions.h => SSC/SSCActions.h} (100%) rename src/Ai/Raid/{SerpentshrineCavern/Util/RaidSSCHelpers.cpp => SSC/SSCHelpers.cpp} (99%) rename src/Ai/Raid/{SerpentshrineCavern/Util/RaidSSCHelpers.h => SSC/SSCHelpers.h} (100%) rename src/Ai/Raid/{SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp => SSC/SSCMultipliers.cpp} (99%) rename src/Ai/Raid/{SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h => SSC/SSCMultipliers.h} (100%) rename src/Ai/Raid/{SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp => SSC/SSCStrategy.cpp} (99%) rename src/Ai/Raid/{SerpentshrineCavern/Strategy/RaidSSCStrategy.h => SSC/SSCStrategy.h} (100%) rename src/Ai/Raid/{SerpentshrineCavern/RaidSSCTriggerContext.h => SSC/SSCTriggerContext.h} (99%) rename src/Ai/Raid/{SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp => SSC/SSCTriggers.cpp} (99%) rename src/Ai/Raid/{SerpentshrineCavern/Trigger/RaidSSCTriggers.h => SSC/SSCTriggers.h} (100%) rename src/Ai/Raid/{TempestKeep/RaidTempestKeepActionContext.h => TK/TKActionContext.h} (99%) rename src/Ai/Raid/{TempestKeep/Action/RaidTempestKeepActions.cpp => TK/TKActions.cpp} (99%) rename src/Ai/Raid/{TempestKeep/Action/RaidTempestKeepActions.h => TK/TKActions.h} (99%) rename src/Ai/Raid/{TempestKeep/Multiplier/RaidTempestKeepMultipliers.cpp => TK/TKMultipliers.cpp} (98%) rename src/Ai/Raid/{TempestKeep/Multiplier/RaidTempestKeepMultipliers.h => TK/TKMultipliers.h} (100%) rename src/Ai/Raid/{TempestKeep/Strategy/RaidTempestKeepStrategy.cpp => TK/TKStrategy.cpp} (99%) rename src/Ai/Raid/{TempestKeep/Strategy/RaidTempestKeepStrategy.h => TK/TKStrategy.h} (100%) rename src/Ai/Raid/{TempestKeep/RaidTempestKeepTriggerContext.h => TK/TKTriggerContext.h} (99%) rename src/Ai/Raid/{TempestKeep/Trigger/RaidTempestKeepTriggers.cpp => TK/TKTriggers.cpp} (99%) rename src/Ai/Raid/{TempestKeep/Trigger/RaidTempestKeepTriggers.h => TK/TKTriggers.h} (100%) rename src/Ai/Raid/{TempestKeep/Util/RaidTempestKeepHelpers.cpp => TK/Util/TKHelpers.cpp} (99%) rename src/Ai/Raid/{TempestKeep/Util/RaidTempestKeepHelpers.h => TK/Util/TKHelpers.h} (100%) rename src/Ai/Raid/{TempestKeep/Util/RaidTempestKeepKaelthasBossAI.h => TK/Util/TKKaelthasBossAI.h} (100%) rename src/Ai/Raid/{TempestKeep/Util/RaidTempestKeepScripts.cpp => TK/Util/TKScripts.cpp} (97%) rename src/Ai/Raid/{Ulduar/RaidUlduarActionContext.h => Uld/UldActionContext.h} (99%) rename src/Ai/Raid/{Ulduar/Action/RaidUlduarActions.cpp => Uld/UldActions.cpp} (99%) rename src/Ai/Raid/{Ulduar/Action/RaidUlduarActions.h => Uld/UldActions.h} (99%) rename src/Ai/Raid/{Ulduar/Strategy/RaidUlduarStrategy.cpp => Uld/UldStrategy.cpp} (99%) rename src/Ai/Raid/{Ulduar/Strategy/RaidUlduarStrategy.h => Uld/UldStrategy.h} (100%) rename src/Ai/Raid/{Ulduar/RaidUlduarTriggerContext.h => Uld/UldTriggerContext.h} (99%) rename src/Ai/Raid/{Ulduar/Trigger/RaidUlduarTriggers.cpp => Uld/UldTriggers.cpp} (99%) rename src/Ai/Raid/{Ulduar/Trigger/RaidUlduarTriggers.h => Uld/UldTriggers.h} (99%) rename src/Ai/Raid/{Ulduar/Util/RaidUlduarBossHelper.cpp => Uld/Util/UldBossHelper.cpp} (99%) rename src/Ai/Raid/{Ulduar/Util/RaidUlduarBossHelper.h => Uld/Util/UldBossHelper.h} (100%) rename src/Ai/Raid/{Ulduar/Util/RaidUlduarScripts.h => Uld/Util/UldScripts.h} (100%) rename src/Ai/Raid/{VaultOfArchavon/RaidVoAActionContext.h => VoA/VoAActionContext.h} (98%) rename src/Ai/Raid/{VaultOfArchavon/Action/RaidVoAActions.cpp => VoA/VoAActions.cpp} (99%) rename src/Ai/Raid/{VaultOfArchavon/Action/RaidVoAActions.h => VoA/VoAActions.h} (100%) rename src/Ai/Raid/{VaultOfArchavon/Strategy/RaidVoAStrategy.cpp => VoA/VoAStrategy.cpp} (97%) rename src/Ai/Raid/{VaultOfArchavon/Strategy/RaidVoAStrategy.h => VoA/VoAStrategy.h} (100%) rename src/Ai/Raid/{VaultOfArchavon/RaidVoATriggerContext.h => VoA/VoATriggerContext.h} (98%) rename src/Ai/Raid/{VaultOfArchavon/Trigger/RaidVoATriggers.cpp => VoA/VoATriggers.cpp} (99%) rename src/Ai/Raid/{VaultOfArchavon/Trigger/RaidVoATriggers.h => VoA/VoATriggers.h} (100%) rename src/Ai/Raid/{ZulAman/RaidZulAmanActionContext.h => ZA/ZAActionContext.h} (99%) rename src/Ai/Raid/{ZulAman/Action/RaidZulAmanActions.cpp => ZA/ZAActions.cpp} (99%) rename src/Ai/Raid/{ZulAman/Action/RaidZulAmanActions.h => ZA/ZAActions.h} (100%) rename src/Ai/Raid/{ZulAman/Util/RaidZulAmanHelpers.cpp => ZA/ZAHelpers.cpp} (99%) rename src/Ai/Raid/{ZulAman/Util/RaidZulAmanHelpers.h => ZA/ZAHelpers.h} (100%) rename src/Ai/Raid/{ZulAman/Multiplier/RaidZulAmanMultipliers.cpp => ZA/ZAMultipliers.cpp} (99%) rename src/Ai/Raid/{ZulAman/Multiplier/RaidZulAmanMultipliers.h => ZA/ZAMultipliers.h} (100%) rename src/Ai/Raid/{ZulAman/Strategy/RaidZulAmanStrategy.cpp => ZA/ZAStrategy.cpp} (98%) rename src/Ai/Raid/{ZulAman/Strategy/RaidZulAmanStrategy.h => ZA/ZAStrategy.h} (100%) rename src/Ai/Raid/{ZulAman/RaidZulAmanTriggerContext.h => ZA/ZATriggerContext.h} (99%) rename src/Ai/Raid/{ZulAman/Trigger/RaidZulAmanTriggers.cpp => ZA/ZATriggers.cpp} (98%) rename src/Ai/Raid/{ZulAman/Trigger/RaidZulAmanTriggers.h => ZA/ZATriggers.h} (100%) diff --git a/src/Ai/Class/Dk/Action/DKActions.cpp b/src/Ai/Class/Dk/DKActions.cpp similarity index 100% rename from src/Ai/Class/Dk/Action/DKActions.cpp rename to src/Ai/Class/Dk/DKActions.cpp diff --git a/src/Ai/Class/Dk/Action/DKActions.h b/src/Ai/Class/Dk/DKActions.h similarity index 100% rename from src/Ai/Class/Dk/Action/DKActions.h rename to src/Ai/Class/Dk/DKActions.h diff --git a/src/Ai/Class/Dk/Trigger/DKTriggers.cpp b/src/Ai/Class/Dk/DKTriggers.cpp similarity index 100% rename from src/Ai/Class/Dk/Trigger/DKTriggers.cpp rename to src/Ai/Class/Dk/DKTriggers.cpp diff --git a/src/Ai/Class/Dk/Trigger/DKTriggers.h b/src/Ai/Class/Dk/DKTriggers.h similarity index 100% rename from src/Ai/Class/Dk/Trigger/DKTriggers.h rename to src/Ai/Class/Dk/DKTriggers.h diff --git a/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp b/src/Ai/Class/Druid/DruidTriggers.cpp similarity index 100% rename from src/Ai/Class/Druid/Trigger/DruidTriggers.cpp rename to src/Ai/Class/Druid/DruidTriggers.cpp diff --git a/src/Ai/Class/Druid/Trigger/DruidTriggers.h b/src/Ai/Class/Druid/DruidTriggers.h similarity index 100% rename from src/Ai/Class/Druid/Trigger/DruidTriggers.h rename to src/Ai/Class/Druid/DruidTriggers.h diff --git a/src/Ai/Class/Hunter/Action/HunterActions.cpp b/src/Ai/Class/Hunter/HunterActions.cpp similarity index 100% rename from src/Ai/Class/Hunter/Action/HunterActions.cpp rename to src/Ai/Class/Hunter/HunterActions.cpp diff --git a/src/Ai/Class/Hunter/Action/HunterActions.h b/src/Ai/Class/Hunter/HunterActions.h similarity index 100% rename from src/Ai/Class/Hunter/Action/HunterActions.h rename to src/Ai/Class/Hunter/HunterActions.h diff --git a/src/Ai/Class/Hunter/Trigger/HunterTriggers.cpp b/src/Ai/Class/Hunter/HunterTriggers.cpp similarity index 100% rename from src/Ai/Class/Hunter/Trigger/HunterTriggers.cpp rename to src/Ai/Class/Hunter/HunterTriggers.cpp diff --git a/src/Ai/Class/Hunter/Trigger/HunterTriggers.h b/src/Ai/Class/Hunter/HunterTriggers.h similarity index 100% rename from src/Ai/Class/Hunter/Trigger/HunterTriggers.h rename to src/Ai/Class/Hunter/HunterTriggers.h diff --git a/src/Ai/Class/Mage/Action/MageActions.cpp b/src/Ai/Class/Mage/MageActions.cpp similarity index 100% rename from src/Ai/Class/Mage/Action/MageActions.cpp rename to src/Ai/Class/Mage/MageActions.cpp diff --git a/src/Ai/Class/Mage/Action/MageActions.h b/src/Ai/Class/Mage/MageActions.h similarity index 100% rename from src/Ai/Class/Mage/Action/MageActions.h rename to src/Ai/Class/Mage/MageActions.h diff --git a/src/Ai/Class/Mage/Trigger/MageTriggers.cpp b/src/Ai/Class/Mage/MageTriggers.cpp similarity index 100% rename from src/Ai/Class/Mage/Trigger/MageTriggers.cpp rename to src/Ai/Class/Mage/MageTriggers.cpp diff --git a/src/Ai/Class/Mage/Trigger/MageTriggers.h b/src/Ai/Class/Mage/MageTriggers.h similarity index 100% rename from src/Ai/Class/Mage/Trigger/MageTriggers.h rename to src/Ai/Class/Mage/MageTriggers.h diff --git a/src/Ai/Class/Paladin/Action/PaladinActions.cpp b/src/Ai/Class/Paladin/Actions/PaladinActions.cpp similarity index 100% rename from src/Ai/Class/Paladin/Action/PaladinActions.cpp rename to src/Ai/Class/Paladin/Actions/PaladinActions.cpp diff --git a/src/Ai/Class/Paladin/Action/PaladinActions.h b/src/Ai/Class/Paladin/Actions/PaladinActions.h similarity index 100% rename from src/Ai/Class/Paladin/Action/PaladinActions.h rename to src/Ai/Class/Paladin/Actions/PaladinActions.h diff --git a/src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.cpp b/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp similarity index 100% rename from src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.cpp rename to src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp diff --git a/src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.h b/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.h similarity index 100% rename from src/Ai/Class/Paladin/Action/PaladinGreaterBlessingAction.h rename to src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.h diff --git a/src/Ai/Class/Paladin/Util/PaladinHelper.h b/src/Ai/Class/Paladin/PaladinHelper.h similarity index 100% rename from src/Ai/Class/Paladin/Util/PaladinHelper.h rename to src/Ai/Class/Paladin/PaladinHelper.h diff --git a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp b/src/Ai/Class/Paladin/PaladinTriggers.cpp similarity index 100% rename from src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp rename to src/Ai/Class/Paladin/PaladinTriggers.cpp diff --git a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h b/src/Ai/Class/Paladin/PaladinTriggers.h similarity index 100% rename from src/Ai/Class/Paladin/Trigger/PaladinTriggers.h rename to src/Ai/Class/Paladin/PaladinTriggers.h diff --git a/src/Ai/Class/Priest/Action/PriestActions.cpp b/src/Ai/Class/Priest/PriestActions.cpp similarity index 100% rename from src/Ai/Class/Priest/Action/PriestActions.cpp rename to src/Ai/Class/Priest/PriestActions.cpp diff --git a/src/Ai/Class/Priest/Action/PriestActions.h b/src/Ai/Class/Priest/PriestActions.h similarity index 100% rename from src/Ai/Class/Priest/Action/PriestActions.h rename to src/Ai/Class/Priest/PriestActions.h diff --git a/src/Ai/Class/Priest/Trigger/PriestTriggers.cpp b/src/Ai/Class/Priest/PriestTriggers.cpp similarity index 100% rename from src/Ai/Class/Priest/Trigger/PriestTriggers.cpp rename to src/Ai/Class/Priest/PriestTriggers.cpp diff --git a/src/Ai/Class/Priest/Trigger/PriestTriggers.h b/src/Ai/Class/Priest/PriestTriggers.h similarity index 100% rename from src/Ai/Class/Priest/Trigger/PriestTriggers.h rename to src/Ai/Class/Priest/PriestTriggers.h diff --git a/src/Ai/Class/Rogue/Trigger/RogueTriggers.cpp b/src/Ai/Class/Rogue/RogueTriggers.cpp similarity index 100% rename from src/Ai/Class/Rogue/Trigger/RogueTriggers.cpp rename to src/Ai/Class/Rogue/RogueTriggers.cpp diff --git a/src/Ai/Class/Rogue/Trigger/RogueTriggers.h b/src/Ai/Class/Rogue/RogueTriggers.h similarity index 100% rename from src/Ai/Class/Rogue/Trigger/RogueTriggers.h rename to src/Ai/Class/Rogue/RogueTriggers.h diff --git a/src/Ai/Class/Shaman/Action/ShamanActions.cpp b/src/Ai/Class/Shaman/ShamanActions.cpp similarity index 100% rename from src/Ai/Class/Shaman/Action/ShamanActions.cpp rename to src/Ai/Class/Shaman/ShamanActions.cpp diff --git a/src/Ai/Class/Shaman/Action/ShamanActions.h b/src/Ai/Class/Shaman/ShamanActions.h similarity index 100% rename from src/Ai/Class/Shaman/Action/ShamanActions.h rename to src/Ai/Class/Shaman/ShamanActions.h diff --git a/src/Ai/Class/Shaman/Trigger/ShamanTriggers.cpp b/src/Ai/Class/Shaman/ShamanTriggers.cpp similarity index 100% rename from src/Ai/Class/Shaman/Trigger/ShamanTriggers.cpp rename to src/Ai/Class/Shaman/ShamanTriggers.cpp diff --git a/src/Ai/Class/Shaman/Trigger/ShamanTriggers.h b/src/Ai/Class/Shaman/ShamanTriggers.h similarity index 100% rename from src/Ai/Class/Shaman/Trigger/ShamanTriggers.h rename to src/Ai/Class/Shaman/ShamanTriggers.h diff --git a/src/Ai/Class/Warlock/Action/WarlockActions.cpp b/src/Ai/Class/Warlock/WarlockActions.cpp similarity index 100% rename from src/Ai/Class/Warlock/Action/WarlockActions.cpp rename to src/Ai/Class/Warlock/WarlockActions.cpp diff --git a/src/Ai/Class/Warlock/Action/WarlockActions.h b/src/Ai/Class/Warlock/WarlockActions.h similarity index 100% rename from src/Ai/Class/Warlock/Action/WarlockActions.h rename to src/Ai/Class/Warlock/WarlockActions.h diff --git a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp b/src/Ai/Class/Warlock/WarlockTriggers.cpp similarity index 100% rename from src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp rename to src/Ai/Class/Warlock/WarlockTriggers.cpp diff --git a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.h b/src/Ai/Class/Warlock/WarlockTriggers.h similarity index 100% rename from src/Ai/Class/Warlock/Trigger/WarlockTriggers.h rename to src/Ai/Class/Warlock/WarlockTriggers.h diff --git a/src/Ai/Class/Warrior/Action/WarriorActions.cpp b/src/Ai/Class/Warrior/WarriorActions.cpp similarity index 100% rename from src/Ai/Class/Warrior/Action/WarriorActions.cpp rename to src/Ai/Class/Warrior/WarriorActions.cpp diff --git a/src/Ai/Class/Warrior/Action/WarriorActions.h b/src/Ai/Class/Warrior/WarriorActions.h similarity index 100% rename from src/Ai/Class/Warrior/Action/WarriorActions.h rename to src/Ai/Class/Warrior/WarriorActions.h diff --git a/src/Ai/Class/Warrior/Trigger/WarriorTriggers.cpp b/src/Ai/Class/Warrior/WarriorTriggers.cpp similarity index 100% rename from src/Ai/Class/Warrior/Trigger/WarriorTriggers.cpp rename to src/Ai/Class/Warrior/WarriorTriggers.cpp diff --git a/src/Ai/Class/Warrior/Trigger/WarriorTriggers.h b/src/Ai/Class/Warrior/WarriorTriggers.h similarity index 100% rename from src/Ai/Class/Warrior/Trigger/WarriorTriggers.h rename to src/Ai/Class/Warrior/WarriorTriggers.h diff --git a/src/Ai/Dungeon/AuchenaiCrypts/AuchenaiCryptsActionContext.h b/src/Ai/Dungeon/AC/ACActionContext.h similarity index 97% rename from src/Ai/Dungeon/AuchenaiCrypts/AuchenaiCryptsActionContext.h rename to src/Ai/Dungeon/AC/ACActionContext.h index 4eea5871611..40921e36168 100644 --- a/src/Ai/Dungeon/AuchenaiCrypts/AuchenaiCryptsActionContext.h +++ b/src/Ai/Dungeon/AC/ACActionContext.h @@ -3,7 +3,7 @@ #include "AiObjectContext.h" #include "Action.h" -#include "AuchenaiCryptsActions.h" +#include "ACActions.h" class TbcDungeonAuchenaiCryptsActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/AuchenaiCrypts/Action/AuchenaiCryptsActions.cpp b/src/Ai/Dungeon/AC/ACActions.cpp similarity index 98% rename from src/Ai/Dungeon/AuchenaiCrypts/Action/AuchenaiCryptsActions.cpp rename to src/Ai/Dungeon/AC/ACActions.cpp index 2ec7907c738..a5646d87756 100644 --- a/src/Ai/Dungeon/AuchenaiCrypts/Action/AuchenaiCryptsActions.cpp +++ b/src/Ai/Dungeon/AC/ACActions.cpp @@ -1,7 +1,7 @@ #include "Playerbots.h" #include "AiFactory.h" -#include "AuchenaiCryptsTriggers.h" -#include "AuchenaiCryptsActions.h" +#include "ACTriggers.h" +#include "ACActions.h" // Shirrak the Dead Watcher diff --git a/src/Ai/Dungeon/AuchenaiCrypts/Action/AuchenaiCryptsActions.h b/src/Ai/Dungeon/AC/ACActions.h similarity index 96% rename from src/Ai/Dungeon/AuchenaiCrypts/Action/AuchenaiCryptsActions.h rename to src/Ai/Dungeon/AC/ACActions.h index 4764efb6509..50240ae0b45 100644 --- a/src/Ai/Dungeon/AuchenaiCrypts/Action/AuchenaiCryptsActions.h +++ b/src/Ai/Dungeon/AC/ACActions.h @@ -3,7 +3,7 @@ #include "AttackAction.h" #include "MovementActions.h" -#include "AuchenaiCryptsTriggers.h" +#include "ACTriggers.h" // Shirrak the Dead Watcher diff --git a/src/Ai/Dungeon/AuchenaiCrypts/Multiplier/AuchenaiCryptsMultipliers.cpp b/src/Ai/Dungeon/AC/ACMultipliers.cpp similarity index 92% rename from src/Ai/Dungeon/AuchenaiCrypts/Multiplier/AuchenaiCryptsMultipliers.cpp rename to src/Ai/Dungeon/AC/ACMultipliers.cpp index 2f74a5a58e7..b457e9038b2 100644 --- a/src/Ai/Dungeon/AuchenaiCrypts/Multiplier/AuchenaiCryptsMultipliers.cpp +++ b/src/Ai/Dungeon/AC/ACMultipliers.cpp @@ -1,6 +1,6 @@ -#include "AuchenaiCryptsMultipliers.h" -#include "AuchenaiCryptsActions.h" -#include "AuchenaiCryptsTriggers.h" +#include "ACMultipliers.h" +#include "ACActions.h" +#include "ACTriggers.h" #include "MovementActions.h" #include "ReachTargetActions.h" #include "FollowActions.h" diff --git a/src/Ai/Dungeon/AuchenaiCrypts/Multiplier/AuchenaiCryptsMultipliers.h b/src/Ai/Dungeon/AC/ACMultipliers.h similarity index 100% rename from src/Ai/Dungeon/AuchenaiCrypts/Multiplier/AuchenaiCryptsMultipliers.h rename to src/Ai/Dungeon/AC/ACMultipliers.h diff --git a/src/Ai/Dungeon/AuchenaiCrypts/Strategy/AuchenaiCryptsStrategy.cpp b/src/Ai/Dungeon/AC/ACStrategy.cpp similarity index 86% rename from src/Ai/Dungeon/AuchenaiCrypts/Strategy/AuchenaiCryptsStrategy.cpp rename to src/Ai/Dungeon/AC/ACStrategy.cpp index f975d46bb60..c19fe9d4779 100644 --- a/src/Ai/Dungeon/AuchenaiCrypts/Strategy/AuchenaiCryptsStrategy.cpp +++ b/src/Ai/Dungeon/AC/ACStrategy.cpp @@ -1,6 +1,6 @@ -#include "AuchenaiCryptsTriggers.h" -#include "AuchenaiCryptsStrategy.h" -#include "AuchenaiCryptsMultipliers.h" +#include "ACTriggers.h" +#include "ACStrategy.h" +#include "ACMultipliers.h" void TbcDungeonAuchenaiCryptsStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Dungeon/AuchenaiCrypts/Strategy/AuchenaiCryptsStrategy.h b/src/Ai/Dungeon/AC/ACStrategy.h similarity index 100% rename from src/Ai/Dungeon/AuchenaiCrypts/Strategy/AuchenaiCryptsStrategy.h rename to src/Ai/Dungeon/AC/ACStrategy.h diff --git a/src/Ai/Dungeon/AuchenaiCrypts/AuchenaiCryptsTriggerContext.h b/src/Ai/Dungeon/AC/ACTriggerContext.h similarity index 97% rename from src/Ai/Dungeon/AuchenaiCrypts/AuchenaiCryptsTriggerContext.h rename to src/Ai/Dungeon/AC/ACTriggerContext.h index 95f15f16a93..f9c7299b4bc 100644 --- a/src/Ai/Dungeon/AuchenaiCrypts/AuchenaiCryptsTriggerContext.h +++ b/src/Ai/Dungeon/AC/ACTriggerContext.h @@ -3,7 +3,7 @@ #include "AiObjectContext.h" #include "TriggerContext.h" -#include "AuchenaiCryptsTriggers.h" +#include "ACTriggers.h" class TbcDungeonAuchenaiCryptsTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/AuchenaiCrypts/Trigger/AuchenaiCryptsTriggers.cpp b/src/Ai/Dungeon/AC/ACTriggers.cpp similarity index 96% rename from src/Ai/Dungeon/AuchenaiCrypts/Trigger/AuchenaiCryptsTriggers.cpp rename to src/Ai/Dungeon/AC/ACTriggers.cpp index 372614d2f1d..38ddc963538 100644 --- a/src/Ai/Dungeon/AuchenaiCrypts/Trigger/AuchenaiCryptsTriggers.cpp +++ b/src/Ai/Dungeon/AC/ACTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "AuchenaiCryptsTriggers.h" +#include "ACTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/AuchenaiCrypts/Trigger/AuchenaiCryptsTriggers.h b/src/Ai/Dungeon/AC/ACTriggers.h similarity index 100% rename from src/Ai/Dungeon/AuchenaiCrypts/Trigger/AuchenaiCryptsTriggers.h rename to src/Ai/Dungeon/AC/ACTriggers.h diff --git a/src/Ai/Dungeon/OldKingdom/OldKingdomActionContext.h b/src/Ai/Dungeon/AK/AKActionContext.h similarity index 96% rename from src/Ai/Dungeon/OldKingdom/OldKingdomActionContext.h rename to src/Ai/Dungeon/AK/AKActionContext.h index 19426c57940..e462de443b7 100644 --- a/src/Ai/Dungeon/OldKingdom/OldKingdomActionContext.h +++ b/src/Ai/Dungeon/AK/AKActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "OldKingdomActions.h" +#include "AKActions.h" class WotlkDungeonOKActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.cpp b/src/Ai/Dungeon/AK/AKActions.cpp similarity index 98% rename from src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.cpp rename to src/Ai/Dungeon/AK/AKActions.cpp index 8a5250a8d82..edb80788c08 100644 --- a/src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.cpp +++ b/src/Ai/Dungeon/AK/AKActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "OldKingdomActions.h" +#include "AKActions.h" bool AttackNadoxGuardianAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.h b/src/Ai/Dungeon/AK/AKActions.h similarity index 96% rename from src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.h rename to src/Ai/Dungeon/AK/AKActions.h index cba51878431..62223022c60 100644 --- a/src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.h +++ b/src/Ai/Dungeon/AK/AKActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "OldKingdomTriggers.h" +#include "AKTriggers.h" class AttackNadoxGuardianAction : public AttackAction { diff --git a/src/Ai/Dungeon/OldKingdom/Multiplier/OldKingdomMultipliers.cpp b/src/Ai/Dungeon/AK/AKMultipliers.cpp similarity index 94% rename from src/Ai/Dungeon/OldKingdom/Multiplier/OldKingdomMultipliers.cpp rename to src/Ai/Dungeon/AK/AKMultipliers.cpp index dbca5231607..4c979cf545a 100644 --- a/src/Ai/Dungeon/OldKingdom/Multiplier/OldKingdomMultipliers.cpp +++ b/src/Ai/Dungeon/AK/AKMultipliers.cpp @@ -1,9 +1,9 @@ -#include "OldKingdomMultipliers.h" -#include "OldKingdomActions.h" +#include "AKMultipliers.h" +#include "AKActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "OldKingdomTriggers.h" +#include "AKTriggers.h" #include "Action.h" float ElderNadoxMultiplier::GetValue(Action* action) diff --git a/src/Ai/Dungeon/OldKingdom/Multiplier/OldKingdomMultipliers.h b/src/Ai/Dungeon/AK/AKMultipliers.h similarity index 100% rename from src/Ai/Dungeon/OldKingdom/Multiplier/OldKingdomMultipliers.h rename to src/Ai/Dungeon/AK/AKMultipliers.h diff --git a/src/Ai/Dungeon/OldKingdom/Strategy/OldKingdomStrategy.cpp b/src/Ai/Dungeon/AK/AKStrategy.cpp similarity index 95% rename from src/Ai/Dungeon/OldKingdom/Strategy/OldKingdomStrategy.cpp rename to src/Ai/Dungeon/AK/AKStrategy.cpp index 484ea6ac48b..181a6ef7a29 100644 --- a/src/Ai/Dungeon/OldKingdom/Strategy/OldKingdomStrategy.cpp +++ b/src/Ai/Dungeon/AK/AKStrategy.cpp @@ -1,5 +1,5 @@ -#include "OldKingdomStrategy.h" -#include "OldKingdomMultipliers.h" +#include "AKStrategy.h" +#include "AKMultipliers.h" void WotlkDungeonOKStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/OldKingdom/Strategy/OldKingdomStrategy.h b/src/Ai/Dungeon/AK/AKStrategy.h similarity index 100% rename from src/Ai/Dungeon/OldKingdom/Strategy/OldKingdomStrategy.h rename to src/Ai/Dungeon/AK/AKStrategy.h diff --git a/src/Ai/Dungeon/OldKingdom/OldKingdomTriggerContext.h b/src/Ai/Dungeon/AK/AKTriggerContext.h similarity index 96% rename from src/Ai/Dungeon/OldKingdom/OldKingdomTriggerContext.h rename to src/Ai/Dungeon/AK/AKTriggerContext.h index 4c25b9a95d3..72d8e607637 100644 --- a/src/Ai/Dungeon/OldKingdom/OldKingdomTriggerContext.h +++ b/src/Ai/Dungeon/AK/AKTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "OldKingdomTriggers.h" +#include "AKTriggers.h" class WotlkDungeonOKTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/OldKingdom/Trigger/OldKingdomTriggers.cpp b/src/Ai/Dungeon/AK/AKTriggers.cpp similarity index 97% rename from src/Ai/Dungeon/OldKingdom/Trigger/OldKingdomTriggers.cpp rename to src/Ai/Dungeon/AK/AKTriggers.cpp index 6b72f656a96..9d945edc7d5 100644 --- a/src/Ai/Dungeon/OldKingdom/Trigger/OldKingdomTriggers.cpp +++ b/src/Ai/Dungeon/AK/AKTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "OldKingdomTriggers.h" +#include "AKTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/OldKingdom/Trigger/OldKingdomTriggers.h b/src/Ai/Dungeon/AK/AKTriggers.h similarity index 100% rename from src/Ai/Dungeon/OldKingdom/Trigger/OldKingdomTriggers.h rename to src/Ai/Dungeon/AK/AKTriggers.h diff --git a/src/Ai/Dungeon/AzjolNerub/AzjolNerubActionContext.h b/src/Ai/Dungeon/AN/ANActionContext.h similarity index 96% rename from src/Ai/Dungeon/AzjolNerub/AzjolNerubActionContext.h rename to src/Ai/Dungeon/AN/ANActionContext.h index 6306643bab7..11a5c4fe51d 100644 --- a/src/Ai/Dungeon/AzjolNerub/AzjolNerubActionContext.h +++ b/src/Ai/Dungeon/AN/ANActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "AzjolNerubActions.h" +#include "ANActions.h" class WotlkDungeonANActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.cpp b/src/Ai/Dungeon/AN/ANActions.cpp similarity index 99% rename from src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.cpp rename to src/Ai/Dungeon/AN/ANActions.cpp index 42026cecf3d..985ab473ec1 100644 --- a/src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.cpp +++ b/src/Ai/Dungeon/AN/ANActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "AzjolNerubActions.h" +#include "ANActions.h" bool AttackWebWrapAction::isUseful() { return !botAI->IsHeal(bot); } bool AttackWebWrapAction::Execute(Event /*event*/) diff --git a/src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.h b/src/Ai/Dungeon/AN/ANActions.h similarity index 96% rename from src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.h rename to src/Ai/Dungeon/AN/ANActions.h index ef388cbdb24..69bcc15b79e 100644 --- a/src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.h +++ b/src/Ai/Dungeon/AN/ANActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "AzjolNerubTriggers.h" +#include "ANTriggers.h" class AttackWebWrapAction : public AttackAction { diff --git a/src/Ai/Dungeon/AzjolNerub/Multiplier/AzjolNerubMultipliers.cpp b/src/Ai/Dungeon/AN/ANMultipliers.cpp similarity index 93% rename from src/Ai/Dungeon/AzjolNerub/Multiplier/AzjolNerubMultipliers.cpp rename to src/Ai/Dungeon/AN/ANMultipliers.cpp index 6cd6673dc5c..035f64bb15c 100644 --- a/src/Ai/Dungeon/AzjolNerub/Multiplier/AzjolNerubMultipliers.cpp +++ b/src/Ai/Dungeon/AN/ANMultipliers.cpp @@ -1,9 +1,9 @@ -#include "AzjolNerubMultipliers.h" -#include "AzjolNerubActions.h" +#include "ANMultipliers.h" +#include "ANActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "AzjolNerubTriggers.h" +#include "ANTriggers.h" #include "Action.h" float KrikthirMultiplier::GetValue(Action* action) diff --git a/src/Ai/Dungeon/AzjolNerub/Multiplier/AzjolNerubMultipliers.h b/src/Ai/Dungeon/AN/ANMultipliers.h similarity index 100% rename from src/Ai/Dungeon/AzjolNerub/Multiplier/AzjolNerubMultipliers.h rename to src/Ai/Dungeon/AN/ANMultipliers.h diff --git a/src/Ai/Dungeon/AzjolNerub/Strategy/AzjolNerubStrategy.cpp b/src/Ai/Dungeon/AN/ANStrategy.cpp similarity index 94% rename from src/Ai/Dungeon/AzjolNerub/Strategy/AzjolNerubStrategy.cpp rename to src/Ai/Dungeon/AN/ANStrategy.cpp index dbbb8f5bf54..7353e94992f 100644 --- a/src/Ai/Dungeon/AzjolNerub/Strategy/AzjolNerubStrategy.cpp +++ b/src/Ai/Dungeon/AN/ANStrategy.cpp @@ -1,5 +1,5 @@ -#include "AzjolNerubStrategy.h" -#include "AzjolNerubMultipliers.h" +#include "ANStrategy.h" +#include "ANMultipliers.h" void WotlkDungeonANStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/AzjolNerub/Strategy/AzjolNerubStrategy.h b/src/Ai/Dungeon/AN/ANStrategy.h similarity index 100% rename from src/Ai/Dungeon/AzjolNerub/Strategy/AzjolNerubStrategy.h rename to src/Ai/Dungeon/AN/ANStrategy.h diff --git a/src/Ai/Dungeon/AzjolNerub/AzjolNerubTriggerContext.h b/src/Ai/Dungeon/AN/ANTriggerContext.h similarity index 97% rename from src/Ai/Dungeon/AzjolNerub/AzjolNerubTriggerContext.h rename to src/Ai/Dungeon/AN/ANTriggerContext.h index a41cebfc6e5..95131501d83 100644 --- a/src/Ai/Dungeon/AzjolNerub/AzjolNerubTriggerContext.h +++ b/src/Ai/Dungeon/AN/ANTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "AzjolNerubTriggers.h" +#include "ANTriggers.h" class WotlkDungeonANTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/AzjolNerub/Trigger/AzjolNerubTriggers.cpp b/src/Ai/Dungeon/AN/ANTriggers.cpp similarity index 98% rename from src/Ai/Dungeon/AzjolNerub/Trigger/AzjolNerubTriggers.cpp rename to src/Ai/Dungeon/AN/ANTriggers.cpp index f9ef4ff9538..c9bbde46381 100644 --- a/src/Ai/Dungeon/AzjolNerub/Trigger/AzjolNerubTriggers.cpp +++ b/src/Ai/Dungeon/AN/ANTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "AzjolNerubTriggers.h" +#include "ANTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/AzjolNerub/Trigger/AzjolNerubTriggers.h b/src/Ai/Dungeon/AN/ANTriggers.h similarity index 100% rename from src/Ai/Dungeon/AzjolNerub/Trigger/AzjolNerubTriggers.h rename to src/Ai/Dungeon/AN/ANTriggers.h diff --git a/src/Ai/Dungeon/CullingOfStratholme/CullingOfStratholmeActionContext.h b/src/Ai/Dungeon/CoS/CoSActionContext.h similarity index 94% rename from src/Ai/Dungeon/CullingOfStratholme/CullingOfStratholmeActionContext.h rename to src/Ai/Dungeon/CoS/CoSActionContext.h index 0e12c908c8b..782c0202bb8 100644 --- a/src/Ai/Dungeon/CullingOfStratholme/CullingOfStratholmeActionContext.h +++ b/src/Ai/Dungeon/CoS/CoSActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "CullingOfStratholmeActions.h" +#include "CoSActions.h" class WotlkDungeonCoSActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/CullingOfStratholme/Action/CullingOfStratholmeActions.cpp b/src/Ai/Dungeon/CoS/CoSActions.cpp similarity index 97% rename from src/Ai/Dungeon/CullingOfStratholme/Action/CullingOfStratholmeActions.cpp rename to src/Ai/Dungeon/CoS/CoSActions.cpp index b2732c3cfee..91b76b51bca 100644 --- a/src/Ai/Dungeon/CullingOfStratholme/Action/CullingOfStratholmeActions.cpp +++ b/src/Ai/Dungeon/CoS/CoSActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "CullingOfStratholmeActions.h" +#include "CoSActions.h" bool ExplodeGhoulSpreadAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/CullingOfStratholme/Action/CullingOfStratholmeActions.h b/src/Ai/Dungeon/CoS/CoSActions.h similarity index 93% rename from src/Ai/Dungeon/CullingOfStratholme/Action/CullingOfStratholmeActions.h rename to src/Ai/Dungeon/CoS/CoSActions.h index 989cb6ab0ad..98ece6eb52f 100644 --- a/src/Ai/Dungeon/CullingOfStratholme/Action/CullingOfStratholmeActions.h +++ b/src/Ai/Dungeon/CoS/CoSActions.h @@ -6,7 +6,7 @@ #include "GenericSpellActions.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "CullingOfStratholmeTriggers.h" +#include "CoSTriggers.h" class ExplodeGhoulSpreadAction : public MovementAction { diff --git a/src/Ai/Dungeon/CullingOfStratholme/Multiplier/CullingOfStratholmeMultipliers.cpp b/src/Ai/Dungeon/CoS/CoSMultipliers.cpp similarity index 76% rename from src/Ai/Dungeon/CullingOfStratholme/Multiplier/CullingOfStratholmeMultipliers.cpp rename to src/Ai/Dungeon/CoS/CoSMultipliers.cpp index 1fbbc6b3e85..9a5852f4823 100644 --- a/src/Ai/Dungeon/CullingOfStratholme/Multiplier/CullingOfStratholmeMultipliers.cpp +++ b/src/Ai/Dungeon/CoS/CoSMultipliers.cpp @@ -1,9 +1,9 @@ -#include "CullingOfStratholmeMultipliers.h" -#include "CullingOfStratholmeActions.h" +#include "CoSMultipliers.h" +#include "CoSActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "CullingOfStratholmeTriggers.h" +#include "CoSTriggers.h" #include "Action.h" float EpochMultiplier::GetValue(Action* action) diff --git a/src/Ai/Dungeon/CullingOfStratholme/Multiplier/CullingOfStratholmeMultipliers.h b/src/Ai/Dungeon/CoS/CoSMultipliers.h similarity index 100% rename from src/Ai/Dungeon/CullingOfStratholme/Multiplier/CullingOfStratholmeMultipliers.h rename to src/Ai/Dungeon/CoS/CoSMultipliers.h diff --git a/src/Ai/Dungeon/CullingOfStratholme/Strategy/CullingOfStratholmeStrategy.cpp b/src/Ai/Dungeon/CoS/CoSStrategy.cpp similarity index 90% rename from src/Ai/Dungeon/CullingOfStratholme/Strategy/CullingOfStratholmeStrategy.cpp rename to src/Ai/Dungeon/CoS/CoSStrategy.cpp index 90765bded45..f51a35255dc 100644 --- a/src/Ai/Dungeon/CullingOfStratholme/Strategy/CullingOfStratholmeStrategy.cpp +++ b/src/Ai/Dungeon/CoS/CoSStrategy.cpp @@ -1,5 +1,5 @@ -#include "CullingOfStratholmeStrategy.h" -#include "CullingOfStratholmeMultipliers.h" +#include "CoSStrategy.h" +#include "CoSMultipliers.h" void WotlkDungeonCoSStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/CullingOfStratholme/Strategy/CullingOfStratholmeStrategy.h b/src/Ai/Dungeon/CoS/CoSStrategy.h similarity index 100% rename from src/Ai/Dungeon/CullingOfStratholme/Strategy/CullingOfStratholmeStrategy.h rename to src/Ai/Dungeon/CoS/CoSStrategy.h diff --git a/src/Ai/Dungeon/CullingOfStratholme/CullingOfStratholmeTriggerContext.h b/src/Ai/Dungeon/CoS/CoSTriggerContext.h similarity index 94% rename from src/Ai/Dungeon/CullingOfStratholme/CullingOfStratholmeTriggerContext.h rename to src/Ai/Dungeon/CoS/CoSTriggerContext.h index 6e35eac8559..302d99381e9 100644 --- a/src/Ai/Dungeon/CullingOfStratholme/CullingOfStratholmeTriggerContext.h +++ b/src/Ai/Dungeon/CoS/CoSTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "CullingOfStratholmeTriggers.h" +#include "CoSTriggers.h" class WotlkDungeonCoSTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/CullingOfStratholme/Trigger/CullingOfStratholmeTriggers.cpp b/src/Ai/Dungeon/CoS/CoSTriggers.cpp similarity index 95% rename from src/Ai/Dungeon/CullingOfStratholme/Trigger/CullingOfStratholmeTriggers.cpp rename to src/Ai/Dungeon/CoS/CoSTriggers.cpp index cb67ad077f5..60b195a8c6d 100644 --- a/src/Ai/Dungeon/CullingOfStratholme/Trigger/CullingOfStratholmeTriggers.cpp +++ b/src/Ai/Dungeon/CoS/CoSTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "CullingOfStratholmeTriggers.h" +#include "CoSTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/CullingOfStratholme/Trigger/CullingOfStratholmeTriggers.h b/src/Ai/Dungeon/CoS/CoSTriggers.h similarity index 100% rename from src/Ai/Dungeon/CullingOfStratholme/Trigger/CullingOfStratholmeTriggers.h rename to src/Ai/Dungeon/CoS/CoSTriggers.h diff --git a/src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepActionContext.h b/src/Ai/Dungeon/DTK/DTKActionContext.h similarity index 98% rename from src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepActionContext.h rename to src/Ai/Dungeon/DTK/DTKActionContext.h index 1435eedbe2d..61c1632923d 100644 --- a/src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepActionContext.h +++ b/src/Ai/Dungeon/DTK/DTKActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "DrakTharonKeepActions.h" +#include "DTKActions.h" class WotlkDungeonDTKActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.cpp b/src/Ai/Dungeon/DTK/DTKActions.cpp similarity index 99% rename from src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.cpp rename to src/Ai/Dungeon/DTK/DTKActions.cpp index 07e23699e78..52258a601b2 100644 --- a/src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.cpp +++ b/src/Ai/Dungeon/DTK/DTKActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "DrakTharonKeepActions.h" +#include "DTKActions.h" bool CorpseExplodeSpreadAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.h b/src/Ai/Dungeon/DTK/DTKActions.h similarity index 98% rename from src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.h rename to src/Ai/Dungeon/DTK/DTKActions.h index 8c59a7c24b3..5eaca264a0e 100644 --- a/src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.h +++ b/src/Ai/Dungeon/DTK/DTKActions.h @@ -6,7 +6,7 @@ #include "GenericSpellActions.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "DrakTharonKeepTriggers.h" +#include "DTKTriggers.h" const Position NOVOS_PARTY_POSITION = Position(-378.852f, -760.349f, 28.587f); diff --git a/src/Ai/Dungeon/DraktharonKeep/Multiplier/DrakTharonKeepMultipliers.cpp b/src/Ai/Dungeon/DTK/DTKMultipliers.cpp similarity index 93% rename from src/Ai/Dungeon/DraktharonKeep/Multiplier/DrakTharonKeepMultipliers.cpp rename to src/Ai/Dungeon/DTK/DTKMultipliers.cpp index 8fd119dc4e0..866a5c6021a 100644 --- a/src/Ai/Dungeon/DraktharonKeep/Multiplier/DrakTharonKeepMultipliers.cpp +++ b/src/Ai/Dungeon/DTK/DTKMultipliers.cpp @@ -1,9 +1,9 @@ -#include "DrakTharonKeepMultipliers.h" -#include "DrakTharonKeepActions.h" +#include "DTKMultipliers.h" +#include "DTKActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "DrakTharonKeepTriggers.h" +#include "DTKTriggers.h" #include "Action.h" float NovosMultiplier::GetValue(Action* action) diff --git a/src/Ai/Dungeon/DraktharonKeep/Multiplier/DrakTharonKeepMultipliers.h b/src/Ai/Dungeon/DTK/DTKMultipliers.h similarity index 100% rename from src/Ai/Dungeon/DraktharonKeep/Multiplier/DrakTharonKeepMultipliers.h rename to src/Ai/Dungeon/DTK/DTKMultipliers.h diff --git a/src/Ai/Dungeon/DraktharonKeep/Strategy/DrakTharonKeepStrategy.cpp b/src/Ai/Dungeon/DTK/DTKStrategy.cpp similarity index 95% rename from src/Ai/Dungeon/DraktharonKeep/Strategy/DrakTharonKeepStrategy.cpp rename to src/Ai/Dungeon/DTK/DTKStrategy.cpp index 791a0ea71ec..96590672ce8 100644 --- a/src/Ai/Dungeon/DraktharonKeep/Strategy/DrakTharonKeepStrategy.cpp +++ b/src/Ai/Dungeon/DTK/DTKStrategy.cpp @@ -1,5 +1,5 @@ -#include "DrakTharonKeepStrategy.h" -#include "DrakTharonKeepMultipliers.h" +#include "DTKStrategy.h" +#include "DTKMultipliers.h" void WotlkDungeonDTKStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/DraktharonKeep/Strategy/DrakTharonKeepStrategy.h b/src/Ai/Dungeon/DTK/DTKStrategy.h similarity index 100% rename from src/Ai/Dungeon/DraktharonKeep/Strategy/DrakTharonKeepStrategy.h rename to src/Ai/Dungeon/DTK/DTKStrategy.h diff --git a/src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepTriggerContext.h b/src/Ai/Dungeon/DTK/DTKTriggerContext.h similarity index 97% rename from src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepTriggerContext.h rename to src/Ai/Dungeon/DTK/DTKTriggerContext.h index ff773910725..954c1da0530 100644 --- a/src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepTriggerContext.h +++ b/src/Ai/Dungeon/DTK/DTKTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "DrakTharonKeepTriggers.h" +#include "DTKTriggers.h" class WotlkDungeonDTKTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/DraktharonKeep/Trigger/DrakTharonKeepTriggers.cpp b/src/Ai/Dungeon/DTK/DTKTriggers.cpp similarity index 97% rename from src/Ai/Dungeon/DraktharonKeep/Trigger/DrakTharonKeepTriggers.cpp rename to src/Ai/Dungeon/DTK/DTKTriggers.cpp index 406f4d43334..9af9cccb4a0 100644 --- a/src/Ai/Dungeon/DraktharonKeep/Trigger/DrakTharonKeepTriggers.cpp +++ b/src/Ai/Dungeon/DTK/DTKTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "DrakTharonKeepTriggers.h" +#include "DTKTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/DraktharonKeep/Trigger/DrakTharonKeepTriggers.h b/src/Ai/Dungeon/DTK/DTKTriggers.h similarity index 100% rename from src/Ai/Dungeon/DraktharonKeep/Trigger/DrakTharonKeepTriggers.h rename to src/Ai/Dungeon/DTK/DTKTriggers.h diff --git a/src/Ai/Dungeon/DungeonStrategyContext.h b/src/Ai/Dungeon/DungeonStrategyContext.h index 07e5fe505a4..e2365ea756b 100644 --- a/src/Ai/Dungeon/DungeonStrategyContext.h +++ b/src/Ai/Dungeon/DungeonStrategyContext.h @@ -2,22 +2,22 @@ #define _PLAYERBOT_DUNGEONSTRATEGYCONTEXT_H #include "Strategy.h" -#include "AuchenaiCrypts/Strategy/AuchenaiCryptsStrategy.h" -#include "UtgardeKeep/Strategy/UtgardeKeepStrategy.h" -#include "Nexus/Strategy/NexusStrategy.h" -#include "AzjolNerub/Strategy/AzjolNerubStrategy.h" -#include "OldKingdom/Strategy/OldKingdomStrategy.h" -#include "DraktharonKeep/Strategy/DrakTharonKeepStrategy.h" -#include "VioletHold/Strategy/VioletHoldStrategy.h" -#include "Gundrak/Strategy/GundrakStrategy.h" -#include "HallsOfStone/Strategy/HallsOfStoneStrategy.h" -#include "HallsOfLightning/Strategy/HallsOfLightningStrategy.h" -#include "Oculus/Strategy/OculusStrategy.h" -#include "UtgardePinnacle/Strategy/UtgardePinnacleStrategy.h" -#include "CullingOfStratholme/Strategy/CullingOfStratholmeStrategy.h" -#include "ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.h" -#include "PitOfSaron/Strategy/PitOfSaronStrategy.h" -#include "TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.h" +#include "ACStrategy.h" +#include "UKStrategy.h" +#include "NexStrategy.h" +#include "ANStrategy.h" +#include "AKStrategy.h" +#include "DTKStrategy.h" +#include "VHStrategy.h" +#include "GDStrategy.h" +#include "HoSStrategy.h" +#include "HoLStrategy.h" +#include "OCStrategy.h" +#include "UPStrategy.h" +#include "CoSStrategy.h" +#include "FoSStrategy.h" +#include "PoSStrategy.h" +#include "TOCStrategy.h" /* Full list/TODO: diff --git a/src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsActionContext.h b/src/Ai/Dungeon/FoS/FoSActionContext.h similarity index 97% rename from src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsActionContext.h rename to src/Ai/Dungeon/FoS/FoSActionContext.h index 4797b69c132..1a87e2647cb 100644 --- a/src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsActionContext.h +++ b/src/Ai/Dungeon/FoS/FoSActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "ForgeOfSoulsActions.h" +#include "FoSActions.h" class WotlkDungeonFoSActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.cpp b/src/Ai/Dungeon/FoS/FoSActions.cpp similarity index 99% rename from src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.cpp rename to src/Ai/Dungeon/FoS/FoSActions.cpp index cb29453e061..34c377dff3d 100644 --- a/src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.cpp +++ b/src/Ai/Dungeon/FoS/FoSActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "ForgeOfSoulsActions.h" +#include "FoSActions.h" bool MoveFromBronjahmAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.h b/src/Ai/Dungeon/FoS/FoSActions.h similarity index 97% rename from src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.h rename to src/Ai/Dungeon/FoS/FoSActions.h index d9b762be683..f91c93a21d0 100644 --- a/src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.h +++ b/src/Ai/Dungeon/FoS/FoSActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "ForgeOfSoulsTriggers.h" +#include "FoSTriggers.h" const Position BRONJAHM_TANK_POSITION = Position(5297.920f, 2506.698f, 686.068f); diff --git a/src/Ai/Dungeon/ForgeOfSouls/Multiplier/ForgeOfSoulsMultipliers.cpp b/src/Ai/Dungeon/FoS/FoSMultipliers.cpp similarity index 88% rename from src/Ai/Dungeon/ForgeOfSouls/Multiplier/ForgeOfSoulsMultipliers.cpp rename to src/Ai/Dungeon/FoS/FoSMultipliers.cpp index 7873e7c1d12..ceed84ea7a6 100644 --- a/src/Ai/Dungeon/ForgeOfSouls/Multiplier/ForgeOfSoulsMultipliers.cpp +++ b/src/Ai/Dungeon/FoS/FoSMultipliers.cpp @@ -1,10 +1,10 @@ -#include "ForgeOfSoulsMultipliers.h" -#include "ForgeOfSoulsActions.h" +#include "FoSMultipliers.h" +#include "FoSActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "ForgeOfSoulsTriggers.h" -#include "ForgeOfSoulsActions.h" +#include "FoSTriggers.h" +#include "FoSActions.h" float BronjahmMultiplier::GetValue(Action* action) { Unit* boss = AI_VALUE2(Unit *, "find target", "bronjahm"); diff --git a/src/Ai/Dungeon/ForgeOfSouls/Multiplier/ForgeOfSoulsMultipliers.h b/src/Ai/Dungeon/FoS/FoSMultipliers.h similarity index 100% rename from src/Ai/Dungeon/ForgeOfSouls/Multiplier/ForgeOfSoulsMultipliers.h rename to src/Ai/Dungeon/FoS/FoSMultipliers.h diff --git a/src/Ai/Dungeon/ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.cpp b/src/Ai/Dungeon/FoS/FoSStrategy.cpp similarity index 91% rename from src/Ai/Dungeon/ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.cpp rename to src/Ai/Dungeon/FoS/FoSStrategy.cpp index 9ff3219829d..d359fe41420 100644 --- a/src/Ai/Dungeon/ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.cpp +++ b/src/Ai/Dungeon/FoS/FoSStrategy.cpp @@ -1,5 +1,5 @@ -#include "ForgeOfSoulsStrategy.h" -#include "ForgeOfSoulsMultipliers.h" +#include "FoSStrategy.h" +#include "FoSMultipliers.h" void WotlkDungeonFoSStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Dungeon/ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.h b/src/Ai/Dungeon/FoS/FoSStrategy.h similarity index 100% rename from src/Ai/Dungeon/ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.h rename to src/Ai/Dungeon/FoS/FoSStrategy.h diff --git a/src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsTriggerContext.h b/src/Ai/Dungeon/FoS/FoSTriggerContext.h similarity index 97% rename from src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsTriggerContext.h rename to src/Ai/Dungeon/FoS/FoSTriggerContext.h index 129124127d9..aa8a0d4af3d 100644 --- a/src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsTriggerContext.h +++ b/src/Ai/Dungeon/FoS/FoSTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "ForgeOfSoulsTriggers.h" +#include "FoSTriggers.h" class WotlkDungeonFoSTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.cpp b/src/Ai/Dungeon/FoS/FoSTriggers.cpp similarity index 97% rename from src/Ai/Dungeon/ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.cpp rename to src/Ai/Dungeon/FoS/FoSTriggers.cpp index eb8e66e11c0..f124ab35da9 100644 --- a/src/Ai/Dungeon/ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.cpp +++ b/src/Ai/Dungeon/FoS/FoSTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "ForgeOfSoulsTriggers.h" +#include "FoSTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.h b/src/Ai/Dungeon/FoS/FoSTriggers.h similarity index 100% rename from src/Ai/Dungeon/ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.h rename to src/Ai/Dungeon/FoS/FoSTriggers.h diff --git a/src/Ai/Dungeon/Gundrak/GundrakActionContext.h b/src/Ai/Dungeon/GD/GDActionContext.h similarity index 96% rename from src/Ai/Dungeon/Gundrak/GundrakActionContext.h rename to src/Ai/Dungeon/GD/GDActionContext.h index 746fed39469..3dd4f7164da 100644 --- a/src/Ai/Dungeon/Gundrak/GundrakActionContext.h +++ b/src/Ai/Dungeon/GD/GDActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "GundrakActions.h" +#include "GDActions.h" class WotlkDungeonGDActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/Gundrak/Action/GundrakActions.cpp b/src/Ai/Dungeon/GD/GDActions.cpp similarity index 98% rename from src/Ai/Dungeon/Gundrak/Action/GundrakActions.cpp rename to src/Ai/Dungeon/GD/GDActions.cpp index 7dbcb6d66fb..4cada90e3fb 100644 --- a/src/Ai/Dungeon/Gundrak/Action/GundrakActions.cpp +++ b/src/Ai/Dungeon/GD/GDActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "GundrakActions.h" +#include "GDActions.h" bool AvoidPoisonNovaAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/Gundrak/Action/GundrakActions.h b/src/Ai/Dungeon/GD/GDActions.h similarity index 96% rename from src/Ai/Dungeon/Gundrak/Action/GundrakActions.h rename to src/Ai/Dungeon/GD/GDActions.h index 095cc5cfa0f..17070ac1ea6 100644 --- a/src/Ai/Dungeon/Gundrak/Action/GundrakActions.h +++ b/src/Ai/Dungeon/GD/GDActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "GundrakTriggers.h" +#include "GDTriggers.h" class AvoidPoisonNovaAction : public MovementAction { diff --git a/src/Ai/Dungeon/Gundrak/Multiplier/GundrakMultipliers.cpp b/src/Ai/Dungeon/GD/GDMultipliers.cpp similarity index 94% rename from src/Ai/Dungeon/Gundrak/Multiplier/GundrakMultipliers.cpp rename to src/Ai/Dungeon/GD/GDMultipliers.cpp index 2d440425519..c9780f286b8 100644 --- a/src/Ai/Dungeon/Gundrak/Multiplier/GundrakMultipliers.cpp +++ b/src/Ai/Dungeon/GD/GDMultipliers.cpp @@ -1,9 +1,9 @@ -#include "GundrakMultipliers.h" -#include "GundrakActions.h" +#include "GDMultipliers.h" +#include "GDActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "GundrakTriggers.h" +#include "GDTriggers.h" #include "Action.h" float SladranMultiplier::GetValue(Action* action) diff --git a/src/Ai/Dungeon/Gundrak/Multiplier/GundrakMultipliers.h b/src/Ai/Dungeon/GD/GDMultipliers.h similarity index 100% rename from src/Ai/Dungeon/Gundrak/Multiplier/GundrakMultipliers.h rename to src/Ai/Dungeon/GD/GDMultipliers.h diff --git a/src/Ai/Dungeon/Gundrak/Strategy/GundrakStrategy.cpp b/src/Ai/Dungeon/GD/GDStrategy.cpp similarity index 94% rename from src/Ai/Dungeon/Gundrak/Strategy/GundrakStrategy.cpp rename to src/Ai/Dungeon/GD/GDStrategy.cpp index c8244e2ca91..a092c30419d 100644 --- a/src/Ai/Dungeon/Gundrak/Strategy/GundrakStrategy.cpp +++ b/src/Ai/Dungeon/GD/GDStrategy.cpp @@ -1,5 +1,5 @@ -#include "GundrakStrategy.h" -#include "GundrakMultipliers.h" +#include "GDStrategy.h" +#include "GDMultipliers.h" void WotlkDungeonGDStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/Gundrak/Strategy/GundrakStrategy.h b/src/Ai/Dungeon/GD/GDStrategy.h similarity index 100% rename from src/Ai/Dungeon/Gundrak/Strategy/GundrakStrategy.h rename to src/Ai/Dungeon/GD/GDStrategy.h diff --git a/src/Ai/Dungeon/Gundrak/GundrakTriggerContext.h b/src/Ai/Dungeon/GD/GDTriggerContext.h similarity index 96% rename from src/Ai/Dungeon/Gundrak/GundrakTriggerContext.h rename to src/Ai/Dungeon/GD/GDTriggerContext.h index 4d0e5dee87c..e85e61a1d50 100644 --- a/src/Ai/Dungeon/Gundrak/GundrakTriggerContext.h +++ b/src/Ai/Dungeon/GD/GDTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "GundrakTriggers.h" +#include "GDTriggers.h" class WotlkDungeonGDTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/Gundrak/Trigger/GundrakTriggers.cpp b/src/Ai/Dungeon/GD/GDTriggers.cpp similarity index 97% rename from src/Ai/Dungeon/Gundrak/Trigger/GundrakTriggers.cpp rename to src/Ai/Dungeon/GD/GDTriggers.cpp index b3e98cfdb68..13107e9205b 100644 --- a/src/Ai/Dungeon/Gundrak/Trigger/GundrakTriggers.cpp +++ b/src/Ai/Dungeon/GD/GDTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "GundrakTriggers.h" +#include "GDTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/Gundrak/Trigger/GundrakTriggers.h b/src/Ai/Dungeon/GD/GDTriggers.h similarity index 100% rename from src/Ai/Dungeon/Gundrak/Trigger/GundrakTriggers.h rename to src/Ai/Dungeon/GD/GDTriggers.h diff --git a/src/Ai/Dungeon/HallsOfReflection/TODO b/src/Ai/Dungeon/HallsOfReflection/TODO deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/Ai/Dungeon/HallsOfLightning/HallsOfLightningActionContext.h b/src/Ai/Dungeon/HoL/HoLActionContext.h similarity index 98% rename from src/Ai/Dungeon/HallsOfLightning/HallsOfLightningActionContext.h rename to src/Ai/Dungeon/HoL/HoLActionContext.h index c3073d49220..f17380c9a4d 100644 --- a/src/Ai/Dungeon/HallsOfLightning/HallsOfLightningActionContext.h +++ b/src/Ai/Dungeon/HoL/HoLActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "HallsOfLightningActions.h" +#include "HoLActions.h" class WotlkDungeonHoLActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/HallsOfLightning/Action/HallsOfLightningActions.cpp b/src/Ai/Dungeon/HoL/HoLActions.cpp similarity index 99% rename from src/Ai/Dungeon/HallsOfLightning/Action/HallsOfLightningActions.cpp rename to src/Ai/Dungeon/HoL/HoLActions.cpp index 7dcf643334f..f665e475da0 100644 --- a/src/Ai/Dungeon/HallsOfLightning/Action/HallsOfLightningActions.cpp +++ b/src/Ai/Dungeon/HoL/HoLActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "HallsOfLightningActions.h" +#include "HoLActions.h" bool BjarngrimTargetAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/HallsOfLightning/Action/HallsOfLightningActions.h b/src/Ai/Dungeon/HoL/HoLActions.h similarity index 98% rename from src/Ai/Dungeon/HallsOfLightning/Action/HallsOfLightningActions.h rename to src/Ai/Dungeon/HoL/HoLActions.h index fe81aa44895..80bb51c2239 100644 --- a/src/Ai/Dungeon/HallsOfLightning/Action/HallsOfLightningActions.h +++ b/src/Ai/Dungeon/HoL/HoLActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "HallsOfLightningTriggers.h" +#include "HoLTriggers.h" const Position IONAR_TANK_POSITION = Position(1078.860f, -261.928f, 61.226f); const Position DISPERSE_POSITION = Position(1161.152f, -261.584f, 53.223f); diff --git a/src/Ai/Dungeon/HallsOfLightning/Multiplier/HallsOfLightningMultipliers.cpp b/src/Ai/Dungeon/HoL/HoLMultipliers.cpp similarity index 96% rename from src/Ai/Dungeon/HallsOfLightning/Multiplier/HallsOfLightningMultipliers.cpp rename to src/Ai/Dungeon/HoL/HoLMultipliers.cpp index 3bf9fcfd635..3fdc645e991 100644 --- a/src/Ai/Dungeon/HallsOfLightning/Multiplier/HallsOfLightningMultipliers.cpp +++ b/src/Ai/Dungeon/HoL/HoLMultipliers.cpp @@ -1,9 +1,9 @@ -#include "HallsOfLightningMultipliers.h" -#include "HallsOfLightningActions.h" +#include "HoLMultipliers.h" +#include "HoLActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "HallsOfLightningTriggers.h" +#include "HoLTriggers.h" #include "Action.h" #include "WarriorActions.h" diff --git a/src/Ai/Dungeon/HallsOfLightning/Multiplier/HallsOfLightningMultipliers.h b/src/Ai/Dungeon/HoL/HoLMultipliers.h similarity index 100% rename from src/Ai/Dungeon/HallsOfLightning/Multiplier/HallsOfLightningMultipliers.h rename to src/Ai/Dungeon/HoL/HoLMultipliers.h diff --git a/src/Ai/Dungeon/HallsOfLightning/Strategy/HallsOfLightningStrategy.cpp b/src/Ai/Dungeon/HoL/HoLStrategy.cpp similarity index 95% rename from src/Ai/Dungeon/HallsOfLightning/Strategy/HallsOfLightningStrategy.cpp rename to src/Ai/Dungeon/HoL/HoLStrategy.cpp index 1266fb0b9cb..e977fd3b585 100644 --- a/src/Ai/Dungeon/HallsOfLightning/Strategy/HallsOfLightningStrategy.cpp +++ b/src/Ai/Dungeon/HoL/HoLStrategy.cpp @@ -1,5 +1,5 @@ -#include "HallsOfLightningStrategy.h" -#include "HallsOfLightningMultipliers.h" +#include "HoLStrategy.h" +#include "HoLMultipliers.h" void WotlkDungeonHoLStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/HallsOfLightning/Strategy/HallsOfLightningStrategy.h b/src/Ai/Dungeon/HoL/HoLStrategy.h similarity index 100% rename from src/Ai/Dungeon/HallsOfLightning/Strategy/HallsOfLightningStrategy.h rename to src/Ai/Dungeon/HoL/HoLStrategy.h diff --git a/src/Ai/Dungeon/HallsOfLightning/HallsOfLightningTriggerContext.h b/src/Ai/Dungeon/HoL/HoLTriggerContext.h similarity index 98% rename from src/Ai/Dungeon/HallsOfLightning/HallsOfLightningTriggerContext.h rename to src/Ai/Dungeon/HoL/HoLTriggerContext.h index 74cd0cd73eb..999abaa24c2 100644 --- a/src/Ai/Dungeon/HallsOfLightning/HallsOfLightningTriggerContext.h +++ b/src/Ai/Dungeon/HoL/HoLTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "HallsOfLightningTriggers.h" +#include "HoLTriggers.h" class WotlkDungeonHoLTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/HallsOfLightning/Trigger/HallsOfLightningTriggers.cpp b/src/Ai/Dungeon/HoL/HoLTriggers.cpp similarity index 98% rename from src/Ai/Dungeon/HallsOfLightning/Trigger/HallsOfLightningTriggers.cpp rename to src/Ai/Dungeon/HoL/HoLTriggers.cpp index da88807cafb..6b63ce5f5a0 100644 --- a/src/Ai/Dungeon/HallsOfLightning/Trigger/HallsOfLightningTriggers.cpp +++ b/src/Ai/Dungeon/HoL/HoLTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "HallsOfLightningTriggers.h" +#include "HoLTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/HallsOfLightning/Trigger/HallsOfLightningTriggers.h b/src/Ai/Dungeon/HoL/HoLTriggers.h similarity index 100% rename from src/Ai/Dungeon/HallsOfLightning/Trigger/HallsOfLightningTriggers.h rename to src/Ai/Dungeon/HoL/HoLTriggers.h diff --git a/src/Ai/Dungeon/HallsOfStone/HallsOfStoneActionContext.h b/src/Ai/Dungeon/HoS/HoSActionContext.h similarity index 95% rename from src/Ai/Dungeon/HallsOfStone/HallsOfStoneActionContext.h rename to src/Ai/Dungeon/HoS/HoSActionContext.h index 40b469b40f9..44ccfafcd54 100644 --- a/src/Ai/Dungeon/HallsOfStone/HallsOfStoneActionContext.h +++ b/src/Ai/Dungeon/HoS/HoSActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "HallsOfStoneActions.h" +#include "HoSActions.h" class WotlkDungeonHoSActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.cpp b/src/Ai/Dungeon/HoS/HoSActions.cpp similarity index 97% rename from src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.cpp rename to src/Ai/Dungeon/HoS/HoSActions.cpp index 51f1072b552..556553f8f2d 100644 --- a/src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.cpp +++ b/src/Ai/Dungeon/HoS/HoSActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "HallsOfStoneActions.h" +#include "HoSActions.h" bool ShatterSpreadAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.h b/src/Ai/Dungeon/HoS/HoSActions.h similarity index 94% rename from src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.h rename to src/Ai/Dungeon/HoS/HoSActions.h index ad7058afdc9..a564a968e12 100644 --- a/src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.h +++ b/src/Ai/Dungeon/HoS/HoSActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "HallsOfStoneTriggers.h" +#include "HoSTriggers.h" class ShatterSpreadAction : public MovementAction { diff --git a/src/Ai/Dungeon/HallsOfStone/Multiplier/HallsOfStoneMultipliers.cpp b/src/Ai/Dungeon/HoS/HoSMultipliers.cpp similarity index 92% rename from src/Ai/Dungeon/HallsOfStone/Multiplier/HallsOfStoneMultipliers.cpp rename to src/Ai/Dungeon/HoS/HoSMultipliers.cpp index 443905bbbbc..3813b1388cd 100644 --- a/src/Ai/Dungeon/HallsOfStone/Multiplier/HallsOfStoneMultipliers.cpp +++ b/src/Ai/Dungeon/HoS/HoSMultipliers.cpp @@ -1,9 +1,9 @@ -#include "HallsOfStoneMultipliers.h" -#include "HallsOfStoneActions.h" +#include "HoSMultipliers.h" +#include "HoSActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "HallsOfStoneTriggers.h" +#include "HoSTriggers.h" #include "Action.h" float KrystallusMultiplier::GetValue(Action* action) diff --git a/src/Ai/Dungeon/HallsOfStone/Multiplier/HallsOfStoneMultipliers.h b/src/Ai/Dungeon/HoS/HoSMultipliers.h similarity index 100% rename from src/Ai/Dungeon/HallsOfStone/Multiplier/HallsOfStoneMultipliers.h rename to src/Ai/Dungeon/HoS/HoSMultipliers.h diff --git a/src/Ai/Dungeon/HallsOfStone/Strategy/HallsOfStoneStrategy.cpp b/src/Ai/Dungeon/HoS/HoSStrategy.cpp similarity index 93% rename from src/Ai/Dungeon/HallsOfStone/Strategy/HallsOfStoneStrategy.cpp rename to src/Ai/Dungeon/HoS/HoSStrategy.cpp index 47006a9e441..7a83c309db2 100644 --- a/src/Ai/Dungeon/HallsOfStone/Strategy/HallsOfStoneStrategy.cpp +++ b/src/Ai/Dungeon/HoS/HoSStrategy.cpp @@ -1,5 +1,5 @@ -#include "HallsOfStoneStrategy.h" -#include "HallsOfStoneMultipliers.h" +#include "HoSStrategy.h" +#include "HoSMultipliers.h" void WotlkDungeonHoSStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/HallsOfStone/Strategy/HallsOfStoneStrategy.h b/src/Ai/Dungeon/HoS/HoSStrategy.h similarity index 100% rename from src/Ai/Dungeon/HallsOfStone/Strategy/HallsOfStoneStrategy.h rename to src/Ai/Dungeon/HoS/HoSStrategy.h diff --git a/src/Ai/Dungeon/HallsOfStone/HallsOfStoneTriggerContext.h b/src/Ai/Dungeon/HoS/HoSTriggerContext.h similarity index 95% rename from src/Ai/Dungeon/HallsOfStone/HallsOfStoneTriggerContext.h rename to src/Ai/Dungeon/HoS/HoSTriggerContext.h index bd3d6dd01b3..c6da70b69ea 100644 --- a/src/Ai/Dungeon/HallsOfStone/HallsOfStoneTriggerContext.h +++ b/src/Ai/Dungeon/HoS/HoSTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "HallsOfStoneTriggers.h" +#include "HoSTriggers.h" class WotlkDungeonHoSTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/HallsOfStone/Trigger/HallsOfStoneTriggers.cpp b/src/Ai/Dungeon/HoS/HoSTriggers.cpp similarity index 95% rename from src/Ai/Dungeon/HallsOfStone/Trigger/HallsOfStoneTriggers.cpp rename to src/Ai/Dungeon/HoS/HoSTriggers.cpp index 2e6e37b86f0..78b37e0ce3c 100644 --- a/src/Ai/Dungeon/HallsOfStone/Trigger/HallsOfStoneTriggers.cpp +++ b/src/Ai/Dungeon/HoS/HoSTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "HallsOfStoneTriggers.h" +#include "HoSTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/HallsOfStone/Trigger/HallsOfStoneTriggers.h b/src/Ai/Dungeon/HoS/HoSTriggers.h similarity index 100% rename from src/Ai/Dungeon/HallsOfStone/Trigger/HallsOfStoneTriggers.h rename to src/Ai/Dungeon/HoS/HoSTriggers.h diff --git a/src/Ai/Dungeon/Nexus/NexusActionContext.h b/src/Ai/Dungeon/Nex/NexActionContext.h similarity index 98% rename from src/Ai/Dungeon/Nexus/NexusActionContext.h rename to src/Ai/Dungeon/Nex/NexActionContext.h index c024226258a..e8d91ba750e 100644 --- a/src/Ai/Dungeon/Nexus/NexusActionContext.h +++ b/src/Ai/Dungeon/Nex/NexActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "NexusActions.h" +#include "NexActions.h" class WotlkDungeonNexActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/Nexus/Action/NexusActions.cpp b/src/Ai/Dungeon/Nex/NexActions.cpp similarity index 99% rename from src/Ai/Dungeon/Nexus/Action/NexusActions.cpp rename to src/Ai/Dungeon/Nex/NexActions.cpp index 12b546b5eaf..1dacc803ead 100644 --- a/src/Ai/Dungeon/Nexus/Action/NexusActions.cpp +++ b/src/Ai/Dungeon/Nex/NexActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "NexusActions.h" +#include "NexActions.h" bool MoveFromWhirlwindAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/Nexus/Action/NexusActions.h b/src/Ai/Dungeon/Nex/NexActions.h similarity index 98% rename from src/Ai/Dungeon/Nexus/Action/NexusActions.h rename to src/Ai/Dungeon/Nex/NexActions.h index 96790c367ce..672cf0778ba 100644 --- a/src/Ai/Dungeon/Nexus/Action/NexusActions.h +++ b/src/Ai/Dungeon/Nex/NexActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "NexusTriggers.h" +#include "NexTriggers.h" class MoveFromWhirlwindAction : public MovementAction { diff --git a/src/Ai/Dungeon/Nexus/Multiplier/NexusMultipliers.cpp b/src/Ai/Dungeon/Nex/NexMultipliers.cpp similarity index 97% rename from src/Ai/Dungeon/Nexus/Multiplier/NexusMultipliers.cpp rename to src/Ai/Dungeon/Nex/NexMultipliers.cpp index 7adc79b4559..129b8a098e6 100644 --- a/src/Ai/Dungeon/Nexus/Multiplier/NexusMultipliers.cpp +++ b/src/Ai/Dungeon/Nex/NexMultipliers.cpp @@ -1,9 +1,9 @@ -#include "NexusMultipliers.h" -#include "NexusActions.h" +#include "NexMultipliers.h" +#include "NexActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "NexusTriggers.h" +#include "NexTriggers.h" float FactionCommanderMultiplier::GetValue(Action* action) { diff --git a/src/Ai/Dungeon/Nexus/Multiplier/NexusMultipliers.h b/src/Ai/Dungeon/Nex/NexMultipliers.h similarity index 100% rename from src/Ai/Dungeon/Nexus/Multiplier/NexusMultipliers.h rename to src/Ai/Dungeon/Nex/NexMultipliers.h diff --git a/src/Ai/Dungeon/Nexus/Strategy/NexusStrategy.cpp b/src/Ai/Dungeon/Nex/NexStrategy.cpp similarity index 97% rename from src/Ai/Dungeon/Nexus/Strategy/NexusStrategy.cpp rename to src/Ai/Dungeon/Nex/NexStrategy.cpp index e633ba98228..7f2e773d757 100644 --- a/src/Ai/Dungeon/Nexus/Strategy/NexusStrategy.cpp +++ b/src/Ai/Dungeon/Nex/NexStrategy.cpp @@ -1,5 +1,5 @@ -#include "NexusStrategy.h" -#include "NexusMultipliers.h" +#include "NexStrategy.h" +#include "NexMultipliers.h" void WotlkDungeonNexStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/Nexus/Strategy/NexusStrategy.h b/src/Ai/Dungeon/Nex/NexStrategy.h similarity index 100% rename from src/Ai/Dungeon/Nexus/Strategy/NexusStrategy.h rename to src/Ai/Dungeon/Nex/NexStrategy.h diff --git a/src/Ai/Dungeon/Nexus/NexusTriggerContext.h b/src/Ai/Dungeon/Nex/NexTriggerContext.h similarity index 98% rename from src/Ai/Dungeon/Nexus/NexusTriggerContext.h rename to src/Ai/Dungeon/Nex/NexTriggerContext.h index 38ebbcb3db7..706215c2f9c 100644 --- a/src/Ai/Dungeon/Nexus/NexusTriggerContext.h +++ b/src/Ai/Dungeon/Nex/NexTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "NexusTriggers.h" +#include "NexTriggers.h" class WotlkDungeonNexTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/Nexus/Trigger/NexusTriggers.cpp b/src/Ai/Dungeon/Nex/NexTriggers.cpp similarity index 99% rename from src/Ai/Dungeon/Nexus/Trigger/NexusTriggers.cpp rename to src/Ai/Dungeon/Nex/NexTriggers.cpp index 02be3f70e30..45febf0df99 100644 --- a/src/Ai/Dungeon/Nexus/Trigger/NexusTriggers.cpp +++ b/src/Ai/Dungeon/Nex/NexTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "NexusTriggers.h" +#include "NexTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/Nexus/Trigger/NexusTriggers.h b/src/Ai/Dungeon/Nex/NexTriggers.h similarity index 100% rename from src/Ai/Dungeon/Nexus/Trigger/NexusTriggers.h rename to src/Ai/Dungeon/Nex/NexTriggers.h diff --git a/src/Ai/Dungeon/Oculus/OculusActionContext.h b/src/Ai/Dungeon/OC/OCActionContext.h similarity index 98% rename from src/Ai/Dungeon/Oculus/OculusActionContext.h rename to src/Ai/Dungeon/OC/OCActionContext.h index fa5036e545e..fefeecd4416 100644 --- a/src/Ai/Dungeon/Oculus/OculusActionContext.h +++ b/src/Ai/Dungeon/OC/OCActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "OculusActions.h" +#include "OCActions.h" class WotlkDungeonOccActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/Oculus/Action/OculusActions.cpp b/src/Ai/Dungeon/OC/OCActions.cpp similarity index 99% rename from src/Ai/Dungeon/Oculus/Action/OculusActions.cpp rename to src/Ai/Dungeon/OC/OCActions.cpp index 3854510036b..babfff206a7 100644 --- a/src/Ai/Dungeon/Oculus/Action/OculusActions.cpp +++ b/src/Ai/Dungeon/OC/OCActions.cpp @@ -1,6 +1,6 @@ -#include "OculusTriggers.h" +#include "OCTriggers.h" #include "Playerbots.h" -#include "OculusActions.h" +#include "OCActions.h" #include "LastSpellCastValue.h" bool AvoidUnstableSphereAction::Execute(Event /*event*/) diff --git a/src/Ai/Dungeon/Oculus/Action/OculusActions.h b/src/Ai/Dungeon/OC/OCActions.h similarity index 100% rename from src/Ai/Dungeon/Oculus/Action/OculusActions.h rename to src/Ai/Dungeon/OC/OCActions.h diff --git a/src/Ai/Dungeon/Oculus/Multiplier/OculusMultipliers.cpp b/src/Ai/Dungeon/OC/OCMultipliers.cpp similarity index 97% rename from src/Ai/Dungeon/Oculus/Multiplier/OculusMultipliers.cpp rename to src/Ai/Dungeon/OC/OCMultipliers.cpp index f8fcdd3f168..a8a21e69381 100644 --- a/src/Ai/Dungeon/Oculus/Multiplier/OculusMultipliers.cpp +++ b/src/Ai/Dungeon/OC/OCMultipliers.cpp @@ -1,9 +1,9 @@ -#include "OculusMultipliers.h" -#include "OculusActions.h" +#include "OCMultipliers.h" +#include "OCActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "OculusTriggers.h" +#include "OCTriggers.h" #include "FollowActions.h" #include "ReachTargetActions.h" #include "Playerbots.h" diff --git a/src/Ai/Dungeon/Oculus/Multiplier/OculusMultipliers.h b/src/Ai/Dungeon/OC/OCMultipliers.h similarity index 100% rename from src/Ai/Dungeon/Oculus/Multiplier/OculusMultipliers.h rename to src/Ai/Dungeon/OC/OCMultipliers.h diff --git a/src/Ai/Dungeon/Oculus/Strategy/OculusStrategy.cpp b/src/Ai/Dungeon/OC/OCStrategy.cpp similarity index 96% rename from src/Ai/Dungeon/Oculus/Strategy/OculusStrategy.cpp rename to src/Ai/Dungeon/OC/OCStrategy.cpp index 4c558469eee..a96cab926a3 100644 --- a/src/Ai/Dungeon/Oculus/Strategy/OculusStrategy.cpp +++ b/src/Ai/Dungeon/OC/OCStrategy.cpp @@ -1,5 +1,5 @@ -#include "OculusStrategy.h" -#include "OculusMultipliers.h" +#include "OCStrategy.h" +#include "OCMultipliers.h" void WotlkDungeonOccStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/Oculus/Strategy/OculusStrategy.h b/src/Ai/Dungeon/OC/OCStrategy.h similarity index 100% rename from src/Ai/Dungeon/Oculus/Strategy/OculusStrategy.h rename to src/Ai/Dungeon/OC/OCStrategy.h diff --git a/src/Ai/Dungeon/Oculus/OculusTriggerContext.h b/src/Ai/Dungeon/OC/OCTriggerContext.h similarity index 98% rename from src/Ai/Dungeon/Oculus/OculusTriggerContext.h rename to src/Ai/Dungeon/OC/OCTriggerContext.h index ccd2d8ca1d2..da92923d945 100644 --- a/src/Ai/Dungeon/Oculus/OculusTriggerContext.h +++ b/src/Ai/Dungeon/OC/OCTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "OculusTriggers.h" +#include "OCTriggers.h" class WotlkDungeonOccTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/Oculus/Trigger/OculusTriggers.cpp b/src/Ai/Dungeon/OC/OCTriggers.cpp similarity index 98% rename from src/Ai/Dungeon/Oculus/Trigger/OculusTriggers.cpp rename to src/Ai/Dungeon/OC/OCTriggers.cpp index 9cc8288f366..e80a82bf8a3 100644 --- a/src/Ai/Dungeon/Oculus/Trigger/OculusTriggers.cpp +++ b/src/Ai/Dungeon/OC/OCTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "OculusTriggers.h" +#include "OCTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" #include "Unit.h" diff --git a/src/Ai/Dungeon/Oculus/Trigger/OculusTriggers.h b/src/Ai/Dungeon/OC/OCTriggers.h similarity index 100% rename from src/Ai/Dungeon/Oculus/Trigger/OculusTriggers.h rename to src/Ai/Dungeon/OC/OCTriggers.h diff --git a/src/Ai/Dungeon/PitOfSaron/PitOfSaronActionContext.h b/src/Ai/Dungeon/PoS/PoSActionContext.h similarity index 95% rename from src/Ai/Dungeon/PitOfSaron/PitOfSaronActionContext.h rename to src/Ai/Dungeon/PoS/PoSActionContext.h index 816202a8a58..b2bf1a16a4f 100644 --- a/src/Ai/Dungeon/PitOfSaron/PitOfSaronActionContext.h +++ b/src/Ai/Dungeon/PoS/PoSActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "PitOfSaronActions.h" +#include "PoSActions.h" class WotlkDungeonPoSActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.cpp b/src/Ai/Dungeon/PoS/PoSActions.cpp similarity index 99% rename from src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.cpp rename to src/Ai/Dungeon/PoS/PoSActions.cpp index 201a4419910..60edaa012ac 100644 --- a/src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.cpp +++ b/src/Ai/Dungeon/PoS/PoSActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "PitOfSaronActions.h" +#include "PoSActions.h" bool IckAndKrickAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.h b/src/Ai/Dungeon/PoS/PoSActions.h similarity index 96% rename from src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.h rename to src/Ai/Dungeon/PoS/PoSActions.h index a467766f1a6..b61b863b4d8 100644 --- a/src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.h +++ b/src/Ai/Dungeon/PoS/PoSActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "PitOfSaronTriggers.h" +#include "PoSTriggers.h" const Position ICKANDKRICK_TANK_POSITION = Position(816.8508f, 102.331505f, 509.1586f); diff --git a/src/Ai/Dungeon/PitOfSaron/Multiplier/PitOfSaronMultipliers.cpp b/src/Ai/Dungeon/PoS/PoSMultipliers.cpp similarity index 93% rename from src/Ai/Dungeon/PitOfSaron/Multiplier/PitOfSaronMultipliers.cpp rename to src/Ai/Dungeon/PoS/PoSMultipliers.cpp index 00de758c1a0..c112e08d602 100644 --- a/src/Ai/Dungeon/PitOfSaron/Multiplier/PitOfSaronMultipliers.cpp +++ b/src/Ai/Dungeon/PoS/PoSMultipliers.cpp @@ -1,9 +1,9 @@ -#include "PitOfSaronMultipliers.h" -#include "PitOfSaronActions.h" +#include "PoSMultipliers.h" +#include "PoSActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "PitOfSaronTriggers.h" +#include "PoSTriggers.h" float IckAndKrickMultiplier::GetValue(Action* action) { diff --git a/src/Ai/Dungeon/PitOfSaron/Multiplier/PitOfSaronMultipliers.h b/src/Ai/Dungeon/PoS/PoSMultipliers.h similarity index 100% rename from src/Ai/Dungeon/PitOfSaron/Multiplier/PitOfSaronMultipliers.h rename to src/Ai/Dungeon/PoS/PoSMultipliers.h diff --git a/src/Ai/Dungeon/PitOfSaron/Strategy/PitOfSaronStrategy.cpp b/src/Ai/Dungeon/PoS/PoSStrategy.cpp similarity index 87% rename from src/Ai/Dungeon/PitOfSaron/Strategy/PitOfSaronStrategy.cpp rename to src/Ai/Dungeon/PoS/PoSStrategy.cpp index 641af0a6d4a..4af650021b9 100644 --- a/src/Ai/Dungeon/PitOfSaron/Strategy/PitOfSaronStrategy.cpp +++ b/src/Ai/Dungeon/PoS/PoSStrategy.cpp @@ -1,5 +1,5 @@ -#include "PitOfSaronStrategy.h" -#include "PitOfSaronMultipliers.h" +#include "PoSStrategy.h" +#include "PoSMultipliers.h" void WotlkDungeonPoSStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Dungeon/PitOfSaron/Strategy/PitOfSaronStrategy.h b/src/Ai/Dungeon/PoS/PoSStrategy.h similarity index 100% rename from src/Ai/Dungeon/PitOfSaron/Strategy/PitOfSaronStrategy.h rename to src/Ai/Dungeon/PoS/PoSStrategy.h diff --git a/src/Ai/Dungeon/PitOfSaron/PitOfSaronTriggerContext.h b/src/Ai/Dungeon/PoS/PoSTriggerContext.h similarity index 95% rename from src/Ai/Dungeon/PitOfSaron/PitOfSaronTriggerContext.h rename to src/Ai/Dungeon/PoS/PoSTriggerContext.h index a31bff949d2..b6f3b2cf0cc 100644 --- a/src/Ai/Dungeon/PitOfSaron/PitOfSaronTriggerContext.h +++ b/src/Ai/Dungeon/PoS/PoSTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "PitOfSaronTriggers.h" +#include "PoSTriggers.h" class WotlkDungeonPoSTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/PitOfSaron/Trigger/PitOfSaronTriggers.cpp b/src/Ai/Dungeon/PoS/PoSTriggers.cpp similarity index 92% rename from src/Ai/Dungeon/PitOfSaron/Trigger/PitOfSaronTriggers.cpp rename to src/Ai/Dungeon/PoS/PoSTriggers.cpp index f744302cd4a..f43b68a8904 100644 --- a/src/Ai/Dungeon/PitOfSaron/Trigger/PitOfSaronTriggers.cpp +++ b/src/Ai/Dungeon/PoS/PoSTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "PitOfSaronTriggers.h" +#include "PoSTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/PitOfSaron/Trigger/PitOfSaronTriggers.h b/src/Ai/Dungeon/PoS/PoSTriggers.h similarity index 100% rename from src/Ai/Dungeon/PitOfSaron/Trigger/PitOfSaronTriggers.h rename to src/Ai/Dungeon/PoS/PoSTriggers.h diff --git a/src/Ai/Dungeon/TrialOfTheChampion/TrialOfTheChampionActionContext.h b/src/Ai/Dungeon/TOC/TOCActionContext.h similarity index 96% rename from src/Ai/Dungeon/TrialOfTheChampion/TrialOfTheChampionActionContext.h rename to src/Ai/Dungeon/TOC/TOCActionContext.h index 21f877f2412..ae6804b866b 100644 --- a/src/Ai/Dungeon/TrialOfTheChampion/TrialOfTheChampionActionContext.h +++ b/src/Ai/Dungeon/TOC/TOCActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "TrialOfTheChampionActions.h" +#include "TOCActions.h" class WotlkDungeonToCActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/TrialOfTheChampion/Action/TrialOfTheChampionActions.cpp b/src/Ai/Dungeon/TOC/TOCActions.cpp similarity index 99% rename from src/Ai/Dungeon/TrialOfTheChampion/Action/TrialOfTheChampionActions.cpp rename to src/Ai/Dungeon/TOC/TOCActions.cpp index f3b071fe0b1..9938ed5bd40 100644 --- a/src/Ai/Dungeon/TrialOfTheChampion/Action/TrialOfTheChampionActions.cpp +++ b/src/Ai/Dungeon/TOC/TOCActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "TrialOfTheChampionActions.h" +#include "TOCActions.h" #include "NearestNpcsValue.h" #include "ObjectAccessor.h" #include "Vehicle.h" diff --git a/src/Ai/Dungeon/TrialOfTheChampion/Action/TrialOfTheChampionActions.h b/src/Ai/Dungeon/TOC/TOCActions.h similarity index 97% rename from src/Ai/Dungeon/TrialOfTheChampion/Action/TrialOfTheChampionActions.h rename to src/Ai/Dungeon/TOC/TOCActions.h index ab2c18ae873..ad1c0fa26cf 100644 --- a/src/Ai/Dungeon/TrialOfTheChampion/Action/TrialOfTheChampionActions.h +++ b/src/Ai/Dungeon/TOC/TOCActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "TrialOfTheChampionTriggers.h" +#include "TOCTriggers.h" #include "MovementActions.h" #include "LastMovementValue.h" #include "ObjectGuid.h" diff --git a/src/Ai/Dungeon/TrialOfTheChampion/Multiplier/TrialOfTheChampionMultipliers.cpp b/src/Ai/Dungeon/TOC/TOCMultipliers.cpp similarity index 59% rename from src/Ai/Dungeon/TrialOfTheChampion/Multiplier/TrialOfTheChampionMultipliers.cpp rename to src/Ai/Dungeon/TOC/TOCMultipliers.cpp index c090ef68bba..b2a72410907 100644 --- a/src/Ai/Dungeon/TrialOfTheChampion/Multiplier/TrialOfTheChampionMultipliers.cpp +++ b/src/Ai/Dungeon/TOC/TOCMultipliers.cpp @@ -1,9 +1,9 @@ -#include "TrialOfTheChampionMultipliers.h" -#include "TrialOfTheChampionActions.h" +#include "TOCMultipliers.h" +#include "TOCActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "TrialOfTheChampionTriggers.h" +#include "TOCTriggers.h" #include "Action.h" //float tocMultiplier::GetValue(Action* action) { return 1.0f; } diff --git a/src/Ai/Dungeon/TrialOfTheChampion/Multiplier/TrialOfTheChampionMultipliers.h b/src/Ai/Dungeon/TOC/TOCMultipliers.h similarity index 100% rename from src/Ai/Dungeon/TrialOfTheChampion/Multiplier/TrialOfTheChampionMultipliers.h rename to src/Ai/Dungeon/TOC/TOCMultipliers.h diff --git a/src/Ai/Dungeon/TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.cpp b/src/Ai/Dungeon/TOC/TOCStrategy.cpp similarity index 94% rename from src/Ai/Dungeon/TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.cpp rename to src/Ai/Dungeon/TOC/TOCStrategy.cpp index 0912a912c87..62ae7fb8d8c 100644 --- a/src/Ai/Dungeon/TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.cpp +++ b/src/Ai/Dungeon/TOC/TOCStrategy.cpp @@ -1,4 +1,4 @@ -#include "TrialOfTheChampionStrategy.h" +#include "TOCStrategy.h" void WotlkDungeonToCStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.h b/src/Ai/Dungeon/TOC/TOCStrategy.h similarity index 92% rename from src/Ai/Dungeon/TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.h rename to src/Ai/Dungeon/TOC/TOCStrategy.h index 12416d5adcb..4a0c7c78310 100644 --- a/src/Ai/Dungeon/TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.h +++ b/src/Ai/Dungeon/TOC/TOCStrategy.h @@ -4,7 +4,7 @@ #include "AiObjectContext.h" #include "Multiplier.h" #include "Strategy.h" -#include "TrialOfTheChampionMultipliers.h" +#include "TOCMultipliers.h" class WotlkDungeonToCStrategy : public Strategy { diff --git a/src/Ai/Dungeon/TrialOfTheChampion/TrialOfTheChampionTriggerContext.h b/src/Ai/Dungeon/TOC/TOCTriggerContext.h similarity index 96% rename from src/Ai/Dungeon/TrialOfTheChampion/TrialOfTheChampionTriggerContext.h rename to src/Ai/Dungeon/TOC/TOCTriggerContext.h index f1b55c52317..2e27aeeef05 100644 --- a/src/Ai/Dungeon/TrialOfTheChampion/TrialOfTheChampionTriggerContext.h +++ b/src/Ai/Dungeon/TOC/TOCTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "TrialOfTheChampionTriggers.h" +#include "TOCTriggers.h" class WotlkDungeonToCTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/TrialOfTheChampion/Trigger/TrialOfTheChampionTriggers.cpp b/src/Ai/Dungeon/TOC/TOCTriggers.cpp similarity index 97% rename from src/Ai/Dungeon/TrialOfTheChampion/Trigger/TrialOfTheChampionTriggers.cpp rename to src/Ai/Dungeon/TOC/TOCTriggers.cpp index 6c388382e71..2f68940435a 100644 --- a/src/Ai/Dungeon/TrialOfTheChampion/Trigger/TrialOfTheChampionTriggers.cpp +++ b/src/Ai/Dungeon/TOC/TOCTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "TrialOfTheChampionTriggers.h" +#include "TOCTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/TrialOfTheChampion/Trigger/TrialOfTheChampionTriggers.h b/src/Ai/Dungeon/TOC/TOCTriggers.h similarity index 100% rename from src/Ai/Dungeon/TrialOfTheChampion/Trigger/TrialOfTheChampionTriggers.h rename to src/Ai/Dungeon/TOC/TOCTriggers.h diff --git a/src/Ai/Dungeon/TbcDungeonActionContext.h b/src/Ai/Dungeon/TbcDungeonActionContext.h index 8c35472248b..6b4bf72a88b 100644 --- a/src/Ai/Dungeon/TbcDungeonActionContext.h +++ b/src/Ai/Dungeon/TbcDungeonActionContext.h @@ -1,6 +1,6 @@ #ifndef _PLAYERBOT_TBCDUNGEONACTIONCONTEXT_H #define _PLAYERBOT_TBCDUNGEONACTIONCONTEXT_H -#include "AuchenaiCrypts/AuchenaiCryptsActionContext.h" +#include "ACActionContext.h" #endif diff --git a/src/Ai/Dungeon/TbcDungeonTriggerContext.h b/src/Ai/Dungeon/TbcDungeonTriggerContext.h index 9a680b7af15..f28933999b5 100644 --- a/src/Ai/Dungeon/TbcDungeonTriggerContext.h +++ b/src/Ai/Dungeon/TbcDungeonTriggerContext.h @@ -1,6 +1,6 @@ #ifndef _PLAYERBOT_TBCDUNGEONTRIGGERCONTEXT_H #define _PLAYERBOT_TBCDUNGEONTRIGGERCONTEXT_H -#include "AuchenaiCrypts/AuchenaiCryptsTriggerContext.h" +#include "ACTriggerContext.h" #endif diff --git a/src/Ai/Dungeon/UtgardeKeep/UtgardeKeepActionContext.h b/src/Ai/Dungeon/UK/UKActionContext.h similarity index 98% rename from src/Ai/Dungeon/UtgardeKeep/UtgardeKeepActionContext.h rename to src/Ai/Dungeon/UK/UKActionContext.h index 0e51e56b40a..b52aa184696 100644 --- a/src/Ai/Dungeon/UtgardeKeep/UtgardeKeepActionContext.h +++ b/src/Ai/Dungeon/UK/UKActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "UtgardeKeepActions.h" +#include "UKActions.h" class WotlkDungeonUKActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/UtgardeKeep/Action/UtgardeKeepActions.cpp b/src/Ai/Dungeon/UK/UKActions.cpp similarity index 98% rename from src/Ai/Dungeon/UtgardeKeep/Action/UtgardeKeepActions.cpp rename to src/Ai/Dungeon/UK/UKActions.cpp index 85d8590ee2b..b11ff674685 100644 --- a/src/Ai/Dungeon/UtgardeKeep/Action/UtgardeKeepActions.cpp +++ b/src/Ai/Dungeon/UK/UKActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "UtgardeKeepActions.h" +#include "UKActions.h" bool AttackFrostTombAction::isUseful() { return !botAI->IsHeal(bot); } bool AttackFrostTombAction::Execute(Event /*event*/) diff --git a/src/Ai/Dungeon/UtgardeKeep/Action/UtgardeKeepActions.h b/src/Ai/Dungeon/UK/UKActions.h similarity index 97% rename from src/Ai/Dungeon/UtgardeKeep/Action/UtgardeKeepActions.h rename to src/Ai/Dungeon/UK/UKActions.h index c9c77d52087..c7d4c59d49c 100644 --- a/src/Ai/Dungeon/UtgardeKeep/Action/UtgardeKeepActions.h +++ b/src/Ai/Dungeon/UK/UKActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "UtgardeKeepTriggers.h" +#include "UKTriggers.h" class AttackFrostTombAction : public AttackAction { diff --git a/src/Ai/Dungeon/UtgardeKeep/Multiplier/UtgardeKeepMultipliers.cpp b/src/Ai/Dungeon/UK/UKMultipliers.cpp similarity index 96% rename from src/Ai/Dungeon/UtgardeKeep/Multiplier/UtgardeKeepMultipliers.cpp rename to src/Ai/Dungeon/UK/UKMultipliers.cpp index e9a411dac8c..98f5528f2e3 100644 --- a/src/Ai/Dungeon/UtgardeKeep/Multiplier/UtgardeKeepMultipliers.cpp +++ b/src/Ai/Dungeon/UK/UKMultipliers.cpp @@ -1,8 +1,8 @@ -#include "UtgardeKeepMultipliers.h" -#include "UtgardeKeepActions.h" +#include "UKMultipliers.h" +#include "UKActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" -#include "UtgardeKeepTriggers.h" +#include "UKTriggers.h" float PrinceKelesethMultiplier::GetValue(Action* action) { diff --git a/src/Ai/Dungeon/UtgardeKeep/Multiplier/UtgardeKeepMultipliers.h b/src/Ai/Dungeon/UK/UKMultipliers.h similarity index 100% rename from src/Ai/Dungeon/UtgardeKeep/Multiplier/UtgardeKeepMultipliers.h rename to src/Ai/Dungeon/UK/UKMultipliers.h diff --git a/src/Ai/Dungeon/UtgardeKeep/Strategy/UtgardeKeepStrategy.cpp b/src/Ai/Dungeon/UK/UKStrategy.cpp similarity index 96% rename from src/Ai/Dungeon/UtgardeKeep/Strategy/UtgardeKeepStrategy.cpp rename to src/Ai/Dungeon/UK/UKStrategy.cpp index 562cb8ec5f0..b298edbc114 100644 --- a/src/Ai/Dungeon/UtgardeKeep/Strategy/UtgardeKeepStrategy.cpp +++ b/src/Ai/Dungeon/UK/UKStrategy.cpp @@ -1,5 +1,5 @@ -#include "UtgardeKeepStrategy.h" -#include "UtgardeKeepMultipliers.h" +#include "UKStrategy.h" +#include "UKMultipliers.h" void WotlkDungeonUKStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/UtgardeKeep/Strategy/UtgardeKeepStrategy.h b/src/Ai/Dungeon/UK/UKStrategy.h similarity index 100% rename from src/Ai/Dungeon/UtgardeKeep/Strategy/UtgardeKeepStrategy.h rename to src/Ai/Dungeon/UK/UKStrategy.h diff --git a/src/Ai/Dungeon/UtgardeKeep/UtgardeKeepTriggerContext.h b/src/Ai/Dungeon/UK/UKTriggerContext.h similarity index 98% rename from src/Ai/Dungeon/UtgardeKeep/UtgardeKeepTriggerContext.h rename to src/Ai/Dungeon/UK/UKTriggerContext.h index 52ec95dae4f..59bc18972d3 100644 --- a/src/Ai/Dungeon/UtgardeKeep/UtgardeKeepTriggerContext.h +++ b/src/Ai/Dungeon/UK/UKTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "UtgardeKeepTriggers.h" +#include "UKTriggers.h" class WotlkDungeonUKTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/UtgardeKeep/Trigger/UtgardeKeepTriggers.cpp b/src/Ai/Dungeon/UK/UKTriggers.cpp similarity index 98% rename from src/Ai/Dungeon/UtgardeKeep/Trigger/UtgardeKeepTriggers.cpp rename to src/Ai/Dungeon/UK/UKTriggers.cpp index e19234bd0ec..4fda7a0a366 100644 --- a/src/Ai/Dungeon/UtgardeKeep/Trigger/UtgardeKeepTriggers.cpp +++ b/src/Ai/Dungeon/UK/UKTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "UtgardeKeepTriggers.h" +#include "UKTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/UtgardeKeep/Trigger/UtgardeKeepTriggers.h b/src/Ai/Dungeon/UK/UKTriggers.h similarity index 100% rename from src/Ai/Dungeon/UtgardeKeep/Trigger/UtgardeKeepTriggers.h rename to src/Ai/Dungeon/UK/UKTriggers.h diff --git a/src/Ai/Dungeon/UtgardePinnacle/UtgardePinnacleActionContext.h b/src/Ai/Dungeon/UP/UPActionContext.h similarity index 96% rename from src/Ai/Dungeon/UtgardePinnacle/UtgardePinnacleActionContext.h rename to src/Ai/Dungeon/UP/UPActionContext.h index 9e7532b9aeb..bab3c83b22e 100644 --- a/src/Ai/Dungeon/UtgardePinnacle/UtgardePinnacleActionContext.h +++ b/src/Ai/Dungeon/UP/UPActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "UtgardePinnacleActions.h" +#include "UPActions.h" class WotlkDungeonUPActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/UtgardePinnacle/Action/UtgardePinnacleActions.cpp b/src/Ai/Dungeon/UP/UPActions.cpp similarity index 98% rename from src/Ai/Dungeon/UtgardePinnacle/Action/UtgardePinnacleActions.cpp rename to src/Ai/Dungeon/UP/UPActions.cpp index 8025f1aac0c..802da7262e4 100644 --- a/src/Ai/Dungeon/UtgardePinnacle/Action/UtgardePinnacleActions.cpp +++ b/src/Ai/Dungeon/UP/UPActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "UtgardePinnacleActions.h" +#include "UPActions.h" bool AvoidFreezingCloudAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/UtgardePinnacle/Action/UtgardePinnacleActions.h b/src/Ai/Dungeon/UP/UPActions.h similarity index 94% rename from src/Ai/Dungeon/UtgardePinnacle/Action/UtgardePinnacleActions.h rename to src/Ai/Dungeon/UP/UPActions.h index 38e90dd159e..444b0e1841b 100644 --- a/src/Ai/Dungeon/UtgardePinnacle/Action/UtgardePinnacleActions.h +++ b/src/Ai/Dungeon/UP/UPActions.h @@ -5,7 +5,7 @@ #include "AttackAction.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "UtgardePinnacleTriggers.h" +#include "UPTriggers.h" class AvoidFreezingCloudAction : public MovementAction { diff --git a/src/Ai/Dungeon/UtgardePinnacle/Multiplier/UtgardePinnacleMultipliers.cpp b/src/Ai/Dungeon/UP/UPMultipliers.cpp similarity index 96% rename from src/Ai/Dungeon/UtgardePinnacle/Multiplier/UtgardePinnacleMultipliers.cpp rename to src/Ai/Dungeon/UP/UPMultipliers.cpp index cc34cb6a4e3..bccf8402837 100644 --- a/src/Ai/Dungeon/UtgardePinnacle/Multiplier/UtgardePinnacleMultipliers.cpp +++ b/src/Ai/Dungeon/UP/UPMultipliers.cpp @@ -1,9 +1,9 @@ -#include "UtgardePinnacleMultipliers.h" -#include "UtgardePinnacleActions.h" +#include "UPMultipliers.h" +#include "UPActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "UtgardePinnacleTriggers.h" +#include "UPTriggers.h" #include "Action.h" float SkadiMultiplier::GetValue(Action* action) diff --git a/src/Ai/Dungeon/UtgardePinnacle/Multiplier/UtgardePinnacleMultipliers.h b/src/Ai/Dungeon/UP/UPMultipliers.h similarity index 100% rename from src/Ai/Dungeon/UtgardePinnacle/Multiplier/UtgardePinnacleMultipliers.h rename to src/Ai/Dungeon/UP/UPMultipliers.h diff --git a/src/Ai/Dungeon/UtgardePinnacle/Strategy/UtgardePinnacleStrategy.cpp b/src/Ai/Dungeon/UP/UPStrategy.cpp similarity index 92% rename from src/Ai/Dungeon/UtgardePinnacle/Strategy/UtgardePinnacleStrategy.cpp rename to src/Ai/Dungeon/UP/UPStrategy.cpp index fe104f34fb9..392c23c2f23 100644 --- a/src/Ai/Dungeon/UtgardePinnacle/Strategy/UtgardePinnacleStrategy.cpp +++ b/src/Ai/Dungeon/UP/UPStrategy.cpp @@ -1,5 +1,5 @@ -#include "UtgardePinnacleStrategy.h" -#include "UtgardePinnacleMultipliers.h" +#include "UPStrategy.h" +#include "UPMultipliers.h" void WotlkDungeonUPStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/UtgardePinnacle/Strategy/UtgardePinnacleStrategy.h b/src/Ai/Dungeon/UP/UPStrategy.h similarity index 100% rename from src/Ai/Dungeon/UtgardePinnacle/Strategy/UtgardePinnacleStrategy.h rename to src/Ai/Dungeon/UP/UPStrategy.h diff --git a/src/Ai/Dungeon/UtgardePinnacle/UtgardePinnacleTriggerContext.h b/src/Ai/Dungeon/UP/UPTriggerContext.h similarity index 95% rename from src/Ai/Dungeon/UtgardePinnacle/UtgardePinnacleTriggerContext.h rename to src/Ai/Dungeon/UP/UPTriggerContext.h index 527453f8bfb..ffcafad67c3 100644 --- a/src/Ai/Dungeon/UtgardePinnacle/UtgardePinnacleTriggerContext.h +++ b/src/Ai/Dungeon/UP/UPTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "UtgardePinnacleTriggers.h" +#include "UPTriggers.h" class WotlkDungeonUPTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/UtgardePinnacle/Trigger/UtgardePinnacleTriggers.cpp b/src/Ai/Dungeon/UP/UPTriggers.cpp similarity index 98% rename from src/Ai/Dungeon/UtgardePinnacle/Trigger/UtgardePinnacleTriggers.cpp rename to src/Ai/Dungeon/UP/UPTriggers.cpp index dc35e8be08e..4baeaa725a1 100644 --- a/src/Ai/Dungeon/UtgardePinnacle/Trigger/UtgardePinnacleTriggers.cpp +++ b/src/Ai/Dungeon/UP/UPTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "UtgardePinnacleTriggers.h" +#include "UPTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/UtgardePinnacle/Trigger/UtgardePinnacleTriggers.h b/src/Ai/Dungeon/UP/UPTriggers.h similarity index 100% rename from src/Ai/Dungeon/UtgardePinnacle/Trigger/UtgardePinnacleTriggers.h rename to src/Ai/Dungeon/UP/UPTriggers.h diff --git a/src/Ai/Dungeon/VioletHold/VioletHoldActionContext.h b/src/Ai/Dungeon/VH/VHActionContext.h similarity index 97% rename from src/Ai/Dungeon/VioletHold/VioletHoldActionContext.h rename to src/Ai/Dungeon/VH/VHActionContext.h index 902332f1585..783d4908161 100644 --- a/src/Ai/Dungeon/VioletHold/VioletHoldActionContext.h +++ b/src/Ai/Dungeon/VH/VHActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "VioletHoldActions.h" +#include "VHActions.h" class WotlkDungeonVHActionContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/VioletHold/Action/VioletHoldActions.cpp b/src/Ai/Dungeon/VH/VHActions.cpp similarity index 98% rename from src/Ai/Dungeon/VioletHold/Action/VioletHoldActions.cpp rename to src/Ai/Dungeon/VH/VHActions.cpp index b61f84bdfbb..aa04fdb41d6 100644 --- a/src/Ai/Dungeon/VioletHold/Action/VioletHoldActions.cpp +++ b/src/Ai/Dungeon/VH/VHActions.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "VioletHoldActions.h" +#include "VHActions.h" bool AttackErekemAction::Execute(Event /*event*/) { diff --git a/src/Ai/Dungeon/VioletHold/Action/VioletHoldActions.h b/src/Ai/Dungeon/VH/VHActions.h similarity index 97% rename from src/Ai/Dungeon/VioletHold/Action/VioletHoldActions.h rename to src/Ai/Dungeon/VH/VHActions.h index fc7ce9d2940..64e5e74f5b4 100644 --- a/src/Ai/Dungeon/VioletHold/Action/VioletHoldActions.h +++ b/src/Ai/Dungeon/VH/VHActions.h @@ -6,7 +6,7 @@ #include "GenericSpellActions.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "VioletHoldTriggers.h" +#include "VHTriggers.h" class AttackErekemAction : public AttackAction { diff --git a/src/Ai/Dungeon/VioletHold/Multiplier/VioletHoldMultipliers.cpp b/src/Ai/Dungeon/VH/VHMultipliers.cpp similarity index 93% rename from src/Ai/Dungeon/VioletHold/Multiplier/VioletHoldMultipliers.cpp rename to src/Ai/Dungeon/VH/VHMultipliers.cpp index 3d3a13cd0f9..d5f79324a1d 100644 --- a/src/Ai/Dungeon/VioletHold/Multiplier/VioletHoldMultipliers.cpp +++ b/src/Ai/Dungeon/VH/VHMultipliers.cpp @@ -1,9 +1,9 @@ -#include "VioletHoldMultipliers.h" -#include "VioletHoldActions.h" +#include "VHMultipliers.h" +#include "VHActions.h" #include "GenericSpellActions.h" #include "ChooseTargetActions.h" #include "MovementActions.h" -#include "VioletHoldTriggers.h" +#include "VHTriggers.h" #include "Action.h" float ErekemMultiplier::GetValue(Action* action) diff --git a/src/Ai/Dungeon/VioletHold/Multiplier/VioletHoldMultipliers.h b/src/Ai/Dungeon/VH/VHMultipliers.h similarity index 100% rename from src/Ai/Dungeon/VioletHold/Multiplier/VioletHoldMultipliers.h rename to src/Ai/Dungeon/VH/VHMultipliers.h diff --git a/src/Ai/Dungeon/VioletHold/Strategy/VioletHoldStrategy.cpp b/src/Ai/Dungeon/VH/VHStrategy.cpp similarity index 95% rename from src/Ai/Dungeon/VioletHold/Strategy/VioletHoldStrategy.cpp rename to src/Ai/Dungeon/VH/VHStrategy.cpp index ffc00e30634..a30fa733a92 100644 --- a/src/Ai/Dungeon/VioletHold/Strategy/VioletHoldStrategy.cpp +++ b/src/Ai/Dungeon/VH/VHStrategy.cpp @@ -1,5 +1,5 @@ -#include "VioletHoldStrategy.h" -#include "VioletHoldMultipliers.h" +#include "VHStrategy.h" +#include "VHMultipliers.h" void WotlkDungeonVHStrategy::InitTriggers(std::vector &triggers) { diff --git a/src/Ai/Dungeon/VioletHold/Strategy/VioletHoldStrategy.h b/src/Ai/Dungeon/VH/VHStrategy.h similarity index 100% rename from src/Ai/Dungeon/VioletHold/Strategy/VioletHoldStrategy.h rename to src/Ai/Dungeon/VH/VHStrategy.h diff --git a/src/Ai/Dungeon/VioletHold/VioletHoldTriggerContext.h b/src/Ai/Dungeon/VH/VHTriggerContext.h similarity index 97% rename from src/Ai/Dungeon/VioletHold/VioletHoldTriggerContext.h rename to src/Ai/Dungeon/VH/VHTriggerContext.h index 45d29812181..9106f2a1379 100644 --- a/src/Ai/Dungeon/VioletHold/VioletHoldTriggerContext.h +++ b/src/Ai/Dungeon/VH/VHTriggerContext.h @@ -3,7 +3,7 @@ #include "NamedObjectContext.h" #include "AiObjectContext.h" -#include "VioletHoldTriggers.h" +#include "VHTriggers.h" class WotlkDungeonVHTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Dungeon/VioletHold/Trigger/VioletHoldTriggers.cpp b/src/Ai/Dungeon/VH/VHTriggers.cpp similarity index 97% rename from src/Ai/Dungeon/VioletHold/Trigger/VioletHoldTriggers.cpp rename to src/Ai/Dungeon/VH/VHTriggers.cpp index 87284b0e495..26d05f96f4f 100644 --- a/src/Ai/Dungeon/VioletHold/Trigger/VioletHoldTriggers.cpp +++ b/src/Ai/Dungeon/VH/VHTriggers.cpp @@ -1,5 +1,5 @@ #include "Playerbots.h" -#include "VioletHoldTriggers.h" +#include "VHTriggers.h" #include "AiObject.h" #include "AiObjectContext.h" diff --git a/src/Ai/Dungeon/VioletHold/Trigger/VioletHoldTriggers.h b/src/Ai/Dungeon/VH/VHTriggers.h similarity index 100% rename from src/Ai/Dungeon/VioletHold/Trigger/VioletHoldTriggers.h rename to src/Ai/Dungeon/VH/VHTriggers.h diff --git a/src/Ai/Dungeon/WotlkDungeonActionContext.h b/src/Ai/Dungeon/WotlkDungeonActionContext.h index c7202de61eb..b77f94fccb1 100644 --- a/src/Ai/Dungeon/WotlkDungeonActionContext.h +++ b/src/Ai/Dungeon/WotlkDungeonActionContext.h @@ -1,21 +1,21 @@ #ifndef _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H #define _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H -#include "UtgardeKeep/UtgardeKeepActionContext.h" -#include "Nexus/NexusActionContext.h" -#include "AzjolNerub/AzjolNerubActionContext.h" -#include "OldKingdom/OldKingdomActionContext.h" -#include "DraktharonKeep/DrakTharonKeepActionContext.h" -#include "VioletHold/VioletHoldActionContext.h" -#include "Gundrak/GundrakActionContext.h" -#include "HallsOfStone/HallsOfStoneActionContext.h" -#include "HallsOfLightning/HallsOfLightningActionContext.h" -#include "Oculus/OculusActionContext.h" -#include "UtgardePinnacle/UtgardePinnacleActionContext.h" -#include "CullingOfStratholme/CullingOfStratholmeActionContext.h" -#include "ForgeOfSouls/ForgeOfSoulsActionContext.h" -#include "PitOfSaron/PitOfSaronActionContext.h" -#include "TrialOfTheChampion/TrialOfTheChampionActionContext.h" +#include "UKActionContext.h" +#include "NexActionContext.h" +#include "ANActionContext.h" +#include "AKActionContext.h" +#include "DTKActionContext.h" +#include "VHActionContext.h" +#include "GDActionContext.h" +#include "HoSActionContext.h" +#include "HoLActionContext.h" +#include "OCActionContext.h" +#include "UPActionContext.h" +#include "CoSActionContext.h" +#include "FoSActionContext.h" +#include "PoSActionContext.h" +#include "TOCActionContext.h" // #include "HallsOfReflection/HallsOfReflectionActionContext.h" #endif diff --git a/src/Ai/Dungeon/WotlkDungeonTriggerContext.h b/src/Ai/Dungeon/WotlkDungeonTriggerContext.h index 630aecbd98f..b07973d6b1d 100644 --- a/src/Ai/Dungeon/WotlkDungeonTriggerContext.h +++ b/src/Ai/Dungeon/WotlkDungeonTriggerContext.h @@ -1,21 +1,21 @@ #ifndef _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H #define _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H -#include "UtgardeKeep/UtgardeKeepTriggerContext.h" -#include "Nexus/NexusTriggerContext.h" -#include "AzjolNerub/AzjolNerubTriggerContext.h" -#include "OldKingdom/OldKingdomTriggerContext.h" -#include "DraktharonKeep/DrakTharonKeepTriggerContext.h" -#include "VioletHold/VioletHoldTriggerContext.h" -#include "Gundrak/GundrakTriggerContext.h" -#include "HallsOfStone/HallsOfStoneTriggerContext.h" -#include "HallsOfLightning/HallsOfLightningTriggerContext.h" -#include "Oculus/OculusTriggerContext.h" -#include "UtgardePinnacle/UtgardePinnacleTriggerContext.h" -#include "CullingOfStratholme/CullingOfStratholmeTriggerContext.h" -#include "ForgeOfSouls/ForgeOfSoulsTriggerContext.h" -#include "PitOfSaron/PitOfSaronTriggerContext.h" -#include "TrialOfTheChampion/TrialOfTheChampionTriggerContext.h" +#include "UKTriggerContext.h" +#include "NexTriggerContext.h" +#include "ANTriggerContext.h" +#include "AKTriggerContext.h" +#include "DTKTriggerContext.h" +#include "VHTriggerContext.h" +#include "GDTriggerContext.h" +#include "HoSTriggerContext.h" +#include "HoLTriggerContext.h" +#include "OCTriggerContext.h" +#include "UPTriggerContext.h" +#include "CoSTriggerContext.h" +#include "FoSTriggerContext.h" +#include "PoSTriggerContext.h" +#include "TOCTriggerContext.h" // #include "HallsOfReflection/HallsOfReflectionTriggerContext.h" #endif diff --git a/src/Ai/Raid/Aq20/RaidAq20ActionContext.h b/src/Ai/Raid/Aq20/Aq20ActionContext.h similarity index 93% rename from src/Ai/Raid/Aq20/RaidAq20ActionContext.h rename to src/Ai/Raid/Aq20/Aq20ActionContext.h index ea3afcf410d..cde831f5c86 100644 --- a/src/Ai/Raid/Aq20/RaidAq20ActionContext.h +++ b/src/Ai/Raid/Aq20/Aq20ActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "RaidAq20Actions.h" +#include "Aq20Actions.h" class RaidAq20ActionContext : public NamedObjectContext { diff --git a/src/Ai/Raid/Aq20/Action/RaidAq20Actions.cpp b/src/Ai/Raid/Aq20/Aq20Actions.cpp similarity index 97% rename from src/Ai/Raid/Aq20/Action/RaidAq20Actions.cpp rename to src/Ai/Raid/Aq20/Aq20Actions.cpp index 5d9b4aaa365..8f4d63801c7 100644 --- a/src/Ai/Raid/Aq20/Action/RaidAq20Actions.cpp +++ b/src/Ai/Raid/Aq20/Aq20Actions.cpp @@ -1,6 +1,6 @@ -#include "RaidAq20Actions.h" +#include "Aq20Actions.h" -#include "RaidAq20Utils.h" +#include "Aq20Utils.h" bool Aq20UseCrystalAction::Execute(Event /*event*/) { diff --git a/src/Ai/Raid/Aq20/Action/RaidAq20Actions.h b/src/Ai/Raid/Aq20/Aq20Actions.h similarity index 100% rename from src/Ai/Raid/Aq20/Action/RaidAq20Actions.h rename to src/Ai/Raid/Aq20/Aq20Actions.h diff --git a/src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.cpp b/src/Ai/Raid/Aq20/Aq20Strategy.cpp similarity index 88% rename from src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.cpp rename to src/Ai/Raid/Aq20/Aq20Strategy.cpp index 93e0462caf6..a16c2b65262 100644 --- a/src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.cpp +++ b/src/Ai/Raid/Aq20/Aq20Strategy.cpp @@ -1,4 +1,4 @@ -#include "RaidAq20Strategy.h" +#include "Aq20Strategy.h" #include "Strategy.h" diff --git a/src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.h b/src/Ai/Raid/Aq20/Aq20Strategy.h similarity index 100% rename from src/Ai/Raid/Aq20/Strategy/RaidAq20Strategy.h rename to src/Ai/Raid/Aq20/Aq20Strategy.h diff --git a/src/Ai/Raid/Aq20/RaidAq20TriggerContext.h b/src/Ai/Raid/Aq20/Aq20TriggerContext.h similarity index 93% rename from src/Ai/Raid/Aq20/RaidAq20TriggerContext.h rename to src/Ai/Raid/Aq20/Aq20TriggerContext.h index b0307ca6a28..cdc101da159 100644 --- a/src/Ai/Raid/Aq20/RaidAq20TriggerContext.h +++ b/src/Ai/Raid/Aq20/Aq20TriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H #include "NamedObjectContext.h" -#include "RaidAq20Triggers.h" +#include "Aq20Triggers.h" class RaidAq20TriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/Aq20/Trigger/RaidAq20Triggers.cpp b/src/Ai/Raid/Aq20/Aq20Triggers.cpp similarity index 96% rename from src/Ai/Raid/Aq20/Trigger/RaidAq20Triggers.cpp rename to src/Ai/Raid/Aq20/Aq20Triggers.cpp index 913cb269062..77bd0395d79 100644 --- a/src/Ai/Raid/Aq20/Trigger/RaidAq20Triggers.cpp +++ b/src/Ai/Raid/Aq20/Aq20Triggers.cpp @@ -1,6 +1,6 @@ -#include "RaidAq20Triggers.h" +#include "Aq20Triggers.h" -#include "RaidAq20Utils.h" +#include "Aq20Utils.h" bool Aq20MoveToCrystalTrigger::IsActive() { diff --git a/src/Ai/Raid/Aq20/Trigger/RaidAq20Triggers.h b/src/Ai/Raid/Aq20/Aq20Triggers.h similarity index 100% rename from src/Ai/Raid/Aq20/Trigger/RaidAq20Triggers.h rename to src/Ai/Raid/Aq20/Aq20Triggers.h diff --git a/src/Ai/Raid/Aq20/Util/RaidAq20Utils.cpp b/src/Ai/Raid/Aq20/Aq20Utils.cpp similarity index 97% rename from src/Ai/Raid/Aq20/Util/RaidAq20Utils.cpp rename to src/Ai/Raid/Aq20/Aq20Utils.cpp index 41637af7b41..e7c74d4f38a 100644 --- a/src/Ai/Raid/Aq20/Util/RaidAq20Utils.cpp +++ b/src/Ai/Raid/Aq20/Aq20Utils.cpp @@ -1,4 +1,4 @@ -#include "RaidAq20Utils.h" +#include "Aq20Utils.h" #include "SpellAuras.h" diff --git a/src/Ai/Raid/Aq20/Util/RaidAq20Utils.h b/src/Ai/Raid/Aq20/Aq20Utils.h similarity index 100% rename from src/Ai/Raid/Aq20/Util/RaidAq20Utils.h rename to src/Ai/Raid/Aq20/Aq20Utils.h diff --git a/src/Ai/Raid/BlackTemple/RaidBlackTempleActionContext.h b/src/Ai/Raid/BT/BTActionContext.h similarity index 99% rename from src/Ai/Raid/BlackTemple/RaidBlackTempleActionContext.h rename to src/Ai/Raid/BT/BTActionContext.h index 8271449b581..7a962857e8c 100644 --- a/src/Ai/Raid/BlackTemple/RaidBlackTempleActionContext.h +++ b/src/Ai/Raid/BT/BTActionContext.h @@ -7,7 +7,7 @@ #define _PLAYERBOT_RAIDBLACKTEMPLEACTIONCONTEXT_H #include "NamedObjectContext.h" -#include "RaidBlackTempleActions.h" +#include "BTActions.h" class RaidBlackTempleActionContext : public NamedObjectContext { diff --git a/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp b/src/Ai/Raid/BT/BTActions.cpp similarity index 99% rename from src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp rename to src/Ai/Raid/BT/BTActions.cpp index cd6c06892f0..42c33509ec4 100644 --- a/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.cpp +++ b/src/Ai/Raid/BT/BTActions.cpp @@ -3,13 +3,13 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidBlackTempleActions.h" +#include "BTActions.h" #include #include "CreatureAI.h" #include "Playerbots.h" -#include "RaidBlackTempleHelpers.h" +#include "BTHelpers.h" #include "RaidBossHelpers.h" using namespace BlackTempleHelpers; diff --git a/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.h b/src/Ai/Raid/BT/BTActions.h similarity index 99% rename from src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.h rename to src/Ai/Raid/BT/BTActions.h index d4bc41d2381..b4386f892c6 100644 --- a/src/Ai/Raid/BlackTemple/Action/RaidBlackTempleActions.h +++ b/src/Ai/Raid/BT/BTActions.h @@ -9,7 +9,7 @@ #include "Action.h" #include "AttackAction.h" #include "MovementActions.h" -#include "RaidBlackTempleHelpers.h" +#include "BTHelpers.h" namespace BlackTempleHelpers { diff --git a/src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.cpp b/src/Ai/Raid/BT/BTHelpers.cpp similarity index 99% rename from src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.cpp rename to src/Ai/Raid/BT/BTHelpers.cpp index 75b0be0baea..2ba4adca2b9 100644 --- a/src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.cpp +++ b/src/Ai/Raid/BT/BTHelpers.cpp @@ -3,7 +3,7 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidBlackTempleHelpers.h" +#include "BTHelpers.h" #include "Playerbots.h" #include "RaidBossHelpers.h" diff --git a/src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.h b/src/Ai/Raid/BT/BTHelpers.h similarity index 100% rename from src/Ai/Raid/BlackTemple/Util/RaidBlackTempleHelpers.h rename to src/Ai/Raid/BT/BTHelpers.h diff --git a/src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.cpp b/src/Ai/Raid/BT/BTMultipliers.cpp similarity index 99% rename from src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.cpp rename to src/Ai/Raid/BT/BTMultipliers.cpp index 94e5143a6ed..6e484e421cc 100644 --- a/src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.cpp +++ b/src/Ai/Raid/BT/BTMultipliers.cpp @@ -3,7 +3,7 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidBlackTempleMultipliers.h" +#include "BTMultipliers.h" #include "ChooseTargetActions.h" #include "DKActions.h" @@ -15,8 +15,8 @@ #include "MageActions.h" #include "PaladinActions.h" #include "PriestActions.h" -#include "RaidBlackTempleActions.h" -#include "RaidBlackTempleHelpers.h" +#include "BTActions.h" +#include "BTHelpers.h" #include "ReachTargetActions.h" #include "RogueActions.h" #include "ShamanActions.h" diff --git a/src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.h b/src/Ai/Raid/BT/BTMultipliers.h similarity index 100% rename from src/Ai/Raid/BlackTemple/Multiplier/RaidBlackTempleMultipliers.h rename to src/Ai/Raid/BT/BTMultipliers.h diff --git a/src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.cpp b/src/Ai/Raid/BT/BTStrategy.cpp similarity index 99% rename from src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.cpp rename to src/Ai/Raid/BT/BTStrategy.cpp index 157469895a4..3fd0adeff65 100644 --- a/src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.cpp +++ b/src/Ai/Raid/BT/BTStrategy.cpp @@ -3,9 +3,9 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidBlackTempleStrategy.h" +#include "BTStrategy.h" -#include "RaidBlackTempleMultipliers.h" +#include "BTMultipliers.h" void RaidBlackTempleStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.h b/src/Ai/Raid/BT/BTStrategy.h similarity index 100% rename from src/Ai/Raid/BlackTemple/Strategy/RaidBlackTempleStrategy.h rename to src/Ai/Raid/BT/BTStrategy.h diff --git a/src/Ai/Raid/BlackTemple/RaidBlackTempleTriggerContext.h b/src/Ai/Raid/BT/BTTriggerContext.h similarity index 99% rename from src/Ai/Raid/BlackTemple/RaidBlackTempleTriggerContext.h rename to src/Ai/Raid/BT/BTTriggerContext.h index d9cc09489f7..ee10108ad1d 100644 --- a/src/Ai/Raid/BlackTemple/RaidBlackTempleTriggerContext.h +++ b/src/Ai/Raid/BT/BTTriggerContext.h @@ -7,7 +7,7 @@ #define _PLAYERBOT_RAIDBLACKTEMPLETRIGGERCONTEXT_H #include "NamedObjectContext.h" -#include "RaidBlackTempleTriggers.h" +#include "BTTriggers.h" class RaidBlackTempleTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.cpp b/src/Ai/Raid/BT/BTTriggers.cpp similarity index 99% rename from src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.cpp rename to src/Ai/Raid/BT/BTTriggers.cpp index 388e28d8b39..6dc5e574c15 100644 --- a/src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.cpp +++ b/src/Ai/Raid/BT/BTTriggers.cpp @@ -3,12 +3,12 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidBlackTempleTriggers.h" +#include "BTTriggers.h" #include "AiFactory.h" #include "Playerbots.h" -#include "RaidBlackTempleActions.h" -#include "RaidBlackTempleHelpers.h" +#include "BTActions.h" +#include "BTHelpers.h" #include "RaidBossHelpers.h" #include "SharedDefines.h" diff --git a/src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.h b/src/Ai/Raid/BT/BTTriggers.h similarity index 100% rename from src/Ai/Raid/BlackTemple/Trigger/RaidBlackTempleTriggers.h rename to src/Ai/Raid/BT/BTTriggers.h diff --git a/src/Ai/Raid/BlackwingLair/RaidBwlActionContext.h b/src/Ai/Raid/BWL/BWLActionContext.h similarity index 97% rename from src/Ai/Raid/BlackwingLair/RaidBwlActionContext.h rename to src/Ai/Raid/BWL/BWLActionContext.h index 4e46b1ca5e2..db93476fe52 100644 --- a/src/Ai/Raid/BlackwingLair/RaidBwlActionContext.h +++ b/src/Ai/Raid/BWL/BWLActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "RaidBwlActions.h" +#include "BWLActions.h" class RaidBwlActionContext : public NamedObjectContext { diff --git a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp b/src/Ai/Raid/BWL/BWLActions.cpp similarity index 95% rename from src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp rename to src/Ai/Raid/BWL/BWLActions.cpp index 7a12c3b8794..10c8dc4ae98 100644 --- a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.cpp +++ b/src/Ai/Raid/BWL/BWLActions.cpp @@ -1,7 +1,7 @@ -#include "RaidBwlActions.h" +#include "BWLActions.h" #include "Playerbots.h" -#include "RaidBwlHelpers.h" +#include "BWLHelpers.h" using namespace BlackwingLairHelpers; diff --git a/src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h b/src/Ai/Raid/BWL/BWLActions.h similarity index 100% rename from src/Ai/Raid/BlackwingLair/Action/RaidBwlActions.h rename to src/Ai/Raid/BWL/BWLActions.h diff --git a/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.cpp b/src/Ai/Raid/BWL/BWLHelpers.cpp similarity index 91% rename from src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.cpp rename to src/Ai/Raid/BWL/BWLHelpers.cpp index cc0714bd134..c4536064f4a 100644 --- a/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.cpp +++ b/src/Ai/Raid/BWL/BWLHelpers.cpp @@ -1,4 +1,4 @@ -#include "RaidBwlHelpers.h" +#include "BWLHelpers.h" namespace BlackwingLairHelpers { diff --git a/src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h b/src/Ai/Raid/BWL/BWLHelpers.h similarity index 100% rename from src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h rename to src/Ai/Raid/BWL/BWLHelpers.h diff --git a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp b/src/Ai/Raid/BWL/BWLStrategy.cpp similarity index 96% rename from src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp rename to src/Ai/Raid/BWL/BWLStrategy.cpp index 76d0da5ef63..1e69be6e801 100644 --- a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.cpp +++ b/src/Ai/Raid/BWL/BWLStrategy.cpp @@ -1,4 +1,4 @@ -#include "RaidBwlStrategy.h" +#include "BWLStrategy.h" void RaidBwlStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h b/src/Ai/Raid/BWL/BWLStrategy.h similarity index 100% rename from src/Ai/Raid/BlackwingLair/Strategy/RaidBwlStrategy.h rename to src/Ai/Raid/BWL/BWLStrategy.h diff --git a/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h b/src/Ai/Raid/BWL/BWLTriggerContext.h similarity index 97% rename from src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h rename to src/Ai/Raid/BWL/BWLTriggerContext.h index a2de3fd5ac1..e3b9b599d1e 100644 --- a/src/Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h +++ b/src/Ai/Raid/BWL/BWLTriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H #include "NamedObjectContext.h" -#include "RaidBwlTriggers.h" +#include "BWLTriggers.h" class RaidBwlTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp b/src/Ai/Raid/BWL/BWLTriggers.cpp similarity index 94% rename from src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp rename to src/Ai/Raid/BWL/BWLTriggers.cpp index 500c81a92ad..c977563d06f 100644 --- a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.cpp +++ b/src/Ai/Raid/BWL/BWLTriggers.cpp @@ -1,7 +1,7 @@ -#include "RaidBwlTriggers.h" +#include "BWLTriggers.h" #include "Playerbots.h" -#include "RaidBwlHelpers.h" +#include "BWLHelpers.h" using namespace BlackwingLairHelpers; diff --git a/src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h b/src/Ai/Raid/BWL/BWLTriggers.h similarity index 100% rename from src/Ai/Raid/BlackwingLair/Trigger/RaidBwlTriggers.h rename to src/Ai/Raid/BWL/BWLTriggers.h diff --git a/src/Ai/Raid/EyeOfEternity/RaidEoEActionContext.h b/src/Ai/Raid/EoE/EoEActionContext.h similarity index 97% rename from src/Ai/Raid/EyeOfEternity/RaidEoEActionContext.h rename to src/Ai/Raid/EoE/EoEActionContext.h index 620610e91dd..230c417b424 100644 --- a/src/Ai/Raid/EyeOfEternity/RaidEoEActionContext.h +++ b/src/Ai/Raid/EoE/EoEActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "RaidEoEActions.h" +#include "EoEActions.h" class RaidEoEActionContext : public NamedObjectContext { diff --git a/src/Ai/Raid/EyeOfEternity/Action/RaidEoEActions.cpp b/src/Ai/Raid/EoE/EoEActions.cpp similarity index 99% rename from src/Ai/Raid/EyeOfEternity/Action/RaidEoEActions.cpp rename to src/Ai/Raid/EoE/EoEActions.cpp index b08db3f33e7..051c51344fa 100644 --- a/src/Ai/Raid/EyeOfEternity/Action/RaidEoEActions.cpp +++ b/src/Ai/Raid/EoE/EoEActions.cpp @@ -1,6 +1,6 @@ #include "Playerbots.h" -#include "RaidEoEActions.h" -#include "RaidEoETriggers.h" +#include "EoEActions.h" +#include "EoETriggers.h" bool MalygosPositionAction::Execute(Event /*event*/) { diff --git a/src/Ai/Raid/EyeOfEternity/Action/RaidEoEActions.h b/src/Ai/Raid/EoE/EoEActions.h similarity index 100% rename from src/Ai/Raid/EyeOfEternity/Action/RaidEoEActions.h rename to src/Ai/Raid/EoE/EoEActions.h diff --git a/src/Ai/Raid/EyeOfEternity/Multiplier/RaidEoEMultipliers.cpp b/src/Ai/Raid/EoE/EoEMultipliers.cpp similarity index 95% rename from src/Ai/Raid/EyeOfEternity/Multiplier/RaidEoEMultipliers.cpp rename to src/Ai/Raid/EoE/EoEMultipliers.cpp index a2d8a3f1dfd..490b3112544 100644 --- a/src/Ai/Raid/EyeOfEternity/Multiplier/RaidEoEMultipliers.cpp +++ b/src/Ai/Raid/EoE/EoEMultipliers.cpp @@ -1,4 +1,4 @@ -#include "RaidEoEMultipliers.h" +#include "EoEMultipliers.h" #include "ChooseTargetActions.h" #include "DKActions.h" @@ -9,8 +9,8 @@ #include "GenericSpellActions.h" #include "MovementActions.h" #include "PaladinActions.h" -#include "RaidEoEActions.h" -#include "RaidEoETriggers.h" +#include "EoEActions.h" +#include "EoETriggers.h" #include "ReachTargetActions.h" #include "ScriptedCreature.h" #include "WarriorActions.h" diff --git a/src/Ai/Raid/EyeOfEternity/Multiplier/RaidEoEMultipliers.h b/src/Ai/Raid/EoE/EoEMultipliers.h similarity index 100% rename from src/Ai/Raid/EyeOfEternity/Multiplier/RaidEoEMultipliers.h rename to src/Ai/Raid/EoE/EoEMultipliers.h diff --git a/src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.cpp b/src/Ai/Raid/EoE/EoEStrategy.cpp similarity index 91% rename from src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.cpp rename to src/Ai/Raid/EoE/EoEStrategy.cpp index 3c0ff7ffd86..273a9aecd1a 100644 --- a/src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.cpp +++ b/src/Ai/Raid/EoE/EoEStrategy.cpp @@ -1,5 +1,5 @@ -#include "RaidEoEStrategy.h" -#include "RaidEoEMultipliers.h" +#include "EoEStrategy.h" +#include "EoEMultipliers.h" #include "Strategy.h" void RaidEoEStrategy::InitTriggers(std::vector& triggers) diff --git a/src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.h b/src/Ai/Raid/EoE/EoEStrategy.h similarity index 100% rename from src/Ai/Raid/EyeOfEternity/Strategy/RaidEoEStrategy.h rename to src/Ai/Raid/EoE/EoEStrategy.h diff --git a/src/Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h b/src/Ai/Raid/EoE/EoETriggerContext.h similarity index 95% rename from src/Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h rename to src/Ai/Raid/EoE/EoETriggerContext.h index c545e10eba6..fee06ac20fc 100644 --- a/src/Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h +++ b/src/Ai/Raid/EoE/EoETriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H #include "NamedObjectContext.h" -#include "RaidEoETriggers.h" +#include "EoETriggers.h" class RaidEoETriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/EyeOfEternity/Trigger/RaidEoETriggers.cpp b/src/Ai/Raid/EoE/EoETriggers.cpp similarity index 97% rename from src/Ai/Raid/EyeOfEternity/Trigger/RaidEoETriggers.cpp rename to src/Ai/Raid/EoE/EoETriggers.cpp index f07456d040f..e98244659de 100644 --- a/src/Ai/Raid/EyeOfEternity/Trigger/RaidEoETriggers.cpp +++ b/src/Ai/Raid/EoE/EoETriggers.cpp @@ -1,4 +1,4 @@ -#include "RaidEoETriggers.h" +#include "EoETriggers.h" #include "SharedDefines.h" diff --git a/src/Ai/Raid/EyeOfEternity/Trigger/RaidEoETriggers.h b/src/Ai/Raid/EoE/EoETriggers.h similarity index 100% rename from src/Ai/Raid/EyeOfEternity/Trigger/RaidEoETriggers.h rename to src/Ai/Raid/EoE/EoETriggers.h diff --git a/src/Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h b/src/Ai/Raid/Gruul/GruulActionContext.h similarity index 99% rename from src/Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h rename to src/Ai/Raid/Gruul/GruulActionContext.h index 3850f58c6d7..1e39d5bfecb 100644 --- a/src/Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h +++ b/src/Ai/Raid/Gruul/GruulActionContext.h @@ -1,7 +1,7 @@ #ifndef _PLAYERBOT_RAIDGRUULSLAIRACTIONCONTEXT_H #define _PLAYERBOT_RAIDGRUULSLAIRACTIONCONTEXT_H -#include "RaidGruulsLairActions.h" +#include "GruulActions.h" #include "NamedObjectContext.h" class RaidGruulsLairActionContext : public NamedObjectContext diff --git a/src/Ai/Raid/GruulsLair/Action/RaidGruulsLairActions.cpp b/src/Ai/Raid/Gruul/GruulActions.cpp similarity index 99% rename from src/Ai/Raid/GruulsLair/Action/RaidGruulsLairActions.cpp rename to src/Ai/Raid/Gruul/GruulActions.cpp index d39d8a5eda3..f4b8b5d3023 100644 --- a/src/Ai/Raid/GruulsLair/Action/RaidGruulsLairActions.cpp +++ b/src/Ai/Raid/Gruul/GruulActions.cpp @@ -1,5 +1,5 @@ -#include "RaidGruulsLairActions.h" -#include "RaidGruulsLairHelpers.h" +#include "GruulActions.h" +#include "GruulHelpers.h" #include "CreatureAI.h" #include "Playerbots.h" #include "RaidBossHelpers.h" diff --git a/src/Ai/Raid/GruulsLair/Action/RaidGruulsLairActions.h b/src/Ai/Raid/Gruul/GruulActions.h similarity index 100% rename from src/Ai/Raid/GruulsLair/Action/RaidGruulsLairActions.h rename to src/Ai/Raid/Gruul/GruulActions.h diff --git a/src/Ai/Raid/GruulsLair/Util/RaidGruulsLairHelpers.cpp b/src/Ai/Raid/Gruul/GruulHelpers.cpp similarity index 99% rename from src/Ai/Raid/GruulsLair/Util/RaidGruulsLairHelpers.cpp rename to src/Ai/Raid/Gruul/GruulHelpers.cpp index 7195f0ebd01..4c90f6e4184 100644 --- a/src/Ai/Raid/GruulsLair/Util/RaidGruulsLairHelpers.cpp +++ b/src/Ai/Raid/Gruul/GruulHelpers.cpp @@ -1,4 +1,4 @@ -#include "RaidGruulsLairHelpers.h" +#include "GruulHelpers.h" #include "AiFactory.h" #include "GroupReference.h" #include "Playerbots.h" diff --git a/src/Ai/Raid/GruulsLair/Util/RaidGruulsLairHelpers.h b/src/Ai/Raid/Gruul/GruulHelpers.h similarity index 100% rename from src/Ai/Raid/GruulsLair/Util/RaidGruulsLairHelpers.h rename to src/Ai/Raid/Gruul/GruulHelpers.h diff --git a/src/Ai/Raid/GruulsLair/Multiplier/RaidGruulsLairMultipliers.cpp b/src/Ai/Raid/Gruul/GruulMultipliers.cpp similarity index 96% rename from src/Ai/Raid/GruulsLair/Multiplier/RaidGruulsLairMultipliers.cpp rename to src/Ai/Raid/Gruul/GruulMultipliers.cpp index 7c8fb731e01..e5a02698f7e 100644 --- a/src/Ai/Raid/GruulsLair/Multiplier/RaidGruulsLairMultipliers.cpp +++ b/src/Ai/Raid/Gruul/GruulMultipliers.cpp @@ -1,6 +1,6 @@ -#include "RaidGruulsLairMultipliers.h" -#include "RaidGruulsLairActions.h" -#include "RaidGruulsLairHelpers.h" +#include "GruulMultipliers.h" +#include "GruulActions.h" +#include "GruulHelpers.h" #include "ChooseTargetActions.h" #include "DruidBearActions.h" #include "DruidCatActions.h" diff --git a/src/Ai/Raid/GruulsLair/Multiplier/RaidGruulsLairMultipliers.h b/src/Ai/Raid/Gruul/GruulMultipliers.h similarity index 100% rename from src/Ai/Raid/GruulsLair/Multiplier/RaidGruulsLairMultipliers.h rename to src/Ai/Raid/Gruul/GruulMultipliers.h diff --git a/src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.cpp b/src/Ai/Raid/Gruul/GruulStrategy.cpp similarity index 97% rename from src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.cpp rename to src/Ai/Raid/Gruul/GruulStrategy.cpp index 249c8e8a804..4f429765052 100644 --- a/src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.cpp +++ b/src/Ai/Raid/Gruul/GruulStrategy.cpp @@ -1,5 +1,5 @@ -#include "RaidGruulsLairStrategy.h" -#include "RaidGruulsLairMultipliers.h" +#include "GruulStrategy.h" +#include "GruulMultipliers.h" void RaidGruulsLairStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.h b/src/Ai/Raid/Gruul/GruulStrategy.h similarity index 100% rename from src/Ai/Raid/GruulsLair/Strategy/RaidGruulsLairStrategy.h rename to src/Ai/Raid/Gruul/GruulStrategy.h diff --git a/src/Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h b/src/Ai/Raid/Gruul/GruulTriggerContext.h similarity index 99% rename from src/Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h rename to src/Ai/Raid/Gruul/GruulTriggerContext.h index 35a0f138eda..110a69e3562 100644 --- a/src/Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h +++ b/src/Ai/Raid/Gruul/GruulTriggerContext.h @@ -1,7 +1,7 @@ #ifndef _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H -#include "RaidGruulsLairTriggers.h" +#include "GruulTriggers.h" #include "NamedObjectContext.h" class RaidGruulsLairTriggerContext : public NamedObjectContext diff --git a/src/Ai/Raid/GruulsLair/Trigger/RaidGruulsLairTriggers.cpp b/src/Ai/Raid/Gruul/GruulTriggers.cpp similarity index 98% rename from src/Ai/Raid/GruulsLair/Trigger/RaidGruulsLairTriggers.cpp rename to src/Ai/Raid/Gruul/GruulTriggers.cpp index 3caadb38411..82c3e18ac2a 100644 --- a/src/Ai/Raid/GruulsLair/Trigger/RaidGruulsLairTriggers.cpp +++ b/src/Ai/Raid/Gruul/GruulTriggers.cpp @@ -1,5 +1,5 @@ -#include "RaidGruulsLairTriggers.h" -#include "RaidGruulsLairHelpers.h" +#include "GruulTriggers.h" +#include "GruulHelpers.h" #include "Playerbots.h" using namespace GruulsLairHelpers; diff --git a/src/Ai/Raid/GruulsLair/Trigger/RaidGruulsLairTriggers.h b/src/Ai/Raid/Gruul/GruulTriggers.h similarity index 100% rename from src/Ai/Raid/GruulsLair/Trigger/RaidGruulsLairTriggers.h rename to src/Ai/Raid/Gruul/GruulTriggers.h diff --git a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h b/src/Ai/Raid/Hyjal/HyjalActionContext.h similarity index 99% rename from src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h rename to src/Ai/Raid/Hyjal/HyjalActionContext.h index 02164df4683..f1977850082 100644 --- a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h +++ b/src/Ai/Raid/Hyjal/HyjalActionContext.h @@ -6,7 +6,7 @@ #ifndef _PLAYERBOT_RAIDHYJALSUMMITACTIONCONTEXT_H #define _PLAYERBOT_RAIDHYJALSUMMITACTIONCONTEXT_H -#include "RaidHyjalSummitActions.h" +#include "HyjalActions.h" #include "NamedObjectContext.h" class RaidHyjalSummitActionContext : public NamedObjectContext diff --git a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp b/src/Ai/Raid/Hyjal/HyjalActions.cpp similarity index 99% rename from src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp rename to src/Ai/Raid/Hyjal/HyjalActions.cpp index 557f3e6b042..ec7a594a337 100644 --- a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.cpp +++ b/src/Ai/Raid/Hyjal/HyjalActions.cpp @@ -3,8 +3,8 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidHyjalSummitActions.h" -#include "RaidHyjalSummitHelpers.h" +#include "HyjalActions.h" +#include "HyjalHelpers.h" #include "Playerbots.h" #include "RaidBossHelpers.h" #include "Timer.h" diff --git a/src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h b/src/Ai/Raid/Hyjal/HyjalActions.h similarity index 100% rename from src/Ai/Raid/HyjalSummit/Action/RaidHyjalSummitActions.h rename to src/Ai/Raid/Hyjal/HyjalActions.h diff --git a/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp b/src/Ai/Raid/Hyjal/HyjalMultipliers.cpp similarity index 98% rename from src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp rename to src/Ai/Raid/Hyjal/HyjalMultipliers.cpp index a9f66646411..7ad9ea12f85 100644 --- a/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.cpp +++ b/src/Ai/Raid/Hyjal/HyjalMultipliers.cpp @@ -3,9 +3,9 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidHyjalSummitMultipliers.h" -#include "RaidHyjalSummitActions.h" -#include "RaidHyjalSummitHelpers.h" +#include "HyjalMultipliers.h" +#include "HyjalActions.h" +#include "HyjalHelpers.h" #include "AiFactory.h" #include "ChooseTargetActions.h" #include "DKActions.h" diff --git a/src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h b/src/Ai/Raid/Hyjal/HyjalMultipliers.h similarity index 100% rename from src/Ai/Raid/HyjalSummit/Multiplier/RaidHyjalSummitMultipliers.h rename to src/Ai/Raid/Hyjal/HyjalMultipliers.h diff --git a/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp b/src/Ai/Raid/Hyjal/HyjalStrategy.cpp similarity index 98% rename from src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp rename to src/Ai/Raid/Hyjal/HyjalStrategy.cpp index 77486fb4859..66ff111c2ca 100644 --- a/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.cpp +++ b/src/Ai/Raid/Hyjal/HyjalStrategy.cpp @@ -3,8 +3,8 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidHyjalSummitStrategy.h" -#include "RaidHyjalSummitMultipliers.h" +#include "HyjalStrategy.h" +#include "HyjalMultipliers.h" void RaidHyjalSummitStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.h b/src/Ai/Raid/Hyjal/HyjalStrategy.h similarity index 100% rename from src/Ai/Raid/HyjalSummit/Strategy/RaidHyjalSummitStrategy.h rename to src/Ai/Raid/Hyjal/HyjalStrategy.h diff --git a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h b/src/Ai/Raid/Hyjal/HyjalTriggerContext.h similarity index 99% rename from src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h rename to src/Ai/Raid/Hyjal/HyjalTriggerContext.h index a7c564ee00a..93aef5d9c42 100644 --- a/src/Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h +++ b/src/Ai/Raid/Hyjal/HyjalTriggerContext.h @@ -6,7 +6,7 @@ #ifndef _PLAYERBOT_RAIDHYJALSUMMITTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDHYJALSUMMITTRIGGERCONTEXT_H -#include "RaidHyjalSummitTriggers.h" +#include "HyjalTriggers.h" #include "NamedObjectContext.h" class RaidHyjalSummitTriggerContext : public NamedObjectContext diff --git a/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp b/src/Ai/Raid/Hyjal/HyjalTriggers.cpp similarity index 98% rename from src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp rename to src/Ai/Raid/Hyjal/HyjalTriggers.cpp index 3b9a6455d5d..403098cd8f4 100644 --- a/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.cpp +++ b/src/Ai/Raid/Hyjal/HyjalTriggers.cpp @@ -3,9 +3,9 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidHyjalSummitTriggers.h" -#include "RaidHyjalSummitHelpers.h" -#include "RaidHyjalSummitActions.h" +#include "HyjalTriggers.h" +#include "HyjalHelpers.h" +#include "HyjalActions.h" #include "AiFactory.h" #include "Playerbots.h" #include "RaidBossHelpers.h" diff --git a/src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h b/src/Ai/Raid/Hyjal/HyjalTriggers.h similarity index 100% rename from src/Ai/Raid/HyjalSummit/Trigger/RaidHyjalSummitTriggers.h rename to src/Ai/Raid/Hyjal/HyjalTriggers.h diff --git a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp b/src/Ai/Raid/Hyjal/Util/HyjalHelpers.cpp similarity index 99% rename from src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp rename to src/Ai/Raid/Hyjal/Util/HyjalHelpers.cpp index 04ed97cd098..d15bacf1b12 100644 --- a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.cpp +++ b/src/Ai/Raid/Hyjal/Util/HyjalHelpers.cpp @@ -3,7 +3,7 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidHyjalSummitHelpers.h" +#include "HyjalHelpers.h" #include diff --git a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h b/src/Ai/Raid/Hyjal/Util/HyjalHelpers.h similarity index 100% rename from src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitHelpers.h rename to src/Ai/Raid/Hyjal/Util/HyjalHelpers.h diff --git a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp b/src/Ai/Raid/Hyjal/Util/HyjalScripts.cpp similarity index 99% rename from src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp rename to src/Ai/Raid/Hyjal/Util/HyjalScripts.cpp index a1ccf4005f7..d3ccc19c973 100644 --- a/src/Ai/Raid/HyjalSummit/Util/RaidHyjalSummitScripts.cpp +++ b/src/Ai/Raid/Hyjal/Util/HyjalScripts.cpp @@ -3,7 +3,7 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidHyjalSummitHelpers.h" +#include "HyjalHelpers.h" #include "AllCreatureScript.h" #include "ObjectAccessor.h" #include "Player.h" diff --git a/src/Ai/Raid/Karazhan/RaidKarazhanActionContext.h b/src/Ai/Raid/Kara/KaraActionContext.h similarity index 99% rename from src/Ai/Raid/Karazhan/RaidKarazhanActionContext.h rename to src/Ai/Raid/Kara/KaraActionContext.h index e555cd14d03..63d64174f77 100644 --- a/src/Ai/Raid/Karazhan/RaidKarazhanActionContext.h +++ b/src/Ai/Raid/Kara/KaraActionContext.h @@ -1,7 +1,7 @@ #ifndef _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H #define _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H -#include "RaidKarazhanActions.h" +#include "KaraActions.h" #include "NamedObjectContext.h" class RaidKarazhanActionContext : public NamedObjectContext diff --git a/src/Ai/Raid/Karazhan/Action/RaidKarazhanActions.cpp b/src/Ai/Raid/Kara/KaraActions.cpp similarity index 99% rename from src/Ai/Raid/Karazhan/Action/RaidKarazhanActions.cpp rename to src/Ai/Raid/Kara/KaraActions.cpp index 9fc25827efd..10d3639b5f3 100644 --- a/src/Ai/Raid/Karazhan/Action/RaidKarazhanActions.cpp +++ b/src/Ai/Raid/Kara/KaraActions.cpp @@ -1,5 +1,5 @@ -#include "RaidKarazhanActions.h" -#include "RaidKarazhanHelpers.h" +#include "KaraActions.h" +#include "KaraHelpers.h" #include "Playerbots.h" #include "PlayerbotTextMgr.h" #include "RaidBossHelpers.h" diff --git a/src/Ai/Raid/Karazhan/Action/RaidKarazhanActions.h b/src/Ai/Raid/Kara/KaraActions.h similarity index 100% rename from src/Ai/Raid/Karazhan/Action/RaidKarazhanActions.h rename to src/Ai/Raid/Kara/KaraActions.h diff --git a/src/Ai/Raid/Karazhan/Util/RaidKarazhanHelpers.cpp b/src/Ai/Raid/Kara/KaraHelpers.cpp similarity index 99% rename from src/Ai/Raid/Karazhan/Util/RaidKarazhanHelpers.cpp rename to src/Ai/Raid/Kara/KaraHelpers.cpp index 82ecbdb7b37..50de2e3a41a 100644 --- a/src/Ai/Raid/Karazhan/Util/RaidKarazhanHelpers.cpp +++ b/src/Ai/Raid/Kara/KaraHelpers.cpp @@ -1,4 +1,4 @@ -#include "RaidKarazhanHelpers.h" +#include "KaraHelpers.h" #include "Playerbots.h" namespace KarazhanHelpers diff --git a/src/Ai/Raid/Karazhan/Util/RaidKarazhanHelpers.h b/src/Ai/Raid/Kara/KaraHelpers.h similarity index 100% rename from src/Ai/Raid/Karazhan/Util/RaidKarazhanHelpers.h rename to src/Ai/Raid/Kara/KaraHelpers.h diff --git a/src/Ai/Raid/Karazhan/Multiplier/RaidKarazhanMultipliers.cpp b/src/Ai/Raid/Kara/KaraMultipliers.cpp similarity index 99% rename from src/Ai/Raid/Karazhan/Multiplier/RaidKarazhanMultipliers.cpp rename to src/Ai/Raid/Kara/KaraMultipliers.cpp index e68a3885daa..5f857539b11 100644 --- a/src/Ai/Raid/Karazhan/Multiplier/RaidKarazhanMultipliers.cpp +++ b/src/Ai/Raid/Kara/KaraMultipliers.cpp @@ -1,6 +1,6 @@ -#include "RaidKarazhanMultipliers.h" -#include "RaidKarazhanActions.h" -#include "RaidKarazhanHelpers.h" +#include "KaraMultipliers.h" +#include "KaraActions.h" +#include "KaraHelpers.h" #include "AttackAction.h" #include "ChooseTargetActions.h" #include "DruidActions.h" diff --git a/src/Ai/Raid/Karazhan/Multiplier/RaidKarazhanMultipliers.h b/src/Ai/Raid/Kara/KaraMultipliers.h similarity index 100% rename from src/Ai/Raid/Karazhan/Multiplier/RaidKarazhanMultipliers.h rename to src/Ai/Raid/Kara/KaraMultipliers.h diff --git a/src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.cpp b/src/Ai/Raid/Kara/KaraStrategy.cpp similarity index 99% rename from src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.cpp rename to src/Ai/Raid/Kara/KaraStrategy.cpp index d9bbf816261..45dbc68d041 100644 --- a/src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.cpp +++ b/src/Ai/Raid/Kara/KaraStrategy.cpp @@ -1,5 +1,5 @@ -#include "RaidKarazhanStrategy.h" -#include "RaidKarazhanMultipliers.h" +#include "KaraStrategy.h" +#include "KaraMultipliers.h" void RaidKarazhanStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.h b/src/Ai/Raid/Kara/KaraStrategy.h similarity index 100% rename from src/Ai/Raid/Karazhan/Strategy/RaidKarazhanStrategy.h rename to src/Ai/Raid/Kara/KaraStrategy.h diff --git a/src/Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h b/src/Ai/Raid/Kara/KaraTriggerContext.h similarity index 99% rename from src/Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h rename to src/Ai/Raid/Kara/KaraTriggerContext.h index a9c430734f5..77d3090e7e6 100644 --- a/src/Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h +++ b/src/Ai/Raid/Kara/KaraTriggerContext.h @@ -1,7 +1,7 @@ #ifndef _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H -#include "RaidKarazhanTriggers.h" +#include "KaraTriggers.h" #include "NamedObjectContext.h" class RaidKarazhanTriggerContext : public NamedObjectContext diff --git a/src/Ai/Raid/Karazhan/Trigger/RaidKarazhanTriggers.cpp b/src/Ai/Raid/Kara/KaraTriggers.cpp similarity index 99% rename from src/Ai/Raid/Karazhan/Trigger/RaidKarazhanTriggers.cpp rename to src/Ai/Raid/Kara/KaraTriggers.cpp index 3c43aa898ca..f11c3919bcb 100644 --- a/src/Ai/Raid/Karazhan/Trigger/RaidKarazhanTriggers.cpp +++ b/src/Ai/Raid/Kara/KaraTriggers.cpp @@ -1,6 +1,6 @@ -#include "RaidKarazhanTriggers.h" -#include "RaidKarazhanHelpers.h" -#include "RaidKarazhanActions.h" +#include "KaraTriggers.h" +#include "KaraHelpers.h" +#include "KaraActions.h" #include "Playerbots.h" #include "RaidBossHelpers.h" diff --git a/src/Ai/Raid/Karazhan/Trigger/RaidKarazhanTriggers.h b/src/Ai/Raid/Kara/KaraTriggers.h similarity index 100% rename from src/Ai/Raid/Karazhan/Trigger/RaidKarazhanTriggers.h rename to src/Ai/Raid/Kara/KaraTriggers.h diff --git a/src/Ai/Raid/MoltenCore/RaidMcActionContext.h b/src/Ai/Raid/MC/MCActionContext.h similarity index 99% rename from src/Ai/Raid/MoltenCore/RaidMcActionContext.h rename to src/Ai/Raid/MC/MCActionContext.h index aaccb80d337..3cc95257ac9 100644 --- a/src/Ai/Raid/MoltenCore/RaidMcActionContext.h +++ b/src/Ai/Raid/MC/MCActionContext.h @@ -4,7 +4,7 @@ #include "Action.h" #include "BossAuraActions.h" #include "NamedObjectContext.h" -#include "RaidMcActions.h" +#include "MCActions.h" class RaidMcActionContext : public NamedObjectContext { diff --git a/src/Ai/Raid/MoltenCore/Action/RaidMcActions.cpp b/src/Ai/Raid/MC/MCActions.cpp similarity index 99% rename from src/Ai/Raid/MoltenCore/Action/RaidMcActions.cpp rename to src/Ai/Raid/MC/MCActions.cpp index d2bc2edc1ba..96050ac4309 100644 --- a/src/Ai/Raid/MoltenCore/Action/RaidMcActions.cpp +++ b/src/Ai/Raid/MC/MCActions.cpp @@ -1,8 +1,8 @@ -#include "RaidMcActions.h" +#include "MCActions.h" #include "Playerbots.h" #include "RtiTargetValue.h" -#include "RaidMcHelpers.h" +#include "MCHelpers.h" static constexpr float LIVING_BOMB_DISTANCE = 20.0f; static constexpr float INFERNO_DISTANCE = 20.0f; diff --git a/src/Ai/Raid/MoltenCore/Action/RaidMcActions.h b/src/Ai/Raid/MC/MCActions.h similarity index 100% rename from src/Ai/Raid/MoltenCore/Action/RaidMcActions.h rename to src/Ai/Raid/MC/MCActions.h diff --git a/src/Ai/Raid/MoltenCore/Util/RaidMcHelpers.h b/src/Ai/Raid/MC/MCHelpers.h similarity index 100% rename from src/Ai/Raid/MoltenCore/Util/RaidMcHelpers.h rename to src/Ai/Raid/MC/MCHelpers.h diff --git a/src/Ai/Raid/MoltenCore/Multiplier/RaidMcMultipliers.cpp b/src/Ai/Raid/MC/MCMultipliers.cpp similarity index 97% rename from src/Ai/Raid/MoltenCore/Multiplier/RaidMcMultipliers.cpp rename to src/Ai/Raid/MC/MCMultipliers.cpp index d1ee936b0d3..d727635643f 100644 --- a/src/Ai/Raid/MoltenCore/Multiplier/RaidMcMultipliers.cpp +++ b/src/Ai/Raid/MC/MCMultipliers.cpp @@ -1,4 +1,4 @@ -#include "RaidMcMultipliers.h" +#include "MCMultipliers.h" #include "Playerbots.h" #include "ChooseTargetActions.h" @@ -9,8 +9,8 @@ #include "ShamanActions.h" #include "WarriorActions.h" #include "DKActions.h" -#include "RaidMcActions.h" -#include "RaidMcHelpers.h" +#include "MCActions.h" +#include "MCHelpers.h" using namespace MoltenCoreHelpers; diff --git a/src/Ai/Raid/MoltenCore/Multiplier/RaidMcMultipliers.h b/src/Ai/Raid/MC/MCMultipliers.h similarity index 100% rename from src/Ai/Raid/MoltenCore/Multiplier/RaidMcMultipliers.h rename to src/Ai/Raid/MC/MCMultipliers.h diff --git a/src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.cpp b/src/Ai/Raid/MC/MCStrategy.cpp similarity index 98% rename from src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.cpp rename to src/Ai/Raid/MC/MCStrategy.cpp index 3eddb76a799..fc63c66b94f 100644 --- a/src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.cpp +++ b/src/Ai/Raid/MC/MCStrategy.cpp @@ -1,6 +1,6 @@ -#include "RaidMcStrategy.h" +#include "MCStrategy.h" -#include "RaidMcMultipliers.h" +#include "MCMultipliers.h" #include "Strategy.h" void RaidMcStrategy::InitTriggers(std::vector& triggers) diff --git a/src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.h b/src/Ai/Raid/MC/MCStrategy.h similarity index 100% rename from src/Ai/Raid/MoltenCore/Strategy/RaidMcStrategy.h rename to src/Ai/Raid/MC/MCStrategy.h diff --git a/src/Ai/Raid/MoltenCore/RaidMcTriggerContext.h b/src/Ai/Raid/MC/MCTriggerContext.h similarity index 99% rename from src/Ai/Raid/MoltenCore/RaidMcTriggerContext.h rename to src/Ai/Raid/MC/MCTriggerContext.h index 1f694fe6502..7efa95298a5 100644 --- a/src/Ai/Raid/MoltenCore/RaidMcTriggerContext.h +++ b/src/Ai/Raid/MC/MCTriggerContext.h @@ -3,7 +3,7 @@ #include "BossAuraTriggers.h" #include "NamedObjectContext.h" -#include "RaidMcTriggers.h" +#include "MCTriggers.h" class RaidMcTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/MoltenCore/Trigger/RaidMcTriggers.cpp b/src/Ai/Raid/MC/MCTriggers.cpp similarity index 95% rename from src/Ai/Raid/MoltenCore/Trigger/RaidMcTriggers.cpp rename to src/Ai/Raid/MC/MCTriggers.cpp index 834d703d33f..b9b2aa1b7e2 100644 --- a/src/Ai/Raid/MoltenCore/Trigger/RaidMcTriggers.cpp +++ b/src/Ai/Raid/MC/MCTriggers.cpp @@ -1,7 +1,7 @@ -#include "RaidMcTriggers.h" +#include "MCTriggers.h" #include "SharedDefines.h" -#include "RaidMcHelpers.h" +#include "MCHelpers.h" using namespace MoltenCoreHelpers; diff --git a/src/Ai/Raid/MoltenCore/Trigger/RaidMcTriggers.h b/src/Ai/Raid/MC/MCTriggers.h similarity index 100% rename from src/Ai/Raid/MoltenCore/Trigger/RaidMcTriggers.h rename to src/Ai/Raid/MC/MCTriggers.h diff --git a/src/Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h b/src/Ai/Raid/Mag/MagActionContext.h similarity index 98% rename from src/Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h rename to src/Ai/Raid/Mag/MagActionContext.h index 48e8cfd3510..2a040ebcd02 100644 --- a/src/Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h +++ b/src/Ai/Raid/Mag/MagActionContext.h @@ -1,7 +1,7 @@ #ifndef _PLAYERBOT_RAIDMAGTHERIDONACTIONCONTEXT_H #define _PLAYERBOT_RAIDMAGTHERIDONACTIONCONTEXT_H -#include "RaidMagtheridonActions.h" +#include "MagActions.h" #include "NamedObjectContext.h" class RaidMagtheridonActionContext : public NamedObjectContext diff --git a/src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.cpp b/src/Ai/Raid/Mag/MagActions.cpp similarity index 99% rename from src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.cpp rename to src/Ai/Raid/Mag/MagActions.cpp index a9e9bcfd747..fc5a9d6801c 100644 --- a/src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.cpp +++ b/src/Ai/Raid/Mag/MagActions.cpp @@ -1,5 +1,5 @@ -#include "RaidMagtheridonActions.h" -#include "RaidMagtheridonHelpers.h" +#include "MagActions.h" +#include "MagHelpers.h" #include "Creature.h" #include "ObjectAccessor.h" #include "ObjectGuid.h" diff --git a/src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.h b/src/Ai/Raid/Mag/MagActions.h similarity index 98% rename from src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.h rename to src/Ai/Raid/Mag/MagActions.h index 7abc493c2cf..dd1660d40da 100644 --- a/src/Ai/Raid/Magtheridon/Action/RaidMagtheridonActions.h +++ b/src/Ai/Raid/Mag/MagActions.h @@ -1,7 +1,7 @@ #ifndef _PLAYERBOT_RAIDMAGTHERIDONACTIONS_H #define _PLAYERBOT_RAIDMAGTHERIDONACTIONS_H -#include "RaidMagtheridonHelpers.h" +#include "MagHelpers.h" #include "Action.h" #include "AttackAction.h" #include "MovementActions.h" diff --git a/src/Ai/Raid/Magtheridon/Util/RaidMagtheridonHelpers.cpp b/src/Ai/Raid/Mag/MagHelpers.cpp similarity index 99% rename from src/Ai/Raid/Magtheridon/Util/RaidMagtheridonHelpers.cpp rename to src/Ai/Raid/Mag/MagHelpers.cpp index e51ad24e5cf..39bfbcf27a1 100644 --- a/src/Ai/Raid/Magtheridon/Util/RaidMagtheridonHelpers.cpp +++ b/src/Ai/Raid/Mag/MagHelpers.cpp @@ -1,4 +1,4 @@ -#include "RaidMagtheridonHelpers.h" +#include "MagHelpers.h" #include "Creature.h" #include "GameObject.h" #include "Map.h" diff --git a/src/Ai/Raid/Magtheridon/Util/RaidMagtheridonHelpers.h b/src/Ai/Raid/Mag/MagHelpers.h similarity index 100% rename from src/Ai/Raid/Magtheridon/Util/RaidMagtheridonHelpers.h rename to src/Ai/Raid/Mag/MagHelpers.h diff --git a/src/Ai/Raid/Magtheridon/Multiplier/RaidMagtheridonMultipliers.cpp b/src/Ai/Raid/Mag/MagMultipliers.cpp similarity index 95% rename from src/Ai/Raid/Magtheridon/Multiplier/RaidMagtheridonMultipliers.cpp rename to src/Ai/Raid/Mag/MagMultipliers.cpp index 55aaf90e94f..94ae6d6a97f 100644 --- a/src/Ai/Raid/Magtheridon/Multiplier/RaidMagtheridonMultipliers.cpp +++ b/src/Ai/Raid/Mag/MagMultipliers.cpp @@ -1,9 +1,9 @@ #include #include -#include "RaidMagtheridonMultipliers.h" -#include "RaidMagtheridonActions.h" -#include "RaidMagtheridonHelpers.h" +#include "MagMultipliers.h" +#include "MagActions.h" +#include "MagHelpers.h" #include "ChooseTargetActions.h" #include "GenericSpellActions.h" #include "Playerbots.h" diff --git a/src/Ai/Raid/Magtheridon/Multiplier/RaidMagtheridonMultipliers.h b/src/Ai/Raid/Mag/MagMultipliers.h similarity index 100% rename from src/Ai/Raid/Magtheridon/Multiplier/RaidMagtheridonMultipliers.h rename to src/Ai/Raid/Mag/MagMultipliers.h diff --git a/src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.cpp b/src/Ai/Raid/Mag/MagStrategy.cpp similarity index 96% rename from src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.cpp rename to src/Ai/Raid/Mag/MagStrategy.cpp index 73d240825f7..fc6d041ff37 100644 --- a/src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.cpp +++ b/src/Ai/Raid/Mag/MagStrategy.cpp @@ -1,5 +1,5 @@ -#include "RaidMagtheridonStrategy.h" -#include "RaidMagtheridonMultipliers.h" +#include "MagStrategy.h" +#include "MagMultipliers.h" void RaidMagtheridonStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.h b/src/Ai/Raid/Mag/MagStrategy.h similarity index 100% rename from src/Ai/Raid/Magtheridon/Strategy/RaidMagtheridonStrategy.h rename to src/Ai/Raid/Mag/MagStrategy.h diff --git a/src/Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h b/src/Ai/Raid/Mag/MagTriggerContext.h similarity index 98% rename from src/Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h rename to src/Ai/Raid/Mag/MagTriggerContext.h index 482152e0ee6..dcaa08fa05c 100644 --- a/src/Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h +++ b/src/Ai/Raid/Mag/MagTriggerContext.h @@ -1,7 +1,7 @@ #ifndef _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H -#include "RaidMagtheridonTriggers.h" +#include "MagTriggers.h" #include "NamedObjectContext.h" class RaidMagtheridonTriggerContext : public NamedObjectContext diff --git a/src/Ai/Raid/Magtheridon/Trigger/RaidMagtheridonTriggers.cpp b/src/Ai/Raid/Mag/MagTriggers.cpp similarity index 98% rename from src/Ai/Raid/Magtheridon/Trigger/RaidMagtheridonTriggers.cpp rename to src/Ai/Raid/Mag/MagTriggers.cpp index 43aa3361f3f..28f110c9745 100644 --- a/src/Ai/Raid/Magtheridon/Trigger/RaidMagtheridonTriggers.cpp +++ b/src/Ai/Raid/Mag/MagTriggers.cpp @@ -1,5 +1,5 @@ -#include "RaidMagtheridonTriggers.h" -#include "RaidMagtheridonHelpers.h" +#include "MagTriggers.h" +#include "MagHelpers.h" #include "Playerbots.h" using namespace MagtheridonHelpers; diff --git a/src/Ai/Raid/Magtheridon/Trigger/RaidMagtheridonTriggers.h b/src/Ai/Raid/Mag/MagTriggers.h similarity index 100% rename from src/Ai/Raid/Magtheridon/Trigger/RaidMagtheridonTriggers.h rename to src/Ai/Raid/Mag/MagTriggers.h diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h b/src/Ai/Raid/Naxx/Action/NaxxActions.h similarity index 99% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h rename to src/Ai/Raid/Naxx/Action/NaxxActions.h index f2712d74dd5..8b854130eb6 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h +++ b/src/Ai/Raid/Naxx/Action/NaxxActions.h @@ -7,7 +7,7 @@ #include "MovementActions.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "RaidNaxxBossHelper.h" +#include "NaxxBossHelper.h" class GrobbulusGoBehindAction : public MovementAction { diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Anubrekhan.cpp similarity index 98% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Anubrekhan.cpp index 4391ba76cf8..fbab03646f1 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Anubrekhan.cpp @@ -1,6 +1,6 @@ #include "ObjectGuid.h" #include "Playerbots.h" -#include "RaidNaxxActions.h" +#include "NaxxActions.h" bool AnubrekhanChooseTargetAction::Execute(Event /*event*/) { diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Faerlina.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Faerlina.cpp similarity index 60% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Faerlina.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Faerlina.cpp index 0c52be7862d..75d20b0fe9e 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Faerlina.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Faerlina.cpp @@ -1,3 +1,3 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" // Reserved for Faerlina-specific actions. diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorsemen.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_FourHorsemen.cpp similarity index 98% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorsemen.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_FourHorsemen.cpp index 291b225e734..7d62d669e0b 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorsemen.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_FourHorsemen.cpp @@ -1,4 +1,4 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" #include "Playerbots.h" diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gluth.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Gluth.cpp similarity index 99% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gluth.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Gluth.cpp index f95f17ff13f..cde175bba58 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gluth.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Gluth.cpp @@ -1,4 +1,4 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" #include "PlayerbotAIConfig.h" #include "Playerbots.h" diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gothik.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Gothik.cpp similarity index 59% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gothik.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Gothik.cpp index a33fef3057b..fcf39d74c3e 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gothik.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Gothik.cpp @@ -1,3 +1,3 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" // Reserved for Gothik-specific actions. diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Grobbulus.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Grobbulus.cpp similarity index 97% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Grobbulus.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Grobbulus.cpp index 1917c3e138c..73bd8463726 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Grobbulus.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Grobbulus.cpp @@ -1,4 +1,4 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" #include "Playerbots.h" diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Heigan.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Heigan.cpp similarity index 97% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Heigan.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Heigan.cpp index 9e6221957ae..78e8c4de8d6 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Heigan.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Heigan.cpp @@ -1,6 +1,6 @@ #include "Playerbots.h" -#include "RaidNaxxActions.h" -#include "RaidNaxxSpellIds.h" +#include "NaxxActions.h" +#include "NaxxSpellIds.h" #include "Spell.h" #include "Timer.h" diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Kelthuzad.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Kelthuzad.cpp similarity index 99% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Kelthuzad.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Kelthuzad.cpp index 5f7741170dd..cb149fc4e11 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Kelthuzad.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Kelthuzad.cpp @@ -1,4 +1,4 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" #include "PlayerbotAIConfig.h" #include "Playerbots.h" diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Loatheb.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Loatheb.cpp similarity index 98% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Loatheb.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Loatheb.cpp index df43cabe64b..8cc2f21bf17 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Loatheb.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Loatheb.cpp @@ -1,4 +1,4 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" #include "Playerbots.h" diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Maexxna.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Maexxna.cpp similarity index 59% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Maexxna.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Maexxna.cpp index cfb7d27db0c..810638b136b 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Maexxna.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Maexxna.cpp @@ -1,3 +1,3 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" // Reserved for Maexxna-specific actions. diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Noth.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Noth.cpp similarity index 57% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Noth.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Noth.cpp index 4f8fe24a3a4..a7e2e7e9538 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Noth.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Noth.cpp @@ -1,3 +1,3 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" // Reserved for Noth-specific actions. diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Patchwerk.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Patchwerk.cpp similarity index 97% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Patchwerk.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Patchwerk.cpp index e0e2ee3e9e2..c7af988f311 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Patchwerk.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Patchwerk.cpp @@ -1,4 +1,4 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" #include #include diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Razuvious.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Razuvious.cpp similarity index 99% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Razuvious.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Razuvious.cpp index ce0ca6cafad..5d91db0c628 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Razuvious.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Razuvious.cpp @@ -1,4 +1,4 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" #include "ObjectGuid.h" #include "PlayerbotAIConfig.h" diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Sapphiron.cpp similarity index 97% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Sapphiron.cpp index 5fb6d868680..55bcdabc4f7 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Sapphiron.cpp @@ -1,9 +1,9 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" #include "PlayerbotAIConfig.h" #include "Playerbots.h" -#include "RaidNaxxBossHelper.h" -#include "RaidNaxxSpellIds.h" +#include "NaxxBossHelper.h" +#include "NaxxSpellIds.h" bool SapphironGroundPositionAction::Execute(Event /*event*/) { diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Shared.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Shared.cpp similarity index 93% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Shared.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Shared.cpp index 50b3bc1d817..300592780e1 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Shared.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Shared.cpp @@ -1,4 +1,4 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" uint32 RotateAroundTheCenterPointAction::FindNearestWaypoint() { diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Thaddius.cpp similarity index 98% rename from src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp rename to src/Ai/Raid/Naxx/Action/NaxxActions_Thaddius.cpp index 145604da0cb..016a9faa590 100644 --- a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Thaddius.cpp @@ -1,8 +1,8 @@ -#include "RaidNaxxActions.h" +#include "NaxxActions.h" #include "PlayerbotAIConfig.h" #include "Playerbots.h" -#include "RaidNaxxSpellIds.h" +#include "NaxxSpellIds.h" bool ThaddiusAttackNearestPetAction::isUseful() { diff --git a/src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h b/src/Ai/Raid/Naxx/NaxxActionContext.h similarity index 99% rename from src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h rename to src/Ai/Raid/Naxx/NaxxActionContext.h index 4aaf9462e77..46645e3158d 100644 --- a/src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h +++ b/src/Ai/Raid/Naxx/NaxxActionContext.h @@ -8,7 +8,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "RaidNaxxActions.h" +#include "NaxxActions.h" class RaidNaxxActionContext : public NamedObjectContext { diff --git a/src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h b/src/Ai/Raid/Naxx/NaxxBossHelper.h similarity index 99% rename from src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h rename to src/Ai/Raid/Naxx/NaxxBossHelper.h index a13a5b893dc..7bec3a7df34 100644 --- a/src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h +++ b/src/Ai/Raid/Naxx/NaxxBossHelper.h @@ -16,7 +16,7 @@ #include "SharedDefines.h" #include "Spell.h" #include "Timer.h" -#include "RaidNaxxSpellIds.h" +#include "NaxxSpellIds.h" const uint32 NAXX_MAP_ID = 533; diff --git a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp b/src/Ai/Raid/Naxx/NaxxMultipliers.cpp similarity index 99% rename from src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp rename to src/Ai/Raid/Naxx/NaxxMultipliers.cpp index 39e7668fa9f..b7700eb1b5a 100644 --- a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp +++ b/src/Ai/Raid/Naxx/NaxxMultipliers.cpp @@ -1,4 +1,4 @@ -#include "RaidNaxxMultipliers.h" +#include "NaxxMultipliers.h" #include "ChooseTargetActions.h" #include "DKActions.h" @@ -12,8 +12,8 @@ #include "MovementActions.h" #include "PaladinActions.h" #include "PriestActions.h" -#include "RaidNaxxActions.h" -#include "RaidNaxxSpellIds.h" +#include "NaxxActions.h" +#include "NaxxSpellIds.h" #include "ReachTargetActions.h" #include "RogueActions.h" #include "ScriptedCreature.h" diff --git a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h b/src/Ai/Raid/Naxx/NaxxMultipliers.h similarity index 98% rename from src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h rename to src/Ai/Raid/Naxx/NaxxMultipliers.h index f51420a2a38..d7eed40294c 100644 --- a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h +++ b/src/Ai/Raid/Naxx/NaxxMultipliers.h @@ -3,7 +3,7 @@ #define _PLAYERBOT_RAIDNAXXMULTIPLIERS_H #include "Multiplier.h" -#include "RaidNaxxBossHelper.h" +#include "NaxxBossHelper.h" class GrobbulusMultiplier : public Multiplier { diff --git a/src/Ai/Raid/Naxxramas/Util/RaidNaxxSpellIds.h b/src/Ai/Raid/Naxx/NaxxSpellIds.h similarity index 100% rename from src/Ai/Raid/Naxxramas/Util/RaidNaxxSpellIds.h rename to src/Ai/Raid/Naxx/NaxxSpellIds.h diff --git a/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp b/src/Ai/Raid/Naxx/NaxxStrategy.cpp similarity index 98% rename from src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp rename to src/Ai/Raid/Naxx/NaxxStrategy.cpp index 51f59f75586..f99ba9b8f44 100644 --- a/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp +++ b/src/Ai/Raid/Naxx/NaxxStrategy.cpp @@ -1,6 +1,6 @@ -#include "RaidNaxxStrategy.h" +#include "NaxxStrategy.h" -#include "RaidNaxxMultipliers.h" +#include "NaxxMultipliers.h" void RaidNaxxStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h b/src/Ai/Raid/Naxx/NaxxStrategy.h similarity index 100% rename from src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h rename to src/Ai/Raid/Naxx/NaxxStrategy.h diff --git a/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h b/src/Ai/Raid/Naxx/NaxxTriggerContext.h similarity index 99% rename from src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h rename to src/Ai/Raid/Naxx/NaxxTriggerContext.h index 83afc273d79..52d40cd6458 100644 --- a/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h +++ b/src/Ai/Raid/Naxx/NaxxTriggerContext.h @@ -7,7 +7,7 @@ #define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H #include "NamedObjectContext.h" -#include "RaidNaxxTriggers.h" +#include "NaxxTriggers.h" class RaidNaxxTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp b/src/Ai/Raid/Naxx/NaxxTriggers.cpp similarity index 99% rename from src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp rename to src/Ai/Raid/Naxx/NaxxTriggers.cpp index 3f0fc98b3a7..62195adbbda 100644 --- a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp +++ b/src/Ai/Raid/Naxx/NaxxTriggers.cpp @@ -1,7 +1,7 @@ -#include "RaidNaxxTriggers.h" +#include "NaxxTriggers.h" #include "Playerbots.h" -#include "RaidNaxxSpellIds.h" +#include "NaxxSpellIds.h" #include "Timer.h" #include "Trigger.h" diff --git a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h b/src/Ai/Raid/Naxx/NaxxTriggers.h similarity index 99% rename from src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h rename to src/Ai/Raid/Naxx/NaxxTriggers.h index c48cadd798c..58b1640b3db 100644 --- a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h +++ b/src/Ai/Raid/Naxx/NaxxTriggers.h @@ -5,7 +5,7 @@ #include "EventMap.h" #include "GenericTriggers.h" #include "PlayerbotAIConfig.h" -#include "RaidNaxxBossHelper.h" +#include "NaxxBossHelper.h" #include "Trigger.h" class MutatingInjectionTrigger : public HasAuraTrigger diff --git a/src/Ai/Raid/ObsidianSanctum/RaidOsActionContext.h b/src/Ai/Raid/OS/OSActionContext.h similarity index 98% rename from src/Ai/Raid/ObsidianSanctum/RaidOsActionContext.h rename to src/Ai/Raid/OS/OSActionContext.h index 55afe52603c..1d3dc7aa9ba 100644 --- a/src/Ai/Raid/ObsidianSanctum/RaidOsActionContext.h +++ b/src/Ai/Raid/OS/OSActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "RaidOsActions.h" +#include "OSActions.h" class RaidOsActionContext : public NamedObjectContext { diff --git a/src/Ai/Raid/ObsidianSanctum/Action/RaidOsActions.cpp b/src/Ai/Raid/OS/OSActions.cpp similarity index 99% rename from src/Ai/Raid/ObsidianSanctum/Action/RaidOsActions.cpp rename to src/Ai/Raid/OS/OSActions.cpp index c4f398d65a4..745da5ed4cb 100644 --- a/src/Ai/Raid/ObsidianSanctum/Action/RaidOsActions.cpp +++ b/src/Ai/Raid/OS/OSActions.cpp @@ -1,5 +1,5 @@ -#include "RaidOsActions.h" -#include "RaidOsTriggers.h" +#include "OSActions.h" +#include "OSTriggers.h" #include "Playerbots.h" diff --git a/src/Ai/Raid/ObsidianSanctum/Action/RaidOsActions.h b/src/Ai/Raid/OS/OSActions.h similarity index 100% rename from src/Ai/Raid/ObsidianSanctum/Action/RaidOsActions.h rename to src/Ai/Raid/OS/OSActions.h diff --git a/src/Ai/Raid/ObsidianSanctum/Multiplier/RaidOsMultipliers.cpp b/src/Ai/Raid/OS/OSMultipliers.cpp similarity index 94% rename from src/Ai/Raid/ObsidianSanctum/Multiplier/RaidOsMultipliers.cpp rename to src/Ai/Raid/OS/OSMultipliers.cpp index bc63f967d0c..754c68d4016 100644 --- a/src/Ai/Raid/ObsidianSanctum/Multiplier/RaidOsMultipliers.cpp +++ b/src/Ai/Raid/OS/OSMultipliers.cpp @@ -1,4 +1,4 @@ -#include "RaidOsMultipliers.h" +#include "OSMultipliers.h" #include "ChooseTargetActions.h" #include "DKActions.h" @@ -9,8 +9,8 @@ #include "GenericSpellActions.h" #include "MovementActions.h" #include "PaladinActions.h" -#include "RaidOsActions.h" -#include "RaidOsTriggers.h" +#include "OSActions.h" +#include "OSTriggers.h" #include "ReachTargetActions.h" #include "ScriptedCreature.h" #include "WarriorActions.h" diff --git a/src/Ai/Raid/ObsidianSanctum/Multiplier/RaidOsMultipliers.h b/src/Ai/Raid/OS/OSMultipliers.h similarity index 100% rename from src/Ai/Raid/ObsidianSanctum/Multiplier/RaidOsMultipliers.h rename to src/Ai/Raid/OS/OSMultipliers.h diff --git a/src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.cpp b/src/Ai/Raid/OS/OSStrategy.cpp similarity index 95% rename from src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.cpp rename to src/Ai/Raid/OS/OSStrategy.cpp index 4468de991f9..71128148e7e 100644 --- a/src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.cpp +++ b/src/Ai/Raid/OS/OSStrategy.cpp @@ -1,5 +1,5 @@ -#include "RaidOsStrategy.h" -#include "RaidOsMultipliers.h" +#include "OSStrategy.h" +#include "OSMultipliers.h" #include "Strategy.h" void RaidOsStrategy::InitTriggers(std::vector& triggers) diff --git a/src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.h b/src/Ai/Raid/OS/OSStrategy.h similarity index 100% rename from src/Ai/Raid/ObsidianSanctum/Strategy/RaidOsStrategy.h rename to src/Ai/Raid/OS/OSStrategy.h diff --git a/src/Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h b/src/Ai/Raid/OS/OSTriggerContext.h similarity index 98% rename from src/Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h rename to src/Ai/Raid/OS/OSTriggerContext.h index 3c1d406928a..e9195bcc230 100644 --- a/src/Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h +++ b/src/Ai/Raid/OS/OSTriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H #include "NamedObjectContext.h" -#include "RaidOsTriggers.h" +#include "OSTriggers.h" class RaidOsTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/ObsidianSanctum/Trigger/RaidOsTriggers.cpp b/src/Ai/Raid/OS/OSTriggers.cpp similarity index 99% rename from src/Ai/Raid/ObsidianSanctum/Trigger/RaidOsTriggers.cpp rename to src/Ai/Raid/OS/OSTriggers.cpp index 710e3cac100..d47fdcd064b 100644 --- a/src/Ai/Raid/ObsidianSanctum/Trigger/RaidOsTriggers.cpp +++ b/src/Ai/Raid/OS/OSTriggers.cpp @@ -1,4 +1,4 @@ -#include "RaidOsTriggers.h" +#include "OSTriggers.h" #include "SharedDefines.h" diff --git a/src/Ai/Raid/ObsidianSanctum/Trigger/RaidOsTriggers.h b/src/Ai/Raid/OS/OSTriggers.h similarity index 100% rename from src/Ai/Raid/ObsidianSanctum/Trigger/RaidOsTriggers.h rename to src/Ai/Raid/OS/OSTriggers.h diff --git a/src/Ai/Raid/Onyxia/RaidOnyxiaActionContext.h b/src/Ai/Raid/Ony/OnyActionContext.h similarity index 97% rename from src/Ai/Raid/Onyxia/RaidOnyxiaActionContext.h rename to src/Ai/Raid/Ony/OnyActionContext.h index b2923f969f2..923186f6197 100644 --- a/src/Ai/Raid/Onyxia/RaidOnyxiaActionContext.h +++ b/src/Ai/Raid/Ony/OnyActionContext.h @@ -3,7 +3,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "RaidOnyxiaActions.h" +#include "OnyActions.h" class RaidOnyxiaActionContext : public NamedObjectContext { diff --git a/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.cpp b/src/Ai/Raid/Ony/OnyActions.cpp similarity index 99% rename from src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.cpp rename to src/Ai/Raid/Ony/OnyActions.cpp index 5b470ee5ca1..d0565c189b5 100644 --- a/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.cpp +++ b/src/Ai/Raid/Ony/OnyActions.cpp @@ -1,4 +1,4 @@ -#include "RaidOnyxiaActions.h" +#include "OnyActions.h" #include "GenericSpellActions.h" #include "LastMovementValue.h" diff --git a/src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.h b/src/Ai/Raid/Ony/OnyActions.h similarity index 100% rename from src/Ai/Raid/Onyxia/Action/RaidOnyxiaActions.h rename to src/Ai/Raid/Ony/OnyActions.h diff --git a/src/Ai/Raid/Onyxia/Strategy/RaidOnyxiaStrategy.cpp b/src/Ai/Raid/Ony/OnyStrategy.cpp similarity index 96% rename from src/Ai/Raid/Onyxia/Strategy/RaidOnyxiaStrategy.cpp rename to src/Ai/Raid/Ony/OnyStrategy.cpp index bb61bc00443..07d763cfedc 100644 --- a/src/Ai/Raid/Onyxia/Strategy/RaidOnyxiaStrategy.cpp +++ b/src/Ai/Raid/Ony/OnyStrategy.cpp @@ -1,4 +1,4 @@ -#include "RaidOnyxiaStrategy.h" +#include "OnyStrategy.h" void RaidOnyxiaStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/Onyxia/Strategy/RaidOnyxiaStrategy.h b/src/Ai/Raid/Ony/OnyStrategy.h similarity index 100% rename from src/Ai/Raid/Onyxia/Strategy/RaidOnyxiaStrategy.h rename to src/Ai/Raid/Ony/OnyStrategy.h diff --git a/src/Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h b/src/Ai/Raid/Ony/OnyTriggerContext.h similarity index 97% rename from src/Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h rename to src/Ai/Raid/Ony/OnyTriggerContext.h index daf624a0bad..1c842560897 100644 --- a/src/Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h +++ b/src/Ai/Raid/Ony/OnyTriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H #include "NamedObjectContext.h" -#include "RaidOnyxiaTriggers.h" +#include "OnyTriggers.h" class RaidOnyxiaTriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/Onyxia/Trigger/RaidOnyxiaTriggers.cpp b/src/Ai/Raid/Ony/OnyTriggers.cpp similarity index 99% rename from src/Ai/Raid/Onyxia/Trigger/RaidOnyxiaTriggers.cpp rename to src/Ai/Raid/Ony/OnyTriggers.cpp index 56486ebf969..fe59c199d15 100644 --- a/src/Ai/Raid/Onyxia/Trigger/RaidOnyxiaTriggers.cpp +++ b/src/Ai/Raid/Ony/OnyTriggers.cpp @@ -1,4 +1,4 @@ -#include "RaidOnyxiaTriggers.h" +#include "OnyTriggers.h" #include "GenericTriggers.h" #include "ObjectAccessor.h" diff --git a/src/Ai/Raid/Onyxia/Trigger/RaidOnyxiaTriggers.h b/src/Ai/Raid/Ony/OnyTriggers.h similarity index 100% rename from src/Ai/Raid/Onyxia/Trigger/RaidOnyxiaTriggers.h rename to src/Ai/Raid/Ony/OnyTriggers.h diff --git a/src/Ai/Raid/RaidStrategyContext.h b/src/Ai/Raid/RaidStrategyContext.h index 732310b28b5..ddb288e33af 100644 --- a/src/Ai/Raid/RaidStrategyContext.h +++ b/src/Ai/Raid/RaidStrategyContext.h @@ -2,23 +2,23 @@ #define _PLAYERBOT_RAIDSTRATEGYCONTEXT_H_ #include "Strategy.h" -#include "RaidAq20Strategy.h" -#include "RaidMcStrategy.h" -#include "RaidBwlStrategy.h" -#include "RaidKarazhanStrategy.h" -#include "RaidGruulsLairStrategy.h" -#include "RaidMagtheridonStrategy.h" -#include "RaidNaxxStrategy.h" -#include "RaidSSCStrategy.h" -#include "RaidTempestKeepStrategy.h" -#include "RaidHyjalSummitStrategy.h" -#include "RaidBlackTempleStrategy.h" -#include "RaidZulAmanStrategy.h" -#include "RaidOsStrategy.h" -#include "RaidEoEStrategy.h" -#include "RaidVoAStrategy.h" -#include "RaidUlduarStrategy.h" -#include "RaidOnyxiaStrategy.h" +#include "Aq20Strategy.h" +#include "MCStrategy.h" +#include "BWLStrategy.h" +#include "KaraStrategy.h" +#include "GruulStrategy.h" +#include "MagStrategy.h" +#include "NaxxStrategy.h" +#include "SSCStrategy.h" +#include "TKStrategy.h" +#include "HyjalStrategy.h" +#include "BTStrategy.h" +#include "ZAStrategy.h" +#include "OSStrategy.h" +#include "EoEStrategy.h" +#include "VoAStrategy.h" +#include "UldStrategy.h" +#include "OnyStrategy.h" #include "ICCStrategy.h" class RaidStrategyContext : public NamedObjectContext diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h b/src/Ai/Raid/SSC/SSCActionContext.h similarity index 99% rename from src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h rename to src/Ai/Raid/SSC/SSCActionContext.h index 1e2c44b49ec..4a14a747c51 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h +++ b/src/Ai/Raid/SSC/SSCActionContext.h @@ -6,7 +6,7 @@ #ifndef _PLAYERBOT_RAIDSSCACTIONCONTEXT_H #define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H -#include "RaidSSCActions.h" +#include "SSCActions.h" #include "NamedObjectContext.h" class RaidSSCActionContext : public NamedObjectContext diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp b/src/Ai/Raid/SSC/SSCActions.cpp similarity index 99% rename from src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp rename to src/Ai/Raid/SSC/SSCActions.cpp index 9ccd67d2b9b..68989f5a6d9 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp +++ b/src/Ai/Raid/SSC/SSCActions.cpp @@ -3,8 +3,8 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidSSCActions.h" -#include "RaidSSCHelpers.h" +#include "SSCActions.h" +#include "SSCHelpers.h" #include "AiFactory.h" #include "Corpse.h" #include "LootAction.h" diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h b/src/Ai/Raid/SSC/SSCActions.h similarity index 100% rename from src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h rename to src/Ai/Raid/SSC/SSCActions.h diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp b/src/Ai/Raid/SSC/SSCHelpers.cpp similarity index 99% rename from src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp rename to src/Ai/Raid/SSC/SSCHelpers.cpp index 110481775af..b3a4ca4a58b 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp +++ b/src/Ai/Raid/SSC/SSCHelpers.cpp @@ -3,7 +3,7 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidSSCHelpers.h" +#include "SSCHelpers.h" #include "AiFactory.h" #include "Creature.h" #include "ObjectAccessor.h" diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h b/src/Ai/Raid/SSC/SSCHelpers.h similarity index 100% rename from src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h rename to src/Ai/Raid/SSC/SSCHelpers.h diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SSC/SSCMultipliers.cpp similarity index 99% rename from src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp rename to src/Ai/Raid/SSC/SSCMultipliers.cpp index 85d2c51a5e8..9e64e10c01f 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SSC/SSCMultipliers.cpp @@ -3,9 +3,9 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidSSCMultipliers.h" -#include "RaidSSCActions.h" -#include "RaidSSCHelpers.h" +#include "SSCMultipliers.h" +#include "SSCActions.h" +#include "SSCHelpers.h" #include "ChooseTargetActions.h" #include "DestroyItemAction.h" #include "DKActions.h" diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h b/src/Ai/Raid/SSC/SSCMultipliers.h similarity index 100% rename from src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h rename to src/Ai/Raid/SSC/SSCMultipliers.h diff --git a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp b/src/Ai/Raid/SSC/SSCStrategy.cpp similarity index 99% rename from src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp rename to src/Ai/Raid/SSC/SSCStrategy.cpp index 624049c8196..49ca37e6afa 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp +++ b/src/Ai/Raid/SSC/SSCStrategy.cpp @@ -3,8 +3,8 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidSSCStrategy.h" -#include "RaidSSCMultipliers.h" +#include "SSCStrategy.h" +#include "SSCMultipliers.h" void RaidSSCStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h b/src/Ai/Raid/SSC/SSCStrategy.h similarity index 100% rename from src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h rename to src/Ai/Raid/SSC/SSCStrategy.h diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h b/src/Ai/Raid/SSC/SSCTriggerContext.h similarity index 99% rename from src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h rename to src/Ai/Raid/SSC/SSCTriggerContext.h index 5b0f8d5e3b1..ac1ce58765c 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h +++ b/src/Ai/Raid/SSC/SSCTriggerContext.h @@ -6,7 +6,7 @@ #ifndef _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H -#include "RaidSSCTriggers.h" +#include "SSCTriggers.h" #include "NamedObjectContext.h" class RaidSSCTriggerContext : public NamedObjectContext diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp b/src/Ai/Raid/SSC/SSCTriggers.cpp similarity index 99% rename from src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp rename to src/Ai/Raid/SSC/SSCTriggers.cpp index c3e7532236a..f54d40a9126 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp +++ b/src/Ai/Raid/SSC/SSCTriggers.cpp @@ -3,9 +3,9 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidSSCTriggers.h" -#include "RaidSSCHelpers.h" -#include "RaidSSCActions.h" +#include "SSCTriggers.h" +#include "SSCHelpers.h" +#include "SSCActions.h" #include "AiFactory.h" #include "Corpse.h" #include "LootObjectStack.h" diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h b/src/Ai/Raid/SSC/SSCTriggers.h similarity index 100% rename from src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h rename to src/Ai/Raid/SSC/SSCTriggers.h diff --git a/src/Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h b/src/Ai/Raid/TK/TKActionContext.h similarity index 99% rename from src/Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h rename to src/Ai/Raid/TK/TKActionContext.h index 125b27c0f15..0c2c1cd71e8 100644 --- a/src/Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h +++ b/src/Ai/Raid/TK/TKActionContext.h @@ -1,7 +1,7 @@ #ifndef _PLAYERBOT_RAIDTEMPESTKEEPACTIONCONTEXT_H #define _PLAYERBOT_RAIDTEMPESTKEEPACTIONCONTEXT_H -#include "RaidTempestKeepActions.h" +#include "TKActions.h" #include "NamedObjectContext.h" class RaidTempestKeepActionContext : public NamedObjectContext diff --git a/src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.cpp b/src/Ai/Raid/TK/TKActions.cpp similarity index 99% rename from src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.cpp rename to src/Ai/Raid/TK/TKActions.cpp index 0255e192838..65933861714 100644 --- a/src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.cpp +++ b/src/Ai/Raid/TK/TKActions.cpp @@ -1,6 +1,6 @@ -#include "RaidTempestKeepActions.h" -#include "RaidTempestKeepHelpers.h" -#include "RaidTempestKeepKaelthasBossAI.h" +#include "TKActions.h" +#include "TKHelpers.h" +#include "TKKaelthasBossAI.h" #include "AiFactory.h" #include "EquipAction.h" #include "LootAction.h" diff --git a/src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.h b/src/Ai/Raid/TK/TKActions.h similarity index 99% rename from src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.h rename to src/Ai/Raid/TK/TKActions.h index 41b087e3c39..0d751ac1633 100644 --- a/src/Ai/Raid/TempestKeep/Action/RaidTempestKeepActions.h +++ b/src/Ai/Raid/TK/TKActions.h @@ -1,8 +1,8 @@ #ifndef _PLAYERBOT_RAIDTEMPESTKEEPACTIONS_H #define _PLAYERBOT_RAIDTEMPESTKEEPACTIONS_H -#include "RaidTempestKeepHelpers.h" -#include "RaidTempestKeepKaelthasBossAI.h" +#include "TKHelpers.h" +#include "TKKaelthasBossAI.h" #include "Action.h" #include "AttackAction.h" #include "MovementActions.h" diff --git a/src/Ai/Raid/TempestKeep/Multiplier/RaidTempestKeepMultipliers.cpp b/src/Ai/Raid/TK/TKMultipliers.cpp similarity index 98% rename from src/Ai/Raid/TempestKeep/Multiplier/RaidTempestKeepMultipliers.cpp rename to src/Ai/Raid/TK/TKMultipliers.cpp index 3bd65aecbbe..9a632a6c7a7 100644 --- a/src/Ai/Raid/TempestKeep/Multiplier/RaidTempestKeepMultipliers.cpp +++ b/src/Ai/Raid/TK/TKMultipliers.cpp @@ -1,7 +1,7 @@ -#include "RaidTempestKeepMultipliers.h" -#include "RaidTempestKeepActions.h" -#include "RaidTempestKeepHelpers.h" -#include "RaidTempestKeepKaelthasBossAI.h" +#include "TKMultipliers.h" +#include "TKActions.h" +#include "TKHelpers.h" +#include "TKKaelthasBossAI.h" #include "ChooseTargetActions.h" #include "DKActions.h" #include "DruidActions.h" diff --git a/src/Ai/Raid/TempestKeep/Multiplier/RaidTempestKeepMultipliers.h b/src/Ai/Raid/TK/TKMultipliers.h similarity index 100% rename from src/Ai/Raid/TempestKeep/Multiplier/RaidTempestKeepMultipliers.h rename to src/Ai/Raid/TK/TKMultipliers.h diff --git a/src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.cpp b/src/Ai/Raid/TK/TKStrategy.cpp similarity index 99% rename from src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.cpp rename to src/Ai/Raid/TK/TKStrategy.cpp index 627d3d7abd0..3a5155ca99f 100644 --- a/src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.cpp +++ b/src/Ai/Raid/TK/TKStrategy.cpp @@ -1,5 +1,5 @@ -#include "RaidTempestKeepStrategy.h" -#include "RaidTempestKeepMultipliers.h" +#include "TKStrategy.h" +#include "TKMultipliers.h" void RaidTempestKeepStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.h b/src/Ai/Raid/TK/TKStrategy.h similarity index 100% rename from src/Ai/Raid/TempestKeep/Strategy/RaidTempestKeepStrategy.h rename to src/Ai/Raid/TK/TKStrategy.h diff --git a/src/Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h b/src/Ai/Raid/TK/TKTriggerContext.h similarity index 99% rename from src/Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h rename to src/Ai/Raid/TK/TKTriggerContext.h index 0bf1d0fdcc8..baa11694c07 100644 --- a/src/Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h +++ b/src/Ai/Raid/TK/TKTriggerContext.h @@ -1,7 +1,7 @@ #ifndef _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H -#include "RaidTempestKeepTriggers.h" +#include "TKTriggers.h" #include "NamedObjectContext.h" class RaidTempestKeepTriggerContext : public NamedObjectContext diff --git a/src/Ai/Raid/TempestKeep/Trigger/RaidTempestKeepTriggers.cpp b/src/Ai/Raid/TK/TKTriggers.cpp similarity index 99% rename from src/Ai/Raid/TempestKeep/Trigger/RaidTempestKeepTriggers.cpp rename to src/Ai/Raid/TK/TKTriggers.cpp index b0b399b102e..d477b6d8f7b 100644 --- a/src/Ai/Raid/TempestKeep/Trigger/RaidTempestKeepTriggers.cpp +++ b/src/Ai/Raid/TK/TKTriggers.cpp @@ -1,7 +1,7 @@ -#include "RaidTempestKeepTriggers.h" -#include "RaidTempestKeepHelpers.h" -#include "RaidTempestKeepActions.h" -#include "RaidTempestKeepKaelthasBossAI.h" +#include "TKTriggers.h" +#include "TKHelpers.h" +#include "TKActions.h" +#include "TKKaelthasBossAI.h" #include "Playerbots.h" #include "RaidBossHelpers.h" diff --git a/src/Ai/Raid/TempestKeep/Trigger/RaidTempestKeepTriggers.h b/src/Ai/Raid/TK/TKTriggers.h similarity index 100% rename from src/Ai/Raid/TempestKeep/Trigger/RaidTempestKeepTriggers.h rename to src/Ai/Raid/TK/TKTriggers.h diff --git a/src/Ai/Raid/TempestKeep/Util/RaidTempestKeepHelpers.cpp b/src/Ai/Raid/TK/Util/TKHelpers.cpp similarity index 99% rename from src/Ai/Raid/TempestKeep/Util/RaidTempestKeepHelpers.cpp rename to src/Ai/Raid/TK/Util/TKHelpers.cpp index 30327771217..d4d8a44be81 100644 --- a/src/Ai/Raid/TempestKeep/Util/RaidTempestKeepHelpers.cpp +++ b/src/Ai/Raid/TK/Util/TKHelpers.cpp @@ -1,5 +1,5 @@ -#include "RaidTempestKeepHelpers.h" -#include "RaidTempestKeepActions.h" +#include "TKHelpers.h" +#include "TKActions.h" #include "LootObjectStack.h" #include "Playerbots.h" #include "RaidBossHelpers.h" diff --git a/src/Ai/Raid/TempestKeep/Util/RaidTempestKeepHelpers.h b/src/Ai/Raid/TK/Util/TKHelpers.h similarity index 100% rename from src/Ai/Raid/TempestKeep/Util/RaidTempestKeepHelpers.h rename to src/Ai/Raid/TK/Util/TKHelpers.h diff --git a/src/Ai/Raid/TempestKeep/Util/RaidTempestKeepKaelthasBossAI.h b/src/Ai/Raid/TK/Util/TKKaelthasBossAI.h similarity index 100% rename from src/Ai/Raid/TempestKeep/Util/RaidTempestKeepKaelthasBossAI.h rename to src/Ai/Raid/TK/Util/TKKaelthasBossAI.h diff --git a/src/Ai/Raid/TempestKeep/Util/RaidTempestKeepScripts.cpp b/src/Ai/Raid/TK/Util/TKScripts.cpp similarity index 97% rename from src/Ai/Raid/TempestKeep/Util/RaidTempestKeepScripts.cpp rename to src/Ai/Raid/TK/Util/TKScripts.cpp index ff26d150040..85703b10069 100644 --- a/src/Ai/Raid/TempestKeep/Util/RaidTempestKeepScripts.cpp +++ b/src/Ai/Raid/TK/Util/TKScripts.cpp @@ -1,4 +1,4 @@ -#include "RaidTempestKeepHelpers.h" +#include "TKHelpers.h" #include "ObjectAccessor.h" #include "Player.h" #include "ScriptMgr.h" diff --git a/src/Ai/Raid/Ulduar/RaidUlduarActionContext.h b/src/Ai/Raid/Uld/UldActionContext.h similarity index 99% rename from src/Ai/Raid/Ulduar/RaidUlduarActionContext.h rename to src/Ai/Raid/Uld/UldActionContext.h index 84c1bb8505c..aec2e68e752 100644 --- a/src/Ai/Raid/Ulduar/RaidUlduarActionContext.h +++ b/src/Ai/Raid/Uld/UldActionContext.h @@ -8,7 +8,7 @@ #include "Action.h" #include "NamedObjectContext.h" -#include "RaidUlduarActions.h" +#include "UldActions.h" #include "BossAuraActions.h" class RaidUlduarActionContext : public NamedObjectContext diff --git a/src/Ai/Raid/Ulduar/Action/RaidUlduarActions.cpp b/src/Ai/Raid/Uld/UldActions.cpp similarity index 99% rename from src/Ai/Raid/Ulduar/Action/RaidUlduarActions.cpp rename to src/Ai/Raid/Uld/UldActions.cpp index 217ab2c06e2..1ea21194975 100644 --- a/src/Ai/Raid/Ulduar/Action/RaidUlduarActions.cpp +++ b/src/Ai/Raid/Uld/UldActions.cpp @@ -1,5 +1,5 @@ -#include "RaidUlduarActions.h" +#include "UldActions.h" #include #include @@ -16,8 +16,8 @@ #include "PlayerbotAIConfig.h" #include "Playerbots.h" #include "Position.h" -#include "RaidUlduarBossHelper.h" -#include "RaidUlduarScripts.h" +#include "UldBossHelper.h" +#include "UldScripts.h" #include "RtiValue.h" #include "ScriptedCreature.h" #include "ServerFacade.h" diff --git a/src/Ai/Raid/Ulduar/Action/RaidUlduarActions.h b/src/Ai/Raid/Uld/UldActions.h similarity index 99% rename from src/Ai/Raid/Ulduar/Action/RaidUlduarActions.h rename to src/Ai/Raid/Uld/UldActions.h index c3d5c0d6371..152de3fcb30 100644 --- a/src/Ai/Raid/Ulduar/Action/RaidUlduarActions.h +++ b/src/Ai/Raid/Uld/UldActions.h @@ -8,8 +8,8 @@ #include "MovementActions.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "RaidUlduarBossHelper.h" -#include "RaidUlduarTriggers.h" +#include "UldBossHelper.h" +#include "UldTriggers.h" #include "Vehicle.h" // diff --git a/src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.cpp b/src/Ai/Raid/Uld/UldStrategy.cpp similarity index 99% rename from src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.cpp rename to src/Ai/Raid/Uld/UldStrategy.cpp index 0a1b76a40f1..c2fa156bd59 100644 --- a/src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.cpp +++ b/src/Ai/Raid/Uld/UldStrategy.cpp @@ -1,4 +1,4 @@ -#include "RaidUlduarStrategy.h" +#include "UldStrategy.h" void RaidUlduarStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.h b/src/Ai/Raid/Uld/UldStrategy.h similarity index 100% rename from src/Ai/Raid/Ulduar/Strategy/RaidUlduarStrategy.h rename to src/Ai/Raid/Uld/UldStrategy.h diff --git a/src/Ai/Raid/Ulduar/RaidUlduarTriggerContext.h b/src/Ai/Raid/Uld/UldTriggerContext.h similarity index 99% rename from src/Ai/Raid/Ulduar/RaidUlduarTriggerContext.h rename to src/Ai/Raid/Uld/UldTriggerContext.h index e093f579745..c1bdb44769e 100644 --- a/src/Ai/Raid/Ulduar/RaidUlduarTriggerContext.h +++ b/src/Ai/Raid/Uld/UldTriggerContext.h @@ -7,7 +7,7 @@ #define _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H #include "NamedObjectContext.h" -#include "RaidUlduarTriggers.h" +#include "UldTriggers.h" #include "BossAuraTriggers.h" class RaidUlduarTriggerContext : public NamedObjectContext diff --git a/src/Ai/Raid/Ulduar/Trigger/RaidUlduarTriggers.cpp b/src/Ai/Raid/Uld/UldTriggers.cpp similarity index 99% rename from src/Ai/Raid/Ulduar/Trigger/RaidUlduarTriggers.cpp rename to src/Ai/Raid/Uld/UldTriggers.cpp index 7b269ac685f..7c84043efa9 100644 --- a/src/Ai/Raid/Ulduar/Trigger/RaidUlduarTriggers.cpp +++ b/src/Ai/Raid/Uld/UldTriggers.cpp @@ -1,11 +1,11 @@ -#include "RaidUlduarTriggers.h" +#include "UldTriggers.h" #include "GameObject.h" #include "Object.h" #include "PlayerbotAI.h" #include "Playerbots.h" -#include "RaidUlduarBossHelper.h" -#include "RaidUlduarScripts.h" +#include "UldBossHelper.h" +#include "UldScripts.h" #include "ScriptedCreature.h" #include "SharedDefines.h" #include "Trigger.h" diff --git a/src/Ai/Raid/Ulduar/Trigger/RaidUlduarTriggers.h b/src/Ai/Raid/Uld/UldTriggers.h similarity index 99% rename from src/Ai/Raid/Ulduar/Trigger/RaidUlduarTriggers.h rename to src/Ai/Raid/Uld/UldTriggers.h index 7f8cb51a83d..f827745fc1c 100644 --- a/src/Ai/Raid/Ulduar/Trigger/RaidUlduarTriggers.h +++ b/src/Ai/Raid/Uld/UldTriggers.h @@ -3,7 +3,7 @@ #include "EventMap.h" #include "GenericTriggers.h" -#include "RaidUlduarBossHelper.h" +#include "UldBossHelper.h" #include "Trigger.h" // diff --git a/src/Ai/Raid/Ulduar/Util/RaidUlduarBossHelper.cpp b/src/Ai/Raid/Uld/Util/UldBossHelper.cpp similarity index 99% rename from src/Ai/Raid/Ulduar/Util/RaidUlduarBossHelper.cpp rename to src/Ai/Raid/Uld/Util/UldBossHelper.cpp index fd6711cf0fc..b2cb5c48dae 100644 --- a/src/Ai/Raid/Ulduar/Util/RaidUlduarBossHelper.cpp +++ b/src/Ai/Raid/Uld/Util/UldBossHelper.cpp @@ -1,4 +1,4 @@ -#include "RaidUlduarBossHelper.h" +#include "UldBossHelper.h" #include "ObjectAccessor.h" #include "GameObject.h" #include "Group.h" diff --git a/src/Ai/Raid/Ulduar/Util/RaidUlduarBossHelper.h b/src/Ai/Raid/Uld/Util/UldBossHelper.h similarity index 100% rename from src/Ai/Raid/Ulduar/Util/RaidUlduarBossHelper.h rename to src/Ai/Raid/Uld/Util/UldBossHelper.h diff --git a/src/Ai/Raid/Ulduar/Util/RaidUlduarScripts.h b/src/Ai/Raid/Uld/Util/UldScripts.h similarity index 100% rename from src/Ai/Raid/Ulduar/Util/RaidUlduarScripts.h rename to src/Ai/Raid/Uld/Util/UldScripts.h diff --git a/src/Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h b/src/Ai/Raid/VoA/VoAActionContext.h similarity index 98% rename from src/Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h rename to src/Ai/Raid/VoA/VoAActionContext.h index 263d03d9b75..312f36a6776 100644 --- a/src/Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h +++ b/src/Ai/Raid/VoA/VoAActionContext.h @@ -9,7 +9,7 @@ #include "Action.h" #include "BossAuraActions.h" #include "NamedObjectContext.h" -#include "RaidVoAActions.h" +#include "VoAActions.h" #include "PlayerbotAI.h" class RaidVoAActionContext : public NamedObjectContext diff --git a/src/Ai/Raid/VaultOfArchavon/Action/RaidVoAActions.cpp b/src/Ai/Raid/VoA/VoAActions.cpp similarity index 99% rename from src/Ai/Raid/VaultOfArchavon/Action/RaidVoAActions.cpp rename to src/Ai/Raid/VoA/VoAActions.cpp index 892ddf3eb43..33d2744611c 100644 --- a/src/Ai/Raid/VaultOfArchavon/Action/RaidVoAActions.cpp +++ b/src/Ai/Raid/VoA/VoAActions.cpp @@ -1,5 +1,5 @@ -#include "RaidVoAActions.h" -#include "RaidVoATriggers.h" +#include "VoAActions.h" +#include "VoATriggers.h" #include "Define.h" #include "Event.h" #include "Group.h" diff --git a/src/Ai/Raid/VaultOfArchavon/Action/RaidVoAActions.h b/src/Ai/Raid/VoA/VoAActions.h similarity index 100% rename from src/Ai/Raid/VaultOfArchavon/Action/RaidVoAActions.h rename to src/Ai/Raid/VoA/VoAActions.h diff --git a/src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.cpp b/src/Ai/Raid/VoA/VoAStrategy.cpp similarity index 97% rename from src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.cpp rename to src/Ai/Raid/VoA/VoAStrategy.cpp index db81477a802..7a87492ff25 100644 --- a/src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.cpp +++ b/src/Ai/Raid/VoA/VoAStrategy.cpp @@ -1,4 +1,4 @@ -#include "RaidVoAStrategy.h" +#include "VoAStrategy.h" #include "Action.h" #include "Strategy.h" #include "Trigger.h" diff --git a/src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.h b/src/Ai/Raid/VoA/VoAStrategy.h similarity index 100% rename from src/Ai/Raid/VaultOfArchavon/Strategy/RaidVoAStrategy.h rename to src/Ai/Raid/VoA/VoAStrategy.h diff --git a/src/Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h b/src/Ai/Raid/VoA/VoATriggerContext.h similarity index 98% rename from src/Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h rename to src/Ai/Raid/VoA/VoATriggerContext.h index 6cb5e0f386b..54f38ecb70e 100644 --- a/src/Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h +++ b/src/Ai/Raid/VoA/VoATriggerContext.h @@ -8,7 +8,7 @@ #include "BossAuraTriggers.h" #include "NamedObjectContext.h" -#include "RaidVoATriggers.h" +#include "VoATriggers.h" class RaidVoATriggerContext : public NamedObjectContext { diff --git a/src/Ai/Raid/VaultOfArchavon/Trigger/RaidVoATriggers.cpp b/src/Ai/Raid/VoA/VoATriggers.cpp similarity index 99% rename from src/Ai/Raid/VaultOfArchavon/Trigger/RaidVoATriggers.cpp rename to src/Ai/Raid/VoA/VoATriggers.cpp index 03a8b9b35da..82038aaa81d 100644 --- a/src/Ai/Raid/VaultOfArchavon/Trigger/RaidVoATriggers.cpp +++ b/src/Ai/Raid/VoA/VoATriggers.cpp @@ -1,4 +1,4 @@ -#include "RaidVoATriggers.h" +#include "VoATriggers.h" #include "EventMap.h" #include "Object.h" diff --git a/src/Ai/Raid/VaultOfArchavon/Trigger/RaidVoATriggers.h b/src/Ai/Raid/VoA/VoATriggers.h similarity index 100% rename from src/Ai/Raid/VaultOfArchavon/Trigger/RaidVoATriggers.h rename to src/Ai/Raid/VoA/VoATriggers.h diff --git a/src/Ai/Raid/ZulAman/RaidZulAmanActionContext.h b/src/Ai/Raid/ZA/ZAActionContext.h similarity index 99% rename from src/Ai/Raid/ZulAman/RaidZulAmanActionContext.h rename to src/Ai/Raid/ZA/ZAActionContext.h index 852a180b321..fd83a4073bf 100644 --- a/src/Ai/Raid/ZulAman/RaidZulAmanActionContext.h +++ b/src/Ai/Raid/ZA/ZAActionContext.h @@ -6,7 +6,7 @@ #ifndef _PLAYERBOT_RAIDZULAMANACTIONCONTEXT_H #define _PLAYERBOT_RAIDZULAMANACTIONCONTEXT_H -#include "RaidZulAmanActions.h" +#include "ZAActions.h" #include "NamedObjectContext.h" class RaidZulAmanActionContext : public NamedObjectContext diff --git a/src/Ai/Raid/ZulAman/Action/RaidZulAmanActions.cpp b/src/Ai/Raid/ZA/ZAActions.cpp similarity index 99% rename from src/Ai/Raid/ZulAman/Action/RaidZulAmanActions.cpp rename to src/Ai/Raid/ZA/ZAActions.cpp index 7e621210497..77826da63b9 100644 --- a/src/Ai/Raid/ZulAman/Action/RaidZulAmanActions.cpp +++ b/src/Ai/Raid/ZA/ZAActions.cpp @@ -3,8 +3,8 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidZulAmanActions.h" -#include "RaidZulAmanHelpers.h" +#include "ZAActions.h" +#include "ZAHelpers.h" #include "Playerbots.h" #include "RaidBossHelpers.h" diff --git a/src/Ai/Raid/ZulAman/Action/RaidZulAmanActions.h b/src/Ai/Raid/ZA/ZAActions.h similarity index 100% rename from src/Ai/Raid/ZulAman/Action/RaidZulAmanActions.h rename to src/Ai/Raid/ZA/ZAActions.h diff --git a/src/Ai/Raid/ZulAman/Util/RaidZulAmanHelpers.cpp b/src/Ai/Raid/ZA/ZAHelpers.cpp similarity index 99% rename from src/Ai/Raid/ZulAman/Util/RaidZulAmanHelpers.cpp rename to src/Ai/Raid/ZA/ZAHelpers.cpp index eeff879a4d6..befee71b7e5 100644 --- a/src/Ai/Raid/ZulAman/Util/RaidZulAmanHelpers.cpp +++ b/src/Ai/Raid/ZA/ZAHelpers.cpp @@ -3,7 +3,7 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidZulAmanHelpers.h" +#include "ZAHelpers.h" #include "Group.h" #include "Playerbots.h" diff --git a/src/Ai/Raid/ZulAman/Util/RaidZulAmanHelpers.h b/src/Ai/Raid/ZA/ZAHelpers.h similarity index 100% rename from src/Ai/Raid/ZulAman/Util/RaidZulAmanHelpers.h rename to src/Ai/Raid/ZA/ZAHelpers.h diff --git a/src/Ai/Raid/ZulAman/Multiplier/RaidZulAmanMultipliers.cpp b/src/Ai/Raid/ZA/ZAMultipliers.cpp similarity index 99% rename from src/Ai/Raid/ZulAman/Multiplier/RaidZulAmanMultipliers.cpp rename to src/Ai/Raid/ZA/ZAMultipliers.cpp index 8a064fead3c..a637ed62f3f 100644 --- a/src/Ai/Raid/ZulAman/Multiplier/RaidZulAmanMultipliers.cpp +++ b/src/Ai/Raid/ZA/ZAMultipliers.cpp @@ -3,9 +3,9 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidZulAmanMultipliers.h" -#include "RaidZulAmanActions.h" -#include "RaidZulAmanHelpers.h" +#include "ZAMultipliers.h" +#include "ZAActions.h" +#include "ZAHelpers.h" #include "ChooseTargetActions.h" #include "DKActions.h" #include "DruidBearActions.h" diff --git a/src/Ai/Raid/ZulAman/Multiplier/RaidZulAmanMultipliers.h b/src/Ai/Raid/ZA/ZAMultipliers.h similarity index 100% rename from src/Ai/Raid/ZulAman/Multiplier/RaidZulAmanMultipliers.h rename to src/Ai/Raid/ZA/ZAMultipliers.h diff --git a/src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.cpp b/src/Ai/Raid/ZA/ZAStrategy.cpp similarity index 98% rename from src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.cpp rename to src/Ai/Raid/ZA/ZAStrategy.cpp index 4ba01a2a14a..d9de00b2198 100644 --- a/src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.cpp +++ b/src/Ai/Raid/ZA/ZAStrategy.cpp @@ -3,8 +3,8 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidZulAmanStrategy.h" -#include "RaidZulAmanMultipliers.h" +#include "ZAStrategy.h" +#include "ZAMultipliers.h" void RaidZulAmanStrategy::InitTriggers(std::vector& triggers) { diff --git a/src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.h b/src/Ai/Raid/ZA/ZAStrategy.h similarity index 100% rename from src/Ai/Raid/ZulAman/Strategy/RaidZulAmanStrategy.h rename to src/Ai/Raid/ZA/ZAStrategy.h diff --git a/src/Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h b/src/Ai/Raid/ZA/ZATriggerContext.h similarity index 99% rename from src/Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h rename to src/Ai/Raid/ZA/ZATriggerContext.h index cb8bac864c0..15fd30b209d 100644 --- a/src/Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h +++ b/src/Ai/Raid/ZA/ZATriggerContext.h @@ -6,7 +6,7 @@ #ifndef _PLAYERBOT_RAIDZULAMANTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDZULAMANTRIGGERCONTEXT_H -#include "RaidZulAmanTriggers.h" +#include "ZATriggers.h" #include "NamedObjectContext.h" class RaidZulAmanTriggerContext : public NamedObjectContext diff --git a/src/Ai/Raid/ZulAman/Trigger/RaidZulAmanTriggers.cpp b/src/Ai/Raid/ZA/ZATriggers.cpp similarity index 98% rename from src/Ai/Raid/ZulAman/Trigger/RaidZulAmanTriggers.cpp rename to src/Ai/Raid/ZA/ZATriggers.cpp index 22b072702e5..60ce03303c6 100644 --- a/src/Ai/Raid/ZulAman/Trigger/RaidZulAmanTriggers.cpp +++ b/src/Ai/Raid/ZA/ZATriggers.cpp @@ -3,9 +3,9 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "RaidZulAmanTriggers.h" -#include "RaidZulAmanHelpers.h" -#include "RaidZulAmanActions.h" +#include "ZATriggers.h" +#include "ZAHelpers.h" +#include "ZAActions.h" #include "Playerbots.h" #include "RaidBossHelpers.h" diff --git a/src/Ai/Raid/ZulAman/Trigger/RaidZulAmanTriggers.h b/src/Ai/Raid/ZA/ZATriggers.h similarity index 100% rename from src/Ai/Raid/ZulAman/Trigger/RaidZulAmanTriggers.h rename to src/Ai/Raid/ZA/ZATriggers.h diff --git a/src/Bot/Engine/BuildSharedActionContexts.cpp b/src/Bot/Engine/BuildSharedActionContexts.cpp index c2972a68f29..901a7f12b62 100644 --- a/src/Bot/Engine/BuildSharedActionContexts.cpp +++ b/src/Bot/Engine/BuildSharedActionContexts.cpp @@ -2,24 +2,24 @@ #include "ActionContext.h" #include "ChatActionContext.h" #include "WorldPacketActionContext.h" -#include "Ai/Raid/Aq20/RaidAq20ActionContext.h" -#include "Ai/Raid/MoltenCore/RaidMcActionContext.h" -#include "Ai/Raid/BlackwingLair/RaidBwlActionContext.h" -#include "Ai/Raid/Karazhan/RaidKarazhanActionContext.h" -#include "Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h" -#include "Ai/Raid/Naxxramas/RaidNaxxActionContext.h" -#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h" -#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h" -#include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h" -#include "Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h" -#include "Ai/Raid/BlackTemple/RaidBlackTempleActionContext.h" -#include "Ai/Raid/ZulAman/RaidZulAmanActionContext.h" -#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h" -#include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h" -#include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h" -#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h" -#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h" -#include "Ai/Raid/ICC/ICCActionContext.h" +#include "Aq20ActionContext.h" +#include "MCActionContext.h" +#include "BWLActionContext.h" +#include "KaraActionContext.h" +#include "GruulActionContext.h" +#include "NaxxActionContext.h" +#include "MagActionContext.h" +#include "SSCActionContext.h" +#include "TKActionContext.h" +#include "HyjalActionContext.h" +#include "BTActionContext.h" +#include "ZAActionContext.h" +#include "OSActionContext.h" +#include "EoEActionContext.h" +#include "VoAActionContext.h" +#include "UldActionContext.h" +#include "OnyActionContext.h" +#include "ICCActionContext.h" #include "Ai/Dungeon/TbcDungeonActionContext.h" #include "Ai/Dungeon/WotlkDungeonActionContext.h" diff --git a/src/Bot/Engine/BuildSharedTriggerContexts.cpp b/src/Bot/Engine/BuildSharedTriggerContexts.cpp index 43b59542c87..704890524d8 100644 --- a/src/Bot/Engine/BuildSharedTriggerContexts.cpp +++ b/src/Bot/Engine/BuildSharedTriggerContexts.cpp @@ -2,24 +2,24 @@ #include "TriggerContext.h" #include "ChatTriggerContext.h" #include "WorldPacketTriggerContext.h" -#include "Ai/Raid/Aq20/RaidAq20TriggerContext.h" -#include "Ai/Raid/MoltenCore/RaidMcTriggerContext.h" -#include "Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h" -#include "Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h" -#include "Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h" -#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h" -#include "Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h" -#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h" -#include "Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h" -#include "Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h" -#include "Ai/Raid/BlackTemple/RaidBlackTempleTriggerContext.h" -#include "Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h" -#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h" -#include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h" -#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h" -#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h" -#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h" -#include "Ai/Raid/ICC/ICCTriggerContext.h" +#include "Aq20TriggerContext.h" +#include "MCTriggerContext.h" +#include "BWLTriggerContext.h" +#include "KaraTriggerContext.h" +#include "GruulTriggerContext.h" +#include "MagTriggerContext.h" +#include "NaxxTriggerContext.h" +#include "SSCTriggerContext.h" +#include "TKTriggerContext.h" +#include "HyjalTriggerContext.h" +#include "BTTriggerContext.h" +#include "ZATriggerContext.h" +#include "OSTriggerContext.h" +#include "EoETriggerContext.h" +#include "VoATriggerContext.h" +#include "UldTriggerContext.h" +#include "OnyTriggerContext.h" +#include "ICCTriggerContext.h" #include "Ai/Dungeon/TbcDungeonTriggerContext.h" #include "Ai/Dungeon/WotlkDungeonTriggerContext.h" From 7cd29783a158c9d2e8e180a81e2ca1cf0b7da9f8 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 6 Jun 2026 17:58:56 -0500 Subject: [PATCH 60/63] Fix errors with greater blessing system PR (#2439) ## Pull Request Description I decided to pick up some randombots and run Naxx40 at level 70, and good thing, because I realized the entire greater blessing system can collapse, especially with >4 Paladins. Dumb shit: - Failure of ComputeBestClassAssignments would cause ComputeGreaterBlessingAssignments to return false, meaning the entire Greater Blessing system would fail and nobody would receive any blessings of any kind. That should be a continue, lol. - How ComputeBestClassAssignments would most likely fail would be if no Paladin was available to cast Sanctuary in a scenario where Sanctuary was expected (for example, if there were 3 Paladins in the raid, Rogues and Warriors expected that at least one would cast Sanctuary, and if none could, ComputeBestClassAssignments would fail). - The priority system for improved talents was silly with the pure scoring (no reason that a Paladin with Improved Blessing of Might should get prioritized once one Paladin with Improved Blessing of Might was already assigned in the four, for example). This made the Sanctuary failure path more likely since basically if you had 4 Ret/Holy Paladins, they'd get picked to be your 4, and even if you had a Prot Paladin in your raid, they'd be excluded, and the whole Blessing system would fail I also increased the pending assignment cache from 100 ms to 500 ms for performance improvement. No reason for it to be so short. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. There shouldn't be any impact here beyond the increased pending assignment cache, which is probably a (very minor) performance improvement. ## How to Test the Changes Try raiding with more than 4 Paladins and different configurations, including missing Sanctuary. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) GPT-5.4 was used to diagnose the issue and propose fixes. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- .../Actions/PaladinGreaterBlessingAction.cpp | 117 ++++++++++++++---- 1 file changed, 90 insertions(+), 27 deletions(-) diff --git a/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp b/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp index a123b29fa5c..6a174d30097 100644 --- a/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp +++ b/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp @@ -23,12 +23,20 @@ namespace ai::gbless namespace { constexpr uint32 GREATER_BLESSING_ASSIGNMENT_CACHE_MS = 4 * 1000; - constexpr uint32 GREATER_BLESSING_PENDING_ASSIGNMENT_CACHE_MS = 100; + constexpr uint32 GREATER_BLESSING_PENDING_ASSIGNMENT_CACHE_MS = 500; constexpr uint8 MAX_BLESSING_SLOTS = 4; constexpr uint8 MAX_CLASS_ID = 12; constexpr size_t BaseBlessingCategoryCount = MAX_BLESSING_SLOTS; + enum PaladinBlessingCapability : uint8 + { + PALADIN_BLESSING_CAPABILITY_NONE = 0, + PALADIN_BLESSING_CAPABILITY_IMPROVED_WISDOM = 1 << 0, + PALADIN_BLESSING_CAPABILITY_IMPROVED_MIGHT = 1 << 1, + PALADIN_BLESSING_CAPABILITY_SANCTUARY = 1 << 2 + }; + constexpr size_t BaseBlessingIndex(BaseBlessingCategory category) { return static_cast(static_cast(category) - static_cast(BASE_MIGHT)); @@ -58,22 +66,38 @@ namespace (!left.byRole || left.role == right.role); } - int TalentScore(Player* player) + uint8 GetPaladinBlessingCapabilities(Player* player) { if (!player) - return 0; + return PALADIN_BLESSING_CAPABILITY_NONE; - int score = 0; + uint8 capabilities = PALADIN_BLESSING_CAPABILITY_NONE; if (player->HasAura(SPELL_IMPROVED_MIGHT_R1) || player->HasAura(SPELL_IMPROVED_MIGHT_R2)) { - score += 2; + capabilities |= PALADIN_BLESSING_CAPABILITY_IMPROVED_MIGHT; } if (player->HasAura(SPELL_IMPROVED_WISDOM_R1) || player->HasAura(SPELL_IMPROVED_WISDOM_R2)) { - score += 1; + capabilities |= PALADIN_BLESSING_CAPABILITY_IMPROVED_WISDOM; } + if (player->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY)) + capabilities |= PALADIN_BLESSING_CAPABILITY_SANCTUARY; + + return capabilities; + } + + int TalentScore(Player* player) + { + uint8 const capabilities = GetPaladinBlessingCapabilities(player); + int score = 0; + + if (capabilities & PALADIN_BLESSING_CAPABILITY_IMPROVED_MIGHT) + score += 2; + + if (capabilities & PALADIN_BLESSING_CAPABILITY_IMPROVED_WISDOM) + score += 1; return score; } @@ -83,23 +107,23 @@ namespace if (!player) return std::numeric_limits::min() / 4; + uint8 const capabilities = GetPaladinBlessingCapabilities(player); + if (category == BASE_SANCTUARY) { - if (!player->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY)) + if (!(capabilities & PALADIN_BLESSING_CAPABILITY_SANCTUARY)) return std::numeric_limits::min() / 4; return 2; } if (category == BASE_MIGHT && - (player->HasAura(SPELL_IMPROVED_MIGHT_R1) || - player->HasAura(SPELL_IMPROVED_MIGHT_R2))) + (capabilities & PALADIN_BLESSING_CAPABILITY_IMPROVED_MIGHT)) { return 1; } if (category == BASE_WISDOM && - (player->HasAura(SPELL_IMPROVED_WISDOM_R1) || - player->HasAura(SPELL_IMPROVED_WISDOM_R2))) + (capabilities & PALADIN_BLESSING_CAPABILITY_IMPROVED_WISDOM)) { return 1; } @@ -107,6 +131,50 @@ namespace return 0; } + void SelectActivePaladinPool( + std::vector& botPaladins) + { + std::sort(botPaladins.begin(), botPaladins.end(), + [](Player* left, Player* right) + { + return left->GetGUID() < right->GetGUID(); + }); + + std::vector selectedPaladins; + selectedPaladins.reserve(botPaladins.size()); + std::vector selected(botPaladins.size(), false); + + auto const selectFirstWithCapability = [&](uint8 capability) + { + for (size_t index = 0; index < botPaladins.size(); ++index) + { + if (selected[index]) + continue; + + if (!(GetPaladinBlessingCapabilities(botPaladins[index]) & capability)) + continue; + + selected[index] = true; + selectedPaladins.push_back(botPaladins[index]); + return; + } + }; + + selectFirstWithCapability(PALADIN_BLESSING_CAPABILITY_SANCTUARY); + selectFirstWithCapability(PALADIN_BLESSING_CAPABILITY_IMPROVED_MIGHT); + selectFirstWithCapability(PALADIN_BLESSING_CAPABILITY_IMPROVED_WISDOM); + + for (size_t index = 0; index < botPaladins.size(); ++index) + { + if (selected[index]) + continue; + + selectedPaladins.push_back(botPaladins[index]); + } + + botPaladins = std::move(selectedPaladins); + } + struct DesiredBlessingSet { std::array ordered = {}; @@ -593,29 +661,22 @@ namespace if (botPaladins.empty()) return false; + SelectActivePaladinPool(botPaladins); + + uint8 activePaladinCount = + std::min(static_cast(botPaladins.size()), MAX_BLESSING_SLOTS); + bool anySanctuaryAvailable = false; - for (Player* paladin : botPaladins) + for (uint8 paladinIndex = 0; paladinIndex < activePaladinCount; ++paladinIndex) { - if (paladin && paladin->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY)) + if (GetPaladinBlessingCapabilities(botPaladins[paladinIndex]) & + PALADIN_BLESSING_CAPABILITY_SANCTUARY) { anySanctuaryAvailable = true; break; } } - std::sort(botPaladins.begin(), botPaladins.end(), - [](Player* a, Player* b) - { - int sa = TalentScore(a); - int sb = TalentScore(b); - if (sa != sb) - return sa > sb; - return a->GetGUID() < b->GetGUID(); - }); - - uint8 activePaladinCount = - std::min(static_cast(botPaladins.size()), MAX_BLESSING_SLOTS); - int mySlot = -1; for (size_t i = 0; i < botPaladins.size(); ++i) { @@ -695,7 +756,9 @@ namespace classBuckets, botPaladins, allPaladins, classWideOwners, exclusiveOwnersByBucket, classWideBases, exclusiveBasesByBucket)) - return false; + { + continue; + } for (size_t index = 0; index < classWideBases.size(); ++index) { From af0c5e7c4b98548ed1bab7d78c7a171189867372 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Sat, 6 Jun 2026 23:58:05 -0700 Subject: [PATCH 61/63] Fix/TellMaster crash (#2434) ## Pull Request Description If you add +debug to a randombot in the world without the randomBotSayWithoutMaster it will crash. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Bot/PlayerbotAI.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 8bd1f2c2f35..eeadbbc5550 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -3027,6 +3027,8 @@ bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel secu { if (sPlayerbotAIConfig.randomBotSayWithoutMaster) return TellMasterNoFacing(text, securityLevel); + + return false; } if (!TellMasterNoFacing(text, securityLevel)) return false; From dda9ff0d40348855895c420746277d24b9a30cdb Mon Sep 17 00:00:00 2001 From: NoxMax <50133316+NoxMax@users.noreply.github.com> Date: Sun, 7 Jun 2026 00:58:25 -0600 Subject: [PATCH 62/63] Fix: Dismount cleanup (#2413) ## Pull Request Description Cleans some stale flight flags, and gets rid of the parachute given in forced flight dismount. Refactors some dismount code, and get rid of the unused mountData parameter in CalculateMasterMountSpeed as well `MOVEMENTFLAG_WATERWALKING` was removed because it might've been added to protect MOVEMENTFLAG_CAN_FLY, but if the bot remounts over water, MOVEMENTFLAG_CAN_FLY would be reapplied anyway. However if it has a function that I missed, we can re-add it. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. Feature is currently at minimum cost. ## How to Test the Changes 1. As GM hover high above Dalaran, but not high enough that you are outside the resting zone. 2. .summon some lvl +70 bots until you get some that are on a flying mount. 3. Watch as they lose their mount after 10 seconds, and be given a parachute as they fall. 4. When they reach the ground, they should be stuck with a parachute forever, and they shouldn't be stuck hovering just above the ground forever. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [ ] No - - [x] Yes (**explain below**) Checks if bot has `HasFeatherFallAura` to remove any stale flight flags. ## AI Assistance Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers Comment paragraph might be verbose, but worth it for whomever is looking at the code years from now trying to figure out why bots need special treatment with dismounting anyway. --- src/Ai/Base/Actions/CheckMountStateAction.cpp | 37 ++++++++++++++----- src/Ai/Base/Actions/CheckMountStateAction.h | 3 +- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/Ai/Base/Actions/CheckMountStateAction.cpp b/src/Ai/Base/Actions/CheckMountStateAction.cpp index 0e3abb72431..5da25079004 100644 --- a/src/Ai/Base/Actions/CheckMountStateAction.cpp +++ b/src/Ai/Base/Actions/CheckMountStateAction.cpp @@ -17,6 +17,7 @@ #include "SpellAuraEffects.h" static constexpr uint32 SPELL_COLD_WEATHER_FLYING = 54197; +static constexpr float PARACHUTE_LAND_THRESHOLD = 15.0f; // Define the static map / init bool for caching bot preferred mount data globally std::unordered_map CheckMountStateAction::mountCache; @@ -61,6 +62,21 @@ MountData CollectMountData(const Player* bot) bool CheckMountStateAction::Execute(Event /*event*/) { + // Forced flight dismount: + // Bots get stale flight movement flags after a forced dismount (e.g: Dalaran) because the post landing dismount cleanup + // needs MSG_MOVE_FALL_LAND (a client opcode) and client movement packets. The stale flags cause the bot to be stuck with + // the parachute, or even keep the bot hovering indefinitely and block MMAP routing. + // Note: Without MSG_MOVE_FALL_LAND, HandleFall doesn't trigger, meaning bots don't get fall damage in forced dismounts anyway, + // so the parachute usage here is more of an immersion feature. + if (bot->HasFeatherFallAura()) + { + float floorZ = bot->GetMapHeight(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); + if (floorZ != INVALID_HEIGHT && floorZ != VMAP_INVALID_HEIGHT_VALUE && + bot->GetPositionZ() - floorZ <= PARACHUTE_LAND_THRESHOLD) + bot->RemoveAurasByType(SPELL_AURA_FEATHER_FALL); + } + ClearStaleFlightFlags(); + // Determine if there are no attackers bool noAttackers = !AI_VALUE2(bool, "combat", "self target") || !AI_VALUE(uint8, "attacker count"); bool enemy = AI_VALUE(Unit*, "enemy player target"); @@ -204,7 +220,7 @@ bool CheckMountStateAction::Mount() // Get bot mount data MountData mountData = CollectMountData(bot); int32 masterMountType = GetMountType(master); - int32 masterSpeed = CalculateMasterMountSpeed(master, mountData); + int32 masterSpeed = CalculateMasterMountSpeed(master); // Try shapeshift if (TryForms(master, masterMountType, masterSpeed)) @@ -234,14 +250,17 @@ void CheckMountStateAction::Dismount() WorldPacket emptyPacket; bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); - bool const wantsFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura(); - bool const isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); - bool const isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING); - bool const hasGravityDisabled = bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); - if (!wantsFly && !isWaterWalking && (isFlying || hasGravityDisabled)) + ClearStaleFlightFlags(); +} + +void CheckMountStateAction::ClearStaleFlightFlags() +{ + if (bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura()) + return; + + if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_DISABLE_GRAVITY)) { - bot->RemoveUnitMovementFlag( - MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY); + bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_DISABLE_GRAVITY | MOVEMENTFLAG_CAN_FLY); if (!bot->IsRooted()) bot->SendMovementFlagUpdate(); } @@ -490,7 +509,7 @@ static bool BotCanUseFlyingMount(Player const* bot) return true; } -int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const MountData& mountData) const +int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master) const { // Check riding skill and level requirements int32 ridingSkill = bot->GetPureSkillValue(SKILL_RIDING); diff --git a/src/Ai/Base/Actions/CheckMountStateAction.h b/src/Ai/Base/Actions/CheckMountStateAction.h index d1faa379802..0bf83cc5e21 100644 --- a/src/Ai/Base/Actions/CheckMountStateAction.h +++ b/src/Ai/Base/Actions/CheckMountStateAction.h @@ -53,9 +53,10 @@ class CheckMountStateAction : public UseItemAction float CalculateDismountDistance() const; float CalculateMountDistance() const; void Dismount(); + void ClearStaleFlightFlags(); bool ShouldFollowMasterMountState(Player* master, bool noAttackers, bool shouldMount) const; bool ShouldDismountForMaster(Player* master) const; - int32 CalculateMasterMountSpeed(Player* master, const MountData& mountData) const; + int32 CalculateMasterMountSpeed(Player* master) const; bool CheckForSwiftMount() const; std::map>> GetAllMountSpells() const; bool TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const; From a76f2ca268adc0c4732bb0b774372cc9d5fce7cd Mon Sep 17 00:00:00 2001 From: Crow Date: Sun, 7 Jun 2026 10:14:53 -0500 Subject: [PATCH 63/63] Refactor HasSpell/HasAura and convert spellIds to constants (#2435) ## Pull Request Description This is a PR to follow-up on a discussion with @kadeshar during the recent mage armor PR. Main changes: - Add new PlayerbotAI::HasSpell() overload that accepts a string for the spell name as an argument, in order to replace repeated bot->HasSpell(spellId) checks for places where multiple ranks of a spell are checked, and to replace a few calls to AI_VALUE(uint32, "spell id", ...) where it existed only to check for the spell being known. - Removed PlayerbotAI::HasAura() overload that takes a spell Id as an argument. It is rarely used, and the few instances of its usage are easily replaced with AC's Unit::HasAura(), which is the predominant usage for spell Id aura checks already anyway. - When modifying DruidPullStrategy to use the new HasSpell() method, I went ahead and also simplified the file overall to pare down duplication (for example, the first check in CanCastSpell is for the spell Id so we don't need to separately check it before calling CanCastSpell()). I then did the same for other pull strategies so they can be consistent. - Changed many magic numbers for spell Ids to be defined as compile-time constants. I didn't do this throughout the code, but I did in the files where I was making changes anyway due to the HasSpell() and/or HasAura() changes. ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. - Minimum logic is described above. - Processing cost shouldn't change much. See Impact Assessment below. ## How to Test the Changes ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [ ] No, not at all - - [x] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Passing a string for the spell to check for it being known or the aura being present is more taxing than looking up a spell Id, although the usefulness of the string-based overload is for situations with spells that have multiple ranks. - Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) - I think these changes should simplify maintenance complexity. ## AI Assistance Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) - I had AI make many of the changes and then review since they were pretty mundane (for example, I'd tell it to just take all the magic numbers from a file and define them in an anonymous namespace, then replace the magic numbers with the spell constants). There isn't anything complicated in this PR; it was mostly busywork. ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --- src/Ai/Base/Actions/TameAction.cpp | 7 +- .../Actions/TradeStatusExtendedAction.cpp | 5 +- src/Ai/Base/Trigger/BossAuraTriggers.cpp | 17 +-- .../Dk/Strategy/DeathKnightPullStrategy.cpp | 22 +--- .../Druid/Action/DruidShapeshiftActions.cpp | 5 +- src/Ai/Class/Druid/DruidTriggers.cpp | 13 +- src/Ai/Class/Druid/DruidTriggers.h | 4 +- .../Druid/Strategy/DruidPullStrategy.cpp | 31 ++--- src/Ai/Class/Hunter/HunterActions.cpp | 23 +--- src/Ai/Class/Mage/MageActions.cpp | 4 +- src/Ai/Class/Mage/MageTriggers.cpp | 31 ++--- .../Mage/Strategy/GenericMageStrategy.cpp | 26 +++- .../Actions/PaladinGreaterBlessingAction.cpp | 3 +- .../Paladin/Strategy/PaladinPullStrategy.cpp | 17 +-- src/Ai/Class/Rogue/Action/RogueActions.cpp | 16 ++- src/Ai/Class/Rogue/RogueTriggers.cpp | 16 ++- .../Shaman/Strategy/TotemsShamanStrategy.cpp | 27 ++-- src/Ai/Class/Warlock/WarlockActions.cpp | 6 +- src/Ai/Class/Warlock/WarlockTriggers.cpp | 6 +- .../Warrior/Strategy/WarriorPullStrategy.cpp | 11 +- src/Ai/Class/Warrior/WarriorActions.cpp | 42 +++--- src/Ai/Class/Warrior/WarriorTriggers.cpp | 24 ++-- .../Naxx/Action/NaxxActions_Razuvious.cpp | 26 ++-- .../Naxx/Action/NaxxActions_Sapphiron.cpp | 2 +- .../Raid/Naxx/Action/NaxxActions_Thaddius.cpp | 6 +- src/Ai/Raid/Naxx/NaxxBossHelper.h | 2 +- src/Ai/Raid/Naxx/NaxxMultipliers.cpp | 2 +- src/Ai/Raid/Naxx/NaxxSpellIds.h | 6 +- src/Ai/Raid/Uld/UldTriggers.cpp | 4 +- src/Bot/Factory/AiFactory.cpp | 17 ++- src/Bot/Factory/PlayerbotFactory.cpp | 122 ++++++++++++------ src/Bot/PlayerbotAI.cpp | 18 +-- src/Bot/PlayerbotAI.h | 2 +- src/Mgr/Item/StatsWeightCalculator.cpp | 55 +++++--- 34 files changed, 329 insertions(+), 289 deletions(-) diff --git a/src/Ai/Base/Actions/TameAction.cpp b/src/Ai/Base/Actions/TameAction.cpp index dfeb61a47f5..57ae24e8cb9 100644 --- a/src/Ai/Base/Actions/TameAction.cpp +++ b/src/Ai/Base/Actions/TameAction.cpp @@ -422,10 +422,9 @@ bool TameAction::RenamePet(const std::string& newName) // Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true); - if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(883)) - { - bot->CastSpell(bot, 883, true); - } + constexpr uint32 SPELL_CALL_PET = 883; + if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(SPELL_CALL_PET)) + bot->CastSpell(bot, SPELL_CALL_PET, true); return true; } diff --git a/src/Ai/Base/Actions/TradeStatusExtendedAction.cpp b/src/Ai/Base/Actions/TradeStatusExtendedAction.cpp index ada779e36d4..f081c336574 100644 --- a/src/Ai/Base/Actions/TradeStatusExtendedAction.cpp +++ b/src/Ai/Base/Actions/TradeStatusExtendedAction.cpp @@ -67,9 +67,10 @@ bool TradeStatusExtendedAction::Execute(Event event) return false; } - if (bot->getClass() == CLASS_ROGUE && bot->HasSpell(1804) && lockbox->IsLocked()) // Pick Lock spell + constexpr uint32 SPELL_PICK_LOCK = 1804; + if (bot->getClass() == CLASS_ROGUE && bot->HasSpell(SPELL_PICK_LOCK) && lockbox->IsLocked()) { - // botAI->CastSpell(1804, bot, lockbox); // Attempt to cast Pick Lock on the lockbox + // botAI->CastSpell(SPELL_PICK_LOCK, bot, lockbox); // Attempt to cast Pick Lock on the lockbox botAI->DoSpecificAction("unlock traded item"); botAI->SetNextCheckDelay(4000); // Delay before accepting trade } diff --git a/src/Ai/Base/Trigger/BossAuraTriggers.cpp b/src/Ai/Base/Trigger/BossAuraTriggers.cpp index 8d2520cf625..4c8dd5b3338 100644 --- a/src/Ai/Base/Trigger/BossAuraTriggers.cpp +++ b/src/Ai/Base/Trigger/BossAuraTriggers.cpp @@ -23,9 +23,7 @@ bool BossFireResistanceTrigger::IsActive() return false; // Check if bot have fire resistance aura - if (bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_5) || bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_4) || - bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_3) || bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_2) || - bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_1)) + if (botAI->HasAura("fire resistance aura", bot)) return false; // Check if bot dont have already have fire resistance strategy @@ -76,9 +74,7 @@ bool BossFrostResistanceTrigger::IsActive() return false; // Check if bot have frost resistance aura - if (bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_5) || bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_4) || - bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_3) || bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_2) || - bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_1)) + if (botAI->HasAura("frost resistance aura", bot)) return false; // Check if bot dont have already have frost resistance strategy @@ -133,8 +129,7 @@ bool BossNatureResistanceTrigger::IsActive() return false; // Check if bot have nature resistance aura - if (bot->HasAura(SPELL_ASPECT_OF_THE_WILD_RANK_4) || bot->HasAura(SPELL_ASPECT_OF_THE_WILD_RANK_3) || - bot->HasAura(SPELL_ASPECT_OF_THE_WILD_RANK_2) || bot->HasAura(SPELL_ASPECT_OF_THE_WILD_RANK_1)) + if (botAI->HasAura("aspect of the wild", bot)) return false; // Check if bot dont have already setted nature resistance aura @@ -184,11 +179,7 @@ bool BossShadowResistanceTrigger::IsActive() return false; // Check if bot have shadow resistance aura - if (bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_5) || - bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_4) || - bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_3) || - bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_2) || - bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_1)) + if (botAI->HasAura("shadow resistance aura", bot)) return false; // Check if bot dont have already have shadow resistance strategy diff --git a/src/Ai/Class/Dk/Strategy/DeathKnightPullStrategy.cpp b/src/Ai/Class/Dk/Strategy/DeathKnightPullStrategy.cpp index be643b50ce0..db58d3135cd 100644 --- a/src/Ai/Class/Dk/Strategy/DeathKnightPullStrategy.cpp +++ b/src/Ai/Class/Dk/Strategy/DeathKnightPullStrategy.cpp @@ -5,38 +5,26 @@ #include "DeathKnightPullStrategy.h" -#include "AiObjectContext.h" #include "Player.h" #include "PlayerbotAI.h" #include "Playerbots.h" std::string DeathKnightPullStrategy::GetPullActionName() const { - Player* bot = botAI->GetBot(); Unit* target = GetTarget(); - if (!bot || !target || + if (!target || (!botAI->HasStrategy("blood", BOT_STATE_COMBAT) && !botAI->HasStrategy("blood", BOT_STATE_NON_COMBAT))) { return PullStrategy::GetPullActionName(); } - uint32 const deathGripSpellId = botAI->GetAiObjectContext()->GetValue("spell id", "death grip")->Get(); - if (deathGripSpellId && bot->HasSpell(deathGripSpellId) && - botAI->CanCastSpell(deathGripSpellId, target)) - { + if (botAI->CanCastSpell("death grip", target)) return "death grip"; - } - uint32 const icyTouchSpellId = botAI->GetAiObjectContext()->GetValue("spell id", "icy touch")->Get(); - if (!icyTouchSpellId || !bot->HasSpell(icyTouchSpellId) || - !botAI->CanCastSpell(icyTouchSpellId, target)) + if (!botAI->CanCastSpell("icy touch", target) && + botAI->CanCastSpell("dark command", target)) { - uint32 const darkCommandSpellId = botAI->GetAiObjectContext()->GetValue("spell id", "dark command")->Get(); - if (darkCommandSpellId && bot->HasSpell(darkCommandSpellId) && - botAI->CanCastSpell(darkCommandSpellId, target)) - { - return "dark command"; - } + return "dark command"; } return PullStrategy::GetPullActionName(); diff --git a/src/Ai/Class/Druid/Action/DruidShapeshiftActions.cpp b/src/Ai/Class/Druid/Action/DruidShapeshiftActions.cpp index f30d4ec032e..97cdcf9d818 100644 --- a/src/Ai/Class/Druid/Action/DruidShapeshiftActions.cpp +++ b/src/Ai/Class/Druid/Action/DruidShapeshiftActions.cpp @@ -50,9 +50,10 @@ bool CastCancelDruidAction::Execute(Event /*event*/) return true; } -bool CastCancelDruidAction::isUseful() { return botAI->HasAura(auraId, bot); } +bool CastCancelDruidAction::isUseful() { return bot->HasAura(auraId); } bool CastTreeFormAction::isUseful() { - return GetTarget() && CastSpellAction::isUseful() && !botAI->HasAura(33891, bot); + constexpr uint32 SPELL_TREE_OF_LIFE = 33891; + return GetTarget() && CastSpellAction::isUseful() && !bot->HasAura(SPELL_TREE_OF_LIFE); } diff --git a/src/Ai/Class/Druid/DruidTriggers.cpp b/src/Ai/Class/Druid/DruidTriggers.cpp index 03b5accc953..22b850329c5 100644 --- a/src/Ai/Class/Druid/DruidTriggers.cpp +++ b/src/Ai/Class/Druid/DruidTriggers.cpp @@ -28,7 +28,11 @@ bool ThornsTrigger::IsActive() { return BuffTrigger::IsActive() && !botAI->HasAu bool BearFormTrigger::IsActive() { return !botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr); } -bool TreeFormTrigger::IsActive() { return !botAI->HasAura(33891, bot); } +bool TreeFormTrigger::IsActive() +{ + constexpr uint32 SPELL_TREE_OF_LIFE = 33891; + return !bot->HasAura(SPELL_TREE_OF_LIFE); +} bool CatFormTrigger::IsActive() { return !botAI->HasAura("cat form", bot); } @@ -43,8 +47,11 @@ bool ProwlTrigger::IsActive() if (botAI->HasAura("prowl", bot) || bot->IsInCombat()) return false; - uint32 prowlId = botAI->GetAiObjectContext()->GetValue("spell id", "prowl")->Get(); - if (!prowlId || !bot->HasSpell(prowlId) || bot->HasSpellCooldown(prowlId)) + if (!botAI->HasSpell("prowl")) + return false; + + uint32 const prowlId = AI_VALUE2(uint32, "spell id", "prowl"); + if (bot->HasSpellCooldown(prowlId)) return false; float distance = 30.f; diff --git a/src/Ai/Class/Druid/DruidTriggers.h b/src/Ai/Class/Druid/DruidTriggers.h index 99002468515..fa0f2113211 100644 --- a/src/Ai/Class/Druid/DruidTriggers.h +++ b/src/Ai/Class/Druid/DruidTriggers.h @@ -393,14 +393,14 @@ class FerociousBiteTimeTrigger : public Trigger class FerociousBiteExecuteTrigger : public Trigger { public: - FerociousBiteExecuteTrigger(PlayerbotAI* ai) : Trigger(ai, "ferocious bite execute") {} + FerociousBiteExecuteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "ferocious bite execute") {} bool IsActive() override { Unit* target = AI_VALUE(Unit*, "current target"); if (!target || !target->IsAlive()) return false; - if (!AI_VALUE2(uint32, "spell id", "ferocious bite")) + if (!botAI->HasSpell("ferocious bite")) return false; if (AI_VALUE2(uint8, "combo", "current target") < 1) diff --git a/src/Ai/Class/Druid/Strategy/DruidPullStrategy.cpp b/src/Ai/Class/Druid/Strategy/DruidPullStrategy.cpp index dc72b9e823d..7c372705703 100644 --- a/src/Ai/Class/Druid/Strategy/DruidPullStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/DruidPullStrategy.cpp @@ -5,34 +5,23 @@ #include "DruidPullStrategy.h" -#include "AiObjectContext.h" -#include "Player.h" #include "PlayerbotAI.h" #include "Playerbots.h" std::string DruidPullStrategy::GetPullActionName() const { - Player* bot = botAI->GetBot(); - std::string actionName = PullStrategy::GetPullActionName(); - if (!bot) - return actionName; - - uint32 const faerieFireFeralId = botAI->GetAiObjectContext()->GetValue("spell id", "faerie fire (feral)")->Get(); - if (faerieFireFeralId && bot->HasSpell(faerieFireFeralId) && - (botAI->HasStrategy("bear", BOT_STATE_COMBAT) || botAI->HasStrategy("cat", BOT_STATE_COMBAT))) - { - actionName = "faerie fire (feral)"; - } + std::string const pullActionName = PullStrategy::GetPullActionName(); + std::string const actionName = + botAI->HasSpell("faerie fire (feral)") && + (botAI->HasStrategy("bear", BOT_STATE_COMBAT) || botAI->HasStrategy("cat", BOT_STATE_COMBAT)) + ? "faerie fire (feral)" : pullActionName; Unit* target = GetTarget(); - uint32 const faerieFireSpellId = botAI->GetAiObjectContext()->GetValue("spell id", actionName)->Get(); - if (target && (!faerieFireSpellId || !bot->HasSpell(faerieFireSpellId) || - !botAI->CanCastSpell(faerieFireSpellId, target))) - { - uint32 const growlSpellId = botAI->GetAiObjectContext()->GetValue("spell id", "growl")->Get(); - if (growlSpellId && bot->HasSpell(growlSpellId) && botAI->CanCastSpell(growlSpellId, target)) - return "growl"; - } + if (!target) + return actionName; + + if (!botAI->CanCastSpell(actionName, target) && botAI->CanCastSpell("growl", target)) + return "growl"; return actionName; } diff --git a/src/Ai/Class/Hunter/HunterActions.cpp b/src/Ai/Class/Hunter/HunterActions.cpp index a49f9a10e63..c3116d1f8f8 100644 --- a/src/Ai/Class/Hunter/HunterActions.cpp +++ b/src/Ai/Class/Hunter/HunterActions.cpp @@ -19,23 +19,13 @@ bool CastViperStingAction::isUseful() bool CastAspectOfTheHawkAction::isUseful() { Unit* target = GetTarget(); - if (!target) - return false; - - if (bot->HasSpell(61846) || bot->HasSpell(61847)) // Aspect of the Dragonhawk spell IDs - return false; - - return true; + return target && !botAI->HasSpell("aspect of the dragonhawk"); } bool CastArcaneShotAction::isUseful() { Unit* target = GetTarget(); - if (!target) - return false; - - if (bot->HasSpell(53301) || bot->HasSpell(60051) || - bot->HasSpell(60052) || bot->HasSpell(60053)) // Explosive Shot spell IDs + if (!target || !botAI->HasSpell("explosive shot")) return false; // Armor Penetration rating check - will not cast Arcane Shot above 435 ArP @@ -50,14 +40,7 @@ bool CastArcaneShotAction::isUseful() bool CastImmolationTrapAction::isUseful() { Unit* target = GetTarget(); - if (!target) - return false; - - if (bot->HasSpell(13813) || bot->HasSpell(14316) || bot->HasSpell(14317) || bot->HasSpell(27025) || - bot->HasSpell(49066) || bot->HasSpell(49067)) // Explosive Trap spell IDs - return false; - - return true; + return target && !botAI->HasSpell("explosive trap"); } Value* CastFreezingTrap::GetTargetValue() diff --git a/src/Ai/Class/Mage/MageActions.cpp b/src/Ai/Class/Mage/MageActions.cpp index 9c190d54c0e..13b40fa551f 100644 --- a/src/Ai/Class/Mage/MageActions.cpp +++ b/src/Ai/Class/Mage/MageActions.cpp @@ -13,7 +13,7 @@ std::vector CastMoltenArmorAction::getAlternatives() { - if (!AI_VALUE2(uint32, "spell id", "molten armor")) + if (!botAI->HasSpell("molten armor")) return NextAction::merge({ NextAction("mage armor") }, CastBuffSpellAction::getAlternatives()); return CastBuffSpellAction::getAlternatives(); @@ -21,7 +21,7 @@ std::vector CastMoltenArmorAction::getAlternatives() std::vector CastMageArmorAction::getAlternatives() { - if (!AI_VALUE2(uint32, "spell id", "mage armor")) + if (!botAI->HasSpell("mage armor")) return NextAction::merge({ NextAction("ice armor") }, CastBuffSpellAction::getAlternatives()); return CastBuffSpellAction::getAlternatives(); diff --git a/src/Ai/Class/Mage/MageTriggers.cpp b/src/Ai/Class/Mage/MageTriggers.cpp index 34babc81e9a..0be6ff13b85 100644 --- a/src/Ai/Class/Mage/MageTriggers.cpp +++ b/src/Ai/Class/Mage/MageTriggers.cpp @@ -39,26 +39,16 @@ bool ArcaneIntellectTrigger::IsActive() bool MageArmorTrigger::IsActive() { Unit* target = GetTarget(); - if (botAI->HasAura("mage armor", target)) - return false; - - if (AI_VALUE2(uint32, "spell id", "mage armor")) - return true; - - return !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) && + return botAI->HasSpell("mage armor") && !botAI->HasAura("mage armor", target) && + !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) && !botAI->HasAura("molten armor", target); } bool MoltenArmorTrigger::IsActive() { Unit* target = GetTarget(); - if (botAI->HasAura("molten armor", target)) - return false; - - if (AI_VALUE2(uint32, "spell id", "molten armor")) - return true; - - return !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) && + return botAI->HasSpell("molten armor") && !botAI->HasAura("molten armor", target) && + !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) && !botAI->HasAura("mage armor", target); } @@ -82,7 +72,8 @@ bool FrostbiteOnTargetTrigger::IsActive() bool NoFocusMagicTrigger::IsActive() { - if (!bot->HasSpell(54646)) // Focus Magic + constexpr uint32 SPELL_FOCUS_MAGIC = 54646; + if (!bot->HasSpell(SPELL_FOCUS_MAGIC)) return false; Group* group = bot->GetGroup(); @@ -95,7 +86,7 @@ bool NoFocusMagicTrigger::IsActive() if (!member || member == bot || !member->IsAlive()) continue; - if (member->HasAura(54646, bot->GetGUID())) + if (member->HasAura(SPELL_FOCUS_MAGIC, bot->GetGUID())) return false; } return true; @@ -103,11 +94,9 @@ bool NoFocusMagicTrigger::IsActive() bool DeepFreezeCooldownTrigger::IsActive() { - // If the bot does NOT have Deep Freeze, treat as "on cooldown" - if (!bot->HasSpell(44572)) // Deep Freeze - return true; - - return SpellCooldownTrigger::IsActive(); + constexpr uint32 SPELL_DEEP_FREEZE = 44572; + return !bot->HasSpell(SPELL_DEEP_FREEZE) || + SpellCooldownTrigger::IsActive(); } const std::unordered_set FlamestrikeNearbyTrigger::FLAMESTRIKE_SPELL_IDS = { diff --git a/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp b/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp index 69389a8c443..36b594ce98a 100644 --- a/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp @@ -8,6 +8,18 @@ #include "Playerbots.h" #include "RangedCombatStrategy.h" +namespace +{ +constexpr uint32 SPELL_CONJURE_MANA_SAPPHIRE = 42985; +constexpr uint32 SPELL_CONJURE_MANA_EMERALD = 27101; +constexpr uint32 SPELL_CONJURE_MANA_RUBY = 10054; +constexpr uint32 SPELL_CONJURE_MANA_CITRINE = 10053; +constexpr uint32 SPELL_CONJURE_MANA_JADE = 3552; +constexpr uint32 SPELL_CONJURE_MANA_AGATE = 759; +constexpr uint32 SPELL_FROSTFIRE_BOLT = 44614; +constexpr uint32 SPELL_ICE_SHARDS = 15047; +} + class GenericMageStrategyActionNodeFactory : public NamedObjectFactory { public: @@ -102,17 +114,17 @@ void GenericMageStrategy::InitTriggers(std::vector& triggers) // Mana Threshold Triggers Player* bot = botAI->GetBot(); - if (bot->HasSpell(42985)) // Mana Sapphire + if (bot->HasSpell(SPELL_CONJURE_MANA_SAPPHIRE)) triggers.push_back(new TriggerNode("high mana", { NextAction("use mana sapphire", 90.0f) })); - else if (bot->HasSpell(27101)) // Mana Emerald + else if (bot->HasSpell(SPELL_CONJURE_MANA_EMERALD)) triggers.push_back(new TriggerNode("high mana", { NextAction("use mana emerald", 90.0f) })); - else if (bot->HasSpell(10054)) // Mana Ruby + else if (bot->HasSpell(SPELL_CONJURE_MANA_RUBY)) triggers.push_back(new TriggerNode("high mana", { NextAction("use mana ruby", 90.0f) })); - else if (bot->HasSpell(10053)) // Mana Citrine + else if (bot->HasSpell(SPELL_CONJURE_MANA_CITRINE)) triggers.push_back(new TriggerNode("high mana", { NextAction("use mana citrine", 90.0f) })); - else if (bot->HasSpell(3552)) // Mana Jade + else if (bot->HasSpell(SPELL_CONJURE_MANA_JADE)) triggers.push_back(new TriggerNode("high mana", { NextAction("use mana jade", 90.0f) })); - else if (bot->HasSpell(759)) // Mana Agate + else if (bot->HasSpell(SPELL_CONJURE_MANA_AGATE)) triggers.push_back(new TriggerNode("high mana", { NextAction("use mana agate", 90.0f) })); triggers.push_back(new TriggerNode("medium mana", { NextAction("mana potion", 90.0f) })); @@ -142,7 +154,7 @@ void MageBoostStrategy::InitTriggers(std::vector& triggers) } else if (tab == MAGE_TAB_FIRE) { - if (bot->HasSpell(44614) /*Frostfire Bolt*/ && bot->HasAura(15047) /*Ice Shards*/) + if (bot->HasSpell(SPELL_FROSTFIRE_BOLT) && bot->HasAura(SPELL_ICE_SHARDS)) { // Frostfire triggers.push_back(new TriggerNode("combustion", { NextAction("combustion", 18.0f) })); triggers.push_back(new TriggerNode("icy veins", { NextAction("icy veins", 17.5f) })); diff --git a/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp b/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp index 6a174d30097..85f7f894f8d 100644 --- a/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp +++ b/src/Ai/Class/Paladin/Actions/PaladinGreaterBlessingAction.cpp @@ -1145,8 +1145,7 @@ bool CastGreaterBlessingAssignmentAction::Execute(Event /*event*/) if (!FindPendingAssignment(assignment, spellName)) return false; - uint32 finalId = AI_VALUE2(uint32, "spell id", spellName); - if (!finalId) + if (!botAI->HasSpell(spellName)) return false; return botAI->CastSpell(spellName, assignment.player); diff --git a/src/Ai/Class/Paladin/Strategy/PaladinPullStrategy.cpp b/src/Ai/Class/Paladin/Strategy/PaladinPullStrategy.cpp index ba0381b5ab3..988d9a987ae 100644 --- a/src/Ai/Class/Paladin/Strategy/PaladinPullStrategy.cpp +++ b/src/Ai/Class/Paladin/Strategy/PaladinPullStrategy.cpp @@ -5,34 +5,23 @@ #include "PaladinPullStrategy.h" -#include "AiObjectContext.h" -#include "Player.h" #include "PlayerbotAI.h" #include "Playerbots.h" std::string PaladinPullStrategy::GetPullActionName() const { - Player* bot = botAI->GetBot(); Unit* target = GetTarget(); - if (!bot || !target || + if (!target || (!botAI->HasStrategy("tank", BOT_STATE_COMBAT) && !botAI->HasStrategy("tank", BOT_STATE_NON_COMBAT))) { return PullStrategy::GetPullActionName(); } - uint32 const avengersShieldSpellId = botAI->GetAiObjectContext()->GetValue("spell id", "avenger's shield")->Get(); - if (avengersShieldSpellId && bot->HasSpell(avengersShieldSpellId) && - botAI->CanCastSpell(avengersShieldSpellId, target)) - { + if (botAI->CanCastSpell("avenger's shield", target)) return "avenger's shield"; - } - uint32 const handOfReckoningSpellId = botAI->GetAiObjectContext()->GetValue("spell id", "hand of reckoning")->Get(); - if (handOfReckoningSpellId && bot->HasSpell(handOfReckoningSpellId) && - botAI->CanCastSpell(handOfReckoningSpellId, target)) - { + if (botAI->CanCastSpell("hand of reckoning", target)) return "hand of reckoning"; - } return PullStrategy::GetPullActionName(); } diff --git a/src/Ai/Class/Rogue/Action/RogueActions.cpp b/src/Ai/Class/Rogue/Action/RogueActions.cpp index 46beaf86c1a..aae0d3eb084 100644 --- a/src/Ai/Class/Rogue/Action/RogueActions.cpp +++ b/src/Ai/Class/Rogue/Action/RogueActions.cpp @@ -11,6 +11,14 @@ #include "PlayerbotAIConfig.h" #include "Playerbots.h" +namespace +{ +constexpr uint32 SPELL_WARSONG_FLAG = 23333; +constexpr uint32 SPELL_SILVERWING_FLAG = 23335; +constexpr uint32 SPELL_NETHERSTORM_FLAG = 34976; +constexpr uint32 SPELL_MASTER_POISONER_RANK_3 = 58410; +} + bool CastStealthAction::isUseful() { Unit* target = AI_VALUE(Unit*, "current target"); @@ -22,7 +30,8 @@ bool CastStealthAction::isUseful() bool CastStealthAction::isPossible() { // do not use with WSG flag or EYE flag - return !botAI->HasAura(23333, bot) && !botAI->HasAura(23335, bot) && !botAI->HasAura(34976, bot); + return !bot->HasAura(SPELL_WARSONG_FLAG) && !bot->HasAura(SPELL_SILVERWING_FLAG) && + !bot->HasAura(SPELL_NETHERSTORM_FLAG); } bool UnstealthAction::Execute(Event /*event*/) @@ -50,7 +59,8 @@ bool CheckStealthAction::Execute(Event /*event*/) bool CastVanishAction::isUseful() { // do not use with WSG flag or EYE flag - return !botAI->HasAura(23333, bot) && !botAI->HasAura(23335, bot) && !botAI->HasAura(34976, bot); + return !bot->HasAura(SPELL_WARSONG_FLAG) && !bot->HasAura(SPELL_SILVERWING_FLAG) && + !bot->HasAura(SPELL_NETHERSTORM_FLAG); } bool CastEnvenomAction::isUseful() @@ -61,7 +71,7 @@ bool CastEnvenomAction::isUseful() bool CastEnvenomAction::isPossible() { // alternate to eviscerate if talents unlearned - return botAI->HasAura(58410, bot) /* Master Poisoner Rank 3 */; + return bot->HasAura(SPELL_MASTER_POISONER_RANK_3); } bool CastTricksOfTheTradeOnMainTankAction::isUseful() diff --git a/src/Ai/Class/Rogue/RogueTriggers.cpp b/src/Ai/Class/Rogue/RogueTriggers.cpp index c33dbb7fe4a..d8fd166a1ce 100644 --- a/src/Ai/Class/Rogue/RogueTriggers.cpp +++ b/src/Ai/Class/Rogue/RogueTriggers.cpp @@ -9,6 +9,12 @@ #include "Playerbots.h" #include "ServerFacade.h" +namespace +{ +constexpr uint32 SPELL_STEALTH = 1784; +constexpr uint32 SPELL_SPRINT_RANK_1 = 2983; +} + // bool AdrenalineRushTrigger::isPossible() // { // return !botAI->HasAura("stealth", bot); @@ -29,7 +35,7 @@ bool UnstealthTrigger::IsActive() bool StealthTrigger::IsActive() { - if (botAI->HasAura("stealth", bot) || bot->IsInCombat() || bot->HasSpellCooldown(1784)) + if (bot->HasAura(SPELL_STEALTH) || bot->IsInCombat() || bot->HasSpellCooldown(SPELL_STEALTH)) return false; float distance = 30.f; @@ -63,13 +69,13 @@ bool StealthTrigger::IsActive() return target && ServerFacade::instance().GetDistance2d(bot, target) < distance; } -bool SapTrigger::IsPossible() { return bot->GetLevel() > 10 && bot->HasSpell(6770) && !bot->IsInCombat(); } +bool SapTrigger::IsPossible() { return bot->GetLevel() > 10 && botAI->HasSpell("sap") && !bot->IsInCombat(); } -bool SprintTrigger::IsPossible() { return bot->HasSpell(2983); } +bool SprintTrigger::IsPossible() { return bot->HasSpell(SPELL_SPRINT_RANK_1); } bool SprintTrigger::IsActive() { - if (bot->HasSpellCooldown(2983)) + if (bot->HasSpellCooldown(SPELL_SPRINT_RANK_1)) return false; float distance = botAI->GetMaster() ? 45.0f : 35.0f; @@ -105,7 +111,7 @@ bool SprintTrigger::IsActive() bool ExposeArmorTrigger::IsActive() { - Unit* target = AI_VALUE(Unit*, "current target"); // Get the bot's current target + Unit* target = AI_VALUE(Unit*, "current target"); return DebuffTrigger::IsActive() && !botAI->HasAura("sunder armor", target, false, false, -1, true) && AI_VALUE2(uint8, "combo", "current target") <= 3; } diff --git a/src/Ai/Class/Shaman/Strategy/TotemsShamanStrategy.cpp b/src/Ai/Class/Shaman/Strategy/TotemsShamanStrategy.cpp index c5ae222c16f..083a67eef89 100644 --- a/src/Ai/Class/Shaman/Strategy/TotemsShamanStrategy.cpp +++ b/src/Ai/Class/Shaman/Strategy/TotemsShamanStrategy.cpp @@ -6,6 +6,17 @@ #include "TotemsShamanStrategy.h" #include "Playerbots.h" +namespace +{ +constexpr uint32 SPELL_TOTEM_OF_WRATH = 30706; +constexpr uint32 SPELL_FLAMETONGUE_TOTEM = 8227; +constexpr uint32 SPELL_CLEANSING_TOTEM = 8170; +constexpr uint32 SPELL_MANA_SPRING_TOTEM = 5675; +constexpr uint32 SPELL_WRATH_OF_AIR_TOTEM = 3738; +constexpr uint32 SPELL_GROUNDING_TOTEM = 8177; +constexpr uint32 SPELL_WINDFURY_TOTEM = 8512; +} + // These combat strategies are used to set the corresponding totems on the bar, and cast the totem when it's missing. // There are special cases for Totem of Wrath, Windfury Totem, Wrath of Air totem, and Cleansing totem - these totems // aren't learned at level 30, and have fallbacks in order to prevent the trigger from continuously firing. @@ -74,9 +85,9 @@ void TotemOfWrathStrategy::InitTriggers(std::vector& triggers) GenericShamanStrategy::InitTriggers(triggers); // If the bot hasn't learned Totem of Wrath yet, set Flametongue Totem instead. Player* bot = botAI->GetBot(); - if (bot->HasSpell(30706)) + if (bot->HasSpell(SPELL_TOTEM_OF_WRATH)) triggers.push_back(new TriggerNode("set totem of wrath", { NextAction("set totem of wrath", 60.0f) })); - else if (bot->HasSpell(8227)) + else if (bot->HasSpell(SPELL_FLAMETONGUE_TOTEM)) triggers.push_back(new TriggerNode("set flametongue totem", { NextAction("set flametongue totem", 60.0f) })); triggers.push_back(new TriggerNode("no fire totem", { NextAction("totem of wrath", 55.0f) })); } @@ -112,9 +123,9 @@ void CleansingTotemStrategy::InitTriggers(std::vector& triggers) GenericShamanStrategy::InitTriggers(triggers); // If the bot hasn't learned Cleansing Totem yet, set Mana Spring Totem instead. Player* bot = botAI->GetBot(); - if (bot->HasSpell(8170)) + if (bot->HasSpell(SPELL_CLEANSING_TOTEM)) triggers.push_back(new TriggerNode("set cleansing totem", { NextAction("set cleansing totem", 60.0f) })); - else if (bot->HasSpell(5675)) + else if (bot->HasSpell(SPELL_MANA_SPRING_TOTEM)) triggers.push_back(new TriggerNode("set mana spring totem", { NextAction("set mana spring totem", 60.0f) })); triggers.push_back(new TriggerNode("no water totem", { NextAction("cleansing totem", 55.0f) })); } @@ -134,9 +145,9 @@ void WrathOfAirTotemStrategy::InitTriggers(std::vector& triggers) GenericShamanStrategy::InitTriggers(triggers); // If the bot hasn't learned Wrath of Air Totem yet, set Grounding Totem instead. Player* bot = botAI->GetBot(); - if (bot->HasSpell(3738)) + if (bot->HasSpell(SPELL_WRATH_OF_AIR_TOTEM)) triggers.push_back(new TriggerNode("set wrath of air totem", { NextAction("set wrath of air totem", 60.0f) })); - else if (bot->HasSpell(8177)) + else if (bot->HasSpell(SPELL_GROUNDING_TOTEM)) triggers.push_back(new TriggerNode("set grounding totem", { NextAction("set grounding totem", 60.0f) })); triggers.push_back( new TriggerNode("no air totem", { NextAction("wrath of air totem", 55.0f) })); } @@ -147,9 +158,9 @@ void WindfuryTotemStrategy::InitTriggers(std::vector& triggers) GenericShamanStrategy::InitTriggers(triggers); // If the bot hasn't learned Windfury Totem yet, set Grounding Totem instead. Player* bot = botAI->GetBot(); - if (bot->HasSpell(8512)) + if (bot->HasSpell(SPELL_WINDFURY_TOTEM)) triggers.push_back(new TriggerNode("set windfury totem", { NextAction("set windfury totem", 60.0f) })); - else if (bot->HasSpell(8177)) + else if (bot->HasSpell(SPELL_GROUNDING_TOTEM)) triggers.push_back(new TriggerNode("set grounding totem", { NextAction("set grounding totem", 60.0f) })); triggers.push_back(new TriggerNode("no air totem", { NextAction("windfury totem", 55.0f) })); } diff --git a/src/Ai/Class/Warlock/WarlockActions.cpp b/src/Ai/Class/Warlock/WarlockActions.cpp index 5e23f1af5ac..2deafd022b4 100644 --- a/src/Ai/Class/Warlock/WarlockActions.cpp +++ b/src/Ai/Class/Warlock/WarlockActions.cpp @@ -77,11 +77,7 @@ bool CastShadowflameAction::isUseful() bool CastRainOfFireAction::isUseful() { Unit* target = GetTarget(); - if (!target) - return false; - if (bot->HasSpell(27243) || bot->HasSpell(47835) || bot->HasSpell(47836)) // Seed of Corruption spell IDs - return false; - return true; + return target && !botAI->HasSpell("seed of corruption"); } // Checks if the enemies are close enough to use Hellfire diff --git a/src/Ai/Class/Warlock/WarlockTriggers.cpp b/src/Ai/Class/Warlock/WarlockTriggers.cpp index 14e7a52e585..52af8b03826 100644 --- a/src/Ai/Class/Warlock/WarlockTriggers.cpp +++ b/src/Ai/Class/Warlock/WarlockTriggers.cpp @@ -104,10 +104,8 @@ bool LifeTapTrigger::IsActive() // Checks if the Life Tap Glyph buff is active bool LifeTapGlyphBuffTrigger::IsActive() { - if (!botAI->HasAura(63320, bot)) - return false; - - return BuffTrigger::IsActive(); + constexpr uint32 SPELL_LIFE_TAP_GLYPH = 63320; + return bot->HasAura(SPELL_LIFE_TAP_GLYPH) && BuffTrigger::IsActive(); } // Checks if the target has a conflicting debuff that is equal to Curse of the Elements diff --git a/src/Ai/Class/Warrior/Strategy/WarriorPullStrategy.cpp b/src/Ai/Class/Warrior/Strategy/WarriorPullStrategy.cpp index cdae7e29cea..40609f5c849 100644 --- a/src/Ai/Class/Warrior/Strategy/WarriorPullStrategy.cpp +++ b/src/Ai/Class/Warrior/Strategy/WarriorPullStrategy.cpp @@ -5,23 +5,16 @@ #include "WarriorPullStrategy.h" -#include "AiObjectContext.h" -#include "Player.h" #include "PlayerbotAI.h" std::string WarriorPullStrategy::GetPullActionName() const { - Player* bot = botAI->GetBot(); Unit* target = GetTarget(); - if (!bot || !target) + if (!target) return PullStrategy::GetPullActionName(); - uint32 const heroicThrowSpellId = botAI->GetAiObjectContext()->GetValue("spell id", "heroic throw")->Get(); - if (heroicThrowSpellId && bot->HasSpell(heroicThrowSpellId) && - botAI->CanCastSpell(heroicThrowSpellId, target)) - { + if (botAI->CanCastSpell("heroic throw", target)) return "heroic throw"; - } return PullStrategy::GetPullActionName(); } diff --git a/src/Ai/Class/Warrior/WarriorActions.cpp b/src/Ai/Class/Warrior/WarriorActions.cpp index b30c1a38bbd..4636f784548 100644 --- a/src/Ai/Class/Warrior/WarriorActions.cpp +++ b/src/Ai/Class/Warrior/WarriorActions.cpp @@ -8,6 +8,15 @@ #include "AiFactory.h" #include "Playerbots.h" +namespace +{ +constexpr uint32 SPELL_RETALIATION = 20230; +constexpr uint32 SPELL_DIVINE_SHIELD = 642; +constexpr uint32 SPELL_ICE_BLOCK = 45438; +constexpr uint32 SPELL_BLESSING_OF_PROTECTION = 41450; +constexpr uint32 SPELL_SHATTERING_THROW = 64382; +} + bool CastBerserkerRageAction::isPossible() { if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true)) @@ -153,14 +162,8 @@ bool CastVigilanceAction::Execute(Event /*event*/) bool CastRetaliationAction::isUseful() { - // Spell cooldown check - if (!bot->HasSpell(20230)) - { - return false; - } - - // Spell cooldown check - if (bot->HasSpellCooldown(20230)) + if (!bot->HasSpell(SPELL_RETALIATION) || bot->HasSpellCooldown(SPELL_RETALIATION) || + bot->HasAura(SPELL_RETALIATION)) { return false; } @@ -199,8 +202,8 @@ bool CastRetaliationAction::isUseful() break; } - // Only cast Retaliation if there are at least 2 melee attackers and the buff is not active - return meleeAttackers >= 2 && !botAI->HasAura("retaliation", bot); + // Only cast Retaliation if there are at least 2 melee attackers + return meleeAttackers >= 2; } Unit* CastShatteringThrowAction::GetTarget() @@ -214,15 +217,15 @@ Unit* CastShatteringThrowAction::GetTarget() continue; if (bot->IsWithinDistInMap(enemy, 25.0f) && - (enemy->HasAura(642) || // Divine Shield - enemy->HasAura(45438) || // Ice Block - enemy->HasAura(41450))) // Blessing of Protection + (enemy->HasAura(SPELL_DIVINE_SHIELD) || + enemy->HasAura(SPELL_ICE_BLOCK) || + enemy->HasAura(SPELL_BLESSING_OF_PROTECTION))) { return enemy; } } - return nullptr; // No valid target + return nullptr; } bool CastShatteringThrowAction::Execute(Event /*event*/) @@ -236,7 +239,7 @@ bool CastShatteringThrowAction::Execute(Event /*event*/) bool CastShatteringThrowAction::isUseful() { - if (!bot->HasSpell(64382) || bot->HasSpellCooldown(64382)) + if (!bot->HasSpell(SPELL_SHATTERING_THROW) || bot->HasSpellCooldown(SPELL_SHATTERING_THROW)) return false; GuidVector enemies = AI_VALUE(GuidVector, "possible targets"); @@ -247,17 +250,16 @@ bool CastShatteringThrowAction::isUseful() if (!enemy || !enemy->IsAlive() || enemy->IsFriendlyTo(bot)) continue; - // Check if the enemy is within 25 yards and has the specific auras if (bot->IsWithinDistInMap(enemy, 25.0f) && - (enemy->HasAura(642) || // Divine Shield - enemy->HasAura(45438) || // Ice Block - enemy->HasAura(41450))) // Blessing of Protection + (enemy->HasAura(SPELL_DIVINE_SHIELD) || + enemy->HasAura(SPELL_ICE_BLOCK) || + enemy->HasAura(SPELL_BLESSING_OF_PROTECTION))) { return true; } } - return false; // No valid targets within range + return false; } bool CastShatteringThrowAction::isPossible() diff --git a/src/Ai/Class/Warrior/WarriorTriggers.cpp b/src/Ai/Class/Warrior/WarriorTriggers.cpp index f6e5bd2345b..e875c403cb9 100644 --- a/src/Ai/Class/Warrior/WarriorTriggers.cpp +++ b/src/Ai/Class/Warrior/WarriorTriggers.cpp @@ -6,6 +6,16 @@ #include "WarriorTriggers.h" #include "Playerbots.h" +namespace +{ +constexpr uint32 SPELL_VIGILANCE = 50720; +constexpr uint32 SPELL_SHATTERING_THROW = 64382; +constexpr uint32 SPELL_DIVINE_SHIELD = 642; +constexpr uint32 SPELL_ICE_BLOCK = 45438; +constexpr uint32 SPELL_BLESSING_OF_PROTECTION = 41450; +constexpr uint32 SPELL_COMMANDING_PRESENCE_RANKS[] = { 12318, 12857, 12858, 12860, 12861 }; +} + bool BloodrageBuffTrigger::IsActive() { return AI_VALUE2(uint8, "health", "self target") >= sPlayerbotAIConfig.mediumHealth && @@ -14,7 +24,7 @@ bool BloodrageBuffTrigger::IsActive() bool VigilanceTrigger::IsActive() { - if (!bot->HasSpell(50720)) + if (!bot->HasSpell(SPELL_VIGILANCE)) return false; Group* group = bot->GetGroup(); @@ -64,7 +74,7 @@ bool VigilanceTrigger::IsActive() bool ShatteringThrowTrigger::IsActive() { - if (!bot->HasSpell(64382) || bot->HasSpellCooldown(64382)) + if (!bot->HasSpell(SPELL_SHATTERING_THROW) || bot->HasSpellCooldown(SPELL_SHATTERING_THROW)) return false; GuidVector enemies = AI_VALUE(GuidVector, "possible targets"); @@ -76,9 +86,9 @@ bool ShatteringThrowTrigger::IsActive() continue; if (bot->IsWithinDistInMap(enemy, 25.0f) && - (enemy->HasAura(642) || // Divine Shield - enemy->HasAura(45438) || // Ice Block - enemy->HasAura(41450))) // Blessing of Protection + (enemy->HasAura(SPELL_DIVINE_SHIELD) || + enemy->HasAura(SPELL_ICE_BLOCK) || + enemy->HasAura(SPELL_BLESSING_OF_PROTECTION))) { return true; } @@ -112,15 +122,13 @@ bool BattleShoutTrigger::IsActive() if (!bsApValue) return false; - static const uint32 commandingPresenceSpells[] = { - 12318, 12857, 12858, 12860, 12861 }; static const float commandingPresenceBonus[] = { 0.05f, 0.10f, 0.15f, 0.20f, 0.25f }; float cpBonus = 0.0f; for (int rank = 4; rank >= 0; --rank) { - if (bot->HasAura(commandingPresenceSpells[rank])) + if (bot->HasAura(SPELL_COMMANDING_PRESENCE_RANKS[rank])) { cpBonus = commandingPresenceBonus[rank]; break; diff --git a/src/Ai/Raid/Naxx/Action/NaxxActions_Razuvious.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Razuvious.cpp index 5d91db0c628..5d865b14ff5 100644 --- a/src/Ai/Raid/Naxx/Action/NaxxActions_Razuvious.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Razuvious.cpp @@ -5,6 +5,13 @@ #include "Playerbots.h" #include "SharedDefines.h" +namespace +{ +constexpr uint32 SPELL_UNDERSTUDY_TAUNT = 29060; +constexpr uint32 SPELL_BONE_BARRIER = 29061; +constexpr uint32 SPELL_BLOOD_STRIKE = 61696; +} + bool RazuviousUseObedienceCrystalAction::Execute(Event /*event*/) { if (!helper.UpdateBossAI()) @@ -42,7 +49,8 @@ bool RazuviousUseObedienceCrystalAction::Execute(Event /*event*/) bool tauntUseful = true; if (forceObedience->GetDuration() <= (duration_time - 5000)) { - if (target->GetVictim() && botAI->HasAura(29061, target->GetVictim())) + Unit* victim = target->GetVictim(); + if (victim && victim->HasAura(SPELL_BONE_BARRIER)) tauntUseful = false; if (forceObedience->GetDuration() <= 3000) @@ -55,19 +63,19 @@ bool RazuviousUseObedienceCrystalAction::Execute(Event /*event*/) if (tauntUseful && !charm->HasSpellCooldown(29060)) { // shield - if (!charm->HasSpellCooldown(29061)) + if (!charm->HasSpellCooldown(SPELL_BONE_BARRIER)) { - charm->CastSpell(charm, 29061, true); - charm->AddSpellCooldown(29061, 0, 30 * 1000); + charm->CastSpell(charm, SPELL_BONE_BARRIER, true); + charm->AddSpellCooldown(SPELL_BONE_BARRIER, 0, 30 * 1000); } - charm->CastSpell(target, 29060, true); - charm->AddSpellCooldown(29060, 0, 20 * 1000); + charm->CastSpell(target, SPELL_UNDERSTUDY_TAUNT, true); + charm->AddSpellCooldown(SPELL_UNDERSTUDY_TAUNT, 0, 20 * 1000); } // strike - if (!charm->HasSpellCooldown(61696)) + if (!charm->HasSpellCooldown(SPELL_BLOOD_STRIKE)) { - charm->CastSpell(target, 61696, true); - charm->AddSpellCooldown(61696, 0, 4 * 1000); + charm->CastSpell(target, SPELL_BLOOD_STRIKE, true); + charm->AddSpellCooldown(SPELL_BLOOD_STRIKE, 0, 4 * 1000); } } } diff --git a/src/Ai/Raid/Naxx/Action/NaxxActions_Sapphiron.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Sapphiron.cpp index 55bcdabc4f7..a0861be395f 100644 --- a/src/Ai/Raid/Naxx/Action/NaxxActions_Sapphiron.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Sapphiron.cpp @@ -75,7 +75,7 @@ bool SapphironFlightPositionAction::MoveToNearestIcebolt() for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) || + if (NaxxSpellIds::HasAnyAura(member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) || botAI->HasAura("icebolt", member, false, false, -1, true)) { if (!playerWithIcebolt || minDistance > bot->GetDistance(member)) diff --git a/src/Ai/Raid/Naxx/Action/NaxxActions_Thaddius.cpp b/src/Ai/Raid/Naxx/Action/NaxxActions_Thaddius.cpp index 016a9faa590..106d43c3691 100644 --- a/src/Ai/Raid/Naxx/Action/NaxxActions_Thaddius.cpp +++ b/src/Ai/Raid/Naxx/Action/NaxxActions_Thaddius.cpp @@ -111,15 +111,13 @@ bool ThaddiusMovePolarityAction::Execute(Event /*event*/) {3504.68f, -2936.68f}, }; uint32 idx; - if (NaxxSpellIds::HasAnyAura( - botAI, bot, + if (NaxxSpellIds::HasAnyAura(bot, {NaxxSpellIds::NegativeCharge10, NaxxSpellIds::NegativeCharge25, NaxxSpellIds::NegativeChargeStack}) || botAI->HasAura("negative charge", bot, false, false, -1, true)) { idx = 0; } - else if (NaxxSpellIds::HasAnyAura( - botAI, bot, + else if (NaxxSpellIds::HasAnyAura(bot, {NaxxSpellIds::PositiveCharge10, NaxxSpellIds::PositiveCharge25, NaxxSpellIds::PositiveChargeStack}) || botAI->HasAura("positive charge", bot, false, false, -1, true)) { diff --git a/src/Ai/Raid/Naxx/NaxxBossHelper.h b/src/Ai/Raid/Naxx/NaxxBossHelper.h index 7bec3a7df34..2df40a70116 100644 --- a/src/Ai/Raid/Naxx/NaxxBossHelper.h +++ b/src/Ai/Raid/Naxx/NaxxBossHelper.h @@ -192,7 +192,7 @@ class SapphironBossHelper : public AiObject { Player* member = ref->GetSource(); if (member && - (NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) || + (NaxxSpellIds::HasAnyAura(member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) || botAI->HasAura("icebolt", member, false, false, -1, true))) { return true; diff --git a/src/Ai/Raid/Naxx/NaxxMultipliers.cpp b/src/Ai/Raid/Naxx/NaxxMultipliers.cpp index b7700eb1b5a..1c2cf05bf24 100644 --- a/src/Ai/Raid/Naxx/NaxxMultipliers.cpp +++ b/src/Ai/Raid/Naxx/NaxxMultipliers.cpp @@ -236,7 +236,7 @@ float AnubrekhanGenericMultiplier::GetValue(Action* action) return 1.0f; if (NaxxSpellIds::HasAnyAura( - botAI, boss, {NaxxSpellIds::LocustSwarm10, NaxxSpellIds::LocustSwarm10Alt, NaxxSpellIds::LocustSwarm25}) || + boss, {NaxxSpellIds::LocustSwarm10, NaxxSpellIds::LocustSwarm10Alt, NaxxSpellIds::LocustSwarm25}) || botAI->HasAura("locust swarm", boss)) { if (dynamic_cast(action)) diff --git a/src/Ai/Raid/Naxx/NaxxSpellIds.h b/src/Ai/Raid/Naxx/NaxxSpellIds.h index 94c013eb7c3..2a7308d811d 100644 --- a/src/Ai/Raid/Naxx/NaxxSpellIds.h +++ b/src/Ai/Raid/Naxx/NaxxSpellIds.h @@ -123,14 +123,14 @@ namespace NaxxSpellIds SPELL_INEVITABLE_DOOM = 29204, SPELL_BERSERK = 26662 */ - inline bool HasAnyAura(PlayerbotAI* botAI, Unit* unit, std::initializer_list spellIds) + inline bool HasAnyAura(Unit* unit, std::initializer_list spellIds) { - if (!botAI || !unit) + if (!unit) return false; for (uint32 spellId : spellIds) { - if (botAI->HasAura(spellId, unit)) + if (unit->HasAura(spellId)) return true; } return false; diff --git a/src/Ai/Raid/Uld/UldTriggers.cpp b/src/Ai/Raid/Uld/UldTriggers.cpp index 7c84043efa9..102bf0f59ee 100644 --- a/src/Ai/Raid/Uld/UldTriggers.cpp +++ b/src/Ai/Raid/Uld/UldTriggers.cpp @@ -1531,7 +1531,7 @@ bool VezaxShadowCrashTrigger::IsActive() if (!boss || !boss->IsAlive()) return false; - return botAI->HasAura(SPELL_VEZAX_SHADOW_CRASH, bot); + return bot->HasAura(SPELL_VEZAX_SHADOW_CRASH); } bool VezaxMarkOfTheFacelessTrigger::IsActive() @@ -1542,7 +1542,7 @@ bool VezaxMarkOfTheFacelessTrigger::IsActive() if (!boss || !boss->IsAlive()) return false; - if (!botAI->HasAura(SPELL_MARK_OF_THE_FACELESS, bot)) + if (!bot->HasAura(SPELL_MARK_OF_THE_FACELESS)) return false; float distance = bot->GetDistance2d(ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionX(), diff --git a/src/Bot/Factory/AiFactory.cpp b/src/Bot/Factory/AiFactory.cpp index b3abbd8ad99..d542449948f 100644 --- a/src/Bot/Factory/AiFactory.cpp +++ b/src/Bot/Factory/AiFactory.cpp @@ -26,6 +26,15 @@ #include "WarlockAiObjectContext.h" #include "WarriorAiObjectContext.h" +namespace +{ +constexpr uint32 SPELL_FROSTFIRE_BOLT = 44614; +constexpr uint32 SPELL_ICE_SHARDS = 15047; +constexpr uint32 SPELL_WHIRLWIND = 1680; +constexpr uint32 SPELL_CAT_FORM = 768; +constexpr uint32 SPELL_DRUID_THICK_HIDE = 16931; +} + AiObjectContext* AiFactory::createAiObjectContext(Player* player, PlayerbotAI* botAI) { switch (player->getClass()) @@ -300,7 +309,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa engine->addStrategiesNoInit("arcane", "bdps", nullptr); else if (tab == MAGE_TAB_FIRE) { - if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/) + if (player->HasSpell(SPELL_FROSTFIRE_BOLT) && player->HasAura(SPELL_ICE_SHARDS)) engine->addStrategiesNoInit("frostfire", "bdps", nullptr); else engine->addStrategiesNoInit("fire", "bdps", nullptr); @@ -313,7 +322,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa case CLASS_WARRIOR: if (tab == WARRIOR_TAB_PROTECTION) engine->addStrategiesNoInit("tank", "tank assist", "pull", "pull back", "aoe", nullptr); - else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(1680)) // Whirlwind + else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(SPELL_WHIRLWIND)) engine->addStrategiesNoInit("arms", "aoe", "dps assist", nullptr); else // if (tab == WARRIOR_TAB_FURY) engine->addStrategiesNoInit("fury", "aoe", "dps assist", nullptr); @@ -345,7 +354,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa engine->addStrategiesNoInit("resto", "cure", "dps assist", "blanketing", "tranquility", nullptr); else { - if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/) + if (player->HasSpell(SPELL_CAT_FORM) && !player->HasAura(SPELL_DRUID_THICK_HIDE)) engine->addStrategiesNoInit("cat", "aoe", "cc", "dps assist", "feral charge", nullptr); else engine->addStrategiesNoInit("bear", "tank assist", "pull", "pull back", "feral charge", nullptr); @@ -535,7 +544,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const case CLASS_DRUID: if (tab == DRUID_TAB_FERAL) { - if (player->GetLevel() >= 20 && !player->HasAura(16931) /*thick hide*/) + if (player->GetLevel() >= 20 && !player->HasAura(SPELL_DRUID_THICK_HIDE)) nonCombatEngine->addStrategy("dps assist", false); else nonCombatEngine->addStrategiesNoInit("tank assist", "pull", nullptr); diff --git a/src/Bot/Factory/PlayerbotFactory.cpp b/src/Bot/Factory/PlayerbotFactory.cpp index 06e3e840585..0abfecfa94c 100644 --- a/src/Bot/Factory/PlayerbotFactory.cpp +++ b/src/Bot/Factory/PlayerbotFactory.cpp @@ -61,6 +61,56 @@ std::vector PlayerbotFactory::enchantGemIdCache; std::unordered_map> PlayerbotFactory::trainerIdCache; std::vector PlayerbotFactory::ccBreakTrinketCache; +namespace +{ +constexpr uint32 SPELL_DRUID_THICK_HIDE = 16931; +constexpr uint32 SPELL_OWLKIN_FRENZY = 48393; +constexpr uint32 SPELL_PRIMAL_TENACITY = 33957; +constexpr uint32 SPELL_IMPROVED_BARKSKIN = 63411; + +constexpr uint32 SPELL_SECOND_WIND = 29838; +constexpr uint32 SPELL_BLOOD_CRAZE = 16492; +constexpr uint32 SPELL_GAG_ORDER = 12958; + +constexpr uint32 SPELL_SACRED_CLEANSING = 53553; +constexpr uint32 SPELL_RECKONING = 20179; +constexpr uint32 SPELL_DIVINE_PURPOSE = 31872; + +constexpr uint32 SPELL_HUNTER_THICK_HIDE = 19612; +constexpr uint32 SPELL_CONCUSSIVE_BARRAGE = 35102; +constexpr uint32 SPELL_ENTRAPMENT = 19388; + +constexpr uint32 SPELL_DEADLY_BREW = 51626; +constexpr uint32 SPELL_THROWING_SPECIALIZATION = 51679; +constexpr uint32 SPELL_WAYLAY = 51696; + +constexpr uint32 SPELL_IMPROVED_MANA_BURN = 14772; +constexpr uint32 SPELL_BODY_AND_SOUL = 64129; +constexpr uint32 SPELL_IMPROVED_VAMPIRIC_EMBRACE = 27840; + +constexpr uint32 SPELL_ABOMINATIONS_MIGHT = 53138; +constexpr uint32 SPELL_IMPROVED_ICY_TALONS = 55610; +constexpr uint32 SPELL_SUDDEN_DOOM = 49529; +constexpr uint32 SPELL_ACCLIMATION = 50152; +constexpr uint32 SPELL_MAGIC_SUPPRESSION = 49611; + +constexpr uint32 SPELL_SHAMAN_DUAL_WIELD = 30798; +constexpr uint32 SPELL_ASTRAL_SHIFT = 51479; +constexpr uint32 SPELL_EARTHEN_POWER = 51524; +constexpr uint32 SPELL_FOCUSED_MIND = 30866; + +constexpr uint32 SPELL_BURNOUT = 44472; +constexpr uint32 SPELL_ICE_SHARDS = 15047; +constexpr uint32 SPELL_IMPROVED_BLINK = 31570; +constexpr uint32 SPELL_FIERY_PAYBACK = 64357; +constexpr uint32 SPELL_SHATTERED_BARRIER = 54787; + +constexpr uint32 SPELL_IMPROVED_HOWL_OF_TERROR = 30057; +constexpr uint32 SPELL_NEMESIS = 63123; +constexpr uint32 SPELL_INTENSITY = 18136; +constexpr uint32 SPELL_NETHER_PROTECTION = 30302; +} + bool PlayerbotFactory::IsPrimaryTradeSkill(uint16 skillId) { SkillLineEntry const* skillLine = sSkillLineStore.LookupEntry(skillId); @@ -1440,7 +1490,7 @@ uint32 PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_temp /// @todo: fix cat druid hardcode if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20) { - bool isCat = !bot->HasAura(16931); + bool isCat = !bot->HasAura(SPELL_DRUID_THICK_HIDE); if (!isCat && bot->GetLevel() == 20) { uint32 bearP = sPlayerbotAIConfig.randomClassSpecProb[cls][1]; @@ -1495,7 +1545,7 @@ uint32 PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_temp if (bot->GetFreeTalentPoints()) InitTalents((specTab + 2) % 3); - if (bot->getClass() == CLASS_SHAMAN && bot->HasSpell(30798)) + if (bot->getClass() == CLASS_SHAMAN && bot->HasSpell(SPELL_SHAMAN_DUAL_WIELD)) { bot->SetSkill(SKILL_DUAL_WIELD, 0, 1, 1); bot->SetCanDualWield(true); @@ -1581,7 +1631,7 @@ void PlayerbotFactory::InitTalentsBySpecNo(Player* bot, int specNo, bool reset) } } - if (bot->getClass() == CLASS_SHAMAN && bot->HasSpell(30798)) + if (bot->getClass() == CLASS_SHAMAN && bot->HasSpell(SPELL_SHAMAN_DUAL_WIELD)) { bot->SetSkill(SKILL_DUAL_WIELD, 0, 1, 1); bot->SetCanDualWield(true); @@ -4170,13 +4220,13 @@ void PlayerbotFactory::InitGlyphs(bool increment) if (bot->getClass() == CLASS_WARRIOR) { // Arms PvP (spec index 3): If the bot has the Second Wind talent - if (bot->HasAura(29838)) + if (bot->HasAura(SPELL_SECOND_WIND)) tab = 3; // Fury PvP (spec index 4): If the bot has the Blood Craze talent - else if (bot->HasAura(16492)) + else if (bot->HasAura(SPELL_BLOOD_CRAZE)) tab = 4; // Protection PvP (spec index 5): If the bot has the Gag Order talent - else if (bot->HasAura(12958)) + else if (bot->HasAura(SPELL_GAG_ORDER)) tab = 5; } @@ -4184,13 +4234,13 @@ void PlayerbotFactory::InitGlyphs(bool increment) if (bot->getClass() == CLASS_PALADIN) { // Holy PvP (spec index 3): If the bot has the Sacred Cleansing talent - if (bot->HasAura(53553)) + if (bot->HasAura(SPELL_SACRED_CLEANSING)) tab = 3; // Protection PvP (spec index 4): If the bot has the Reckoning talent - else if (bot->HasAura(20179)) + else if (bot->HasAura(SPELL_RECKONING)) tab = 4; // Retribution PvP (spec index 5): If the bot has the Divine Purpose talent - else if (bot->HasAura(31872)) + else if (bot->HasAura(SPELL_DIVINE_PURPOSE)) tab = 5; } @@ -4198,13 +4248,13 @@ void PlayerbotFactory::InitGlyphs(bool increment) if (bot->getClass() == CLASS_HUNTER) { // Beast Mastery PvP (spec index 3): If the bot has the Thick Hide talent - if (bot->HasAura(19612)) + if (bot->HasAura(SPELL_HUNTER_THICK_HIDE)) tab = 3; // Marksmanship PvP (spec index 4): If the bot has the Concussive Barrage talent - else if (bot->HasAura(35102)) + else if (bot->HasAura(SPELL_CONCUSSIVE_BARRAGE)) tab = 4; // Survival PvP (spec index 5): If the bot has the Entrapment talent and does NOT have the Concussive Barrage talent - else if (bot->HasAura(19388) && !bot->HasAura(35102)) + else if (bot->HasAura(SPELL_ENTRAPMENT) && !bot->HasAura(SPELL_CONCUSSIVE_BARRAGE)) tab = 5; } @@ -4212,13 +4262,13 @@ void PlayerbotFactory::InitGlyphs(bool increment) if (bot->getClass() == CLASS_ROGUE) { // Assassination PvP (spec index 3): If the bot has the Deadly Brew talent - if (bot->HasAura(51626)) + if (bot->HasAura(SPELL_DEADLY_BREW)) tab = 3; // Combat PvP (spec index 4): If the bot has the Throwing Specialization talent - else if (bot->HasAura(51679)) + else if (bot->HasAura(SPELL_THROWING_SPECIALIZATION)) tab = 4; // Subtlety PvP (spec index 5): If the bot has the Waylay talent - else if (bot->HasAura(51696)) + else if (bot->HasAura(SPELL_WAYLAY)) tab = 5; } @@ -4226,13 +4276,13 @@ void PlayerbotFactory::InitGlyphs(bool increment) if (bot->getClass() == CLASS_PRIEST) { // Discipline PvP (spec index 3): If the bot has the Improved Mana Burn talent - if (bot->HasAura(14772)) + if (bot->HasAura(SPELL_IMPROVED_MANA_BURN)) tab = 3; // Holy PvP (spec index 4): If the bot has the Body and Soul talent - else if (bot->HasAura(64129)) + else if (bot->HasAura(SPELL_BODY_AND_SOUL)) tab = 4; // Shadow PvP (spec index 5): If the bot has the Improved Vampiric Embrace talent - else if (bot->HasAura(27840)) + else if (bot->HasAura(SPELL_IMPROVED_VAMPIRIC_EMBRACE)) tab = 5; } @@ -4241,16 +4291,16 @@ void PlayerbotFactory::InitGlyphs(bool increment) { // Double Aura Blood PvE (spec index 3): If the bot has both the Abomination's Might and Improved Icy Talons // talents - if (bot->HasAura(53138) && bot->HasAura(55610)) + if (bot->HasAura(SPELL_ABOMINATIONS_MIGHT) && bot->HasAura(SPELL_IMPROVED_ICY_TALONS)) tab = 3; // Blood PvP (spec index 4): If the bot has the Sudden Doom talent - else if (bot->HasAura(49529)) + else if (bot->HasAura(SPELL_SUDDEN_DOOM)) tab = 4; // Frost PvP (spec index 5): If the bot has the Acclimation talent - else if (bot->HasAura(50152)) + else if (bot->HasAura(SPELL_ACCLIMATION)) tab = 5; // Unholy PvP (spec index 6): If the bot has the Magic Suppression talent - else if (bot->HasAura(49611)) + else if (bot->HasAura(SPELL_MAGIC_SUPPRESSION)) tab = 6; } @@ -4258,13 +4308,13 @@ void PlayerbotFactory::InitGlyphs(bool increment) if (bot->getClass() == CLASS_SHAMAN) { // Elemental PvP (spec index 3): If the bot has the Astral Shift talent - if (bot->HasAura(51479)) + if (bot->HasAura(SPELL_ASTRAL_SHIFT)) tab = 3; // Enhancement PvP (spec index 4): If the bot has the Earthen Power talent - else if (bot->HasAura(51524)) + else if (bot->HasAura(SPELL_EARTHEN_POWER)) tab = 4; // Restoration PvP (spec index 5): If the bot has the Focused Mind talent - else if (bot->HasAura(30866)) + else if (bot->HasAura(SPELL_FOCUSED_MIND)) tab = 5; } @@ -4272,16 +4322,16 @@ void PlayerbotFactory::InitGlyphs(bool increment) if (bot->getClass() == CLASS_MAGE) { // Frostfire PvE (spec index 3): If the bot has both the Burnout talent and the Ice Shards talent - if (bot->HasAura(44472) && bot->HasAura(15047)) + if (bot->HasAura(SPELL_BURNOUT) && bot->HasAura(SPELL_ICE_SHARDS)) tab = 3; // Arcane PvP (spec index 4): If the bot has the Improved Blink talent - else if (bot->HasAura(31570)) + else if (bot->HasAura(SPELL_IMPROVED_BLINK)) tab = 4; // Fire PvP (spec index 5): If the bot has the Fiery Payback talent - else if (bot->HasAura(64357)) + else if (bot->HasAura(SPELL_FIERY_PAYBACK)) tab = 5; // Frost PvP (spec index 6): If the bot has the Shattered Barrier talent - else if (bot->HasAura(54787)) + else if (bot->HasAura(SPELL_SHATTERED_BARRIER)) tab = 6; } @@ -4289,13 +4339,13 @@ void PlayerbotFactory::InitGlyphs(bool increment) if (bot->getClass() == CLASS_WARLOCK) { // Affliction PvP (spec index 3): If the bot has the Improved Howl of Terror talent - if (bot->HasAura(30057)) + if (bot->HasAura(SPELL_IMPROVED_HOWL_OF_TERROR)) tab = 3; // Demonology PvP (spec index 4): If the bot has both the Nemesis talent and the Intensity talent - else if (bot->HasAura(63123) && bot->HasAura(18136)) + else if (bot->HasAura(SPELL_NEMESIS) && bot->HasAura(SPELL_INTENSITY)) tab = 4; // Destruction PvP (spec index 5): If the bot has the Nether Protection talent - else if (bot->HasAura(30302)) + else if (bot->HasAura(SPELL_NETHER_PROTECTION)) tab = 5; } @@ -4303,16 +4353,16 @@ void PlayerbotFactory::InitGlyphs(bool increment) if (bot->getClass() == CLASS_DRUID) { // Cat PvE (spec index 3): If the bot is Feral spec, level 20 or higher, and does NOT have the Thick Hide talent - if (tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(16931)) + if (tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(SPELL_DRUID_THICK_HIDE)) tab = 3; // Balance PvP (spec index 4): If the bot has the Owlkin Frenzy talent - else if (bot->HasAura(48393)) + else if (bot->HasAura(SPELL_OWLKIN_FRENZY)) tab = 4; // Feral PvP (spec index 5): If the bot has the Primal Tenacity talent - else if (bot->HasAura(33957)) + else if (bot->HasAura(SPELL_PRIMAL_TENACITY)) tab = 5; // Resto PvP (spec index 6): If the bot has the Improved Barkskin talent - else if (bot->HasAura(63411)) + else if (bot->HasAura(SPELL_IMPROVED_BARKSKIN)) tab = 6; } diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index eeadbbc5550..00f878686ec 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -3152,20 +3152,10 @@ bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, boo return false; } -bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit) +bool PlayerbotAI::HasSpell(std::string const spellName) const { - if (!spellId || !unit) - return false; - - return unit->HasAura(spellId); - // for (uint8 effect = EFFECT_0; effect <= EFFECT_2; effect++) - // { - // AuraEffect const* aurEff = unit->GetAuraEffect(spellId, effect); - // if (IsRealAura(bot, aurEff, unit)) - // return true; - // } - - // return false; + uint32 const spellId = aiObjectContext->GetValue("spell id", spellName)->Get(); + return spellId && bot->HasSpell(spellId); } Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack) @@ -4269,7 +4259,7 @@ void PlayerbotAI::InterruptSpell() void PlayerbotAI::RemoveAura(std::string const name) { uint32 spellid = aiObjectContext->GetValue("spell id", name)->Get(); - if (spellid && HasAura(spellid, bot)) + if (spellid && bot->HasAura(spellid)) bot->RemoveAurasDueToSpell(spellid); } diff --git a/src/Bot/PlayerbotAI.h b/src/Bot/PlayerbotAI.h index 2e31c9a9e7f..033b6aaeb3c 100644 --- a/src/Bot/PlayerbotAI.h +++ b/src/Bot/PlayerbotAI.h @@ -497,6 +497,7 @@ class PlayerbotAI : public PlayerbotAIBase virtual bool CanCastSpell(std::string const name, Unit* target, Item* itemTarget = nullptr); virtual bool CastSpell(std::string const name, Unit* target, Item* itemTarget = nullptr); + virtual bool HasSpell(std::string const spellName) const; virtual bool HasAura(std::string const spellName, Unit* player, bool maxStack = false, bool checkIsOwner = false, int maxAmount = -1, bool checkDuration = false); virtual bool HasAnyAuraOf(Unit* player, ...); @@ -509,7 +510,6 @@ class PlayerbotAI : public PlayerbotAIBase bool CanCastSpell(uint32 spellid, float x, float y, float z, bool checkHasSpell = true, Item* itemTarget = nullptr); - bool HasAura(uint32 spellId, Unit const* player); Aura* GetAura(std::string const spellName, Unit* unit, bool checkIsOwner = false, bool checkDuration = false, int checkStack = -1); bool CastSpell(uint32 spellId, Unit* target, Item* itemTarget = nullptr); diff --git a/src/Mgr/Item/StatsWeightCalculator.cpp b/src/Mgr/Item/StatsWeightCalculator.cpp index 2e11f0a38cf..4e61dd09ea6 100644 --- a/src/Mgr/Item/StatsWeightCalculator.cpp +++ b/src/Mgr/Item/StatsWeightCalculator.cpp @@ -23,13 +23,29 @@ namespace { -constexpr uint32 SPELL_MOLTEN_ARMOR_RANK_1 = 30482; -constexpr uint32 SPELL_MOLTEN_ARMOR_RANK_2 = 43045; -constexpr uint32 SPELL_MOLTEN_ARMOR_RANK_3 = 43046; -constexpr uint32 SPELL_FEL_ARMOR_RANK_1 = 28176; -constexpr uint32 SPELL_FEL_ARMOR_RANK_2 = 28189; -constexpr uint32 SPELL_FEL_ARMOR_RANK_3 = 47892; -constexpr uint32 SPELL_FEL_ARMOR_RANK_4 = 47893; +constexpr uint32 SPELL_MOLTEN_ARMOR_RANKS[] = { 30482, 43045, 43046 }; +constexpr uint32 SPELL_FEL_ARMOR_RANKS[] = { 28176, 28189, 47892, 47893 }; +constexpr uint32 SPELL_CAREFUL_AIM = 34484; +constexpr uint32 SPELL_HUNTER_VS_WILD = 56341; +constexpr uint32 SPELL_ARMORED_TO_THE_TEETH = 61222; +constexpr uint32 SPELL_MENTAL_DEXTERITY = 51885; +constexpr uint32 SPELL_ROGUE_SWORD_SPECIALIZATION = 13964; +constexpr uint32 SPELL_POLEAXE_SPECIALIZATION = 12785; +constexpr uint32 SPELL_NERVES_OF_COLD_STEEL = 50138; +constexpr uint32 SPELL_SHADOW_FOCUS = 15835; +constexpr uint32 SPELL_ARCANE_FOCUS = 12840; +} + +template +bool HasAnySpell(Player* player, uint32 const (&spellIds)[Size]) +{ + for (uint32 const spellId : spellIds) + { + if (player->HasSpell(spellId)) + return true; + } + + return false; } StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player) @@ -512,26 +528,24 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player) // int tab = AiFactory::GetPlayerSpecTab(player); if (cls == CLASS_HUNTER) { - if (player->HasAura(34484)) + if (player->HasAura(SPELL_CAREFUL_AIM)) stats_weights_[STATS_TYPE_INTELLECT] += 1.1f; - if (player->HasAura(56341)) + if (player->HasAura(SPELL_HUNTER_VS_WILD)) stats_weights_[STATS_TYPE_STAMINA] += 0.3f; } else if (cls == CLASS_WARRIOR) { - if (player->HasAura(61222)) + if (player->HasAura(SPELL_ARMORED_TO_THE_TEETH)) stats_weights_[STATS_TYPE_ARMOR] += 0.03f; } else if (cls == CLASS_SHAMAN) { - if (player->HasAura(51885)) + if (player->HasAura(SPELL_MENTAL_DEXTERITY)) stats_weights_[STATS_TYPE_INTELLECT] += 1.1f; } else if (cls == CLASS_MAGE) { - if (!player->HasSpell(SPELL_MOLTEN_ARMOR_RANK_1) - && !player->HasSpell(SPELL_MOLTEN_ARMOR_RANK_2) - && !player->HasSpell(SPELL_MOLTEN_ARMOR_RANK_3)) + if (!HasAnySpell(player, SPELL_MOLTEN_ARMOR_RANKS)) { if (tab != MAGE_TAB_FIRE) stats_weights_[STATS_TYPE_SPIRIT] -= 0.6f; @@ -541,8 +555,7 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player) } else if (cls == CLASS_WARLOCK) { - if (!player->HasSpell(SPELL_FEL_ARMOR_RANK_1) && !player->HasSpell(SPELL_FEL_ARMOR_RANK_2) && - !player->HasSpell(SPELL_FEL_ARMOR_RANK_3) && !player->HasSpell(SPELL_FEL_ARMOR_RANK_4)) + if (!HasAnySpell(player, SPELL_FEL_ARMOR_RANKS)) stats_weights_[STATS_TYPE_SPIRIT] -= 0.4f; } @@ -690,17 +703,17 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) weight_ *= 1.5; } - if (cls == CLASS_ROGUE && player_->HasAura(13964) && + if (cls == CLASS_ROGUE && player_->HasAura(SPELL_ROGUE_SWORD_SPECIALIZATION) && (proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE)) { weight_ *= 1.1; } - if (cls == CLASS_WARRIOR && player_->HasAura(12785) && + if (cls == CLASS_WARRIOR && player_->HasAura(SPELL_POLEAXE_SPECIALIZATION) && (proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2)) { weight_ *= 1.1; } - if (cls == CLASS_DEATH_KNIGHT && player_->HasAura(50138) && !isDoubleHand) + if (cls == CLASS_DEATH_KNIGHT && player_->HasAura(SPELL_NERVES_OF_COLD_STEEL) && !isDoubleHand) { weight_ *= 1.3; } @@ -739,9 +752,9 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) player->GetTotalAuraModifier(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT); // suppression (18176) hit_current += player->GetRatingBonusValue(CR_HIT_SPELL); - if (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW && player->HasAura(15835)) // Shadow Focus + if (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW && player->HasAura(SPELL_SHADOW_FOCUS)) hit_current += 3; - if (cls == CLASS_MAGE && tab == MAGE_TAB_ARCANE && player->HasAura(12840)) // Arcane Focus + if (cls == CLASS_MAGE && tab == MAGE_TAB_ARCANE && player->HasAura(SPELL_ARCANE_FOCUS)) hit_current += 3; hit_overflow = SPELL_HIT_OVERFLOW;