Compare commits

..

2 Commits

Author SHA1 Message Date
bash
6620def962 auto-format 2025-11-24 11:59:06 +01:00
bash
cddd406b1c removed movementAction changed made due core movement changes 2025-11-24 11:55:03 +01:00
67 changed files with 2338 additions and 5021 deletions

View File

@@ -1185,7 +1185,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951" 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 don't pvp)
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973" AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
# Improve reaction speeds in battlegrounds and arenas (may cause lag) # Improve reaction speeds in battlegrounds and arenas (may cause lag)
AiPlayerbot.FastReactInBG = 1 AiPlayerbot.FastReactInBG = 1

View File

@@ -1,255 +0,0 @@
DELETE FROM ai_playerbot_texts WHERE name IN (
'pet_usage_error',
'pet_no_pet_error',
'pet_stance_report',
'pet_no_target_error',
'pet_target_dead_error',
'pet_invalid_target_error',
'pet_pvp_prohibited_error',
'pet_attack_success',
'pet_attack_failed',
'pet_follow_success',
'pet_stay_success',
'pet_unknown_command_error',
'pet_stance_set_success',
'pet_type_pet',
'pet_type_guardian',
'pet_stance_aggressive',
'pet_stance_defensive',
'pet_stance_passive',
'pet_stance_unknown'
);
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
'pet_usage_error',
'pet_no_pet_error',
'pet_stance_report',
'pet_no_target_error',
'pet_target_dead_error',
'pet_invalid_target_error',
'pet_pvp_prohibited_error',
'pet_attack_success',
'pet_attack_failed',
'pet_follow_success',
'pet_stay_success',
'pet_unknown_command_error',
'pet_stance_set_success',
'pet_type_pet',
'pet_type_guardian',
'pet_stance_aggressive',
'pet_stance_defensive',
'pet_stance_passive',
'pet_stance_unknown'
);
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
(1717, 'pet_usage_error', "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
"사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
(1718, 'pet_no_pet_error', "You have no pet or guardian pet.", 0, 0,
"펫이나 수호자 펫이 없습니다.",
"Vous n'avez pas de familier ou gardien.",
"Du hast kein Tier oder Wächter.",
"你没有宠物或守护者宠物。",
"你沒有寵物或守護者寵物。",
"No tienes mascota o mascota guardián.",
"No tienes mascota o mascota guardián.",
"У вас нет питомца или защитника."),
(1719, 'pet_stance_report', "Current stance of %type \"%name\": %stance.", 0, 0,
"%type \"%name\"의 현재 태세: %stance.",
"Position actuelle du %type \"%name\": %stance.",
"Aktuelle Haltung des %type \"%name\": %stance.",
"%type \"%name\" 的当前姿态: %stance。",
"%type \"%name\" 的當前姿態: %stance。",
"Postura actual del %type \"%name\": %stance.",
"Postura actual del %type \"%name\": %stance.",
"Текущая позиция %type \"%name\": %stance."),
(1720, 'pet_no_target_error', "No valid target selected by master.", 0, 0,
"주인이 유효한 대상을 선택하지 않았습니다.",
"Aucune cible valide sélectionnée par le maître.",
"Kein gültiges Ziel vom Meister ausgewählt.",
"主人未选择有效目标。",
"主人未選擇有效目標。",
"No hay objetivo válido seleccionado por el maestro.",
"No hay objetivo válido seleccionado por el maestro.",
"Хозяин не выбрал действительную цель."),
(1721, 'pet_target_dead_error', "Target is not alive.", 0, 0,
"대상이 살아있지 않습니다.",
"La cible n'est pas vivante.",
"Das Ziel ist nicht am Leben.",
"目标未存活。",
"目標未存活。",
"El objetivo no está vivo.",
"El objetivo no está vivo.",
"Цель не жива."),
(1722, 'pet_invalid_target_error', "Target is not a valid attack target for the bot.", 0, 0,
"대상이 봇에게 유효한 공격 대상이 아닙니다.",
"La cible n'est pas une cible d'attaque valide pour le bot.",
"Das Ziel ist kein gültiges Angriffsziel für den Bot.",
"目标不是机器人的有效攻击目标。",
"目標不是機器人的有效攻擊目標。",
"El objetivo no es un objetivo de ataque válido para el bot.",
"El objetivo no es un objetivo de ataque válido para el bot.",
"Цель не является допустимой целью атаки для бота."),
(1723, 'pet_pvp_prohibited_error', "I cannot command my pet to attack players in PvP prohibited areas.", 0, 0,
"PvP 금지 지역에서는 펫에게 플레이어 공격 명령을 내릴 수 없습니다.",
"Je ne peux pas commander à mon familier d'attaquer des joueurs dans les zones où le PvP est interdit.",
"Ich kann meinem Tier nicht befehlen, Spieler in PvP-verbotenen Gebieten anzugreifen.",
"我不能命令我的宠物在禁止PvP的区域攻击玩家。",
"我不能命令我的寵物在禁止PvP的區域攻擊玩家。",
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
"Я не могу приказать своему питомцу атаковать игроков в зонах, где PvP запрещено."),
(1724, 'pet_attack_success', "Pet commanded to attack your target.", 0, 0,
"펫이 당신의 대상을 공격하도록 명령했습니다.",
"Le familier a reçu l'ordre d'attaquer votre cible.",
"Tier wurde befohlen, dein Ziel anzugreifen.",
"宠物已命令攻击你的目标。",
"寵物已命令攻擊你的目標。",
"Mascota ordenada a atacar tu objetivo.",
"Mascota ordenada a atacar tu objetivo.",
"Питомцу приказано атаковать вашу цель."),
(1725, 'pet_attack_failed', "Pet did not attack. (Already attacking or unable to attack target)", 0, 0,
"펫이 공격하지 않았습니다. (이미 공격 중이거나 대상 공격 불가)",
"Le familier n'a pas attaqué. (Attaque déjà en cours ou impossible d'attaquer la cible)",
"Tier hat nicht angegriffen. (Greift bereits an oder kann Ziel nicht angreifen)",
"宠物未攻击。(已在攻击或无法攻击目标)",
"寵物未攻擊。(已在攻擊或無法攻擊目標)",
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
"Питомец не атаковал. (Уже атакует или не может атаковать цель)"),
(1726, 'pet_follow_success', "Pet commanded to follow.", 0, 0,
"펫이 따라오도록 명령했습니다.",
"Le familier a reçu l'ordre de suivre.",
"Tier wurde befohlen zu folgen.",
"宠物已命令跟随。",
"寵物已命令跟隨。",
"Mascota ordenada a seguir.",
"Mascota ordenada a seguir.",
"Питомцу приказано следовать."),
(1727, 'pet_stay_success', "Pet commanded to stay.", 0, 0,
"펫이 머물도록 명령했습니다.",
"Le familier a reçu l'ordre de rester.",
"Tier wurde befohlen zu bleiben.",
"宠物已命令停留。",
"寵物已命令停留。",
"Mascota ordenada a quedarse.",
"Mascota ordenada a quedarse.",
"Питомцу приказано остаться."),
(1728, 'pet_unknown_command_error', "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
"알 수 없는 펫 명령: %param. 사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Commande de familier inconnue: %param. Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Unbekannter Tierbefehl: %param. Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"未知宠物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"未知寵物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
"Неизвестная команда питомца: %param. Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
(1729, 'pet_stance_set_success', "Pet stance set to %stance.", 0, 0,
"펫 태세가 %stance(으)로 설정되었습니다.",
"Position du familier définie sur %stance.",
"Tierhaltung auf %stance gesetzt.",
"宠物姿态设置为 %stance。",
"寵物姿態設置為 %stance。",
"Postura de mascota establecida en %stance.",
"Postura de mascota establecida en %stance.",
"Позиция питомца установлена на %stance."),
(1730, 'pet_type_pet', "pet", 0, 0,
"",
"familier",
"Tier",
"宠物",
"寵物",
"mascota",
"mascota",
"питомец"),
(1731, 'pet_type_guardian', "guardian", 0, 0,
"수호자",
"gardien",
"Wächter",
"守护者",
"守護者",
"guardián",
"guardián",
"защитник"),
(1732, 'pet_stance_aggressive', "aggressive", 0, 0,
"공격적",
"agressif",
"aggressiv",
"进攻",
"進攻",
"agresivo",
"agresivo",
"агрессивная"),
(1733, 'pet_stance_defensive', "defensive", 0, 0,
"방어적",
"défensif",
"defensiv",
"防御",
"防禦",
"defensivo",
"defensivo",
"защитная"),
(1734, 'pet_stance_passive', "passive", 0, 0,
"수동적",
"passif",
"passiv",
"被动",
"被動",
"pasivo",
"pasivo",
"пассивная"),
(1735, 'pet_stance_unknown', "unknown", 0, 0,
"알 수 없음",
"inconnu",
"unbekannt",
"未知",
"未知",
"desconocido",
"desconocido",
"неизвестная");
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
('pet_usage_error', 100),
('pet_no_pet_error', 100),
('pet_stance_report', 100),
('pet_no_target_error', 100),
('pet_target_dead_error', 100),
('pet_invalid_target_error', 100),
('pet_pvp_prohibited_error', 100),
('pet_attack_success', 100),
('pet_attack_failed', 100),
('pet_follow_success', 100),
('pet_stay_success', 100),
('pet_unknown_command_error', 100),
('pet_stance_set_success', 100),
('pet_type_pet', 100),
('pet_type_guardian', 100),
('pet_stance_aggressive', 100),
('pet_stance_defensive', 100),
('pet_stance_passive', 100),
('pet_stance_unknown', 100);

View File

@@ -365,7 +365,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
} }
// Update the bot's group status (moved to helper function) // Update the bot's group status (moved to helper function)
UpdateAIGroupMaster(); UpdateAIGroupAndMaster();
// Update internal AI // Update internal AI
UpdateAIInternal(elapsed, minimal); UpdateAIInternal(elapsed, minimal);
@@ -373,7 +373,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
} }
// Helper function for UpdateAI to check group membership and handle removal if necessary // Helper function for UpdateAI to check group membership and handle removal if necessary
void PlayerbotAI::UpdateAIGroupMaster() void PlayerbotAI::UpdateAIGroupAndMaster()
{ {
if (!bot) if (!bot)
return; return;
@@ -420,7 +420,7 @@ void PlayerbotAI::UpdateAIGroupMaster()
{ {
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
if (botAI->GetMaster() == botAI->GetGroupLeader()) if (botAI->GetMaster() == botAI->GetGroupMaster())
botAI->TellMaster("Hello, I follow you!"); botAI->TellMaster("Hello, I follow you!");
else else
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!"); botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
@@ -431,6 +431,8 @@ void PlayerbotAI::UpdateAIGroupMaster()
botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT);
} }
} }
else if (!newMaster && !bot->InBattleground())
LeaveOrDisbandGroup();
} }
} }
@@ -1384,6 +1386,9 @@ void PlayerbotAI::DoNextAction(bool min)
else if (bot->isAFK()) else if (bot->isAFK())
bot->ToggleAFK(); bot->ToggleAFK();
Group* group = bot->GetGroup();
PlayerbotAI* masterBotAI = nullptr;
if (master && master->IsInWorld()) if (master && master->IsInWorld())
{ {
float distance = sServerFacade->GetDistance2d(bot, master); float distance = sServerFacade->GetDistance2d(bot, master);
@@ -1456,7 +1461,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
strategyName = "onyxia"; // Onyxia's Lair strategyName = "onyxia"; // Onyxia's Lair
break; break;
case 409: case 409:
strategyName = "moltencore"; // Molten Core strategyName = "mc"; // Molten Core
break; break;
case 469: case 469:
strategyName = "bwl"; // Blackwing Lair strategyName = "bwl"; // Blackwing Lair
@@ -1472,7 +1477,6 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
break; break;
case 544: case 544:
strategyName = "magtheridon"; // Magtheridon's Lair strategyName = "magtheridon"; // Magtheridon's Lair
break;
case 565: case 565:
strategyName = "gruulslair"; // Gruul's Lair strategyName = "gruulslair"; // Gruul's Lair
break; break;
@@ -2244,7 +2248,7 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player)
bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); } bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); }
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers) bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
{ {
Group* group = player->GetGroup(); Group* group = player->GetGroup();
if (!group) if (!group)
@@ -2261,9 +2265,6 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDead
continue; continue;
} }
if (ignoreDeadPlayers && !member->IsAlive())
continue;
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
{ {
if (index == counter) if (index == counter)
@@ -2283,9 +2284,6 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDead
continue; continue;
} }
if (ignoreDeadPlayers && !member->IsAlive())
continue;
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
{ {
if (index == counter) if (index == counter)
@@ -4094,7 +4092,7 @@ Player* PlayerbotAI::FindNewMaster()
if (!group) if (!group)
return nullptr; return nullptr;
Player* groupLeader = GetGroupLeader(); Player* groupLeader = GetGroupMaster();
PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(groupLeader); PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(groupLeader);
if (!leaderBotAI || leaderBotAI->IsRealPlayer()) if (!leaderBotAI || leaderBotAI->IsRealPlayer())
return groupLeader; return groupLeader;
@@ -4145,7 +4143,7 @@ bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(m
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); } bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
Player* PlayerbotAI::GetGroupLeader() Player* PlayerbotAI::GetGroupMaster()
{ {
if (!bot->InBattleground()) if (!bot->InBattleground())
if (Group* group = bot->GetGroup()) if (Group* group = bot->GetGroup())

View File

@@ -428,7 +428,7 @@ public:
static bool IsMainTank(Player* player); static bool IsMainTank(Player* player);
static uint32 GetGroupTankNum(Player* player); static uint32 GetGroupTankNum(Player* player);
static bool IsAssistTank(Player* player); static bool IsAssistTank(Player* player);
static bool IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers = false); static bool IsAssistTankOfIndex(Player* player, int index);
static bool IsHealAssistantOfIndex(Player* player, int index); static bool IsHealAssistantOfIndex(Player* player, int index);
static bool IsRangedDpsAssistantOfIndex(Player* player, int index); static bool IsRangedDpsAssistantOfIndex(Player* player, int index);
bool HasAggro(Unit* unit); bool HasAggro(Unit* unit);
@@ -540,7 +540,7 @@ public:
// Get the group leader or the master of the bot. // Get the group leader or the master of the bot.
// Checks if the bot is summoned as alt of a player // Checks if the bot is summoned as alt of a player
bool IsAlt(); bool IsAlt();
Player* GetGroupLeader(); Player* GetGroupMaster();
// Returns a semi-random (cycling) number that is fixed for each bot. // Returns a semi-random (cycling) number that is fixed for each bot.
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1); uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1);
GrouperType GetGrouperType(); GrouperType GetGrouperType();
@@ -612,7 +612,7 @@ private:
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore, static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
bool mixed = false); bool mixed = false);
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
void UpdateAIGroupMaster(); void UpdateAIGroupAndMaster();
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const; Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
void HandleCommands(); void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL); void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);

View File

@@ -165,7 +165,7 @@ bool PlayerbotAIConfig::Initialize()
pvpProhibitedZoneIds); pvpProhibitedZoneIds);
LoadList<std::vector<uint32>>( LoadList<std::vector<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds",
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"), "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
pvpProhibitedAreaIds); pvpProhibitedAreaIds);
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true); fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
LoadList<std::vector<uint32>>( LoadList<std::vector<uint32>>(

View File

@@ -251,9 +251,9 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
out << "I am currently leading a group. I can invite you if you want."; out << "I am currently leading a group. I can invite you if you want.";
break; break;
case PLAYERBOT_DENY_NOT_LEADER: case PLAYERBOT_DENY_NOT_LEADER:
if (botAI->GetGroupLeader()) if (botAI->GetGroupMaster())
{ {
out << "I am in a group with " << botAI->GetGroupLeader()->GetName() out << "I am in a group with " << botAI->GetGroupMaster()->GetName()
<< ". You can ask him for invite."; << ". You can ask him for invite.";
} }
else else

View File

@@ -25,7 +25,6 @@
#include "Metric.h" #include "Metric.h"
#include "PlayerScript.h" #include "PlayerScript.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "PlayerbotSpellCache.h"
#include "PlayerbotWorldThreadProcessor.h" #include "PlayerbotWorldThreadProcessor.h"
#include "RandomPlayerbotMgr.h" #include "RandomPlayerbotMgr.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
@@ -83,12 +82,12 @@ public:
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", { PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", {
PLAYERHOOK_ON_LOGIN, PLAYERHOOK_ON_LOGIN,
PLAYERHOOK_ON_AFTER_UPDATE, PLAYERHOOK_ON_AFTER_UPDATE,
PLAYERHOOK_ON_CHAT,
PLAYERHOOK_ON_CHAT_WITH_CHANNEL,
PLAYERHOOK_ON_CHAT_WITH_GROUP,
PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS, PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS,
PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE, PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE,
PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT, PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT,
PLAYERHOOK_CAN_PLAYER_USE_GROUP_CHAT,
PLAYERHOOK_CAN_PLAYER_USE_GUILD_CHAT,
PLAYERHOOK_CAN_PLAYER_USE_CHANNEL_CHAT,
PLAYERHOOK_ON_GIVE_EXP, PLAYERHOOK_ON_GIVE_EXP,
PLAYERHOOK_ON_BEFORE_TELEPORT PLAYERHOOK_ON_BEFORE_TELEPORT
}) {} }) {}
@@ -165,17 +164,14 @@ public:
{ {
botAI->HandleCommand(type, msg, player); botAI->HandleCommand(type, msg, player);
// hotfix; otherwise the server will crash when whispering logout return false;
// https://github.com/mod-playerbots/mod-playerbots/pull/1838
// TODO: find the root cause and solve it. (does not happen in party chat)
if (msg == "logout")
return false;
} }
} }
return true; return true;
} }
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
{ {
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{ {
@@ -187,10 +183,9 @@ public:
} }
} }
} }
return true;
} }
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Guild* guild) override void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg) override
{ {
if (type == CHAT_MSG_GUILD) if (type == CHAT_MSG_GUILD)
{ {
@@ -209,10 +204,9 @@ public:
} }
} }
} }
return true;
} }
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
{ {
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player)) if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
{ {
@@ -223,7 +217,6 @@ public:
} }
sRandomPlayerbotMgr->HandleCommand(type, msg, player); sRandomPlayerbotMgr->HandleCommand(type, msg, player);
return true;
} }
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
@@ -239,39 +232,34 @@ public:
void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 /*xpSource*/) override void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 /*xpSource*/) override
{ {
if (sPlayerbotAIConfig->randomBotXPRate == 1.0f || !player || !player->IsInWorld()) // early return
if (sPlayerbotAIConfig->randomBotXPRate == 1.0 || !player)
return; return;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); // no XP multiplier, when player is no bot.
if (!botAI || !sRandomPlayerbotMgr->IsRandomBot(player)) if (!player->GetSession()->IsBot() || !sRandomPlayerbotMgr->IsRandomBot(player))
return; return;
// No XP gain if master is a real player with XP gain disabled // no XP multiplier, when bot is in a group with a real player.
if (const Player* master = botAI->GetMaster())
{
if (WorldSession* masterSession = master->GetSession();
masterSession && !masterSession->IsBot() && master->HasPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN))
{
amount = 0; // disable XP multiplier
return;
}
}
// No XP multiplier if bot is in a group with at least one real player
if (Group* group = player->GetGroup()) if (Group* group = player->GetGroup())
{ {
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{ {
if (Player* member = gref->GetSource()) Player* member = gref->GetSource();
if (!member)
{ {
if (!member->GetSession()->IsBot()) continue;
return; }
if (!member->GetSession()->IsBot())
{
return;
} }
} }
} }
// Otherwise apply XP multiplier // otherwise apply bot XP multiplier.
amount = static_cast<uint32>(std::round(amount * sPlayerbotAIConfig->randomBotXPRate)); amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->randomBotXPRate));
} }
}; };
@@ -343,9 +331,6 @@ public:
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " "); LOG_INFO("server.loading", " ");
sPlayerbotSpellCache->Initialize();
LOG_INFO("server.loading", "Playerbots World Thread Processor initialized"); LOG_INFO("server.loading", "Playerbots World Thread Processor initialized");
} }

View File

@@ -1480,10 +1480,10 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
if (!sRandomPlayerbotMgr->IsRandomBot(player)) if (!sRandomPlayerbotMgr->IsRandomBot(player))
update = false; update = false;
if (player->GetGroup() && botAI->GetGroupLeader()) if (player->GetGroup() && botAI->GetGroupMaster())
{ {
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader()); PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer()) if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
{ {
update = false; update = false;
} }
@@ -2594,14 +2594,17 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
bool RandomPlayerbotMgr::IsRandomBot(Player* bot) bool RandomPlayerbotMgr::IsRandomBot(Player* bot)
{ {
if (!bot) if (bot && GET_PLAYERBOT_AI(bot))
return false; {
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
return false;
}
if (bot)
{
return IsRandomBot(bot->GetGUID().GetCounter());
}
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); return false;
if (!botAI || botAI->IsRealPlayer())
return false;
return IsRandomBot(bot->GetGUID().GetCounter());
} }
bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot) bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)

View File

@@ -1,45 +0,0 @@
#include "PlayerbotSpellCache.h"
void PlayerbotSpellCache::Initialize()
{
LOG_INFO("playerbots",
"Playerbots: ListSpellsAction caches initialized");
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
{
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
skillSpells[skillLine->Spell] = skillLine;
}
// Fill the vendorItems cache once from the world database.
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
if (results)
{
do
{
Field* fields = results->Fetch();
int32 entry = fields[0].Get<int32>();
if (entry <= 0)
continue;
vendorItems.insert(static_cast<uint32>(entry));
}
while (results->NextRow());
}
LOG_DEBUG("playerbots",
"ListSpellsAction: initialized caches (skillSpells={}, vendorItems={}).",
skillSpells.size(), vendorItems.size());
}
SkillLineAbilityEntry const* PlayerbotSpellCache::GetSkillLine(uint32 spellId) const
{
auto itr = skillSpells.find(spellId);
if (itr != skillSpells.end())
return itr->second;
return nullptr;
}
bool PlayerbotSpellCache::IsItemBuyable(uint32 itemId) const
{
return vendorItems.find(itemId) != vendorItems.end();
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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_PLAYERBOTSPELLCACHE_H
#define _PLAYERBOT_PLAYERBOTSPELLCACHE_H
#include "Playerbots.h"
class PlayerbotSpellCache
{
public:
static PlayerbotSpellCache* Instance()
{
static PlayerbotSpellCache instance;
return &instance;
}
void Initialize(); // call once on startup
SkillLineAbilityEntry const* GetSkillLine(uint32 spellId) const;
bool IsItemBuyable(uint32 itemId) const;
private:
PlayerbotSpellCache() = default;
std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
std::set<uint32> vendorItems;
};
#define sPlayerbotSpellCache PlayerbotSpellCache::Instance()
#endif

View File

@@ -4099,7 +4099,6 @@ void PlayerbotFactory::InitImmersive()
void PlayerbotFactory::InitArenaTeam() void PlayerbotFactory::InitArenaTeam()
{ {
if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId())) if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId()))
return; return;
@@ -4186,34 +4185,10 @@ void PlayerbotFactory::InitArenaTeam()
if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need? if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need?
{ {
// Add bot to arena team
arenateam->AddMember(bot->GetGUID()); arenateam->AddMember(bot->GetGUID());
arenateam->SaveToDB();
// Only synchronize ratings once the team is full (avoid redundant work)
// The captain was added with incorrect ratings when the team was created,
// so we fix everyone's ratings once the roster is complete
if (arenateam->GetMembersSize() >= (uint32)arenateam->GetType())
{
uint32 teamRating = arenateam->GetRating();
// Use SetRatingForAll to align all members with team rating
arenateam->SetRatingForAll(teamRating);
// For bot-only teams, keep MMR synchronized with team rating
// This ensures matchmaking reflects the artificial team strength (1000-2000 range)
// instead of being influenced by the global CONFIG_ARENA_START_MATCHMAKER_RATING
for (auto& member : arenateam->GetMembers())
{
// Set MMR to match personal rating (which already matches team rating)
member.MatchMakerRating = member.PersonalRating;
member.MaxMMR = std::max(member.MaxMMR, member.PersonalRating);
}
// Force save all member data to database
arenateam->SaveToDB(true);
}
} }
} }
arenateams.erase(arenateams.begin() + index); arenateams.erase(arenateams.begin() + index);
} }

View File

@@ -121,7 +121,7 @@ public:
creators["shoot"] = &ActionContext::shoot; creators["shoot"] = &ActionContext::shoot;
creators["follow"] = &ActionContext::follow; creators["follow"] = &ActionContext::follow;
creators["move from group"] = &ActionContext::move_from_group; creators["move from group"] = &ActionContext::move_from_group;
creators["flee to group leader"] = &ActionContext::flee_to_group_leader; creators["flee to master"] = &ActionContext::flee_to_master;
creators["runaway"] = &ActionContext::runaway; creators["runaway"] = &ActionContext::runaway;
creators["stay"] = &ActionContext::stay; creators["stay"] = &ActionContext::stay;
creators["sit"] = &ActionContext::sit; creators["sit"] = &ActionContext::sit;
@@ -318,7 +318,7 @@ private:
static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); } static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); }
static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); } static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); }
static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); } static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); }
static Action* flee_to_group_leader(PlayerbotAI* botAI) { return new FleeToGroupLeaderAction(botAI); } static Action* flee_to_master(PlayerbotAI* botAI) { return new FleeToMasterAction(botAI); }
static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); } static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); }
static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); } static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); }
static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); } static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); }

View File

@@ -15,19 +15,20 @@
#include "SharedDefines.h" #include "SharedDefines.h"
#include "Unit.h" #include "Unit.h"
bool AttackAction::Execute(Event /*event*/) bool AttackAction::Execute(Event event)
{ {
Unit* target = GetTarget(); Unit* target = GetTarget();
if (!target) if (!target)
return false; return false;
if (!target->IsInWorld()) if (!target->IsInWorld())
{
return false; return false;
}
return Attack(target); return Attack(target);
} }
bool AttackMyTargetAction::Execute(Event /*event*/) bool AttackMyTargetAction::Execute(Event event)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
@@ -50,7 +51,7 @@ bool AttackMyTargetAction::Execute(Event /*event*/)
return result; return result;
} }
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
{ {
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get(); Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot); bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
@@ -80,15 +81,12 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
{ {
if (verbose) if (verbose)
botAI->TellError(std::string(target->GetName()) + " is no longer in the world."); botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
return false; return false;
} }
// Check if bot OR target is in prohibited zone/area (skip for duels) if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
if ((target->IsPlayer() || target->IsPet()) && sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId()))
(!bot->duel || bot->duel->Opponent != target) && && (target->IsPlayer() || target->IsPet()))
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
{ {
if (verbose) if (verbose)
botAI->TellError("I cannot attack other players in PvP prohibited areas."); botAI->TellError("I cannot attack other players in PvP prohibited areas.");
@@ -100,7 +98,6 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
{ {
if (verbose) if (verbose)
botAI->TellError(std::string(target->GetName()) + " is friendly to me."); botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
return false; return false;
} }
@@ -108,7 +105,6 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
{ {
if (verbose) if (verbose)
botAI->TellError(std::string(target->GetName()) + " is dead."); botAI->TellError(std::string(target->GetName()) + " is dead.");
return false; return false;
} }
@@ -116,7 +112,6 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
{ {
if (verbose) if (verbose)
botAI->TellError(std::string(target->GetName()) + " is not in my sight."); botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
return false; return false;
} }
@@ -124,7 +119,6 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
{ {
if (verbose) if (verbose)
botAI->TellError("I am already attacking " + std::string(target->GetName()) + "."); botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
return false; return false;
} }
@@ -160,8 +154,9 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
} }
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target)) if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
{
sServerFacade->SetFacingTo(bot, target); sServerFacade->SetFacingTo(bot, target);
}
botAI->ChangeEngine(BOT_STATE_COMBAT); botAI->ChangeEngine(BOT_STATE_COMBAT);
bot->Attack(target, shouldMelee); bot->Attack(target, shouldMelee);
@@ -191,4 +186,4 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); } bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); } bool AttackDuelOpponentAction::Execute(Event event) { return Attack(AI_VALUE(Unit*, "duel target")); }

View File

@@ -311,7 +311,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target)
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos) bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
{ {
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
Player* groupLeader = botAI->GetGroupLeader(); Player* gmaster = botAI->GetGroupMaster();
Player* realMaster = botAI->GetMaster(); Player* realMaster = botAI->GetMaster();
AiObjectContext* context = botAI->GetAiObjectContext(); AiObjectContext* context = botAI->GetAiObjectContext();
@@ -327,30 +327,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
return false; return false;
} }
if (!groupLeader || bot == groupLeader) if (!gmaster || bot == gmaster)
return true; return true;
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
return true; return true;
if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2) if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2)
return false; return false;
Formation* formation = AI_VALUE(Formation*, "formation"); Formation* formation = AI_VALUE(Formation*, "formation");
float distance = groupLeader->GetDistance2d(pos.getX(), pos.getY()); float distance = gmaster->GetDistance2d(pos.getX(), pos.getY());
if (!botAI->HasActivePlayerMaster() && distance < 50.0f) if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
{ {
Player* player = groupLeader; Player* player = gmaster;
if (groupLeader && !groupLeader->isMoving() || if (gmaster && !gmaster->isMoving() ||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance) PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
return true; return true;
} }
if ((inDungeon || !groupLeader->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == groupLeader && distance > 5.0f) if ((inDungeon || !gmaster->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == gmaster && distance > 5.0f)
return false; return false;
if (!groupLeader->isMoving() && distance < 25.0f) if (!gmaster->isMoving() && distance < 25.0f)
return true; return true;
if (distance < formation->GetMaxDistance()) if (distance < formation->GetMaxDistance())

View File

@@ -180,7 +180,7 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget) void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
{ {
// Tell the master where we are going. // Tell the master where we are going.
if (!bot->GetGroup() || (botAI->GetGroupLeader() == bot)) if (!bot->GetGroup() || (botAI->GetGroupMaster() == bot))
ReportTravelTarget(newTarget, oldTarget); ReportTravelTarget(newTarget, oldTarget);
// If we are heading to a creature/npc clear it from the ignore list. // If we are heading to a creature/npc clear it from the ignore list.

View File

@@ -70,7 +70,7 @@ bool FollowAction::isUseful()
if (!target.empty()) if (!target.empty())
fTarget = AI_VALUE(Unit*, target); fTarget = AI_VALUE(Unit*, target);
else else
fTarget = AI_VALUE(Unit*, "group leader"); fTarget = AI_VALUE(Unit*, "master target");
if (fTarget) if (fTarget)
{ {
@@ -114,9 +114,9 @@ bool FollowAction::CanDeadFollow(Unit* target)
return true; return true;
} }
bool FleeToGroupLeaderAction::Execute(Event event) bool FleeToMasterAction::Execute(Event event)
{ {
Unit* fTarget = AI_VALUE(Unit*, "group leader"); Unit* fTarget = AI_VALUE(Unit*, "master target");
bool canFollow = Follow(fTarget); bool canFollow = Follow(fTarget);
if (!canFollow) if (!canFollow)
{ {
@@ -146,22 +146,22 @@ bool FleeToGroupLeaderAction::Execute(Event event)
return true; return true;
} }
bool FleeToGroupLeaderAction::isUseful() bool FleeToMasterAction::isUseful()
{ {
if (!botAI->GetGroupLeader()) if (!botAI->GetGroupMaster())
return false; return false;
if (botAI->GetGroupLeader() == bot) if (botAI->GetGroupMaster() == bot)
return false; return false;
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (target && botAI->GetGroupLeader()->GetTarget() == target->GetGUID()) if (target && botAI->GetGroupMaster()->GetTarget() == target->GetGUID())
return false; return false;
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
return false; return false;
Unit* fTarget = AI_VALUE(Unit*, "group leader"); Unit* fTarget = AI_VALUE(Unit*, "master target");
if (!CanDeadFollow(fTarget)) if (!CanDeadFollow(fTarget))
return false; return false;

View File

@@ -20,10 +20,10 @@ public:
bool CanDeadFollow(Unit* target); bool CanDeadFollow(Unit* target);
}; };
class FleeToGroupLeaderAction : public FollowAction class FleeToMasterAction : public FollowAction
{ {
public: public:
FleeToGroupLeaderAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to group leader") {} FleeToMasterAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to master") {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override; bool isUseful() override;

View File

@@ -141,7 +141,7 @@ bool InviteNearbyToGroupAction::isUseful()
if (group->isRaidGroup() && group->IsFull()) if (group->isRaidGroup() && group->IsFull())
return false; return false;
if (botAI->GetGroupLeader() != bot) if (botAI->GetGroupMaster() != bot)
return false; return false;
uint32 memberCount = group->GetMembersCount(); uint32 memberCount = group->GetMembersCount();

View File

@@ -109,22 +109,22 @@ bool LeaveFarAwayAction::isUseful()
if (!bot->GetGroup()) if (!bot->GetGroup())
return false; return false;
Player* groupLeader = botAI->GetGroupLeader(); Player* master = botAI->GetGroupMaster();
Player* trueMaster = botAI->GetMaster(); Player* trueMaster = botAI->GetMaster();
if (!groupLeader || (bot == groupLeader && !botAI->IsRealPlayer())) if (!master || (bot == master && !botAI->IsRealPlayer()))
return false; return false;
PlayerbotAI* groupLeaderBotAI = nullptr; PlayerbotAI* masterBotAI = nullptr;
if (groupLeader) if (master)
groupLeaderBotAI = GET_PLAYERBOT_AI(groupLeader); masterBotAI = GET_PLAYERBOT_AI(master);
if (groupLeader && !groupLeaderBotAI) if (master && !masterBotAI)
return false; return false;
if (trueMaster && !GET_PLAYERBOT_AI(trueMaster)) if (trueMaster && !GET_PLAYERBOT_AI(trueMaster))
return false; return false;
if (botAI->IsAlt() && if (botAI->IsAlt() &&
(!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player groupLeader. (!masterBotAI || masterBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player master.
return false; return false;
if (botAI->GetGrouperType() == GrouperType::SOLO) if (botAI->GetGrouperType() == GrouperType::SOLO)
@@ -138,19 +138,19 @@ bool LeaveFarAwayAction::isUseful()
if (dCount > 4 && !botAI->HasRealPlayerMaster()) if (dCount > 4 && !botAI->HasRealPlayerMaster())
return true; return true;
if (bot->GetGuildId() == groupLeader->GetGuildId()) if (bot->GetGuildId() == master->GetGuildId())
{ {
if (bot->GetLevel() > groupLeader->GetLevel() + 5) if (bot->GetLevel() > master->GetLevel() + 5)
{ {
if (AI_VALUE(bool, "should get money")) if (AI_VALUE(bool, "should get money"))
return false; return false;
} }
} }
if (abs(int32(groupLeader->GetLevel() - bot->GetLevel())) > 4) if (abs(int32(master->GetLevel() - bot->GetLevel())) > 4)
return true; return true;
if (bot->GetMapId() != groupLeader->GetMapId() || bot->GetDistance2d(groupLeader) >= 2 * sPlayerbotAIConfig->rpgDistance) if (bot->GetMapId() != master->GetMapId() || bot->GetDistance2d(master) >= 2 * sPlayerbotAIConfig->rpgDistance)
{ {
return true; return true;
} }

View File

@@ -7,43 +7,90 @@
#include "Event.h" #include "Event.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PlayerbotSpellCache.h"
using SpellListEntry = std::pair<uint32, std::string>; std::map<uint32, SkillLineAbilityEntry const*> ListSpellsAction::skillSpells;
std::set<uint32> ListSpellsAction::vendorItems;
// CHANGE: Simplified and cheap comparator used in MapUpdater worker thread. bool CompareSpells(const std::pair<uint32, std::string>& s1, const std::pair<uint32, std::string>& s2)
// It now avoids scanning the entire SkillLineAbilityStore for each comparison
// and only relies on spell school and spell name to keep sorting fast and bounded.
// lhs = the left element, rhs = the right element.
static bool CompareSpells(SpellListEntry const& lhSpell, SpellListEntry const& rhSpell)
{ {
SpellInfo const* lhSpellInfo = sSpellMgr->GetSpellInfo(lhSpell.first); SpellInfo const* si1 = sSpellMgr->GetSpellInfo(s1.first);
SpellInfo const* rhSpellInfo = sSpellMgr->GetSpellInfo(rhSpell.first); SpellInfo const* si2 = sSpellMgr->GetSpellInfo(s2.first);
if (!si1 || !si2)
if (!lhSpellInfo || !rhSpellInfo)
{ {
LOG_ERROR("playerbots", "SpellInfo missing for spell {} or {}", lhSpell.first, rhSpell.first); LOG_ERROR("playerbots", "SpellInfo missing. {} {}", s1.first, s2.first);
// Fallback: order by spell id to keep comparator strict and deterministic. return false;
return lhSpell.first < rhSpell.first; }
uint32 p1 = si1->SchoolMask * 20000;
uint32 p2 = si2->SchoolMask * 20000;
uint32 skill1 = 0, skill2 = 0;
uint32 skillValue1 = 0, skillValue2 = 0;
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
{
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
{
if (skillLine->Spell == s1.first)
{
skill1 = skillLine->SkillLine;
skillValue1 = skillLine->TrivialSkillLineRankLow;
}
if (skillLine->Spell == s2.first)
{
skill2 = skillLine->SkillLine;
skillValue2 = skillLine->TrivialSkillLineRankLow;
}
}
if (skill1 && skill2)
break;
} }
uint32 lhsKey = lhSpellInfo->SchoolMask; p1 += skill1 * 500;
uint32 rhsKey = rhSpellInfo->SchoolMask; p2 += skill2 * 500;
if (lhsKey == rhsKey) p1 += skillValue1;
p2 += skillValue2;
if (p1 == p2)
{ {
// Defensive check: if DBC data is broken and spell names are nullptr, return strcmp(si1->SpellName[0], si2->SpellName[0]) > 0;
// fall back to id ordering instead of risking a crash in std::strcmp.
if (!lhSpellInfo->SpellName[0] || !rhSpellInfo->SpellName[0])
return lhSpell.first < rhSpell.first;
return std::strcmp(lhSpellInfo->SpellName[0], rhSpellInfo->SpellName[0]) > 0;
} }
return lhsKey > rhsKey;
return p1 > p2;
} }
std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::string filter) std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::string filter)
{ {
if (skillSpells.empty())
{
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
{
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
skillSpells[skillLine->Spell] = skillLine;
}
}
if (vendorItems.empty())
{
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
if (results)
{
do
{
Field* fields = results->Fetch();
int32 entry = fields[0].Get<int32>();
if (entry <= 0)
continue;
vendorItems.insert(entry);
} while (results->NextRow());
}
}
std::ostringstream posOut;
std::ostringstream negOut;
uint32 skill = 0; uint32 skill = 0;
std::vector<std::string> ss = split(filter, ' '); std::vector<std::string> ss = split(filter, ' ');
@@ -52,15 +99,13 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
skill = chat->parseSkill(ss[0]); skill = chat->parseSkill(ss[0]);
if (skill != SKILL_NONE) if (skill != SKILL_NONE)
{ {
filter = ss.size() > 1 ? ss[1] : ""; filter = ss.size() > 1 ? filter = ss[1] : "";
} }
// Guard access to ss[1]/ss[2] to avoid out-of-bounds if (ss[0] == "first" && ss[1] == "aid")
// when the player only types "first" without "aid".
if (ss[0] == "first" && ss.size() > 1 && ss[1] == "aid")
{ {
skill = SKILL_FIRST_AID; skill = SKILL_FIRST_AID;
filter = ss.size() > 2 ? ss[2] : ""; filter = ss.size() > 2 ? filter = ss[2] : "";
} }
} }
@@ -70,57 +115,26 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
uint32 minLevel = 0; uint32 minLevel = 0;
uint32 maxLevel = 0; uint32 maxLevel = 0;
if (filter.find('-') != std::string::npos) if (filter.find("-") != std::string::npos)
{ {
std::vector<std::string> ff = split(filter, '-'); std::vector<std::string> ff = split(filter, '-');
if (ff.size() >= 2) minLevel = atoi(ff[0].c_str());
{ maxLevel = atoi(ff[1].c_str());
minLevel = std::atoi(ff[0].c_str()); filter = "";
maxLevel = std::atoi(ff[1].c_str());
if (minLevel > maxLevel)
std::swap(minLevel, maxLevel);
}
filter.clear();
} }
bool canCraftNow = false; bool craftableOnly = false;
if (filter.find('+') != std::string::npos) if (filter.find("+") != std::string::npos)
{ {
canCraftNow = true; craftableOnly = true;
// Support "+<skill>" syntax (e.g. "spells +tailoring" or "spells tailoring+").
// If no explicit skill was detected yet, try to parse the filter (without '+')
// as a profession/skill name so that craftable-only filters still work with skills.
if (skill == SKILL_NONE)
{
std::string skillFilter = filter;
// Remove '+' before trying to interpret the first token as a skill name.
skillFilter.erase(remove(skillFilter.begin(), skillFilter.end(), '+'), skillFilter.end());
std::vector<std::string> skillTokens = split(skillFilter, ' ');
if (!skillTokens.empty())
{
uint32 parsedSkill = chat->parseSkill(skillTokens[0]);
if (parsedSkill != SKILL_NONE)
{
skill = parsedSkill;
// Any remaining text after the skill token becomes the "name" filter
// (e.g. "spells +tailoring cloth" -> skill = tailoring, filter = "cloth").
filter = skillTokens.size() > 1 ? skillTokens[1] : "";
}
}
}
// Finally remove '+' from the filter that will be used for name/range parsing.
filter.erase(remove(filter.begin(), filter.end(), '+'), filter.end()); filter.erase(remove(filter.begin(), filter.end(), '+'), filter.end());
} }
uint32 slot = chat->parseSlot(filter); uint32 slot = chat->parseSlot(filter);
if (slot != EQUIPMENT_SLOT_END) if (slot != EQUIPMENT_SLOT_END)
filter.clear(); filter = "";
std::vector<SpellListEntry> spells; std::vector<std::pair<uint32, std::string>> spells;
for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr) for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr)
{ {
if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active) if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active)
@@ -136,7 +150,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
if (spellInfo->IsPassive()) if (spellInfo->IsPassive())
continue; continue;
SkillLineAbilityEntry const* skillLine = sPlayerbotSpellCache->GetSkillLine(itr->first); SkillLineAbilityEntry const* skillLine = skillSpells[itr->first];
if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill)) if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill))
continue; continue;
@@ -148,7 +162,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
continue; continue;
bool first = true; bool first = true;
int32 craftsPossible = -1; int32 craftCount = -1;
std::ostringstream materials; std::ostringstream materials;
for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x) for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x)
{ {
@@ -175,12 +189,12 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
FindItemByIdVisitor visitor(itemid); FindItemByIdVisitor visitor(itemid);
uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor); uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor);
bool buyable = sPlayerbotSpellCache->IsItemBuyable(itemid); bool buyable = (vendorItems.find(itemid) != vendorItems.end());
if (!buyable) if (!buyable)
{ {
uint32 craftable = reagentsInInventory / reagentsRequired; uint32 craftable = reagentsInInventory / reagentsRequired;
if (craftsPossible < 0 || craftsPossible > static_cast<int32>(craftable)) if (craftCount < 0 || craftCount > craftable)
craftsPossible = static_cast<int32>(craftable); craftCount = craftable;
} }
if (reagentsInInventory) if (reagentsInInventory)
@@ -191,8 +205,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
} }
} }
if (craftsPossible < 0) if (craftCount < 0)
craftsPossible = 0; craftCount = 0;
std::ostringstream out; std::ostringstream out;
bool filtered = false; bool filtered = false;
@@ -204,8 +218,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
{ {
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(spellInfo->Effects[i].ItemType)) if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(spellInfo->Effects[i].ItemType))
{ {
if (craftsPossible) if (craftCount)
out << "|cffffff00(x" << craftsPossible << ")|r "; out << "|cffffff00(x" << craftCount << ")|r ";
out << chat->FormatItem(proto); out << chat->FormatItem(proto);
@@ -232,7 +246,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
if (filtered) if (filtered)
continue; continue;
if (canCraftNow && !craftsPossible) if (craftableOnly && !craftCount)
continue; continue;
out << materials.str(); out << materials.str();
@@ -261,9 +275,10 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
continue; continue;
if (itr->first == 0) if (itr->first == 0)
{
LOG_ERROR("playerbots", "?! {}", itr->first); LOG_ERROR("playerbots", "?! {}", itr->first);
}
spells.emplace_back(itr->first, out.str()); spells.push_back(std::pair<uint32, std::string>(itr->first, out.str()));
alreadySeenList += spellInfo->SpellName[0]; alreadySeenList += spellInfo->SpellName[0];
alreadySeenList += ","; alreadySeenList += ",";
} }
@@ -279,28 +294,25 @@ bool ListSpellsAction::Execute(Event event)
std::string const filter = event.getParam(); std::string const filter = event.getParam();
std::vector<SpellListEntry> spells = GetSpellList(filter); std::vector<std::pair<uint32, std::string>> spells = GetSpellList(filter);
if (spells.empty())
{
// CHANGE: Give early feedback when no spells match the filter.
botAI->TellMaster("No spells found.");
return true;
}
botAI->TellMaster("=== Spells ==="); botAI->TellMaster("=== Spells ===");
std::sort(spells.begin(), spells.end(), CompareSpells); std::sort(spells.begin(), spells.end(), CompareSpells);
// CHANGE: Send the full spell list again so client-side addons uint32 count = 0;
// (e.g. Multibot / Unbot) can reconstruct the for (std::vector<std::pair<uint32, std::string>>::iterator i = spells.begin(); i != spells.end(); ++i)
// complete spellbook for configuration. The heavy part that caused {
// freezes before was the old CompareSpells implementation scanning
// the entire SkillLineAbility DBC on every comparison. With the new
// cheap comparator above, sending all lines here is safe and keeps
// behaviour compatible with existing addons.
for (std::vector<SpellListEntry>::const_iterator i = spells.begin(); i != spells.end(); ++i)
botAI->TellMasterNoFacing(i->second); botAI->TellMasterNoFacing(i->second);
// if (++count >= 50)
// {
// std::ostringstream msg;
// msg << (spells.size() - 50) << " more...";
// botAI->TellMasterNoFacing(msg.str());
// break;
// }
}
return true; return true;
} }

View File

@@ -18,8 +18,9 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
virtual std::vector<std::pair<uint32, std::string>> GetSpellList(std::string filter = ""); virtual std::vector<std::pair<uint32, std::string>> GetSpellList(std::string filter = "");
static void InitSpellCaches(); private:
static std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
static std::set<uint32> vendorItems;
}; };
#endif #endif

View File

@@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
WorldLocation location = *target->getPosition(); WorldLocation location = *target->getPosition();
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (group && !urand(0, 1) && bot == botAI->GetGroupLeader() && !bot->IsInCombat()) if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat())
{ {
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {

View File

@@ -192,23 +192,30 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
{ {
return false; return false;
} }
bool generatePath = !bot->IsFlying() && !bot->isSwimming(); bool generatePath = !bot->IsFlying() && !bot->isSwimming();
bool disableMoveSplinePath = bool disableMoveSplinePath = sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
sPlayerbotAIConfig->disableMoveSplinePath >= 2 || (sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
if (Vehicle* vehicle = bot->GetVehicle()) if (Vehicle* vehicle = bot->GetVehicle())
{ {
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot); VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
Unit* vehicleBase = vehicle->GetBase(); Unit* vehicleBase = vehicle->GetBase();
generatePath = !vehicleBase || !vehicleBase->CanFly(); generatePath = vehicleBase->CanFly();
if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway
return false; return false;
float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot
if (distance > 0.01f) if (distance > 0.01f)
{ {
DoMovePoint(vehicleBase, x, y, z, generatePath, backwards); MotionMaster& mm = *vehicleBase->GetMotionMaster(); // need to move vehicle, not bot
mm.Clear();
if (!backwards)
{
mm.MovePoint(0, x, y, z, generatePath);
}
else
{
mm.MovePointBackwards(0, x, y, z, generatePath);
}
float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN); float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN);
float delay = 1000.0f * (distance / speed); float delay = 1000.0f * (distance / speed);
if (lessDelay) if (lessDelay)
@@ -234,7 +241,16 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop(); // bot->CastStop();
// botAI->InterruptSpell(); // botAI->InterruptSpell();
// } // }
DoMovePoint(bot, x, y, z, generatePath, backwards); MotionMaster& mm = *bot->GetMotionMaster();
mm.Clear();
if (!backwards)
{
mm.MovePoint(0, x, y, z, generatePath);
}
else
{
mm.MovePointBackwards(0, x, y, z, generatePath);
}
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
{ {
@@ -252,7 +268,9 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
Movement::PointsArray path = Movement::PointsArray path =
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only); SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only);
if (modifiedZ == INVALID_HEIGHT) if (modifiedZ == INVALID_HEIGHT)
{
return false; return false;
}
float distance = bot->GetExactDist(x, y, modifiedZ); float distance = bot->GetExactDist(x, y, modifiedZ);
if (distance > 0.01f) if (distance > 0.01f)
{ {
@@ -264,8 +282,17 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop(); // bot->CastStop();
// botAI->InterruptSpell(); // botAI->InterruptSpell();
// } // }
MotionMaster& mm = *bot->GetMotionMaster();
G3D::Vector3 endP = path.back(); G3D::Vector3 endP = path.back();
DoMovePoint(bot, x, y, z, generatePath, backwards); mm.Clear();
if (!backwards)
{
mm.MovePoint(0, x, y, z, generatePath);
}
else
{
mm.MovePointBackwards(0, x, y, z, generatePath);
}
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
{ {
@@ -482,8 +509,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// { // {
// AI_VALUE(LastMovement&, "last area trigger").lastAreaTrigger = entry; // AI_VALUE(LastMovement&, "last area trigger").lastAreaTrigger = entry;
// } // }
// else // else {
// {
// LOG_DEBUG("playerbots", "!entry"); // LOG_DEBUG("playerbots", "!entry");
// return bot->TeleportTo(movePosition.getMapId(), movePosition.getX(), movePosition.getY(), // return bot->TeleportTo(movePosition.getMapId(), movePosition.getX(), movePosition.getY(),
// movePosition.getZ(), movePosition.getO(), 0); // movePosition.getZ(), movePosition.getO(), 0);
@@ -533,7 +559,9 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1); // bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1);
// if (botAI->HasCheat(BotCheatMask::gold)) // if (botAI->HasCheat(BotCheatMask::gold))
// {
// bot->SetMoney(botMoney); // bot->SetMoney(botMoney);
// }
// LOG_DEBUG("playerbots", "goTaxi"); // LOG_DEBUG("playerbots", "goTaxi");
// return goTaxi; // return goTaxi;
// } // }
@@ -946,129 +974,78 @@ bool MovementAction::IsWaitingForLastMove(MovementPriority priority)
bool MovementAction::IsMovingAllowed() bool MovementAction::IsMovingAllowed()
{ {
// Most common checks: confused, stunned, fleeing, jumping, charging. All these // do not allow if not vehicle driver
// states are set when handling certain aura effects. We don't check against if (botAI->IsInVehicle() && !botAI->IsInVehicle(true))
// UNIT_STATE_ROOT here, because this state is used by vehicles.
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false; return false;
// Death state (w/o spirit release) and Spirit of Redemption aura (priest) if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
if ((bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || bot->HasSpiritOfRedemptionAura()) bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false; return false;
// Common CC effects, ordered by frequency: rooted > frozen > polymorphed
if (bot->IsRooted() || bot->isFrozen() || bot->IsPolymorphed())
return false;
// Check for the MM controlled slot types: feared, confused, fleeing, etc.
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE) if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
{
return false; return false;
}
// Traveling state: taxi flight and being teleported (relatively rare) // if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING)) {
if (bot->IsInFlight() || bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE || // return false;
bot->IsBeingTeleported()) // }
return false; return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
// Vehicle state: is in the vehicle and can control it (rare, content-specific).
// We need to check charmed state AFTER vehicle one, cuz that's how it works:
// passengers are set to charmed by vehicle with CHARM_TYPE_VEHICLE.
if ((bot->GetVehicle() && !botAI->IsInVehicle(true)) || bot->IsCharmed())
return false;
return true;
} }
bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); } bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); }
void MovementAction::UpdateMovementState() void MovementAction::UpdateMovementState()
{ {
const bool isCurrentlyRestricted = // see if the bot is currently slowed, rooted, or otherwise unable to move int8 botInLiquidState = bot->GetLiquidData().Status;
bot->HasUnitState(UNIT_STATE_LOST_CONTROL) ||
bot->IsRooted() ||
bot->isFrozen() ||
bot->IsPolymorphed();
// no update movement flags while movement is current restricted. if (botInLiquidState == LIQUID_MAP_IN_WATER || botInLiquidState == LIQUID_MAP_UNDER_WATER)
if (!isCurrentlyRestricted && bot->IsAlive())
{ {
// state flags bot->SetSwim(true);
const auto master = botAI ? botAI->GetMaster() : nullptr; // real player or not }
const bool masterIsFlying = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) : true; else
const bool masterIsSwimming = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) : true; {
const auto liquidState = bot->GetLiquidData().Status; // default LIQUID_MAP_NO_WATER bot->SetSwim(false);
const float gZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
const bool wantsToFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
const bool isWaterArea = liquidState != LIQUID_MAP_NO_WATER;
const bool isUnderWater = liquidState == LIQUID_MAP_UNDER_WATER;
const bool isInWater = liquidState == LIQUID_MAP_IN_WATER;
const bool isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
const bool isSwimming = bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
const bool wantsToWaterWalk = bot->HasWaterWalkAura();
const bool wantsToSwim = isInWater || isUnderWater;
const bool onGroundZ = (bot->GetPositionZ() < gZ + 1.f) && !isWaterArea;
bool movementFlagsUpdated = false;
// handle water state
if (isWaterArea && !isFlying)
{
// water walking
if (wantsToWaterWalk && !isWaterWalking && !masterIsSwimming)
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
bot->AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
movementFlagsUpdated = true;
}
// swimming
else if (wantsToSwim && !isSwimming && masterIsSwimming)
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
bot->AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
movementFlagsUpdated = true;
}
}
else if (isSwimming || isWaterWalking)
{
// reset water flags
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
movementFlagsUpdated = true;
}
// handle flying state
if (wantsToFly && !isFlying && masterIsFlying)
{
bot->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
bot->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
movementFlagsUpdated = true;
}
else if ((!wantsToFly || onGroundZ) && isFlying)
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
movementFlagsUpdated = true;
}
// detect if movement restrictions have been lifted, CC just ended.
if (wasMovementRestricted)
movementFlagsUpdated = true; // refresh movement state to ensure animations play correctly
if (movementFlagsUpdated)
bot->SendMovementFlagUpdate();
} }
// Save current state for the next check bool onGround = bot->GetPositionZ() <
bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()) + 1.0f;
// Keep bot->SendMovementFlagUpdate() withing the if statements to not intefere with bot behavior on
// ground/(shallow) waters
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) &&
bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !onGround)
{
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
bot->SendMovementFlagUpdate();
}
else if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) &&
(!bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || onGround))
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
bot->SendMovementFlagUpdate();
}
// See if the bot is currently slowed, rooted, or otherwise unable to move
bool isCurrentlyRestricted = bot->isFrozen() || bot->IsPolymorphed() || bot->HasRootAura() || bot->HasStunAura() ||
bot->HasConfuseAura() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
// Detect if movement restrictions have been lifted
if (wasMovementRestricted && !isCurrentlyRestricted && bot->IsAlive())
{
// CC just ended - refresh movement state to ensure animations play correctly
bot->SendMovementFlagUpdate();
}
// Save current state for the next check
wasMovementRestricted = isCurrentlyRestricted; wasMovementRestricted = isCurrentlyRestricted;
// Temporary speed increase in group // Temporary speed increase in group
// if (botAI->HasRealPlayerMaster()) // if (botAI->HasRealPlayerMaster()) {
// {
// bot->SetSpeedRate(MOVE_RUN, 1.1f); // bot->SetSpeedRate(MOVE_RUN, 1.1f);
// } // } else {
// else
// {
// bot->SetSpeedRate(MOVE_RUN, 1.0f); // bot->SetSpeedRate(MOVE_RUN, 1.0f);
// } // }
// check if target is not reachable (from Vmangos) // check if target is not reachable (from Vmangos)
@@ -1077,7 +1054,7 @@ void MovementAction::UpdateMovementState()
// { // {
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot)) // if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
// { // {
// if (!bot->IsWithinMeleeRange(pTarget) && pTarget->IsCreature()) // if (!bot->IsWithinMeleeRange(pTarget) && pTarget->GetTypeId() == TYPEID_UNIT)
// { // {
// float angle = bot->GetAngle(pTarget); // float angle = bot->GetAngle(pTarget);
// float distance = 5.0f; // float distance = 5.0f;
@@ -1111,7 +1088,7 @@ void MovementAction::UpdateMovementState()
// { // {
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot)) // if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
// { // {
// if (pTarget != botAI->GetGroupLeader()) // if (pTarget != botAI->GetGroupMaster())
// return; // return;
// if (!bot->IsWithinMeleeRange(pTarget)) // if (!bot->IsWithinMeleeRange(pTarget))
@@ -1705,8 +1682,7 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
// float MovementAction::SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range, bool // float MovementAction::SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range, bool
// normal_only, float step) // normal_only, float step)
// { // {
// if (!generatePath) // if (!generatePath) {
// {
// return z; // return z;
// } // }
// float min_length = 100000.0f; // float min_length = 100000.0f;
@@ -1717,12 +1693,10 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta); // modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
// PathGenerator gen(bot); // PathGenerator gen(bot);
// gen.CalculatePath(x, y, modified_z); // gen.CalculatePath(x, y, modified_z);
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) // if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) {
// {
// min_length = gen.getPathLength(); // min_length = gen.getPathLength();
// current_z = modified_z; // current_z = modified_z;
// if (abs(current_z - z) < 0.5f) // if (abs(current_z - z) < 0.5f) {
// {
// return current_z; // return current_z;
// } // }
// } // }
@@ -1731,34 +1705,30 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta); // modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
// PathGenerator gen(bot); // PathGenerator gen(bot);
// gen.CalculatePath(x, y, modified_z); // gen.CalculatePath(x, y, modified_z);
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) // if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) {
// {
// min_length = gen.getPathLength(); // min_length = gen.getPathLength();
// current_z = modified_z; // current_z = modified_z;
// if (abs(current_z - z) < 0.5f) // if (abs(current_z - z) < 0.5f) {
// return current_z; // return current_z;
// }
// } // }
// } // }
// for (delta = range / 2 + step; delta <= range; delta += 2) { // for (delta = range / 2 + step; delta <= range; delta += 2) {
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta); // modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
// PathGenerator gen(bot); // PathGenerator gen(bot);
// gen.CalculatePath(x, y, modified_z); // gen.CalculatePath(x, y, modified_z);
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) // if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) {
// {
// min_length = gen.getPathLength(); // min_length = gen.getPathLength();
// current_z = modified_z; // current_z = modified_z;
// if (abs(current_z - z) < 0.5f) // if (abs(current_z - z) < 0.5f) {
// {
// return current_z; // return current_z;
// } // }
// } // }
// } // }
// if (current_z == INVALID_HEIGHT && normal_only) // if (current_z == INVALID_HEIGHT && normal_only) {
// {
// return INVALID_HEIGHT; // return INVALID_HEIGHT;
// } // }
// if (current_z == INVALID_HEIGHT && !normal_only) // if (current_z == INVALID_HEIGHT && !normal_only) {
// {
// return z; // return z;
// } // }
// return current_z; // return current_z;
@@ -1833,46 +1803,6 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
return result; return result;
} }
void MovementAction::DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards)
{
if (!unit)
return;
MotionMaster* mm = unit->GetMotionMaster();
if (!mm)
return;
// enable water walking
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING))
{
float gZ = unit->GetMapWaterOrGroundLevel(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ());
unit->UpdatePosition(unit->GetPositionX(), unit->GetPositionY(), gZ, false);
// z = gZ; no overwrite Z axe otherwise you cant steer the bots into swimming when water walking.
}
mm->Clear();
if (backwards)
{
mm->MovePointBackwards(
/*id*/ 0,
/*coords*/ x, y, z,
/*generatePath*/ generatePath,
/*forceDestination*/ false);
return;
}
else
{
mm->MovePoint(
/*id*/ 0,
/*coords*/ x, y, z,
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
/*speed*/ 0.f,
/*orientation*/ 0.f,
/*generatePath*/ generatePath, // true => terrain path (2d mmap); false => straight spline (3d vmap)
/*forceDestination*/ false);
}
}
bool FleeAction::Execute(Event event) bool FleeAction::Execute(Event event)
{ {
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true); return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
@@ -2133,8 +2063,8 @@ Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
if (currentTarget) if (currentTarget)
{ {
// Normally, move to left or right is the best position // Normally, move to left or right is the best position
bool isTanking = (!currentTarget->isFrozen() bool isTanking =
&& !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot); (!currentTarget->isFrozen() && !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
float angle = bot->GetAngle(currentTarget); float angle = bot->GetAngle(currentTarget);
float angleLeft = angle + (float)M_PI / 2; float angleLeft = angle + (float)M_PI / 2;
float angleRight = angle - (float)M_PI / 2; float angleRight = angle - (float)M_PI / 2;
@@ -2550,7 +2480,9 @@ bool RearFlankAction::isUseful()
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (!target) if (!target)
{
return false; return false;
}
// Need to double the front angle check to account for mirrored angle. // Need to double the front angle check to account for mirrored angle.
bool inFront = target->HasInArc(2.f * minAngle, bot); bool inFront = target->HasInArc(2.f * minAngle, bot);
@@ -2565,7 +2497,9 @@ bool RearFlankAction::Execute(Event event)
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (!target) if (!target)
{
return false; return false;
}
float angle = frand(minAngle, maxAngle); float angle = frand(minAngle, maxAngle);
float baseDistance = bot->GetMeleeRange(target) * 0.5f; float baseDistance = bot->GetMeleeRange(target) * 0.5f;
@@ -2672,7 +2606,7 @@ bool DisperseSetAction::Execute(Event event)
return true; return true;
} }
bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "group leader")); } bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "master target")); }
bool MoveToLootAction::Execute(Event event) bool MoveToLootAction::Execute(Event event)
{ {
@@ -2859,7 +2793,7 @@ bool MoveAwayFromCreatureAction::Execute(Event event)
// Find all creatures with the specified Id // Find all creatures with the specified Id
std::vector<Unit*> creatures; std::vector<Unit*> creatures;
for (auto const& guid : targets) for (const auto& guid : targets)
{ {
Unit* unit = botAI->GetUnit(guid); Unit* unit = botAI->GetUnit(guid);
if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId) if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId)

View File

@@ -18,14 +18,14 @@ bool PetsAction::Execute(Event event)
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.) // Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
std::string param = event.getParam(); std::string param = event.getParam();
if (param.empty() && !defaultCmd.empty()) if (param.empty() && !defaultCmd.empty())
{
param = defaultCmd; param = defaultCmd;
}
if (param.empty()) if (param.empty())
{ {
// If no parameter is provided, show usage instructions and return. // If no parameter is provided, show usage instructions and return.
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
"pet_usage_error", "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", {});
botAI->TellError(text);
return false; return false;
} }
@@ -52,9 +52,7 @@ bool PetsAction::Execute(Event event)
// If no pets or guardians are found, notify and return. // If no pets or guardians are found, notify and return.
if (targets.empty()) if (targets.empty())
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( botAI->TellError("You have no pet or guardian pet.");
"pet_no_pet_error", "You have no pet or guardian pet.", {});
botAI->TellError(text);
return false; return false;
} }
@@ -65,54 +63,42 @@ bool PetsAction::Execute(Event event)
if (param == "aggressive") if (param == "aggressive")
{ {
react = REACT_AGGRESSIVE; react = REACT_AGGRESSIVE;
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( stanceText = "aggressive";
"pet_stance_aggressive", "aggressive", {});
} }
else if (param == "defensive") else if (param == "defensive")
{ {
react = REACT_DEFENSIVE; react = REACT_DEFENSIVE;
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( stanceText = "defensive";
"pet_stance_defensive", "defensive", {});
} }
else if (param == "passive") else if (param == "passive")
{ {
react = REACT_PASSIVE; react = REACT_PASSIVE;
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( stanceText = "passive";
"pet_stance_passive", "passive", {});
} }
// The "stance" command simply reports the current stance of each pet/guardian. // The "stance" command simply reports the current stance of each pet/guardian.
else if (param == "stance") else if (param == "stance")
{ {
for (Creature* target : targets) for (Creature* target : targets)
{ {
std::string type = target->IsPet() ? std::string type = target->IsPet() ? "pet" : "guardian";
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_pet", "pet", {}) :
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_guardian", "guardian", {});
std::string name = target->GetName(); std::string name = target->GetName();
std::string stance; std::string stance;
switch (target->GetReactState()) switch (target->GetReactState())
{ {
case REACT_AGGRESSIVE: case REACT_AGGRESSIVE:
stance = sPlayerbotTextMgr->GetBotTextOrDefault( stance = "aggressive";
"pet_stance_aggressive", "aggressive", {});
break; break;
case REACT_DEFENSIVE: case REACT_DEFENSIVE:
stance = sPlayerbotTextMgr->GetBotTextOrDefault( stance = "defensive";
"pet_stance_defensive", "defensive", {});
break; break;
case REACT_PASSIVE: case REACT_PASSIVE:
stance = sPlayerbotTextMgr->GetBotTextOrDefault( stance = "passive";
"pet_stance_passive", "passive", {});
break; break;
default: default:
stance = sPlayerbotTextMgr->GetBotTextOrDefault( stance = "unknown";
"pet_stance_unknown", "unknown", {});
break; break;
} }
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + ".");
"pet_stance_report", "Current stance of %type \"%name\": %stance.",
{{"type", type}, {"name", name}, {"stance", stance}});
botAI->TellMaster(text);
} }
return true; return true;
} }
@@ -127,38 +113,25 @@ bool PetsAction::Execute(Event event)
{ {
ObjectGuid masterTargetGuid = master->GetTarget(); ObjectGuid masterTargetGuid = master->GetTarget();
if (!masterTargetGuid.IsEmpty()) if (!masterTargetGuid.IsEmpty())
{
targetUnit = botAI->GetUnit(masterTargetGuid); targetUnit = botAI->GetUnit(masterTargetGuid);
}
} }
// If no valid target is selected, show an error and return. // If no valid target is selected, show an error and return.
if (!targetUnit) if (!targetUnit)
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( botAI->TellError("No valid target selected by master.");
"pet_no_target_error", "No valid target selected by master.", {});
botAI->TellError(text);
return false; return false;
} }
if (!targetUnit->IsAlive()) if (!targetUnit->IsAlive())
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( botAI->TellError("Target is not alive.");
"pet_target_dead_error", "Target is not alive.", {});
botAI->TellError(text);
return false; return false;
} }
if (!bot->IsValidAttackTarget(targetUnit)) if (!bot->IsValidAttackTarget(targetUnit))
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( botAI->TellError("Target is not a valid attack target for the bot.");
"pet_invalid_target_error", "Target is not a valid attack target for the bot.", {});
botAI->TellError(text);
return false;
}
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) &&
(targetUnit->IsPlayer() || targetUnit->IsPet()) &&
(!bot->duel || bot->duel->Opponent != targetUnit))
{
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
"pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {});
botAI->TellError(text);
return false; return false;
} }
@@ -209,17 +182,9 @@ bool PetsAction::Execute(Event event)
} }
// Inform the master if the command succeeded or failed. // Inform the master if the command succeeded or failed.
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1) if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1)
{ botAI->TellMaster("Pet commanded to attack your target.");
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
"pet_attack_success", "Pet commanded to attack your target.", {});
botAI->TellMaster(text);
}
else if (!didAttack) else if (!didAttack)
{ botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)");
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
"pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {});
botAI->TellError(text);
}
return didAttack; return didAttack;
} }
// The "follow" command makes all pets/guardians follow the bot. // The "follow" command makes all pets/guardians follow the bot.
@@ -227,11 +192,7 @@ bool PetsAction::Execute(Event event)
{ {
botAI->PetFollow(); botAI->PetFollow();
if (sPlayerbotAIConfig->petChatCommandDebug == 1) if (sPlayerbotAIConfig->petChatCommandDebug == 1)
{ botAI->TellMaster("Pet commanded to follow.");
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
"pet_follow_success", "Pet commanded to follow.", {});
botAI->TellMaster(text);
}
return true; return true;
} }
// The "stay" command causes all pets/guardians to stop and stay in place. // The "stay" command causes all pets/guardians to stop and stay in place.
@@ -268,20 +229,14 @@ bool PetsAction::Execute(Event event)
} }
} }
if (sPlayerbotAIConfig->petChatCommandDebug == 1) if (sPlayerbotAIConfig->petChatCommandDebug == 1)
{ botAI->TellMaster("Pet commanded to stay.");
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
"pet_stay_success", "Pet commanded to stay.", {});
botAI->TellMaster(text);
}
return true; return true;
} }
// Unknown command: show usage instructions and return. // Unknown command: show usage instructions and return.
else else
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( botAI->TellError("Unknown pet command: " + param +
"pet_unknown_command_error", "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>", ". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
{{"param", param}});
botAI->TellError(text);
return false; return false;
} }
@@ -296,12 +251,7 @@ bool PetsAction::Execute(Event event)
// Inform the master of the new stance if debug is enabled. // Inform the master of the new stance if debug is enabled.
if (sPlayerbotAIConfig->petChatCommandDebug == 1) if (sPlayerbotAIConfig->petChatCommandDebug == 1)
{ botAI->TellMaster("Pet stance set to " + stanceText + ".");
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
"pet_stance_set_success", "Pet stance set to %stance.",
{{"stance", stanceText}});
botAI->TellMaster(text);
}
return true; return true;
} }

View File

@@ -13,10 +13,10 @@ bool RandomBotUpdateAction::Execute(Event event)
if (!sRandomPlayerbotMgr->IsRandomBot(bot)) if (!sRandomPlayerbotMgr->IsRandomBot(bot))
return false; return false;
if (bot->GetGroup() && botAI->GetGroupLeader()) if (bot->GetGroup() && botAI->GetGroupMaster())
{ {
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader()); PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer()) if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
return true; return true;
} }

View File

@@ -168,15 +168,15 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
if (!bot->GetGroup()) if (!bot->GetGroup())
return true; return true;
Player* groupLeader = botAI->GetGroupLeader(); Player* groupMaster = botAI->GetGroupMaster();
if (!groupLeader || groupLeader == bot) if (!groupMaster || groupMaster == bot)
return true; return true;
if (!botAI->HasActivePlayerMaster()) if (!botAI->HasActivePlayerMaster())
return true; return true;
if (botAI->HasActivePlayerMaster() && if (botAI->HasActivePlayerMaster() &&
groupLeader->GetMapId() == bot->GetMapId() && groupMaster->GetMapId() == bot->GetMapId() &&
bot->GetMap() && bot->GetMap() &&
(bot->GetMap()->IsRaid() || bot->GetMap()->IsDungeon())) (bot->GetMap()->IsRaid() || bot->GetMap()->IsDungeon()))
{ {
@@ -184,7 +184,7 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
} }
return sServerFacade->IsDistanceGreaterThan( return sServerFacade->IsDistanceGreaterThan(
AI_VALUE2(float, "distance", "group leader"), AI_VALUE2(float, "distance", "master target"),
sPlayerbotAIConfig->sightDistance); sPlayerbotAIConfig->sightDistance);
} }

View File

@@ -16,4 +16,4 @@ bool ResetInstancesAction::Execute(Event event)
return true; return true;
} }
bool ResetInstancesAction::isUseful() { return botAI->GetGroupLeader() == bot; }; bool ResetInstancesAction::isUseful() { return botAI->GetGroupMaster() == bot; };

View File

@@ -17,14 +17,14 @@
bool ReviveFromCorpseAction::Execute(Event event) bool ReviveFromCorpseAction::Execute(Event event)
{ {
Player* groupLeader = botAI->GetGroupLeader(); Player* master = botAI->GetGroupMaster();
Corpse* corpse = bot->GetCorpse(); Corpse* corpse = bot->GetCorpse();
// follow group Leader when group Leader revives // follow master when master revives
WorldPacket& p = event.getPacket(); WorldPacket& p = event.getPacket();
if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && groupLeader && !corpse && bot->IsAlive()) if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && master && !corpse && bot->IsAlive())
{ {
if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"), if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
sPlayerbotAIConfig->farDistance)) sPlayerbotAIConfig->farDistance))
{ {
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
@@ -43,10 +43,10 @@ bool ReviveFromCorpseAction::Execute(Event event)
// time(nullptr)) // time(nullptr))
// return false; // return false;
if (groupLeader) if (master)
{ {
if (!GET_PLAYERBOT_AI(groupLeader) && groupLeader->isDead() && groupLeader->GetCorpse() && if (!GET_PLAYERBOT_AI(master) && master->isDead() && master->GetCorpse() &&
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"), sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
sPlayerbotAIConfig->farDistance)) sPlayerbotAIConfig->farDistance))
return false; return false;
} }
@@ -79,15 +79,15 @@ bool FindCorpseAction::Execute(Event event)
if (bot->InBattleground()) if (bot->InBattleground())
return false; return false;
Player* groupLeader = botAI->GetGroupLeader(); Player* master = botAI->GetGroupMaster();
Corpse* corpse = bot->GetCorpse(); Corpse* corpse = bot->GetCorpse();
if (!corpse) if (!corpse)
return false; return false;
// if (groupLeader) // if (master)
// { // {
// if (!GET_PLAYERBOT_AI(groupLeader) && // if (!GET_PLAYERBOT_AI(master) &&
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"), // sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
// sPlayerbotAIConfig->farDistance)) return false; // sPlayerbotAIConfig->farDistance)) return false;
// } // }
@@ -110,20 +110,20 @@ bool FindCorpseAction::Execute(Event event)
WorldPosition botPos(bot); WorldPosition botPos(bot);
WorldPosition corpsePos(corpse); WorldPosition corpsePos(corpse);
WorldPosition moveToPos = corpsePos; WorldPosition moveToPos = corpsePos;
WorldPosition leaderPos(groupLeader); WorldPosition masterPos(master);
float reclaimDist = CORPSE_RECLAIM_RADIUS - 5.0f; float reclaimDist = CORPSE_RECLAIM_RADIUS - 5.0f;
float corpseDist = botPos.distance(corpsePos); float corpseDist = botPos.distance(corpsePos);
int64 deadTime = time(nullptr) - corpse->GetGhostTime(); int64 deadTime = time(nullptr) - corpse->GetGhostTime();
bool moveToLeader = groupLeader && groupLeader != bot && leaderPos.fDist(corpsePos) < reclaimDist; bool moveToMaster = master && master != bot && masterPos.fDist(corpsePos) < reclaimDist;
// Should we ressurect? If so, return false. // Should we ressurect? If so, return false.
if (corpseDist < reclaimDist) if (corpseDist < reclaimDist)
{ {
if (moveToLeader) // We are near group leader. if (moveToMaster) // We are near master.
{ {
if (botPos.fDist(leaderPos) < sPlayerbotAIConfig->spellDistance) if (botPos.fDist(masterPos) < sPlayerbotAIConfig->spellDistance)
return false; return false;
} }
else if (deadTime > 8 * MINUTE) // We have walked too long already. else if (deadTime > 8 * MINUTE) // We have walked too long already.
@@ -140,8 +140,8 @@ bool FindCorpseAction::Execute(Event event)
// If we are getting close move to a save ressurrection spot instead of just the corpse. // If we are getting close move to a save ressurrection spot instead of just the corpse.
if (corpseDist < sPlayerbotAIConfig->reactDistance) if (corpseDist < sPlayerbotAIConfig->reactDistance)
{ {
if (moveToLeader) if (moveToMaster)
moveToPos = leaderPos; moveToPos = masterPos;
else else
{ {
FleeManager manager(bot, reclaimDist, 0.0, urand(0, 1), moveToPos); FleeManager manager(bot, reclaimDist, 0.0, urand(0, 1), moveToPos);
@@ -215,12 +215,12 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
if (!startZone && ClosestGrave) if (!startZone && ClosestGrave)
return ClosestGrave; return ClosestGrave;
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupLeader() && botAI->GetGroupLeader() != bot) if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupMaster() && botAI->GetGroupMaster() != bot)
{ {
Player* groupLeader = botAI->GetGroupLeader(); Player* master = botAI->GetGroupMaster();
if (groupLeader && groupLeader != bot) if (master && master != bot)
{ {
ClosestGrave = sGraveyard->GetClosestGraveyard(groupLeader, bot->GetTeamId()); ClosestGrave = sGraveyard->GetClosestGraveyard(master, bot->GetTeamId());
if (ClosestGrave) if (ClosestGrave)
return ClosestGrave; return ClosestGrave;

View File

@@ -35,8 +35,8 @@ bool RewardAction::Execute(Event event)
return true; return true;
} }
Unit* groupLeaderUnit = AI_VALUE(Unit*, "group leader"); Unit* mtar = AI_VALUE(Unit*, "master target");
if (groupLeaderUnit && Reward(itemId, groupLeaderUnit)) if (mtar && Reward(itemId, mtar))
return true; return true;
botAI->TellError("Cannot talk to quest giver"); botAI->TellError("Cannot talk to quest giver");

View File

@@ -76,7 +76,7 @@ void RpgHelper::setFacing(GuidPosition guidPosition)
void RpgHelper::setDelay(bool waitForGroup) void RpgHelper::setDelay(bool waitForGroup)
{ {
if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupLeader() == bot && bot->GetGroup())) if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupMaster() == bot && bot->GetGroup()))
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay); botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay);
else else
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay / 5); botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay / 5);

View File

@@ -22,8 +22,8 @@ bool SecurityCheckAction::Execute(Event event)
ItemQualities threshold = group->GetLootThreshold(); ItemQualities threshold = group->GetLootThreshold();
if (method == MASTER_LOOT || method == FREE_FOR_ALL || threshold > ITEM_QUALITY_UNCOMMON) if (method == MASTER_LOOT || method == FREE_FOR_ALL || threshold > ITEM_QUALITY_UNCOMMON)
{ {
if ((botAI->GetGroupLeader()->GetSession()->GetSecurity() == SEC_PLAYER) && if ((botAI->GetGroupMaster()->GetSession()->GetSecurity() == SEC_PLAYER) &&
(!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupLeader()->GetGuildId())) (!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupMaster()->GetGuildId()))
{ {
botAI->TellError("I will play with this loot type only if I'm in your guild :/"); botAI->TellError("I will play with this loot type only if I'm in your guild :/");
botAI->ChangeStrategy("+passive,+stay", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("+passive,+stay", BOT_STATE_NON_COMBAT);

View File

@@ -22,7 +22,7 @@ bool OutOfReactRangeAction::Execute(Event event)
bool OutOfReactRangeAction::isUseful() bool OutOfReactRangeAction::isUseful()
{ {
bool canFollow = Follow(AI_VALUE(Unit*, "group leader")); bool canFollow = Follow(AI_VALUE(Unit*, "master target"));
if (!canFollow) if (!canFollow)
{ {
return false; return false;

View File

@@ -64,7 +64,7 @@ bool MoveToDarkPortalAction::Execute(Event event)
{ {
if (bot->GetGroup()) if (bot->GetGroup())
if (bot->GetGroup()->GetLeaderGUID() != bot->GetGUID() && if (bot->GetGroup()->GetLeaderGUID() != bot->GetGUID() &&
!GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupLeader())) !GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupMaster()))
return false; return false;
if (bot->GetLevel() > 57) if (bot->GetLevel() > 57)

View File

@@ -11,6 +11,7 @@ void GroupStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("invite nearby", 4.0f), nullptr))); triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("invite nearby", 4.0f), nullptr)));
triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("invite guild", 4.0f), nullptr))); triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("invite guild", 4.0f), nullptr)));
triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("leave far away", 4.0f), nullptr))); triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("leave far away", 4.0f), nullptr)));
triggers.push_back(new TriggerNode("seldom", NextAction::array(0, new NextAction("reset instances", 1.0f), nullptr))); triggers.push_back(
new TriggerNode("seldom", NextAction::array(0, new NextAction("reset instances", 1.0f), nullptr)));
} }

View File

@@ -5,6 +5,8 @@
#include "WorldPacketHandlerStrategy.h" #include "WorldPacketHandlerStrategy.h"
#include "Playerbots.h"
void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
PassTroughStrategy::InitTriggers(triggers); PassTroughStrategy::InitTriggers(triggers);
@@ -67,7 +69,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("questgiver quest details", NextAction::array(0, new NextAction("turn in query quest", relevance), nullptr))); triggers.push_back(new TriggerNode("questgiver quest details", NextAction::array(0, new NextAction("turn in query quest", relevance), nullptr)));
// loot roll // loot roll
triggers.push_back(new TriggerNode("very often", NextAction::array(0, new NextAction("loot roll", relevance), nullptr))); triggers.push_back(new TriggerNode("very often", NextAction::array(0, new NextAction("loot roll", 10.0f), nullptr)));
} }
WorldPacketHandlerStrategy::WorldPacketHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) WorldPacketHandlerStrategy::WorldPacketHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)

View File

@@ -22,7 +22,7 @@ public:
RaidStrategyContext() : NamedObjectContext<Strategy>(false, true) RaidStrategyContext() : NamedObjectContext<Strategy>(false, true)
{ {
creators["aq20"] = &RaidStrategyContext::aq20; creators["aq20"] = &RaidStrategyContext::aq20;
creators["moltencore"] = &RaidStrategyContext::moltencore; creators["mc"] = &RaidStrategyContext::mc;
creators["bwl"] = &RaidStrategyContext::bwl; creators["bwl"] = &RaidStrategyContext::bwl;
creators["karazhan"] = &RaidStrategyContext::karazhan; creators["karazhan"] = &RaidStrategyContext::karazhan;
creators["magtheridon"] = &RaidStrategyContext::magtheridon; creators["magtheridon"] = &RaidStrategyContext::magtheridon;
@@ -38,7 +38,7 @@ public:
private: private:
static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); } static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); }
static Strategy* moltencore(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); } static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); }
static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); } static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); }
static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); } static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); }
static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); } static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); }

View File

@@ -1,5 +1,5 @@
#ifndef _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H #ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H
#define _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H #define _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H
#include "RaidKarazhanActions.h" #include "RaidKarazhanActions.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
@@ -9,255 +9,77 @@ class RaidKarazhanActionContext : public NamedObjectContext<Action>
public: public:
RaidKarazhanActionContext() RaidKarazhanActionContext()
{ {
// Trash creators["karazhan attumen the huntsman stack behind"] = &RaidKarazhanActionContext::karazhan_attumen_the_huntsman_stack_behind;
creators["mana warp stun creature before warp breach"] =
&RaidKarazhanActionContext::mana_warp_stun_creature_before_warp_breach;
// Attumen the Huntsman creators["karazhan moroes mark target"] = &RaidKarazhanActionContext::karazhan_moroes_mark_target;
creators["attumen the huntsman mark target"] =
&RaidKarazhanActionContext::attumen_the_huntsman_mark_target;
creators["attumen the huntsman split bosses"] = creators["karazhan maiden of virtue position boss"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_boss;
&RaidKarazhanActionContext::attumen_the_huntsman_split_bosses; creators["karazhan maiden of virtue position ranged"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_ranged;
creators["attumen the huntsman stack behind"] = creators["karazhan big bad wolf position boss"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_position_boss;
&RaidKarazhanActionContext::attumen_the_huntsman_stack_behind; creators["karazhan big bad wolf run away"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_run_away;
creators["attumen the huntsman manage dps timer"] = creators["karazhan romulo and julianne mark target"] = &RaidKarazhanActionContext::karazhan_romulo_and_julianne_mark_target;
&RaidKarazhanActionContext::attumen_the_huntsman_manage_dps_timer;
// Moroes creators["karazhan wizard of oz mark target"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_mark_target;
creators["moroes main tank attack boss"] = creators["karazhan wizard of oz scorch strawman"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_scorch_strawman;
&RaidKarazhanActionContext::moroes_main_tank_attack_boss;
creators["moroes mark target"] = creators["karazhan the curator mark target"] = &RaidKarazhanActionContext::karazhan_the_curator_mark_target;
&RaidKarazhanActionContext::moroes_mark_target; creators["karazhan the curator position boss"] = &RaidKarazhanActionContext::karazhan_the_curator_position_boss;
creators["karazhan the curator spread ranged"] = &RaidKarazhanActionContext::karazhan_the_curator_spread_ranged;
// Maiden of Virtue creators["karazhan terestian illhoof mark target"] = &RaidKarazhanActionContext::karazhan_terestian_illhoof_mark_target;
creators["maiden of virtue move boss to healer"] =
&RaidKarazhanActionContext::maiden_of_virtue_move_boss_to_healer;
creators["maiden of virtue position ranged"] = creators["karazhan shade of aran arcane explosion run away"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_arcane_explosion_run_away;
&RaidKarazhanActionContext::maiden_of_virtue_position_ranged; creators["karazhan shade of aran flame wreath stop movement"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_flame_wreath_stop_movement;
creators["karazhan shade of aran mark conjured elemental"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_mark_conjured_elemental;
creators["karazhan shade of aran spread ranged"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_spread_ranged;
// The Big Bad Wolf creators["karazhan netherspite block red beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_red_beam;
creators["big bad wolf position boss"] = creators["karazhan netherspite block blue beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_blue_beam;
&RaidKarazhanActionContext::big_bad_wolf_position_boss; creators["karazhan netherspite block green beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_green_beam;
creators["karazhan netherspite avoid beam and void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_avoid_beam_and_void_zone;
creators["karazhan netherspite banish phase avoid void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_banish_phase_avoid_void_zone;
creators["big bad wolf run away from boss"] = creators["karazhan prince malchezaar non tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_non_tank_avoid_hazard;
&RaidKarazhanActionContext::big_bad_wolf_run_away_from_boss; creators["karazhan prince malchezaar tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_tank_avoid_hazard;
// Romulo and Julianne
creators["romulo and julianne mark target"] =
&RaidKarazhanActionContext::romulo_and_julianne_mark_target;
// The Wizard of Oz
creators["wizard of oz mark target"] =
&RaidKarazhanActionContext::wizard_of_oz_mark_target;
creators["wizard of oz scorch strawman"] =
&RaidKarazhanActionContext::wizard_of_oz_scorch_strawman;
// The Curator
creators["the curator mark astral flare"] =
&RaidKarazhanActionContext::the_curator_mark_astral_flare;
creators["the curator position boss"] =
&RaidKarazhanActionContext::the_curator_position_boss;
creators["the curator spread ranged"] =
&RaidKarazhanActionContext::the_curator_spread_ranged;
// Terestian Illhoof
creators["terestian illhoof mark target"] =
&RaidKarazhanActionContext::terestian_illhoof_mark_target;
// Shade of Aran
creators["shade of aran run away from arcane explosion"] =
&RaidKarazhanActionContext::shade_of_aran_run_away_from_arcane_explosion;
creators["shade of aran stop moving during flame wreath"] =
&RaidKarazhanActionContext::shade_of_aran_stop_moving_during_flame_wreath;
creators["shade of aran mark conjured elemental"] =
&RaidKarazhanActionContext::shade_of_aran_mark_conjured_elemental;
creators["shade of aran ranged maintain distance"] =
&RaidKarazhanActionContext::shade_of_aran_ranged_maintain_distance;
// Netherspite
creators["netherspite block red beam"] =
&RaidKarazhanActionContext::netherspite_block_red_beam;
creators["netherspite block blue beam"] =
&RaidKarazhanActionContext::netherspite_block_blue_beam;
creators["netherspite block green beam"] =
&RaidKarazhanActionContext::netherspite_block_green_beam;
creators["netherspite avoid beam and void zone"] =
&RaidKarazhanActionContext::netherspite_avoid_beam_and_void_zone;
creators["netherspite banish phase avoid void zone"] =
&RaidKarazhanActionContext::netherspite_banish_phase_avoid_void_zone;
creators["netherspite manage timers and trackers"] =
&RaidKarazhanActionContext::netherspite_manage_timers_and_trackers;
// Prince Malchezaar
creators["prince malchezaar enfeebled avoid hazard"] =
&RaidKarazhanActionContext::prince_malchezaar_enfeebled_avoid_hazard;
creators["prince malchezaar non tank avoid infernal"] =
&RaidKarazhanActionContext::prince_malchezaar_non_tank_avoid_infernal;
creators["prince malchezaar main tank movement"] =
&RaidKarazhanActionContext::prince_malchezaar_main_tank_movement;
// Nightbane
creators["nightbane ground phase position boss"] =
&RaidKarazhanActionContext::nightbane_ground_phase_position_boss;
creators["nightbane ground phase rotate ranged positions"] =
&RaidKarazhanActionContext::nightbane_ground_phase_rotate_ranged_positions;
creators["nightbane cast fear ward on main tank"] =
&RaidKarazhanActionContext::nightbane_cast_fear_ward_on_main_tank;
creators["nightbane control pet aggression"] =
&RaidKarazhanActionContext::nightbane_control_pet_aggression;
creators["nightbane flight phase movement"] =
&RaidKarazhanActionContext::nightbane_flight_phase_movement;
creators["nightbane manage timers and trackers"] =
&RaidKarazhanActionContext::nightbane_manage_timers_and_trackers;
} }
private: private:
// Trash static Action* karazhan_attumen_the_huntsman_stack_behind(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanStackBehindAction(botAI); }
static Action* mana_warp_stun_creature_before_warp_breach(
PlayerbotAI* botAI) { return new ManaWarpStunCreatureBeforeWarpBreachAction(botAI); }
// Attumen the Huntsman static Action* karazhan_moroes_mark_target(PlayerbotAI* botAI) { return new KarazhanMoroesMarkTargetAction(botAI); }
static Action* attumen_the_huntsman_mark_target(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanMarkTargetAction(botAI); }
static Action* attumen_the_huntsman_split_bosses( static Action* karazhan_maiden_of_virtue_position_boss(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionBossAction(botAI); }
PlayerbotAI* botAI) { return new AttumenTheHuntsmanSplitBossesAction(botAI); } static Action* karazhan_maiden_of_virtue_position_ranged(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionRangedAction(botAI); }
static Action* attumen_the_huntsman_stack_behind( static Action* karazhan_big_bad_wolf_position_boss(PlayerbotAI* botAI) { return new KarazhanBigBadWolfPositionBossAction(botAI); }
PlayerbotAI* botAI) { return new AttumenTheHuntsmanStackBehindAction(botAI); } static Action* karazhan_big_bad_wolf_run_away(PlayerbotAI* botAI) { return new KarazhanBigBadWolfRunAwayAction(botAI); }
static Action* attumen_the_huntsman_manage_dps_timer( static Action* karazhan_romulo_and_julianne_mark_target(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneMarkTargetAction(botAI); }
PlayerbotAI* botAI) { return new AttumenTheHuntsmanManageDpsTimerAction(botAI); }
// Moroes static Action* karazhan_wizard_of_oz_mark_target(PlayerbotAI* botAI) { return new KarazhanWizardOfOzMarkTargetAction(botAI); }
static Action* moroes_main_tank_attack_boss( static Action* karazhan_wizard_of_oz_scorch_strawman(PlayerbotAI* botAI) { return new KarazhanWizardOfOzScorchStrawmanAction(botAI); }
PlayerbotAI* botAI) { return new MoroesMainTankAttackBossAction(botAI); }
static Action* moroes_mark_target( static Action* karazhan_the_curator_mark_target(PlayerbotAI* botAI) { return new KarazhanTheCuratorMarkTargetAction(botAI); }
PlayerbotAI* botAI) { return new MoroesMarkTargetAction(botAI); } static Action* karazhan_the_curator_position_boss(PlayerbotAI* botAI) { return new KarazhanTheCuratorPositionBossAction(botAI); }
static Action* karazhan_the_curator_spread_ranged(PlayerbotAI* botAI) { return new KarazhanTheCuratorSpreadRangedAction(botAI); }
// Maiden of Virtue static Action* karazhan_terestian_illhoof_mark_target(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofMarkTargetAction(botAI); }
static Action* maiden_of_virtue_move_boss_to_healer(
PlayerbotAI* botAI) { return new MaidenOfVirtueMoveBossToHealerAction(botAI); }
static Action* maiden_of_virtue_position_ranged( static Action* karazhan_shade_of_aran_arcane_explosion_run_away(PlayerbotAI* botAI) { return new KarazhanShadeOfAranArcaneExplosionRunAwayAction(botAI); }
PlayerbotAI* botAI) { return new MaidenOfVirtuePositionRangedAction(botAI); } static Action* karazhan_shade_of_aran_flame_wreath_stop_movement(PlayerbotAI* botAI) { return new KarazhanShadeOfAranFlameWreathStopMovementAction(botAI); }
static Action* karazhan_shade_of_aran_mark_conjured_elemental(PlayerbotAI* botAI) { return new KarazhanShadeOfAranMarkConjuredElementalAction(botAI); }
static Action* karazhan_shade_of_aran_spread_ranged(PlayerbotAI* botAI) { return new KarazhanShadeOfAranSpreadRangedAction(botAI); }
// The Big Bad Wolf static Action* karazhan_netherspite_block_red_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockRedBeamAction(botAI); }
static Action* big_bad_wolf_position_boss( static Action* karazhan_netherspite_block_blue_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockBlueBeamAction(botAI); }
PlayerbotAI* botAI) { return new BigBadWolfPositionBossAction(botAI); } static Action* karazhan_netherspite_block_green_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockGreenBeamAction(botAI); }
static Action* karazhan_netherspite_avoid_beam_and_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteAvoidBeamAndVoidZoneAction(botAI); }
static Action* karazhan_netherspite_banish_phase_avoid_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(botAI); }
static Action* big_bad_wolf_run_away_from_boss( static Action* karazhan_prince_malchezaar_non_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarNonTankAvoidHazardAction(botAI); }
PlayerbotAI* botAI) { return new BigBadWolfRunAwayFromBossAction(botAI); } static Action* karazhan_prince_malchezaar_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTankAvoidHazardAction(botAI); }
// Romulo and Julianne
static Action* romulo_and_julianne_mark_target(
PlayerbotAI* botAI) { return new RomuloAndJulianneMarkTargetAction(botAI); }
// The Wizard of Oz
static Action* wizard_of_oz_mark_target(
PlayerbotAI* botAI) { return new WizardOfOzMarkTargetAction(botAI); }
static Action* wizard_of_oz_scorch_strawman(
PlayerbotAI* botAI) { return new WizardOfOzScorchStrawmanAction(botAI); }
// The Curator
static Action* the_curator_mark_astral_flare(
PlayerbotAI* botAI) { return new TheCuratorMarkAstralFlareAction(botAI); }
static Action* the_curator_position_boss(
PlayerbotAI* botAI) { return new TheCuratorPositionBossAction(botAI); }
static Action* the_curator_spread_ranged(
PlayerbotAI* botAI) { return new TheCuratorSpreadRangedAction(botAI); }
// Terestian Illhoof
static Action* terestian_illhoof_mark_target(
PlayerbotAI* botAI) { return new TerestianIllhoofMarkTargetAction(botAI); }
// Shade of Aran
static Action* shade_of_aran_run_away_from_arcane_explosion(
PlayerbotAI* botAI) { return new ShadeOfAranRunAwayFromArcaneExplosionAction(botAI); }
static Action* shade_of_aran_stop_moving_during_flame_wreath(
PlayerbotAI* botAI) { return new ShadeOfAranStopMovingDuringFlameWreathAction(botAI); }
static Action* shade_of_aran_mark_conjured_elemental(
PlayerbotAI* botAI) { return new ShadeOfAranMarkConjuredElementalAction(botAI); }
static Action* shade_of_aran_ranged_maintain_distance(
PlayerbotAI* botAI) { return new ShadeOfAranRangedMaintainDistanceAction(botAI); }
// Netherspite
static Action* netherspite_block_red_beam(
PlayerbotAI* botAI) { return new NetherspiteBlockRedBeamAction(botAI); }
static Action* netherspite_block_blue_beam(
PlayerbotAI* botAI) { return new NetherspiteBlockBlueBeamAction(botAI); }
static Action* netherspite_block_green_beam(
PlayerbotAI* botAI) { return new NetherspiteBlockGreenBeamAction(botAI); }
static Action* netherspite_avoid_beam_and_void_zone(
PlayerbotAI* botAI) { return new NetherspiteAvoidBeamAndVoidZoneAction(botAI); }
static Action* netherspite_banish_phase_avoid_void_zone(
PlayerbotAI* botAI) { return new NetherspiteBanishPhaseAvoidVoidZoneAction(botAI); }
static Action* netherspite_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NetherspiteManageTimersAndTrackersAction(botAI); }
// Prince Malchezaar
static Action* prince_malchezaar_enfeebled_avoid_hazard(
PlayerbotAI* botAI) { return new PrinceMalchezaarEnfeebledAvoidHazardAction(botAI); }
static Action* prince_malchezaar_non_tank_avoid_infernal(
PlayerbotAI* botAI) { return new PrinceMalchezaarNonTankAvoidInfernalAction(botAI); }
static Action* prince_malchezaar_main_tank_movement(
PlayerbotAI* botAI) { return new PrinceMalchezaarMainTankMovementAction(botAI); }
// Nightbane
static Action* nightbane_ground_phase_position_boss(
PlayerbotAI* botAI) { return new NightbaneGroundPhasePositionBossAction(botAI); }
static Action* nightbane_ground_phase_rotate_ranged_positions(
PlayerbotAI* botAI) { return new NightbaneGroundPhaseRotateRangedPositionsAction(botAI); }
static Action* nightbane_cast_fear_ward_on_main_tank(
PlayerbotAI* botAI) { return new NightbaneCastFearWardOnMainTankAction(botAI); }
static Action* nightbane_control_pet_aggression(
PlayerbotAI* botAI) { return new NightbaneControlPetAggressionAction(botAI); }
static Action* nightbane_flight_phase_movement(
PlayerbotAI* botAI) { return new NightbaneFlightPhaseMovementAction(botAI); }
static Action* nightbane_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NightbaneManageTimersAndTrackersAction(botAI); }
}; };
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@@ -2,321 +2,217 @@
#define _PLAYERBOT_RAIDKARAZHANACTIONS_H #define _PLAYERBOT_RAIDKARAZHANACTIONS_H
#include "Action.h" #include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h" #include "MovementActions.h"
class ManaWarpStunCreatureBeforeWarpBreachAction : public AttackAction class KarazhanAttumenTheHuntsmanStackBehindAction : public MovementAction
{ {
public: public:
ManaWarpStunCreatureBeforeWarpBreachAction( KarazhanAttumenTheHuntsmanStackBehindAction(PlayerbotAI* botAI, std::string const name = "karazhan attumen the huntsman stack behind") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "mana warp stun creature before warp breach") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanMoroesMarkTargetAction : public Action
{
public:
KarazhanMoroesMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan moroes mark target") : Action(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class AttumenTheHuntsmanMarkTargetAction : public AttackAction class KarazhanMaidenOfVirtuePositionBossAction : public MovementAction
{ {
public: public:
AttumenTheHuntsmanMarkTargetAction( KarazhanMaidenOfVirtuePositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position boss") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "attumen the huntsman mark target") : AttackAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
class AttumenTheHuntsmanSplitBossesAction : public AttackAction class KarazhanMaidenOfVirtuePositionRangedAction : public MovementAction
{ {
public: public:
AttumenTheHuntsmanSplitBossesAction( KarazhanMaidenOfVirtuePositionRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position ranged") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "attumen the huntsman split bosses") : AttackAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
class AttumenTheHuntsmanStackBehindAction : public MovementAction class KarazhanBigBadWolfPositionBossAction : public MovementAction
{ {
public: public:
AttumenTheHuntsmanStackBehindAction( KarazhanBigBadWolfPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf position boss") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "attumen the huntsman stack behind") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
class AttumenTheHuntsmanManageDpsTimerAction : public Action class KarazhanBigBadWolfRunAwayAction : public MovementAction
{ {
public: public:
AttumenTheHuntsmanManageDpsTimerAction( KarazhanBigBadWolfRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf run away") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "attumen the huntsman manage dps timer") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class MoroesMainTankAttackBossAction : public AttackAction
{
public:
MoroesMainTankAttackBossAction(
PlayerbotAI* botAI, std::string const name = "moroes main tank attack boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class MoroesMarkTargetAction : public Action
{
public:
MoroesMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "moroes mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class MaidenOfVirtueMoveBossToHealerAction : public AttackAction
{
public:
MaidenOfVirtueMoveBossToHealerAction(
PlayerbotAI* botAI, std::string const name = "maiden of virtue move boss to healer") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class MaidenOfVirtuePositionRangedAction : public MovementAction
{
public:
MaidenOfVirtuePositionRangedAction(
PlayerbotAI* botAI, std::string const name = "maiden of virtue position ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class BigBadWolfPositionBossAction : public AttackAction
{
public:
BigBadWolfPositionBossAction(
PlayerbotAI* botAI, std::string const name = "big bad wolf position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class BigBadWolfRunAwayFromBossAction : public MovementAction
{
public:
BigBadWolfRunAwayFromBossAction(
PlayerbotAI* botAI, std::string const name = "big bad wolf run away from boss") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class RomuloAndJulianneMarkTargetAction : public Action
{
public:
RomuloAndJulianneMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "romulo and julianne mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class WizardOfOzMarkTargetAction : public Action
{
public:
WizardOfOzMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "wizard of oz mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class WizardOfOzScorchStrawmanAction : public Action
{
public:
WizardOfOzScorchStrawmanAction(
PlayerbotAI* botAI, std::string const name = "wizard of oz scorch strawman") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class TheCuratorMarkAstralFlareAction : public Action
{
public:
TheCuratorMarkAstralFlareAction(
PlayerbotAI* botAI, std::string const name = "the curator mark astral flare") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class TheCuratorPositionBossAction : public AttackAction
{
public:
TheCuratorPositionBossAction(
PlayerbotAI* botAI, std::string const name = "the curator position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheCuratorSpreadRangedAction : public MovementAction
{
public:
TheCuratorSpreadRangedAction(
PlayerbotAI* botAI, std::string const name = "the curator spread ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class TerestianIllhoofMarkTargetAction : public Action
{
public:
TerestianIllhoofMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "terestian illhoof mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranRunAwayFromArcaneExplosionAction : public MovementAction
{
public:
ShadeOfAranRunAwayFromArcaneExplosionAction(
PlayerbotAI* botAI, std::string const name = "shade of aran run away from arcane explosion") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranStopMovingDuringFlameWreathAction : public MovementAction
{
public:
ShadeOfAranStopMovingDuringFlameWreathAction(
PlayerbotAI* botAI, std::string const name = "shade of aran stop moving during flame wreath") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranMarkConjuredElementalAction : public Action
{
public:
ShadeOfAranMarkConjuredElementalAction(
PlayerbotAI* botAI, std::string const name = "shade of aran mark conjured elemental") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranRangedMaintainDistanceAction : public MovementAction
{
public:
ShadeOfAranRangedMaintainDistanceAction(
PlayerbotAI* botAI, std::string const name = "shade of aran ranged maintain distance") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class NetherspiteBlockRedBeamAction : public MovementAction
{
public:
NetherspiteBlockRedBeamAction(
PlayerbotAI* botAI, std::string const name = "netherspite block red beam") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
private: private:
Position GetPositionOnBeam(Unit* netherspite, Unit* portal, float distanceFromBoss); size_t currentIndex = 0;
std::unordered_map<ObjectGuid, bool> _wasBlockingRedBeam;
}; };
class NetherspiteBlockBlueBeamAction : public MovementAction class KarazhanRomuloAndJulianneMarkTargetAction : public Action
{ {
public: public:
NetherspiteBlockBlueBeamAction( KarazhanRomuloAndJulianneMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan romulo and julianne mark target") : Action(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "netherspite block blue beam") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
std::unordered_map<ObjectGuid, bool> _wasBlockingBlueBeam;
};
class NetherspiteBlockGreenBeamAction : public MovementAction
{
public:
NetherspiteBlockGreenBeamAction(
PlayerbotAI* botAI, std::string const name = "netherspite block green beam") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
std::unordered_map<ObjectGuid, bool> _wasBlockingGreenBeam;
};
class NetherspiteAvoidBeamAndVoidZoneAction : public MovementAction
{
public:
NetherspiteAvoidBeamAndVoidZoneAction(
PlayerbotAI* botAI, std::string const name = "netherspite avoid beam and void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
struct BeamAvoid
{
Unit* portal;
float minDist, maxDist;
};
bool IsAwayFromBeams(float x, float y, const std::vector<BeamAvoid>& beams, Unit* netherspite);
};
class NetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction
{
public:
NetherspiteBanishPhaseAvoidVoidZoneAction(
PlayerbotAI* botAI, std::string const name = "netherspite banish phase avoid void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class NetherspiteManageTimersAndTrackersAction : public Action class KarazhanWizardOfOzMarkTargetAction : public Action
{ {
public: public:
NetherspiteManageTimersAndTrackersAction( KarazhanWizardOfOzMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz mark target") : Action(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "netherspite manage timers and trackers") : Action(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class PrinceMalchezaarEnfeebledAvoidHazardAction : public MovementAction class KarazhanWizardOfOzScorchStrawmanAction : public Action
{ {
public: public:
PrinceMalchezaarEnfeebledAvoidHazardAction( KarazhanWizardOfOzScorchStrawmanAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz scorch strawman") : Action(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "prince malchezaar enfeebled avoid hazard") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class PrinceMalchezaarNonTankAvoidInfernalAction : public MovementAction class KarazhanTheCuratorMarkTargetAction : public Action
{ {
public: public:
PrinceMalchezaarNonTankAvoidInfernalAction( KarazhanTheCuratorMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator mark target") : Action(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "prince malchezaar non tank avoid infernal") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class PrinceMalchezaarMainTankMovementAction : public AttackAction class KarazhanTheCuratorPositionBossAction : public MovementAction
{ {
public: public:
PrinceMalchezaarMainTankMovementAction( KarazhanTheCuratorPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator position boss") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "prince malchezaar main tank movement") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanTheCuratorSpreadRangedAction : public MovementAction
{
public:
KarazhanTheCuratorSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator spread ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanTerestianIllhoofMarkTargetAction : public Action
{
public:
KarazhanTerestianIllhoofMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan terestian illhoof mark target") : Action(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class NightbaneGroundPhasePositionBossAction : public AttackAction class KarazhanShadeOfAranArcaneExplosionRunAwayAction : public MovementAction
{ {
public: public:
NightbaneGroundPhasePositionBossAction( KarazhanShadeOfAranArcaneExplosionRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran arcane explosion run away") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "nightbane ground phase position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanShadeOfAranFlameWreathStopMovementAction : public MovementAction
{
public:
KarazhanShadeOfAranFlameWreathStopMovementAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran flame wreath stop bot") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class NightbaneGroundPhaseRotateRangedPositionsAction : public MovementAction class KarazhanShadeOfAranMarkConjuredElementalAction : public Action
{ {
public: public:
NightbaneGroundPhaseRotateRangedPositionsAction( KarazhanShadeOfAranMarkConjuredElementalAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran mark conjured elemental") : Action(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "nightbane ground phase rotate ranged positions") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class NightbaneCastFearWardOnMainTankAction : public Action class KarazhanShadeOfAranSpreadRangedAction : public MovementAction
{ {
public: public:
NightbaneCastFearWardOnMainTankAction( KarazhanShadeOfAranSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran spread ranged") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "nightbane cast fear ward on main tank") : Action(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
class NightbaneControlPetAggressionAction : public Action class KarazhanNetherspiteBlockRedBeamAction : public MovementAction
{ {
public: public:
NightbaneControlPetAggressionAction( KarazhanNetherspiteBlockRedBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block red beam") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "nightbane control pet aggression") : Action(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
class NightbaneFlightPhaseMovementAction : public MovementAction class KarazhanNetherspiteBlockBlueBeamAction : public MovementAction
{ {
public: public:
NightbaneFlightPhaseMovementAction( KarazhanNetherspiteBlockBlueBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block blue beam") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "nightbane flight phase movement") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
class NightbaneManageTimersAndTrackersAction : public Action class KarazhanNetherspiteBlockGreenBeamAction : public MovementAction
{ {
public: public:
NightbaneManageTimersAndTrackersAction( KarazhanNetherspiteBlockGreenBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block green beam") : MovementAction(botAI, name) {}
PlayerbotAI* botAI, std::string const name = "nightbane manage timers and trackers") : Action(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanNetherspiteAvoidBeamAndVoidZoneAction : public MovementAction
{
public:
KarazhanNetherspiteAvoidBeamAndVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite avoid beam and void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction
{
public:
KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite banish phase avoid void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanPrinceMalchezaarNonTankAvoidHazardAction : public MovementAction
{
public:
KarazhanPrinceMalchezaarNonTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar non-tank avoid hazard") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanPrinceMalchezaarTankAvoidHazardAction : public MovementAction
{
public:
KarazhanPrinceMalchezaarTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar tank avoid hazard") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
}; };
#endif #endif

View File

@@ -1,356 +1,316 @@
#include <algorithm>
#include <map>
#include "RaidKarazhanHelpers.h" #include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h" #include "RaidKarazhanActions.h"
#include "Playerbots.h" #include "AiObjectContext.h"
#include "RtiTargetValue.h" #include "PlayerbotMgr.h"
#include "Position.h"
#include "Spell.h"
namespace KarazhanHelpers const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION = Position(-10945.881f, -2103.782f, 92.712f);
const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8] =
{ {
// Attumen the Huntsman { -10931.178f, -2116.580f, 92.179f },
std::unordered_map<uint32, time_t> attumenDpsWaitTimer; { -10925.828f, -2102.425f, 92.180f },
// Big Bad Wolf { -10933.089f, -2088.5017f, 92.180f },
std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex; { -10947.59f, -2082.8147f, 92.180f },
// Netherspite { -10960.912f, -2090.4368f, 92.179f },
std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer; { -10966.017f, -2105.288f, 92.175f },
std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer; { -10959.242f, -2119.6172f, 92.180f },
std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways; { -10944.495f, -2123.857f, 92.180f },
// Nightbane };
std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer;
std::unordered_map<ObjectGuid, uint8> nightbaneTankStep;
std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
const Position MAIDEN_OF_VIRTUE_BOSS_POSITION = { -10945.881f, -2103.782f, 92.712f }; const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION = Position(-10913.391f, -1773.508f, 90.477f);
const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8] = const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4] =
{
{ -10875.456f, -1779.036f, 90.477f },
{ -10872.281f, -1751.638f, 90.477f },
{ -10910.492f, -1747.401f, 90.477f },
{ -10913.391f, -1773.508f, 90.477f },
};
const Position KARAZHAN_THE_CURATOR_BOSS_POSITION = Position(-11139.463f, -1884.645f, 165.765f);
void RaidKarazhanHelpers::MarkTargetWithSkull(Unit* target)
{
if (!target)
{ {
{ -10931.178f, -2116.580f, 92.179f }, return;
{ -10925.828f, -2102.425f, 92.180f }, }
{ -10933.089f, -2088.502f, 92.180f },
{ -10947.590f, -2082.815f, 92.180f },
{ -10960.912f, -2090.437f, 92.179f },
{ -10966.017f, -2105.288f, 92.175f },
{ -10959.242f, -2119.617f, 92.180f },
{ -10944.495f, -2123.857f, 92.180f },
};
const Position BIG_BAD_WOLF_BOSS_POSITION = { -10913.391f, -1773.508f, 90.477f }; if (Group* group = bot->GetGroup())
const Position BIG_BAD_WOLF_RUN_POSITION[4] =
{ {
{ -10875.456f, -1779.036f, 90.477f }, constexpr uint8_t skullIconId = 7;
{ -10872.281f, -1751.638f, 90.477f }, ObjectGuid skullGuid = group->GetTargetIcon(skullIconId);
{ -10910.492f, -1747.401f, 90.477f },
{ -10913.391f, -1773.508f, 90.477f },
};
const Position THE_CURATOR_BOSS_POSITION = { -11139.463f, -1884.645f, 165.765f }; if (skullGuid != target->GetGUID())
const Position NIGHTBANE_TRANSITION_BOSS_POSITION = { -11160.646f, -1932.773f, 91.473f }; // near some ribs
const Position NIGHTBANE_FINAL_BOSS_POSITION = { -11173.530f, -1940.707f, 91.473f };
const Position NIGHTBANE_RANGED_POSITION1 = { -11145.949f, -1970.927f, 91.473f };
const Position NIGHTBANE_RANGED_POSITION2 = { -11143.594f, -1954.981f, 91.473f };
const Position NIGHTBANE_RANGED_POSITION3 = { -11159.778f, -1961.031f, 91.473f };
const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel
const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f };
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
if (!target)
return;
if (Group* group = bot->GetGroup())
{ {
ObjectGuid currentGuid = group->GetTargetIcon(iconId); group->SetTargetIcon(skullIconId, bot->GetGUID(), target->GetGUID());
if (currentGuid != target->GetGUID()) }
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID()); }
}
Unit* RaidKarazhanHelpers::GetFirstAliveUnit(const std::vector<Unit*>& units)
{
for (Unit* unit : units)
{
if (unit && unit->IsAlive())
{
return unit;
} }
} }
void MarkTargetWithSkull(Player* bot, Unit* target) return nullptr;
}
Unit* RaidKarazhanHelpers::GetFirstAliveUnitByEntry(uint32 entry)
{
const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto const& npcGuid : npcs)
{ {
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex); Unit* unit = botAI->GetUnit(npcGuid);
}
void MarkTargetWithSquare(Player* bot, Unit* target) if (unit && unit->IsAlive() && unit->GetEntry() == entry)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithMoon(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{ {
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName); return unit;
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
} }
} }
// Only one bot is needed to set/reset mapwide timers return nullptr;
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot) }
Unit* RaidKarazhanHelpers::GetNearestPlayerInRadius(float radius)
{
if (Group* group = bot->GetGroup())
{ {
if (Group* group = bot->GetGroup()) for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{ {
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) Player* member = itr->GetSource();
if (!member || !member->IsAlive() || member == bot)
{ {
Player* member = ref->GetSource(); continue;
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return false;
}
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units)
{
for (Unit* unit : units)
{
if (unit && unit->IsAlive())
return unit;
}
return nullptr;
}
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry)
{
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
return unit;
}
return nullptr;
}
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
}
return nearestPlayer;
}
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot)
{
Unit* aran = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "shade of aran")->Get();
Spell* currentSpell = aran ? aran->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr;
if (currentSpell && currentSpell->m_spellInfo &&
currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH_CAST)
return true;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (member->HasAura(SPELL_FLAME_WREATH_AURA))
return true;
}
}
return false;
}
// Red beam blockers: tank bots, no Nether Exhaustion Red
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot)
{
std::vector<Player*> redBlockers;
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) || !GET_PLAYERBOT_AI(member) ||
member->HasAura(SPELL_NETHER_EXHAUSTION_RED))
continue;
redBlockers.push_back(member);
}
}
return redBlockers;
}
// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and <24 stacks of Blue Beam debuff
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot)
{
std::vector<Player*> blueBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE);
Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF);
bool overStack = blueBuff && blueBuff->GetStackAmount() >= 24;
bool isDps = botAI->IsDps(member);
bool isWarrior = member->getClass() == CLASS_WARRIOR;
bool isRogue = member->getClass() == CLASS_ROGUE;
if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack)
blueBlockers.push_back(member);
}
}
return blueBlockers;
}
// Green beam blockers:
// (1) Prioritize Rogues and non-tank Warrior bots, no Nether Exhaustion Green
// (2) Then assign Healer bots, no Nether Exhaustion Green and <24 stacks of Green Beam debuff
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot)
{
std::vector<Player*> greenBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
bool isRogue = member->getClass() == CLASS_ROGUE;
bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member);
bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion;
if (eligibleRogueWarrior)
greenBlockers.push_back(member);
} }
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) if (bot->GetExactDist2d(member) < radius)
{ {
Player* member = ref->GetSource(); return member;
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF);
bool overStack = greenBuff && greenBuff->GetStackAmount() >= 24;
bool isHealer = botAI->IsHeal(member);
bool eligibleHealer = isHealer && !hasExhaustion && !overStack;
if (eligibleHealer)
greenBlockers.push_back(member);
} }
} }
return greenBlockers;
} }
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot) return nullptr;
}
bool RaidKarazhanHelpers::IsFlameWreathActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
Spell* currentSpell = boss ? boss->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr;
if (currentSpell && currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH)
{ {
static ObjectGuid currentRedBlocker; return true;
static ObjectGuid currentGreenBlocker; }
static ObjectGuid currentBlueBlocker;
Player* redBlocker = nullptr; if (Group* group = bot->GetGroup())
Player* greenBlocker = nullptr; {
Player* blueBlocker = nullptr; for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
std::vector<Player*> redBlockers = GetRedBlockers(botAI, bot);
if (!redBlockers.empty())
{ {
auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* player) Player* member = itr->GetSource();
if (!member || !member->IsAlive())
{ {
return player && player->GetGUID() == currentRedBlocker; continue;
}); }
if (member->HasAura(SPELL_AURA_FLAME_WREATH))
{
return true;
}
}
}
if (it != redBlockers.end()) return false;
redBlocker = *it; }
else
redBlocker = redBlockers.front();
currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty; // Red beam blockers: tank bots, no Nether Exhaustion Red
std::vector<Player*> RaidKarazhanHelpers::GetRedBlockers()
{
std::vector<Player*> redBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) ||
member->HasAura(SPELL_NETHER_EXHAUSTION_RED))
{
continue;
}
redBlockers.push_back(member);
}
}
return redBlockers;
}
// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and ≤25 stacks of Blue Beam debuff
std::vector<Player*> RaidKarazhanHelpers::GetBlueBlockers()
{
std::vector<Player*> blueBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
{
continue;
}
bool isDps = botAI->IsDps(member);
bool isWarrior = member->getClass() == CLASS_WARRIOR;
bool isRogue = member->getClass() == CLASS_ROGUE;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE);
Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF);
bool overStack = blueBuff && blueBuff->GetStackAmount() >= 26;
if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack)
{
blueBlockers.push_back(member);
}
}
}
return blueBlockers;
}
// Green beam blockers:
// (1) Rogue and non-tank Warrior bots, no Nether Exhaustion Green
// (2) Healer bots, no Nether Exhaustion Green and ≤25 stacks of Green Beam debuff
std::vector<Player*> RaidKarazhanHelpers::GetGreenBlockers()
{
std::vector<Player*> greenBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
{
continue;
}
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF);
bool overStack = greenBuff && greenBuff->GetStackAmount() >= 26;
bool isRogue = member->getClass() == CLASS_ROGUE;
bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member);
bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion;
bool isHealer = botAI->IsHeal(member);
bool eligibleHealer = isHealer && !hasExhaustion && !overStack;
if (eligibleRogueWarrior || eligibleHealer)
{
greenBlockers.push_back(member);
}
}
}
return greenBlockers;
}
Position RaidKarazhanHelpers::GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss)
{
float bx = boss->GetPositionX();
float by = boss->GetPositionY();
float bz = boss->GetPositionZ();
float px = portal->GetPositionX();
float py = portal->GetPositionY();
float dx = px - bx;
float dy = py - by;
float length = sqrt(dx*dx + dy*dy);
if (length == 0.0f)
{
return Position(bx, by, bz);
}
dx /= length;
dy /= length;
float targetX = bx + dx * distanceFromBoss;
float targetY = by + dy * distanceFromBoss;
float targetZ = bz;
return Position(targetX, targetY, targetZ);
}
std::tuple<Player*, Player*, Player*> RaidKarazhanHelpers::GetCurrentBeamBlockers()
{
static ObjectGuid currentRedBlocker;
static ObjectGuid currentGreenBlocker;
static ObjectGuid currentBlueBlocker;
Player* redBlocker = nullptr;
Player* greenBlocker = nullptr;
Player* blueBlocker = nullptr;
std::vector<Player*> redBlockers = GetRedBlockers();
if (!redBlockers.empty())
{
auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* p)
{
return p && p->GetGUID() == currentRedBlocker;
});
if (it != redBlockers.end())
{
redBlocker = *it;
} }
else else
{ {
currentRedBlocker = ObjectGuid::Empty; redBlocker = redBlockers.front();
redBlocker = nullptr;
} }
currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty;
}
else
{
currentRedBlocker = ObjectGuid::Empty;
redBlocker = nullptr;
}
std::vector<Player*> greenBlockers = GetGreenBlockers(botAI, bot); std::vector<Player*> greenBlockers = GetGreenBlockers();
if (!greenBlockers.empty()) if (!greenBlockers.empty())
{
auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* p)
{ {
auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* player) return p && p->GetGUID() == currentGreenBlocker;
{ });
return player && player->GetGUID() == currentGreenBlocker; if (it != greenBlockers.end())
}); {
greenBlocker = *it;
if (it != greenBlockers.end())
greenBlocker = *it;
else
greenBlocker = greenBlockers.front();
currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty;
} }
else else
{ {
currentGreenBlocker = ObjectGuid::Empty; greenBlocker = greenBlockers.front();
greenBlocker = nullptr;
} }
currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty;
}
else
{
currentGreenBlocker = ObjectGuid::Empty;
greenBlocker = nullptr;
}
std::vector<Player*> blueBlockers = GetBlueBlockers(botAI, bot); std::vector<Player*> blueBlockers = GetBlueBlockers();
if (!blueBlockers.empty()) if (!blueBlockers.empty())
{ {
auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* player) auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* p)
{ {
return player && player->GetGUID() == currentBlueBlocker; return p && p->GetGUID() == currentBlueBlocker;
}); });
if (it != blueBlockers.end()) if (it != blueBlockers.end())
{
blueBlocker = *it; blueBlocker = *it;
}
else else
{
blueBlocker = blueBlockers.front(); blueBlocker = blueBlockers.front();
}
currentBlueBlocker = blueBlocker ? blueBlocker->GetGUID() : ObjectGuid::Empty; currentBlueBlocker = blueBlocker ? blueBlocker->GetGUID() : ObjectGuid::Empty;
} }
else else
@@ -359,132 +319,91 @@ namespace KarazhanHelpers
blueBlocker = nullptr; blueBlocker = nullptr;
} }
return std::make_tuple(redBlocker, greenBlocker, blueBlocker); return std::make_tuple(redBlocker, greenBlocker, blueBlocker);
} }
std::vector<Unit*> GetAllVoidZones(PlayerbotAI* botAI, Player* bot) std::vector<Unit*> RaidKarazhanHelpers::GetAllVoidZones()
{
std::vector<Unit*> voidZones;
const float radius = 30.0f;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{ {
std::vector<Unit*> voidZones; Unit* unit = botAI->GetUnit(npcGuid);
const float radius = 30.0f; if (!unit || unit->GetEntry() != NPC_VOID_ZONE)
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{ {
Unit* unit = botAI->GetUnit(npcGuid); continue;
if (!unit || unit->GetEntry() != NPC_VOID_ZONE) }
continue; float dist = bot->GetExactDist2d(unit);
if (dist < radius)
float dist = bot->GetExactDist2d(unit); {
if (dist < radius) voidZones.push_back(unit);
voidZones.push_back(unit);
} }
return voidZones;
} }
bool IsSafePosition(float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius) return voidZones;
}
bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z,
const std::vector<Unit*>& hazards, float hazardRadius)
{
for (Unit* hazard : hazards)
{ {
float dist = std::sqrt(std::pow(x - hazard->GetPositionX(), 2) + std::pow(y - hazard->GetPositionY(), 2));
if (dist < hazardRadius)
{
return false;
}
}
return true;
}
std::vector<Unit*> RaidKarazhanHelpers::GetSpawnedInfernals() const
{
std::vector<Unit*> infernals;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL)
{
infernals.push_back(unit);
}
}
return infernals;
}
bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Position& target, const std::vector<Unit*>& hazards, float hazardRadius, float stepSize)
{
float sx = start.GetPositionX();
float sy = start.GetPositionY();
float sz = start.GetPositionZ();
float tx = target.GetPositionX();
float ty = target.GetPositionY();
float tz = target.GetPositionZ();
float totalDist = std::sqrt(std::pow(tx - sx, 2) + std::pow(ty - sy, 2));
if (totalDist == 0.0f)
{
return true;
}
for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize)
{
float t = checkDist / totalDist;
float checkX = sx + (tx - sx) * t;
float checkY = sy + (ty - sy) * t;
float checkZ = sz + (tz - sz) * t;
for (Unit* hazard : hazards) for (Unit* hazard : hazards)
{ {
float dist = hazard->GetExactDist2d(x, y); float hazardDist = std::sqrt(std::pow(checkX - hazard->GetPositionX(), 2) + std::pow(checkY - hazard->GetPositionY(), 2));
if (dist < hazardRadius) if (hazardDist < hazardRadius)
{
return false; return false;
}
return true;
}
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI)
{
std::vector<Unit*> infernals;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL)
infernals.push_back(unit);
}
return infernals;
}
bool IsStraightPathSafe(const Position& start, const Position& target, const std::vector<Unit*>& hazards,
float hazardRadius, float stepSize)
{
float sx = start.GetPositionX();
float sy = start.GetPositionY();
float sz = start.GetPositionZ();
float tx = target.GetPositionX();
float ty = target.GetPositionY();
float tz = target.GetPositionZ();
const float totalDist = start.GetExactDist2d(target.GetPositionX(), target.GetPositionY());
if (totalDist == 0.0f)
return true;
for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize)
{
float t = checkDist / totalDist;
float checkX = sx + (tx - sx) * t;
float checkY = sy + (ty - sy) * t;
float checkZ = sz + (tz - sz) * t;
for (Unit* hazard : hazards)
{
const float hx = checkX - hazard->GetPositionX();
const float hy = checkY - hazard->GetPositionY();
if ((hx*hx + hy*hy) < hazardRadius * hazardRadius)
return false;
} }
} }
return true;
} }
bool TryFindSafePositionWithSafePath( return true;
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ,
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles,
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ)
{
float bestMoveDist = std::numeric_limits<float>::max();
bool found = false;
for (int i = 0; i < numAngles; ++i)
{
float angle = (2.0f * M_PI * i) / numAngles;
float dx = cos(angle);
float dy = sin(angle);
for (float dist = stepSize; dist <= maxSampleDist; dist += stepSize)
{
float x = centerX + dx * dist;
float y = centerY + dy * dist;
float z = centerZ;
float destX = x, destY = y, destZ = z;
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, centerX, centerY, centerZ,
destX, destY, destZ, true))
continue;
if (!IsSafePosition(destX, destY, destZ, hazards, safeDistance))
continue;
if (requireSafePath)
{
if (!IsStraightPathSafe(Position(originX, originY, originZ), Position(destX, destY, destZ),
hazards, safeDistance, stepSize))
continue;
}
const float moveDist = Position(originX, originY, originZ).GetExactDist2d(destX, destY);
if (moveDist < bestMoveDist)
{
bestMoveDist = moveDist;
bestDestX = destX;
bestDestY = destY;
bestDestZ = destZ;
found = true;
}
}
}
return found;
}
} }

View File

@@ -1,136 +1,85 @@
#ifndef _PLAYERBOT_RAIDKARAZHANHELPERS_H_ #ifndef _PLAYERBOT_RAIDKARAZHANHELPERS_H_
#define _PLAYERBOT_RAIDKARAZHANHELPERS_H_ #define _PLAYERBOT_RAIDKARAZHANHELPERS_H_
#include <ctime>
#include <unordered_map>
#include "AiObject.h" #include "AiObject.h"
#include "Playerbots.h"
#include "Position.h" #include "Position.h"
#include "Unit.h"
namespace KarazhanHelpers enum KarazhanSpells
{ {
enum KarazhanSpells // Maiden of Virtue
{ SPELL_REPENTANCE = 29511,
// Maiden of Virtue
SPELL_REPENTANCE = 29511,
// Opera Event // Opera Event
SPELL_LITTLE_RED_RIDING_HOOD = 30756, SPELL_LITTLE_RED_RIDING_HOOD = 30756,
// The Curator // Shade of Aran
SPELL_CURATOR_EVOCATION = 30254, SPELL_FLAME_WREATH = 30004,
SPELL_AURA_FLAME_WREATH = 29946,
SPELL_ARCANE_EXPLOSION = 29973,
SPELL_WARLOCK_BANISH = 18647, // Rank 2
// Shade of Aran
SPELL_FLAME_WREATH_CAST = 30004,
SPELL_FLAME_WREATH_AURA = 29946,
SPELL_ARCANE_EXPLOSION = 29973,
// Netherspite
SPELL_RED_BEAM_DEBUFF = 30421, // "Nether Portal - Perseverance" (player aura)
SPELL_GREEN_BEAM_DEBUFF = 30422, // "Nether Portal - Serenity" (player aura)
SPELL_BLUE_BEAM_DEBUFF = 30423, // "Nether Portal - Dominance" (player aura)
SPELL_GREEN_BEAM_HEAL = 30467, // "Nether Portal - Serenity" (Netherspite aura)
SPELL_NETHER_EXHAUSTION_RED = 38637,
SPELL_NETHER_EXHAUSTION_GREEN = 38638,
SPELL_NETHER_EXHAUSTION_BLUE = 38639,
SPELL_NETHERSPITE_BANISHED = 39833, // "Vortex Shade Black"
// Prince Malchezaar
SPELL_ENFEEBLE = 30843,
// Nightbane
SPELL_CHARRED_EARTH = 30129,
SPELL_BELLOWING_ROAR = 36922,
SPELL_RAIN_OF_BONES = 37091,
// Warlock
SPELL_WARLOCK_BANISH = 18647,
// Priest
SPELL_FEAR_WARD = 6346,
};
enum KarazhanNPCs
{
// Trash
NPC_SPECTRAL_RETAINER = 16410,
NPC_MANA_WARP = 16530,
// Attumen the Huntsman
NPC_ATTUMEN_THE_HUNTSMAN = 15550,
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
// Shade of Aran
NPC_CONJURED_ELEMENTAL = 17167,
// Netherspite
NPC_VOID_ZONE = 16697,
NPC_GREEN_PORTAL = 17367, // "Nether Portal - Serenity <Healing Portal>"
NPC_BLUE_PORTAL = 17368, // "Nether Portal - Dominance <Damage Portal>"
NPC_RED_PORTAL = 17369, // "Nether Portal - Perseverance <Tanking Portal>"
// Prince Malchezaar
NPC_NETHERSPITE_INFERNAL = 17646,
};
const uint32 KARAZHAN_MAP_ID = 532;
const float NIGHTBANE_FLIGHT_Z = 95.0f;
// Attumen the Huntsman
extern std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
// Big Bad Wolf
extern std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex;
// Netherspite // Netherspite
extern std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer; SPELL_GREEN_BEAM_DEBUFF = 30422,
extern std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer; SPELL_BLUE_BEAM_DEBUFF = 30423,
extern std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways; SPELL_NETHER_EXHAUSTION_RED = 38637,
// Nightbane SPELL_NETHER_EXHAUSTION_GREEN = 38638,
extern std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer; SPELL_NETHER_EXHAUSTION_BLUE = 38639,
extern std::unordered_map<ObjectGuid, uint8> nightbaneTankStep; SPELL_NETHERSPITE_BANISHED = 39833,
extern std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
extern std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
extern std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
extern const Position MAIDEN_OF_VIRTUE_BOSS_POSITION; // Prince Malchezaar
extern const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8]; SPELL_ENFEEBLE = 30843,
extern const Position BIG_BAD_WOLF_BOSS_POSITION; };
extern const Position BIG_BAD_WOLF_RUN_POSITION[4];
extern const Position THE_CURATOR_BOSS_POSITION;
extern const Position NIGHTBANE_TRANSITION_BOSS_POSITION;
extern const Position NIGHTBANE_FINAL_BOSS_POSITION;
extern const Position NIGHTBANE_RANGED_POSITION1;
extern const Position NIGHTBANE_RANGED_POSITION2;
extern const Position NIGHTBANE_RANGED_POSITION3;
extern const Position NIGHTBANE_FLIGHT_STACK_POSITION;
extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION;
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId); enum KarazhanNpcs
void MarkTargetWithSkull(Player* bot, Unit* target); {
void MarkTargetWithSquare(Player* bot, Unit* target); // Attumen the Huntsman
void MarkTargetWithStar(Player* bot, Unit* target); NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target); // Terestian Illhoof
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target); NPC_KILREK = 17229,
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot); NPC_DEMON_CHAINS = 17248,
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry); // Shade of Aran
Unit* GetNearestPlayerInRadius(Player* bot, float radius); NPC_CONJURED_ELEMENTAL = 17167,
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot); // Netherspite
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot); NPC_VOID_ZONE = 16697,
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot); NPC_RED_PORTAL = 17369,
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot); NPC_BLUE_PORTAL = 17368,
std::vector<Unit*> GetAllVoidZones(PlayerbotAI *botAI, Player* bot); NPC_GREEN_PORTAL = 17367,
bool IsSafePosition (float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius);
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI); // Prince Malchezaar
bool IsStraightPathSafe( NPC_NETHERSPITE_INFERNAL = 17646,
const Position& start, const Position& target, };
const std::vector<Unit*>& hazards, float hazardRadius, float stepSize);
bool TryFindSafePositionWithSafePath( extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION;
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ, extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8];
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles, extern const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION;
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ); extern const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4];
} extern const Position KARAZHAN_THE_CURATOR_BOSS_POSITION;
class RaidKarazhanHelpers : public AiObject
{
public:
explicit RaidKarazhanHelpers(PlayerbotAI* botAI) : AiObject(botAI) {}
void MarkTargetWithSkull(Unit* /*target*/);
Unit* GetFirstAliveUnit(const std::vector<Unit*>& /*units*/);
Unit* GetFirstAliveUnitByEntry(uint32 /*entry*/);
Unit* GetNearestPlayerInRadius(float /*radius*/ = 5.0f);
bool IsFlameWreathActive();
Position GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss);
std::vector<Player*> GetRedBlockers();
std::vector<Player*> GetBlueBlockers();
std::vector<Player*> GetGreenBlockers();
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers();
std::vector<Unit*> GetAllVoidZones();
bool IsSafePosition (float x, float y, float z,
const std::vector<Unit*>& hazards, float hazardRadius);
std::vector<Unit*> GetSpawnedInfernals() const;
bool IsStraightPathSafe(const Position& start, const Position& target,
const std::vector<Unit*>& hazards, float hazardRadius, float stepSize);
};
#endif #endif

View File

@@ -1,359 +1,265 @@
#include "RaidKarazhanMultipliers.h" #include "RaidKarazhanMultipliers.h"
#include "RaidKarazhanActions.h" #include "RaidKarazhanActions.h"
#include "RaidKarazhanHelpers.h" #include "RaidKarazhanHelpers.h"
#include "AiObjectContext.h"
#include "AttackAction.h" #include "AttackAction.h"
#include "ChooseTargetActions.h" #include "DruidBearActions.h"
#include "DruidActions.h" #include "DruidCatActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "Playerbots.h"
#include "PriestActions.h"
#include "ReachTargetActions.h"
#include "RogueActions.h" #include "RogueActions.h"
#include "ShamanActions.h" #include "WarriorActions.h"
using namespace KarazhanHelpers; static bool IsChargeAction(Action* action)
// Keep tanks from jumping back and forth between Attumen and Midnight
float AttumenTheHuntsmanDisableTankAssistMultiplier::GetValue(Action* action)
{ {
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"); return dynamic_cast<CastChargeAction*>(action) ||
if (!midnight) dynamic_cast<CastInterceptAction*>(action) ||
return 1.0f; dynamic_cast<CastFeralChargeBearAction*>(action) ||
dynamic_cast<CastFeralChargeCatAction*>(action);
}
Unit* attumen = AI_VALUE2(Unit*, "find target", "attumen the huntsman"); float KarazhanAttumenTheHuntsmanMultiplier::GetValue(Action* action)
if (!attumen) {
return 1.0f; RaidKarazhanHelpers karazhanHelper(botAI);
Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action)) if (boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot) &&
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<KarazhanAttumenTheHuntsmanStackBehindAction*>(action)))
{
return 0.0f; return 0.0f;
return 1.0f;
}
// Try to get rid of jittering when bots are stacked behind Attumen
float AttumenTheHuntsmanStayStackedMultiplier::GetValue(Action* action)
{
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (!attumenMounted)
return 1.0f;
if (!botAI->IsMainTank(bot) && attumenMounted->GetVictim() != bot)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
} }
return 1.0f; return 1.0f;
} }
// Give the main tank 8 seconds to grab aggro when Attumen mounts Midnight float KarazhanBigBadWolfMultiplier::GetValue(Action* action)
// In reality it's shorter because it takes Attumen a few seconds to aggro after mounting
float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
{ {
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf");
if (!attumenMounted) if (!boss)
return 1.0f;
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8;
auto it = attumenDpsWaitTimer.find(KARAZHAN_MAP_ID);
if (it == attumenDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{ {
if (!botAI->IsMainTank(bot)) return 1.0f;
}
if (bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
{
if ((dynamic_cast<MovementAction*>(action) && !dynamic_cast<KarazhanBigBadWolfRunAwayAction*>(action)) ||
(dynamic_cast<AttackAction*>(action)))
{ {
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) && return 0.0f;
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
} }
} }
return 1.0f; return 1.0f;
} }
// The assist tank should stay on the boss to be 2nd on aggro and tank Hateful Bolts float KarazhanShadeOfAranMultiplier::GetValue(Action* action)
float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action)
{ {
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator"); Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
if (!curator) if (!boss)
{
return 1.0f; return 1.0f;
}
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action)) if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION))
{
if (IsChargeAction(action))
{
return 0.0f;
}
if (dynamic_cast<MovementAction*>(action))
{
const float safeDistance = 20.0f;
if (bot->GetDistance2d(boss) >= safeDistance)
{
return 0.0f;
}
}
}
bool flameWreathActive = boss->HasAura(SPELL_FLAME_WREATH);
if (!flameWreathActive && bot->GetGroup())
{
for (GroupReference* itr = bot->GetGroup()->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (member && member->HasAura(SPELL_AURA_FLAME_WREATH))
{
flameWreathActive = true;
break;
}
}
}
if (flameWreathActive)
{
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
{
return 0.0f;
}
}
return 1.0f;
}
float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
if (!boss || !boss->IsAlive())
{
return 1.0f;
}
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<CastKillingSpreeAction*>(action))
{
return 0.0f; return 0.0f;
return 1.0f;
}
// Save Bloodlust/Heroism for Evocation (100% increased damage)
float TheCuratorDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
if (!curator)
return 1.0f;
if (!curator->HasAura(SPELL_CURATOR_EVOCATION))
{
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
} }
return 1.0f; RaidKarazhanHelpers karazhanHelper(botAI);
} auto [redBlocker /*unused*/, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers();
bool isBlocker = (bot == greenBlocker || bot == blueBlocker);
// Don't charge back in when running from Arcane Explosion if (isBlocker)
float ShadeOfAranArcaneExplosionDisableChargeMultiplier::GetValue(Action* action)
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
if (!aran)
return 1.0f;
if (aran->HasUnitState(UNIT_STATE_CASTING) &&
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION))
{ {
if (dynamic_cast<CastReachTargetSpellAction*>(action)) Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f);
return 0.0f; Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f);
bool inBeam = false;
if (bot->GetDistance2d(aran) >= 20.0f) for (Unit* portal : {bluePortal, greenPortal})
{ {
if (dynamic_cast<CombatFormationMoveAction*>(action) || if (!portal)
dynamic_cast<FleeAction*>(action) || {
dynamic_cast<FollowAction*>(action) || continue;
dynamic_cast<ReachTargetAction*>(action) || }
dynamic_cast<AvoidAoeAction*>(action)) float bx = boss->GetPositionX(), by = boss->GetPositionY();
return 0.0f; float px = portal->GetPositionX(), py = portal->GetPositionY();
float dx = px - bx, dy = py - by;
float length = sqrt(dx*dx + dy*dy);
if (length == 0.0f)
{
continue;
}
dx /= length; dy /= length;
float botdx = bot->GetPositionX() - bx, botdy = bot->GetPositionY() - by;
float t = (botdx * dx + botdy * dy);
float beamX = bx + dx * t, beamY = by + dy * t;
float distToBeam = sqrt(pow(bot->GetPositionX() - beamX, 2) + pow(bot->GetPositionY() - beamY, 2));
if (distToBeam < 0.3f && t > 0.0f && t < length)
{
inBeam = true;
break;
}
}
if (inBeam)
{
std::vector<Unit*> voidZones = karazhanHelper.GetAllVoidZones();
bool inVoidZone = false;
for (Unit* vz : voidZones)
{
if (bot->GetExactDist2d(vz) < 4.0f)
{
inVoidZone = true;
break;
}
}
if (!inVoidZone)
{
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
{
return 0.0f;
}
}
} }
} }
return 1.0f; return 1.0f;
} }
// I will not move when Flame Wreath is cast or the raid blows up float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action)
float ShadeOfAranFlameWreathDisableMovementMultiplier::GetValue(Action* action)
{ {
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran"); Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
if (!aran) if (!boss || !boss->IsAlive())
{
return 1.0f; return 1.0f;
if (IsFlameWreathActive(botAI, bot))
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<AvoidAoeAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
} }
return 1.0f;
}
// Try to rid of the jittering when blocking beams
float NetherspiteKeepBlockingBeamMultiplier::GetValue(Action* action)
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return 1.0f;
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
if (bot == redBlocker)
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
if (bot == blueBlocker)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action))
return 0.0f;
}
if (bot == greenBlocker)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Give tanks 5 seconds to get aggro during phase transitions
float NetherspiteWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return 1.0f;
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 5;
auto it = netherspiteDpsWaitTimer.find(KARAZHAN_MAP_ID);
if (it == netherspiteDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsTank(bot))
{
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
// Disable standard "avoid aoe" strategy, which may interfere with scripted avoidance
float PrinceMalchezaarDisableAvoidAoeMultiplier::GetValue(Action* action)
{
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!malchezaar)
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action)) if (dynamic_cast<AvoidAoeAction*>(action))
{
return 0.0f; return 0.0f;
return 1.0f;
}
// Don't run back into Shadow Nova when Enfeebled
float PrinceMalchezaarEnfeebleKeepDistanceMultiplier::GetValue(Action* action)
{
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!malchezaar)
return 1.0f;
if (bot->HasAura(SPELL_ENFEEBLE))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<PrinceMalchezaarEnfeebledAvoidHazardAction*>(action))
return 0.0f;
} }
return 1.0f; RaidKarazhanHelpers karazhanHelper(botAI);
} auto [redBlocker, greenBlocker /*unused*/, blueBlocker /*unused*/] = karazhanHelper.GetCurrentBeamBlockers();
static std::map<ObjectGuid, uint32> beamMoveTimes;
// Wait until Phase 3 to use Bloodlust/Heroism static std::map<ObjectGuid, bool> lastBeamMoveSideways;
float PrinceMalchezaarDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) ObjectGuid botGuid = bot->GetGUID();
{ Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f);
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar"); if (bot == redBlocker && boss && redPortal)
if (!malchezaar)
return 1.0f;
if (malchezaar->GetHealthPct() > 30.0f)
{ {
if (dynamic_cast<CastBloodlustAction*>(action) || Position blockingPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f);
dynamic_cast<CastHeroismAction*>(action)) float bx = boss->GetPositionX();
return 0.0f; float by = boss->GetPositionY();
} float px = redPortal->GetPositionX();
float py = redPortal->GetPositionY();
return 1.0f; float dx = px - bx;
} float dy = py - by;
float length = sqrt(dx*dx + dy*dy);
// Pets tend to run out of bounds and cause skeletons to spawn off the map if (length != 0.0f)
// Pets also tend to pull adds from inside of the tower through the floor
// This multiplier DOES NOT impact Hunter and Warlock pets
// Hunter and Warlock pets are addressed in ControlPetAggressionAction
float NightbaneDisablePetsMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return 1.0f;
if (dynamic_cast<CastForceOfNatureAction*>(action) ||
dynamic_cast<CastFeralSpiritAction*>(action) ||
dynamic_cast<CastFireElementalTotemAction*>(action) ||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
dynamic_cast<CastSummonWaterElementalAction*>(action) ||
dynamic_cast<CastShadowfiendAction*>(action))
return 0.0f;
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
{
if (dynamic_cast<PetAttackAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Give the main tank 8 seconds to get aggro during phase transitions
float NightbaneWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
return 1.0f;
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8;
auto it = nightbaneDpsWaitTimer.find(KARAZHAN_MAP_ID);
if (it == nightbaneDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot))
{ {
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) && dx /= length;
!dynamic_cast<CastHealingSpellAction*>(action))) dy /= length;
return 0.0f; float perpDx = -dy;
float perpDy = dx;
Position sidewaysPos(blockingPos.GetPositionX() + perpDx * 3.0f,
blockingPos.GetPositionY() + perpDy * 3.0f,
blockingPos.GetPositionZ());
uint32 intervalSecs = 5;
if (beamMoveTimes[botGuid] == 0)
{
beamMoveTimes[botGuid] = time(nullptr);
lastBeamMoveSideways[botGuid] = false;
}
if (time(nullptr) - beamMoveTimes[botGuid] >= intervalSecs)
{
lastBeamMoveSideways[botGuid] = !lastBeamMoveSideways[botGuid];
beamMoveTimes[botGuid] = time(nullptr);
}
Position targetPos = lastBeamMoveSideways[botGuid] ? sidewaysPos : blockingPos;
float distToTarget = bot->GetExactDist2d(targetPos.GetPositionX(), targetPos.GetPositionY());
const float positionTolerance = 1.5f;
if (distToTarget < positionTolerance)
{
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
{
return 0.0f;
}
}
} }
} }
return 1.0f; return 1.0f;
} }
// The "avoid aoe" strategy must be disabled for the main tank float KarazhanPrinceMalchezaarMultiplier::GetValue(Action* action)
// Otherwise, the main tank will spin Nightbane to avoid Charred Earth and wipe the raid
// It is also disabled for all bots during the flight phase
float NightbaneDisableAvoidAoeMultiplier::GetValue(Action* action)
{ {
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!nightbane) if (!boss || !boss->IsAlive())
return 1.0f;
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z || botAI->IsMainTank(bot))
{ {
if (dynamic_cast<AvoidAoeAction*>(action)) return 1.0f;
return 0.0f;
} }
return 1.0f; if (dynamic_cast<AvoidAoeAction*>(action))
} {
// Disable some movement actions that conflict with the strategies
float NightbaneDisableMovementMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return 1.0f;
if (dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f; return 0.0f;
}
// Disable CombatFormationMoveAction for all bots except: if (botAI->IsMelee(bot) && bot->HasAura(SPELL_ENFEEBLE) &&
// (1) main tank and (2) only during the ground phase, other melee !dynamic_cast<KarazhanPrinceMalchezaarNonTankAvoidHazardAction*>(action))
if (botAI->IsRanged(bot) ||
(botAI->IsMelee(bot) && !botAI->IsMainTank(bot) &&
nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z))
{ {
if (dynamic_cast<CombatFormationMoveAction*>(action)) return 0.0f;
return 0.0f; }
if (botAI->IsRanged(bot) && bot->HasAura(SPELL_ENFEEBLE) &&
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<KarazhanPrinceMalchezaarNonTankAvoidHazardAction*>(action)))
{
return 0.0f;
} }
return 1.0f; return 1.0f;

View File

@@ -3,131 +3,45 @@
#include "Multiplier.h" #include "Multiplier.h"
class AttumenTheHuntsmanDisableTankAssistMultiplier : public Multiplier class KarazhanAttumenTheHuntsmanMultiplier : public Multiplier
{ {
public: public:
AttumenTheHuntsmanDisableTankAssistMultiplier( KarazhanAttumenTheHuntsmanMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan attumen the huntsman multiplier") {}
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman disable tank assist multiplier") {}
virtual float GetValue(Action* action); virtual float GetValue(Action* action);
}; };
class AttumenTheHuntsmanStayStackedMultiplier : public Multiplier class KarazhanBigBadWolfMultiplier : public Multiplier
{ {
public: public:
AttumenTheHuntsmanStayStackedMultiplier( KarazhanBigBadWolfMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan big bad wolf multiplier") {}
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman stay stacked multiplier") {}
virtual float GetValue(Action* action); virtual float GetValue(Action* action);
}; };
class AttumenTheHuntsmanWaitForDpsMultiplier : public Multiplier class KarazhanShadeOfAranMultiplier : public Multiplier
{ {
public: public:
AttumenTheHuntsmanWaitForDpsMultiplier( KarazhanShadeOfAranMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan shade of aran multiplier") {}
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman wait for dps multiplier") {}
virtual float GetValue(Action* action); virtual float GetValue(Action* action);
}; };
class TheCuratorDisableTankAssistMultiplier : public Multiplier class KarazhanNetherspiteBlueAndGreenBeamMultiplier : public Multiplier
{ {
public: public:
TheCuratorDisableTankAssistMultiplier( KarazhanNetherspiteBlueAndGreenBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite blue and green beam multiplier") {}
PlayerbotAI* botAI) : Multiplier(botAI, "the curator disable tank assist multiplier") {}
virtual float GetValue(Action* action); virtual float GetValue(Action* action);
}; };
class TheCuratorDelayBloodlustAndHeroismMultiplier : public Multiplier class KarazhanNetherspiteRedBeamMultiplier : public Multiplier
{ {
public: public:
TheCuratorDelayBloodlustAndHeroismMultiplier( KarazhanNetherspiteRedBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite red beam multiplier") {}
PlayerbotAI* botAI) : Multiplier(botAI, "the curator delay bloodlust and heroism multiplier") {}
virtual float GetValue(Action* action); virtual float GetValue(Action* action);
}; };
class ShadeOfAranArcaneExplosionDisableChargeMultiplier : public Multiplier class KarazhanPrinceMalchezaarMultiplier : public Multiplier
{ {
public: public:
ShadeOfAranArcaneExplosionDisableChargeMultiplier( KarazhanPrinceMalchezaarMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan prince malchezaar multiplier") {}
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran arcane explosion disable charge multiplier") {}
virtual float GetValue(Action* action);
};
class ShadeOfAranFlameWreathDisableMovementMultiplier : public Multiplier
{
public:
ShadeOfAranFlameWreathDisableMovementMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran flame wreath disable movement multiplier") {}
virtual float GetValue(Action* action);
};
class NetherspiteKeepBlockingBeamMultiplier : public Multiplier
{
public:
NetherspiteKeepBlockingBeamMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite keep blocking beam multiplier") {}
virtual float GetValue(Action* action);
};
class NetherspiteWaitForDpsMultiplier : public Multiplier
{
public:
NetherspiteWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite wait for dps multiplier") {}
virtual float GetValue(Action* action);
};
class PrinceMalchezaarDisableAvoidAoeMultiplier : public Multiplier
{
public:
PrinceMalchezaarDisableAvoidAoeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar disable avoid aoe multiplier") {}
virtual float GetValue(Action* action);
};
class PrinceMalchezaarEnfeebleKeepDistanceMultiplier : public Multiplier
{
public:
PrinceMalchezaarEnfeebleKeepDistanceMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar enfeeble keep distance multiplier") {}
virtual float GetValue(Action* action);
};
class PrinceMalchezaarDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:
PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar delay bloodlust and heroism multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneDisablePetsMultiplier : public Multiplier
{
public:
NightbaneDisablePetsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable pets multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneWaitForDpsMultiplier : public Multiplier
{
public:
NightbaneWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane wait for dps multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneDisableAvoidAoeMultiplier : public Multiplier
{
public:
NightbaneDisableAvoidAoeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable avoid aoe multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneDisableMovementMultiplier : public Multiplier
{
public:
NightbaneDisableMovementMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable movement multiplier") {}
virtual float GetValue(Action* action); virtual float GetValue(Action* action);
}; };

View File

@@ -3,160 +3,79 @@
void RaidKarazhanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void RaidKarazhanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
// Trash triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("mana warp is about to explode", "karazhan attumen the huntsman", NextAction::array(0,
NextAction::array(0, new NextAction("mana warp stun creature before warp breach", ACTION_EMERGENCY + 6), nullptr) new NextAction("karazhan attumen the huntsman stack behind", ACTION_RAID + 1),
)); nullptr)));
// Attumen the Huntsman triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("attumen the huntsman need target priority", "karazhan moroes", NextAction::array(0,
NextAction::array(0, new NextAction("attumen the huntsman mark target", ACTION_RAID + 1), nullptr) new NextAction("karazhan moroes mark target", ACTION_RAID + 1),
)); nullptr)));
triggers.push_back(new TriggerNode("attumen the huntsman attumen spawned",
NextAction::array(0, new NextAction("attumen the huntsman split bosses", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode("attumen the huntsman attumen is mounted",
NextAction::array(0, new NextAction("attumen the huntsman stack behind", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("attumen the huntsman boss wipes aggro when mounting",
NextAction::array(0, new NextAction("attumen the huntsman manage dps timer", ACTION_RAID + 2), nullptr)
));
// Moroes triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("moroes boss engaged by main tank", "karazhan maiden of virtue", NextAction::array(0,
NextAction::array(0, new NextAction("moroes main tank attack boss", ACTION_RAID + 1), nullptr) new NextAction("karazhan maiden of virtue position ranged", ACTION_RAID + 1),
)); new NextAction("karazhan maiden of virtue position boss", ACTION_RAID + 1),
triggers.push_back(new TriggerNode("moroes need target priority", nullptr)));
NextAction::array(0, new NextAction("moroes mark target", ACTION_RAID + 1), nullptr)
));
// Maiden of Virtue triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("maiden of virtue healers are stunned by repentance", "karazhan big bad wolf", NextAction::array(0,
NextAction::array(0, new NextAction("maiden of virtue move boss to healer", ACTION_RAID + 1), nullptr) new NextAction("karazhan big bad wolf run away", ACTION_EMERGENCY + 6),
)); new NextAction("karazhan big bad wolf position boss", ACTION_RAID + 1),
triggers.push_back(new TriggerNode("maiden of virtue holy wrath deals chain damage", nullptr)));
NextAction::array(0, new NextAction("maiden of virtue position ranged", ACTION_RAID + 1), nullptr)
));
// The Big Bad Wolf triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("big bad wolf boss is chasing little red riding hood", "karazhan romulo and julianne", NextAction::array(0,
NextAction::array(0, new NextAction("big bad wolf run away from boss", ACTION_EMERGENCY + 6), nullptr) new NextAction("karazhan romulo and julianne mark target", ACTION_RAID + 1),
)); nullptr)));
triggers.push_back(new TriggerNode("big bad wolf boss engaged by tank",
NextAction::array(0, new NextAction("big bad wolf position boss", ACTION_RAID + 1), nullptr)
));
// Romulo and Julianne triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("romulo and julianne both bosses revived", "karazhan wizard of oz", NextAction::array(0,
NextAction::array(0, new NextAction("romulo and julianne mark target", ACTION_RAID + 1), nullptr) new NextAction("karazhan wizard of oz scorch strawman", ACTION_RAID + 2),
)); new NextAction("karazhan wizard of oz mark target", ACTION_RAID + 1),
nullptr)));
// The Wizard of Oz triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("wizard of oz need target priority", "karazhan the curator", NextAction::array(0,
NextAction::array(0, new NextAction("wizard of oz mark target", ACTION_RAID + 1), nullptr) new NextAction("karazhan the curator spread ranged", ACTION_RAID + 2),
)); new NextAction("karazhan the curator position boss", ACTION_RAID + 2),
triggers.push_back(new TriggerNode("wizard of oz strawman is vulnerable to fire", new NextAction("karazhan the curator mark target", ACTION_RAID + 1),
NextAction::array(0, new NextAction("wizard of oz scorch strawman", ACTION_RAID + 2), nullptr) nullptr)));
));
// The Curator triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("the curator astral flare spawned", "karazhan terestian illhoof", NextAction::array(0,
NextAction::array(0, new NextAction("the curator mark astral flare", ACTION_RAID + 1), nullptr) new NextAction("karazhan terestian illhoof mark target", ACTION_RAID + 1),
)); nullptr)));
triggers.push_back(new TriggerNode("the curator boss engaged by tanks",
NextAction::array(0, new NextAction("the curator position boss", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode("the curator astral flares cast arcing sear",
NextAction::array(0, new NextAction("the curator spread ranged", ACTION_RAID + 2), nullptr)
));
// Terestian Illhoof triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("terestian illhoof need target priority", "karazhan shade of aran", NextAction::array(0,
NextAction::array(0, new NextAction("terestian illhoof mark target", ACTION_RAID + 1), nullptr) new NextAction("karazhan shade of aran flame wreath stop movement", ACTION_EMERGENCY + 7),
)); new NextAction("karazhan shade of aran arcane explosion run away", ACTION_EMERGENCY + 6),
new NextAction("karazhan shade of aran spread ranged", ACTION_RAID + 2),
new NextAction("karazhan shade of aran mark conjured elemental", ACTION_RAID + 1),
nullptr)));
// Shade of Aran triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("shade of aran arcane explosion is casting", "karazhan netherspite", NextAction::array(0,
NextAction::array(0, new NextAction("shade of aran run away from arcane explosion", ACTION_EMERGENCY + 6), nullptr) new NextAction("karazhan netherspite block red beam", ACTION_EMERGENCY + 8),
)); new NextAction("karazhan netherspite block blue beam", ACTION_EMERGENCY + 8),
triggers.push_back(new TriggerNode("shade of aran flame wreath is active", new NextAction("karazhan netherspite block green beam", ACTION_EMERGENCY + 8),
NextAction::array(0, new NextAction("shade of aran stop moving during flame wreath", ACTION_EMERGENCY + 7), nullptr) new NextAction("karazhan netherspite avoid beam and void zone", ACTION_EMERGENCY + 7),
)); new NextAction("karazhan netherspite banish phase avoid void zone", ACTION_RAID + 1),
triggers.push_back(new TriggerNode("shade of aran conjured elementals summoned", nullptr)));
NextAction::array(0, new NextAction("shade of aran mark conjured elemental", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("shade of aran boss uses counterspell and blizzard",
NextAction::array(0, new NextAction("shade of aran ranged maintain distance", ACTION_RAID + 2), nullptr)
));
// Netherspite triggers.push_back(new TriggerNode(
triggers.push_back(new TriggerNode("netherspite red beam is active", "karazhan prince malchezaar", NextAction::array(0,
NextAction::array(0, new NextAction("netherspite block red beam", ACTION_EMERGENCY + 8), nullptr) new NextAction("karazhan prince malchezaar non tank avoid hazard", ACTION_EMERGENCY + 6),
)); new NextAction("karazhan prince malchezaar tank avoid hazard", ACTION_EMERGENCY + 6),
triggers.push_back(new TriggerNode("netherspite blue beam is active", nullptr)));
NextAction::array(0, new NextAction("netherspite block blue beam", ACTION_EMERGENCY + 8), nullptr)
));
triggers.push_back(new TriggerNode("netherspite green beam is active",
NextAction::array(0, new NextAction("netherspite block green beam", ACTION_EMERGENCY + 8), nullptr)
));
triggers.push_back(new TriggerNode("netherspite bot is not beam blocker",
NextAction::array(0, new NextAction("netherspite avoid beam and void zone", ACTION_EMERGENCY + 7), nullptr)
));
triggers.push_back(new TriggerNode("netherspite boss is banished",
NextAction::array(0, new NextAction("netherspite banish phase avoid void zone", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("netherspite need to manage timers and trackers",
NextAction::array(0, new NextAction("netherspite manage timers and trackers", ACTION_EMERGENCY + 10), nullptr)
));
// Prince Malchezaar
triggers.push_back(new TriggerNode("prince malchezaar bot is enfeebled",
NextAction::array(0, new NextAction("prince malchezaar enfeebled avoid hazard", ACTION_EMERGENCY + 6), nullptr)
));
triggers.push_back(new TriggerNode("prince malchezaar infernals are spawned",
NextAction::array(0, new NextAction("prince malchezaar non tank avoid infernal", ACTION_EMERGENCY + 1), nullptr)
));
triggers.push_back(new TriggerNode("prince malchezaar boss engaged by main tank",
NextAction::array(0, new NextAction("prince malchezaar main tank movement", ACTION_EMERGENCY + 6), nullptr)
));
// Nightbane
triggers.push_back(new TriggerNode("nightbane boss engaged by main tank",
NextAction::array(0, new NextAction("nightbane ground phase position boss", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("nightbane ranged bots are in charred earth",
NextAction::array(0, new NextAction("nightbane ground phase rotate ranged positions", ACTION_EMERGENCY + 1), nullptr)
));
triggers.push_back(new TriggerNode("nightbane main tank is susceptible to fear",
NextAction::array(0, new NextAction("nightbane cast fear ward on main tank", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode("nightbane pets ignore collision to chase flying boss",
NextAction::array(0, new NextAction("nightbane control pet aggression", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode("nightbane boss is flying",
NextAction::array(0, new NextAction("nightbane flight phase movement", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("nightbane need to manage timers and trackers",
NextAction::array(0, new NextAction("nightbane manage timers and trackers", ACTION_EMERGENCY + 10), nullptr)
));
} }
void RaidKarazhanStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers) void RaidKarazhanStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{ {
multipliers.push_back(new AttumenTheHuntsmanDisableTankAssistMultiplier(botAI)); multipliers.push_back(new KarazhanShadeOfAranMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanStayStackedMultiplier(botAI)); multipliers.push_back(new KarazhanNetherspiteBlueAndGreenBeamMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanWaitForDpsMultiplier(botAI)); multipliers.push_back(new KarazhanNetherspiteRedBeamMultiplier(botAI));
multipliers.push_back(new TheCuratorDisableTankAssistMultiplier(botAI)); multipliers.push_back(new KarazhanPrinceMalchezaarMultiplier(botAI));
multipliers.push_back(new TheCuratorDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new ShadeOfAranArcaneExplosionDisableChargeMultiplier(botAI));
multipliers.push_back(new ShadeOfAranFlameWreathDisableMovementMultiplier(botAI));
multipliers.push_back(new NetherspiteKeepBlockingBeamMultiplier(botAI));
multipliers.push_back(new NetherspiteWaitForDpsMultiplier(botAI));
multipliers.push_back(new PrinceMalchezaarDisableAvoidAoeMultiplier(botAI));
multipliers.push_back(new PrinceMalchezaarEnfeebleKeepDistanceMultiplier(botAI));
multipliers.push_back(new PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new NightbaneDisablePetsMultiplier(botAI));
multipliers.push_back(new NightbaneWaitForDpsMultiplier(botAI));
multipliers.push_back(new NightbaneDisableAvoidAoeMultiplier(botAI));
multipliers.push_back(new NightbaneDisableMovementMultiplier(botAI));
} }

View File

@@ -7,7 +7,7 @@
class RaidKarazhanStrategy : public Strategy class RaidKarazhanStrategy : public Strategy
{ {
public: public:
RaidKarazhanStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} RaidKarazhanStrategy(PlayerbotAI* ai) : Strategy(ai) {}
std::string const getName() override { return "karazhan"; } std::string const getName() override { return "karazhan"; }

View File

@@ -9,255 +9,31 @@ class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger>
public: public:
RaidKarazhanTriggerContext() RaidKarazhanTriggerContext()
{ {
// Trash creators["karazhan attumen the huntsman"] = &RaidKarazhanTriggerContext::karazhan_attumen_the_huntsman;
creators["mana warp is about to explode"] = creators["karazhan moroes"] = &RaidKarazhanTriggerContext::karazhan_moroes;
&RaidKarazhanTriggerContext::mana_warp_is_about_to_explode; creators["karazhan maiden of virtue"] = &RaidKarazhanTriggerContext::karazhan_maiden_of_virtue;
creators["karazhan big bad wolf"] = &RaidKarazhanTriggerContext::karazhan_big_bad_wolf;
// Attumen the Huntsman creators["karazhan romulo and julianne"] = &RaidKarazhanTriggerContext::karazhan_romulo_and_julianne;
creators["attumen the huntsman need target priority"] = creators["karazhan wizard of oz"] = &RaidKarazhanTriggerContext::karazhan_wizard_of_oz;
&RaidKarazhanTriggerContext::attumen_the_huntsman_need_target_priority; creators["karazhan the curator"] = &RaidKarazhanTriggerContext::karazhan_the_curator;
creators["karazhan terestian illhoof"] = &RaidKarazhanTriggerContext::karazhan_terestian_illhoof;
creators["attumen the huntsman attumen spawned"] = creators["karazhan shade of aran"] = &RaidKarazhanTriggerContext::karazhan_shade_of_aran;
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_spawned; creators["karazhan netherspite"] = &RaidKarazhanTriggerContext::karazhan_netherspite;
creators["karazhan prince malchezaar"] = &RaidKarazhanTriggerContext::karazhan_prince_malchezaar;
creators["attumen the huntsman attumen is mounted"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_is_mounted;
creators["attumen the huntsman boss wipes aggro when mounting"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_boss_wipes_aggro_when_mounting;
// Moroes
creators["moroes boss engaged by main tank"] =
&RaidKarazhanTriggerContext::moroes_boss_engaged_by_main_tank;
creators["moroes need target priority"] =
&RaidKarazhanTriggerContext::moroes_need_target_priority;
// Maiden of Virtue
creators["maiden of virtue healers are stunned by repentance"] =
&RaidKarazhanTriggerContext::maiden_of_virtue_healers_are_stunned_by_repentance;
creators["maiden of virtue holy wrath deals chain damage"] =
&RaidKarazhanTriggerContext::maiden_of_virtue_holy_wrath_deals_chain_damage;
// The Big Bad Wolf
creators["big bad wolf boss engaged by tank"] =
&RaidKarazhanTriggerContext::big_bad_wolf_boss_engaged_by_tank;
creators["big bad wolf boss is chasing little red riding hood"] =
&RaidKarazhanTriggerContext::big_bad_wolf_boss_is_chasing_little_red_riding_hood;
// Romulo and Julianne
creators["romulo and julianne both bosses revived"] =
&RaidKarazhanTriggerContext::romulo_and_julianne_both_bosses_revived;
// The Wizard of Oz
creators["wizard of oz need target priority"] =
&RaidKarazhanTriggerContext::wizard_of_oz_need_target_priority;
creators["wizard of oz strawman is vulnerable to fire"] =
&RaidKarazhanTriggerContext::wizard_of_oz_strawman_is_vulnerable_to_fire;
// The Curator
creators["the curator astral flare spawned"] =
&RaidKarazhanTriggerContext::the_curator_astral_flare_spawned;
creators["the curator boss engaged by tanks"] =
&RaidKarazhanTriggerContext::the_curator_boss_engaged_by_tanks;
creators["the curator astral flares cast arcing sear"] =
&RaidKarazhanTriggerContext::the_curator_astral_flares_cast_arcing_sear;
// Terestian Illhoof
creators["terestian illhoof need target priority"] =
&RaidKarazhanTriggerContext::terestian_illhoof_need_target_priority;
// Shade of Aran
creators["shade of aran arcane explosion is casting"] =
&RaidKarazhanTriggerContext::shade_of_aran_arcane_explosion_is_casting;
creators["shade of aran flame wreath is active"] =
&RaidKarazhanTriggerContext::shade_of_aran_flame_wreath_is_active;
creators["shade of aran conjured elementals summoned"] =
&RaidKarazhanTriggerContext::shade_of_aran_conjured_elementals_summoned;
creators["shade of aran boss uses counterspell and blizzard"] =
&RaidKarazhanTriggerContext::shade_of_aran_boss_uses_counterspell_and_blizzard;
// Netherspite
creators["netherspite red beam is active"] =
&RaidKarazhanTriggerContext::netherspite_red_beam_is_active;
creators["netherspite blue beam is active"] =
&RaidKarazhanTriggerContext::netherspite_blue_beam_is_active;
creators["netherspite green beam is active"] =
&RaidKarazhanTriggerContext::netherspite_green_beam_is_active;
creators["netherspite bot is not beam blocker"] =
&RaidKarazhanTriggerContext::netherspite_bot_is_not_beam_blocker;
creators["netherspite boss is banished"] =
&RaidKarazhanTriggerContext::netherspite_boss_is_banished;
creators["netherspite need to manage timers and trackers"] =
&RaidKarazhanTriggerContext::netherspite_need_to_manage_timers_and_trackers;
// Prince Malchezaar
creators["prince malchezaar bot is enfeebled"] =
&RaidKarazhanTriggerContext::prince_malchezaar_bot_is_enfeebled;
creators["prince malchezaar infernals are spawned"] =
&RaidKarazhanTriggerContext::prince_malchezaar_infernals_are_spawned;
creators["prince malchezaar boss engaged by main tank"] =
&RaidKarazhanTriggerContext::prince_malchezaar_boss_engaged_by_main_tank;
// Nightbane
creators["nightbane boss engaged by main tank"] =
&RaidKarazhanTriggerContext::nightbane_boss_engaged_by_main_tank;
creators["nightbane ranged bots are in charred earth"] =
&RaidKarazhanTriggerContext::nightbane_ranged_bots_are_in_charred_earth;
creators["nightbane main tank is susceptible to fear"] =
&RaidKarazhanTriggerContext::nightbane_main_tank_is_susceptible_to_fear;
creators["nightbane pets ignore collision to chase flying boss"] =
&RaidKarazhanTriggerContext::nightbane_pets_ignore_collision_to_chase_flying_boss;
creators["nightbane boss is flying"] =
&RaidKarazhanTriggerContext::nightbane_boss_is_flying;
creators["nightbane need to manage timers and trackers"] =
&RaidKarazhanTriggerContext::nightbane_need_to_manage_timers_and_trackers;
} }
private: private:
// Trash static Trigger* karazhan_attumen_the_huntsman(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanTrigger(botAI); }
static Trigger* mana_warp_is_about_to_explode( static Trigger* karazhan_moroes(PlayerbotAI* botAI) { return new KarazhanMoroesTrigger(botAI); }
PlayerbotAI* botAI) { return new ManaWarpIsAboutToExplodeTrigger(botAI); } static Trigger* karazhan_maiden_of_virtue(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtueTrigger(botAI); }
static Trigger* karazhan_big_bad_wolf(PlayerbotAI* botAI) { return new KarazhanBigBadWolfTrigger(botAI); }
// Attumen the Huntsman static Trigger* karazhan_romulo_and_julianne(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneTrigger(botAI); }
static Trigger* attumen_the_huntsman_need_target_priority( static Trigger* karazhan_wizard_of_oz(PlayerbotAI* botAI) { return new KarazhanWizardOfOzTrigger(botAI); }
PlayerbotAI* botAI) { return new AttumenTheHuntsmanNeedTargetPriorityTrigger(botAI); } static Trigger* karazhan_the_curator(PlayerbotAI* botAI) { return new KarazhanTheCuratorTrigger(botAI); }
static Trigger* karazhan_terestian_illhoof(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofTrigger(botAI); }
static Trigger* attumen_the_huntsman_attumen_spawned( static Trigger* karazhan_shade_of_aran(PlayerbotAI* botAI) { return new KarazhanShadeOfAranTrigger(botAI); }
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenSpawnedTrigger(botAI); } static Trigger* karazhan_netherspite(PlayerbotAI* botAI) { return new KarazhanNetherspiteTrigger(botAI); }
static Trigger* karazhan_prince_malchezaar(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTrigger(botAI); }
static Trigger* attumen_the_huntsman_attumen_is_mounted(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenIsMountedTrigger(botAI); }
static Trigger* attumen_the_huntsman_boss_wipes_aggro_when_mounting(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(botAI); }
// Moroes
static Trigger* moroes_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new MoroesBossEngagedByMainTankTrigger(botAI); }
static Trigger* moroes_need_target_priority(
PlayerbotAI* botAI) { return new MoroesNeedTargetPriorityTrigger(botAI); }
// Maiden of Virtue
static Trigger* maiden_of_virtue_healers_are_stunned_by_repentance(
PlayerbotAI* botAI) { return new MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(botAI); }
static Trigger* maiden_of_virtue_holy_wrath_deals_chain_damage(
PlayerbotAI* botAI) { return new MaidenOfVirtueHolyWrathDealsChainDamageTrigger(botAI); }
// The Big Bad Wolf
static Trigger* big_bad_wolf_boss_engaged_by_tank(
PlayerbotAI* botAI) { return new BigBadWolfBossEngagedByTankTrigger(botAI); }
static Trigger* big_bad_wolf_boss_is_chasing_little_red_riding_hood(
PlayerbotAI* botAI) { return new BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(botAI); }
// Romulo and Julianne
static Trigger* romulo_and_julianne_both_bosses_revived(
PlayerbotAI* botAI) { return new RomuloAndJulianneBothBossesRevivedTrigger(botAI); }
// The Wizard of Oz
static Trigger* wizard_of_oz_need_target_priority(
PlayerbotAI* botAI) { return new WizardOfOzNeedTargetPriorityTrigger(botAI); }
static Trigger* wizard_of_oz_strawman_is_vulnerable_to_fire(
PlayerbotAI* botAI) { return new WizardOfOzStrawmanIsVulnerableToFireTrigger(botAI); }
// The Curator
static Trigger* the_curator_astral_flare_spawned(
PlayerbotAI* botAI) { return new TheCuratorAstralFlareSpawnedTrigger(botAI); }
static Trigger* the_curator_boss_engaged_by_tanks(
PlayerbotAI* botAI) { return new TheCuratorBossEngagedByTanksTrigger(botAI); }
static Trigger* the_curator_astral_flares_cast_arcing_sear(
PlayerbotAI* botAI) { return new TheCuratorBossAstralFlaresCastArcingSearTrigger(botAI); }
// Terestian Illhoof
static Trigger* terestian_illhoof_need_target_priority(
PlayerbotAI* botAI) { return new TerestianIllhoofNeedTargetPriorityTrigger(botAI); }
// Shade of Aran
static Trigger* shade_of_aran_arcane_explosion_is_casting(
PlayerbotAI* botAI) { return new ShadeOfAranArcaneExplosionIsCastingTrigger(botAI); }
static Trigger* shade_of_aran_flame_wreath_is_active(
PlayerbotAI* botAI) { return new ShadeOfAranFlameWreathIsActiveTrigger(botAI); }
static Trigger* shade_of_aran_conjured_elementals_summoned(
PlayerbotAI* botAI) { return new ShadeOfAranConjuredElementalsSummonedTrigger(botAI); }
static Trigger* shade_of_aran_boss_uses_counterspell_and_blizzard(
PlayerbotAI* botAI) { return new ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(botAI); }
// Netherspite
static Trigger* netherspite_red_beam_is_active(
PlayerbotAI* botAI) { return new NetherspiteRedBeamIsActiveTrigger(botAI); }
static Trigger* netherspite_blue_beam_is_active(
PlayerbotAI* botAI) { return new NetherspiteBlueBeamIsActiveTrigger(botAI); }
static Trigger* netherspite_green_beam_is_active(
PlayerbotAI* botAI) { return new NetherspiteGreenBeamIsActiveTrigger(botAI); }
static Trigger* netherspite_bot_is_not_beam_blocker(
PlayerbotAI* botAI) { return new NetherspiteBotIsNotBeamBlockerTrigger(botAI); }
static Trigger* netherspite_boss_is_banished(
PlayerbotAI* botAI) { return new NetherspiteBossIsBanishedTrigger(botAI); }
static Trigger* netherspite_need_to_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NetherspiteNeedToManageTimersAndTrackersTrigger(botAI); }
// Prince Malchezaar
static Trigger* prince_malchezaar_bot_is_enfeebled(
PlayerbotAI* botAI) { return new PrinceMalchezaarBotIsEnfeebledTrigger(botAI); }
static Trigger* prince_malchezaar_infernals_are_spawned(
PlayerbotAI* botAI) { return new PrinceMalchezaarInfernalsAreSpawnedTrigger(botAI); }
static Trigger* prince_malchezaar_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new PrinceMalchezaarBossEngagedByMainTankTrigger(botAI); }
// Nightbane
static Trigger* nightbane_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new NightbaneBossEngagedByMainTankTrigger(botAI); }
static Trigger* nightbane_ranged_bots_are_in_charred_earth(
PlayerbotAI* botAI) { return new NightbaneRangedBotsAreInCharredEarthTrigger(botAI); }
static Trigger* nightbane_main_tank_is_susceptible_to_fear(
PlayerbotAI* botAI) { return new NightbaneMainTankIsSusceptibleToFearTrigger(botAI); }
static Trigger* nightbane_pets_ignore_collision_to_chase_flying_boss(
PlayerbotAI* botAI) { return new NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(botAI); }
static Trigger* nightbane_boss_is_flying(
PlayerbotAI* botAI) { return new NightbaneBossIsFlyingTrigger(botAI); }
static Trigger* nightbane_need_to_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NightbaneNeedToManageTimersAndTrackersTrigger(botAI); }
}; };
#endif #endif

View File

@@ -3,64 +3,17 @@
#include "RaidKarazhanActions.h" #include "RaidKarazhanActions.h"
#include "Playerbots.h" #include "Playerbots.h"
using namespace KarazhanHelpers; bool KarazhanAttumenTheHuntsmanTrigger::IsActive()
bool ManaWarpIsAboutToExplodeTrigger::IsActive()
{ {
Unit* manaWarp = AI_VALUE2(Unit*, "find target", "mana warp"); RaidKarazhanHelpers helpers(botAI);
return manaWarp && manaWarp->GetHealthPct() < 15; Unit* boss = helpers.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
return boss && boss->IsAlive();
} }
bool AttumenTheHuntsmanNeedTargetPriorityTrigger::IsActive() bool KarazhanMoroesTrigger::IsActive()
{ {
if (botAI->IsHeal(bot))
return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
return midnight != nullptr;
}
bool AttumenTheHuntsmanAttumenSpawnedTrigger::IsActive()
{
if (!botAI->IsAssistTankOfIndex(bot, 0))
return false;
Unit* attumen = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN);
return attumen != nullptr;
}
bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
{
if (botAI->IsMainTank(bot))
return false;
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
return attumenMounted && attumenMounted->GetVictim() != bot;
}
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
{
if (!IsMapIDTimerManager(botAI, bot))
return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
return midnight != nullptr;
}
bool MoroesBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* moroes = AI_VALUE2(Unit*, "find target", "moroes"); Unit* moroes = AI_VALUE2(Unit*, "find target", "moroes");
return moroes != nullptr;
}
bool MoroesNeedTargetPriorityTrigger::IsActive()
{
if (!botAI->IsDps(bot))
return false;
Unit* dorothea = AI_VALUE2(Unit*, "find target", "baroness dorothea millstipe"); Unit* dorothea = AI_VALUE2(Unit*, "find target", "baroness dorothea millstipe");
Unit* catriona = AI_VALUE2(Unit*, "find target", "lady catriona von'indi"); Unit* catriona = AI_VALUE2(Unit*, "find target", "lady catriona von'indi");
Unit* keira = AI_VALUE2(Unit*, "find target", "lady keira berrybuck"); Unit* keira = AI_VALUE2(Unit*, "find target", "lady keira berrybuck");
@@ -68,67 +21,39 @@ bool MoroesNeedTargetPriorityTrigger::IsActive()
Unit* robin = AI_VALUE2(Unit*, "find target", "lord robin daris"); Unit* robin = AI_VALUE2(Unit*, "find target", "lord robin daris");
Unit* crispin = AI_VALUE2(Unit*, "find target", "lord crispin ference"); Unit* crispin = AI_VALUE2(Unit*, "find target", "lord crispin ference");
Unit* target = GetFirstAliveUnit({ dorothea, catriona, keira, rafe, robin, crispin }); return ((moroes && moroes->IsAlive()) ||
return target != nullptr; (dorothea && dorothea->IsAlive()) ||
(catriona && catriona->IsAlive()) ||
(keira && keira->IsAlive()) ||
(rafe && rafe->IsAlive()) ||
(robin && robin->IsAlive()) ||
(crispin && crispin->IsAlive()));
} }
bool MaidenOfVirtueHealersAreStunnedByRepentanceTrigger::IsActive() bool KarazhanMaidenOfVirtueTrigger::IsActive()
{ {
if (!botAI->IsTank(bot)) Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue");
return false;
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue"); return boss && boss->IsAlive();
return maiden && maiden->GetVictim() == bot;
} }
bool MaidenOfVirtueHolyWrathDealsChainDamageTrigger::IsActive() bool KarazhanBigBadWolfTrigger::IsActive()
{ {
if (!botAI->IsRanged(bot)) Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf");
return false;
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue"); return boss && boss->IsAlive();
return maiden != nullptr;
} }
bool BigBadWolfBossEngagedByTankTrigger::IsActive() bool KarazhanRomuloAndJulianneTrigger::IsActive()
{ {
if (!botAI->IsTank(bot) || bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
return false;
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
return wolf != nullptr;
}
bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
{
if (!bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
return false;
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
return wolf != nullptr;
}
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
{
if (!IsMapIDTimerManager(botAI, bot))
return false;
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
if (!romulo)
return false;
Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne"); Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne");
if (!julianne) Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
return false;
return true; return julianne && julianne->IsAlive() && romulo && romulo->IsAlive();
} }
bool WizardOfOzNeedTargetPriorityTrigger::IsActive() bool KarazhanWizardOfOzTrigger::IsActive()
{ {
if (!IsMapIDTimerManager(botAI, bot))
return false;
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee"); Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
Unit* tito = AI_VALUE2(Unit*, "find target", "tito"); Unit* tito = AI_VALUE2(Unit*, "find target", "tito");
Unit* roar = AI_VALUE2(Unit*, "find target", "roar"); Unit* roar = AI_VALUE2(Unit*, "find target", "roar");
@@ -136,249 +61,45 @@ bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
Unit* tinhead = AI_VALUE2(Unit*, "find target", "tinhead"); Unit* tinhead = AI_VALUE2(Unit*, "find target", "tinhead");
Unit* crone = AI_VALUE2(Unit*, "find target", "the crone"); Unit* crone = AI_VALUE2(Unit*, "find target", "the crone");
Unit* target = GetFirstAliveUnit({ dorothee, tito, roar, strawman, tinhead, crone }); return ((dorothee && dorothee->IsAlive()) ||
return target != nullptr; (tito && tito->IsAlive()) ||
(roar && roar->IsAlive()) ||
(strawman && strawman->IsAlive()) ||
(tinhead && tinhead->IsAlive()) ||
(crone && crone->IsAlive()));
} }
bool WizardOfOzStrawmanIsVulnerableToFireTrigger::IsActive() bool KarazhanTheCuratorTrigger::IsActive()
{ {
if (bot->getClass() != CLASS_MAGE) Unit* boss = AI_VALUE2(Unit*, "find target", "the curator");
return false;
Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman"); return boss && boss->IsAlive();
return strawman && strawman->IsAlive();
} }
bool TheCuratorAstralFlareSpawnedTrigger::IsActive() bool KarazhanTerestianIllhoofTrigger::IsActive()
{ {
if (!botAI->IsDps(bot)) Unit* boss = AI_VALUE2(Unit*, "find target", "terestian illhoof");
return false;
Unit* flare = AI_VALUE2(Unit*, "find target", "astral flare"); return boss && boss->IsAlive();
return flare != nullptr;
} }
bool TheCuratorBossEngagedByTanksTrigger::IsActive() bool KarazhanShadeOfAranTrigger::IsActive()
{ {
if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0)) Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
return false;
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator"); return boss && boss->IsAlive();
return curator != nullptr;
} }
bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive() bool KarazhanNetherspiteTrigger::IsActive()
{ {
if (!botAI->IsRanged(bot)) Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
return false;
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator"); return boss && boss->IsAlive();
return curator != nullptr;
} }
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive() bool KarazhanPrinceMalchezaarTrigger::IsActive()
{ {
if (!IsMapIDTimerManager(botAI, bot)) Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar");
return false;
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof"); return boss && boss->IsAlive();
return illhoof != nullptr;
}
bool ShadeOfAranArcaneExplosionIsCastingTrigger::IsActive()
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
return aran && aran->HasUnitState(UNIT_STATE_CASTING) &&
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION) &&
!IsFlameWreathActive(botAI, bot);
}
bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
return aran && IsFlameWreathActive(botAI, bot);
}
// Exclusion of Banish is so the player may Banish elementals if they wish
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
{
if (!IsMapIDTimerManager(botAI, bot))
return false;
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
return elemental && elemental->IsAlive() &&
!elemental->HasAura(SPELL_WARLOCK_BANISH);
}
bool ShadeOfAranBossUsesCounterspellAndBlizzardTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
return aran && !(aran->HasUnitState(UNIT_STATE_CASTING) &&
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) &&
!IsFlameWreathActive(botAI, bot);
}
bool NetherspiteRedBeamIsActiveTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f);
return redPortal != nullptr;
}
bool NetherspiteBlueBeamIsActiveTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f);
return bluePortal != nullptr;
}
bool NetherspiteGreenBeamIsActiveTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f);
return greenPortal != nullptr;
}
bool NetherspiteBotIsNotBeamBlockerTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
return bot != redBlocker && bot != blueBlocker && bot != greenBlocker;
}
bool NetherspiteBossIsBanishedTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || !netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
std::vector<Unit*> voidZones = GetAllVoidZones(botAI, bot);
for (Unit* vz : voidZones)
{
if (bot->GetExactDist2d(vz) < 4.0f)
return true;
}
return false;
}
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
{
if (!botAI->IsTank(bot) && !IsMapIDTimerManager(botAI, bot))
return false;
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
return netherspite != nullptr;
}
bool PrinceMalchezaarBotIsEnfeebledTrigger::IsActive()
{
return bot->HasAura(SPELL_ENFEEBLE);
}
bool PrinceMalchezaarInfernalsAreSpawnedTrigger::IsActive()
{
if (botAI->IsMainTank(bot) || bot->HasAura(SPELL_ENFEEBLE))
return false;
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
return malchezaar != nullptr;
}
bool PrinceMalchezaarBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
return malchezaar != nullptr;
}
bool NightbaneBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
}
bool NightbaneRangedBotsAreInCharredEarthTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
}
bool NightbaneMainTankIsSusceptibleToFearTrigger::IsActive()
{
if (bot->getClass() != CLASS_PRIEST)
return false;
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return false;
Player* mainTank = nullptr;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && botAI->IsMainTank(member))
{
mainTank = member;
break;
}
}
}
return mainTank && !mainTank->HasAura(SPELL_FEAR_WARD) &&
botAI->CanCastSpell("fear ward", mainTank);
}
bool NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger::IsActive()
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return false;
Pet* pet = bot->GetPet();
return pet && pet->IsAlive();
}
bool NightbaneBossIsFlyingTrigger::IsActive()
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z)
return false;
const time_t now = std::time(nullptr);
const uint8 flightPhaseDurationSeconds = 35;
return nightbaneFlightPhaseStartTimer.find(KARAZHAN_MAP_ID) != nightbaneFlightPhaseStartTimer.end() &&
(now - nightbaneFlightPhaseStartTimer[KARAZHAN_MAP_ID] < flightPhaseDurationSeconds);
}
bool NightbaneNeedToManageTimersAndTrackersTrigger::IsActive()
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
return nightbane != nullptr;
} }

View File

@@ -3,298 +3,80 @@
#include "Trigger.h" #include "Trigger.h"
class ManaWarpIsAboutToExplodeTrigger : public Trigger class KarazhanAttumenTheHuntsmanTrigger : public Trigger
{ {
public: public:
ManaWarpIsAboutToExplodeTrigger( KarazhanAttumenTheHuntsmanTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan attumen the huntsman") {}
PlayerbotAI* botAI) : Trigger(botAI, "mana warp is about to explode") {}
bool IsActive() override; bool IsActive() override;
}; };
class AttumenTheHuntsmanNeedTargetPriorityTrigger : public Trigger class KarazhanMoroesTrigger : public Trigger
{ {
public: public:
AttumenTheHuntsmanNeedTargetPriorityTrigger( KarazhanMoroesTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan moroes") {}
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman need target priority") {}
bool IsActive() override; bool IsActive() override;
}; };
class AttumenTheHuntsmanAttumenSpawnedTrigger : public Trigger class KarazhanMaidenOfVirtueTrigger : public Trigger
{ {
public: public:
AttumenTheHuntsmanAttumenSpawnedTrigger( KarazhanMaidenOfVirtueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan maiden of virtue") {}
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen spawned") {}
bool IsActive() override; bool IsActive() override;
}; };
class AttumenTheHuntsmanAttumenIsMountedTrigger : public Trigger class KarazhanBigBadWolfTrigger : public Trigger
{ {
public: public:
AttumenTheHuntsmanAttumenIsMountedTrigger( KarazhanBigBadWolfTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan big bad wolf") {}
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen is mounted") {}
bool IsActive() override; bool IsActive() override;
}; };
class AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger : public Trigger class KarazhanRomuloAndJulianneTrigger : public Trigger
{ {
public: public:
AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger( KarazhanRomuloAndJulianneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan romulo and julianne") {}
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman boss wipes aggro when mounting") {}
bool IsActive() override; bool IsActive() override;
}; };
class MoroesBossEngagedByMainTankTrigger : public Trigger class KarazhanWizardOfOzTrigger : public Trigger
{ {
public: public:
MoroesBossEngagedByMainTankTrigger( KarazhanWizardOfOzTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan wizard of oz") {}
PlayerbotAI* botAI) : Trigger(botAI, "moroes boss engaged by main tank") {}
bool IsActive() override; bool IsActive() override;
}; };
class MoroesNeedTargetPriorityTrigger : public Trigger class KarazhanTheCuratorTrigger : public Trigger
{ {
public: public:
MoroesNeedTargetPriorityTrigger( KarazhanTheCuratorTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan the curator") {}
PlayerbotAI* botAI) : Trigger(botAI, "moroes need target priority") {}
bool IsActive() override; bool IsActive() override;
}; };
class MaidenOfVirtueHealersAreStunnedByRepentanceTrigger : public Trigger class KarazhanTerestianIllhoofTrigger : public Trigger
{ {
public: public:
MaidenOfVirtueHealersAreStunnedByRepentanceTrigger( KarazhanTerestianIllhoofTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan terestian illhoof") {}
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue healers are stunned by repentance") {}
bool IsActive() override; bool IsActive() override;
}; };
class MaidenOfVirtueHolyWrathDealsChainDamageTrigger : public Trigger class KarazhanShadeOfAranTrigger : public Trigger
{ {
public: public:
MaidenOfVirtueHolyWrathDealsChainDamageTrigger( KarazhanShadeOfAranTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan shade of aran") {}
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue holy wrath deals chain damage") {}
bool IsActive() override; bool IsActive() override;
}; };
class BigBadWolfBossEngagedByTankTrigger : public Trigger class KarazhanNetherspiteTrigger : public Trigger
{ {
public: public:
BigBadWolfBossEngagedByTankTrigger( KarazhanNetherspiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan netherspite") {}
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss engaged by tank") {}
bool IsActive() override; bool IsActive() override;
}; };
class BigBadWolfBossIsChasingLittleRedRidingHoodTrigger : public Trigger class KarazhanPrinceMalchezaarTrigger : public Trigger
{ {
public: public:
BigBadWolfBossIsChasingLittleRedRidingHoodTrigger( KarazhanPrinceMalchezaarTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan prince malchezaar") {}
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss is chasing little red riding hood") {}
bool IsActive() override;
};
class RomuloAndJulianneBothBossesRevivedTrigger : public Trigger
{
public:
RomuloAndJulianneBothBossesRevivedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "romulo and julianne both bosses revived") {}
bool IsActive() override;
};
class WizardOfOzNeedTargetPriorityTrigger : public Trigger
{
public:
WizardOfOzNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz need target priority") {}
bool IsActive() override;
};
class WizardOfOzStrawmanIsVulnerableToFireTrigger : public Trigger
{
public:
WizardOfOzStrawmanIsVulnerableToFireTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz strawman is vulnerable to fire") {}
bool IsActive() override;
};
class TheCuratorAstralFlareSpawnedTrigger : public Trigger
{
public:
TheCuratorAstralFlareSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flare spawned") {}
bool IsActive() override;
};
class TheCuratorBossEngagedByTanksTrigger : public Trigger
{
public:
TheCuratorBossEngagedByTanksTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the curator boss engaged by tanks") {}
bool IsActive() override;
};
class TheCuratorBossAstralFlaresCastArcingSearTrigger : public Trigger
{
public:
TheCuratorBossAstralFlaresCastArcingSearTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flares cast arcing sear") {}
bool IsActive() override;
};
class TerestianIllhoofNeedTargetPriorityTrigger : public Trigger
{
public:
TerestianIllhoofNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "terestian illhoof need target priority") {}
bool IsActive() override;
};
class ShadeOfAranArcaneExplosionIsCastingTrigger : public Trigger
{
public:
ShadeOfAranArcaneExplosionIsCastingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran arcane explosion is casting") {}
bool IsActive() override;
};
class ShadeOfAranFlameWreathIsActiveTrigger : public Trigger
{
public:
ShadeOfAranFlameWreathIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran flame wreath is active") {}
bool IsActive() override;
};
class ShadeOfAranConjuredElementalsSummonedTrigger : public Trigger
{
public:
ShadeOfAranConjuredElementalsSummonedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran conjured elementals summoned") {}
bool IsActive() override;
};
class ShadeOfAranBossUsesCounterspellAndBlizzardTrigger : public Trigger
{
public:
ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran boss uses counterspell and blizzard") {}
bool IsActive() override;
};
class NetherspiteRedBeamIsActiveTrigger : public Trigger
{
public:
NetherspiteRedBeamIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite red beam is active") {}
bool IsActive() override;
};
class NetherspiteBlueBeamIsActiveTrigger : public Trigger
{
public:
NetherspiteBlueBeamIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite blue beam is active") {}
bool IsActive() override;
};
class NetherspiteGreenBeamIsActiveTrigger : public Trigger
{
public:
NetherspiteGreenBeamIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite green beam is active") {}
bool IsActive() override;
};
class NetherspiteBotIsNotBeamBlockerTrigger : public Trigger
{
public:
NetherspiteBotIsNotBeamBlockerTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite bot is not beam blocker") {}
bool IsActive() override;
};
class NetherspiteBossIsBanishedTrigger : public Trigger
{
public:
NetherspiteBossIsBanishedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite boss is banished") {}
bool IsActive() override;
};
class NetherspiteNeedToManageTimersAndTrackersTrigger : public Trigger
{
public:
NetherspiteNeedToManageTimersAndTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite need to manage timers and trackers") {}
bool IsActive() override;
};
class PrinceMalchezaarBotIsEnfeebledTrigger : public Trigger
{
public:
PrinceMalchezaarBotIsEnfeebledTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar bot is enfeebled") {}
bool IsActive() override;
};
class PrinceMalchezaarInfernalsAreSpawnedTrigger : public Trigger
{
public:
PrinceMalchezaarInfernalsAreSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar infernals are spawned") {}
bool IsActive() override;
};
class PrinceMalchezaarBossEngagedByMainTankTrigger : public Trigger
{
public:
PrinceMalchezaarBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar boss engaged by main tank") {}
bool IsActive() override;
};
class NightbaneBossEngagedByMainTankTrigger : public Trigger
{
public:
NightbaneBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss engaged by main tank") {}
bool IsActive() override;
};
class NightbaneRangedBotsAreInCharredEarthTrigger : public Trigger
{
public:
NightbaneRangedBotsAreInCharredEarthTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane ranged bots are in charred earth") {}
bool IsActive() override;
};
class NightbaneMainTankIsSusceptibleToFearTrigger : public Trigger
{
public:
NightbaneMainTankIsSusceptibleToFearTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane main tank is susceptible to fear") {}
bool IsActive() override;
};
class NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger : public Trigger
{
public:
NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane pets ignore collision to chase flying boss") {}
bool IsActive() override;
};
class NightbaneBossIsFlyingTrigger : public Trigger
{
public:
NightbaneBossIsFlyingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss is flying") {}
bool IsActive() override;
};
class NightbaneNeedToManageTimersAndTrackersTrigger : public Trigger
{
public:
NightbaneNeedToManageTimersAndTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane need to manage timers and trackers") {}
bool IsActive() override; bool IsActive() override;
}; };

View File

@@ -10,39 +10,13 @@ class RaidMcActionContext : public NamedObjectContext<Action>
public: public:
RaidMcActionContext() RaidMcActionContext()
{ {
creators["mc lucifron shadow resistance"] = &RaidMcActionContext::lucifron_shadow_resistance; creators["mc check should move from group"] = &RaidMcActionContext::check_should_move_from_group;
creators["mc magmadar fire resistance"] = &RaidMcActionContext::magmadar_fire_resistance;
creators["mc gehennas shadow resistance"] = &RaidMcActionContext::gehennas_shadow_resistance;
creators["mc garr fire resistance"] = &RaidMcActionContext::garr_fire_resistance;
creators["mc baron geddon fire resistance"] = &RaidMcActionContext::baron_geddon_fire_resistance;
creators["mc move from group"] = &RaidMcActionContext::check_should_move_from_group;
creators["mc move from baron geddon"] = &RaidMcActionContext::move_from_baron_geddon; creators["mc move from baron geddon"] = &RaidMcActionContext::move_from_baron_geddon;
creators["mc shazzrah move away"] = &RaidMcActionContext::shazzrah_move_away;
creators["mc sulfuron harbinger fire resistance"] = &RaidMcActionContext::sulfuron_harbinger_fire_resistance;
creators["mc golemagg fire resistance"] = &RaidMcActionContext::golemagg_fire_resistance;
creators["mc golemagg mark boss"] = &RaidMcActionContext::golemagg_mark_boss;
creators["mc golemagg main tank attack golemagg"] = &RaidMcActionContext::golemagg_main_tank_attack_golemagg;
creators["mc golemagg assist tank attack core rager"] = &RaidMcActionContext::golemagg_assist_tank_attack_core_rager;
creators["mc majordomo shadow resistance"] = &RaidMcActionContext::majordomo_shadow_resistance;
creators["mc ragnaros fire resistance"] = &RaidMcActionContext::ragnaros_fire_resistance;
} }
private: private:
static Action* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "lucifron"); } static Action* check_should_move_from_group(PlayerbotAI* ai) { return new McCheckShouldMoveFromGroupAction(ai); }
static Action* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "magmadar"); } static Action* move_from_baron_geddon(PlayerbotAI* ai) { return new McMoveFromBaronGeddonAction(ai); }
static Action* gehennas_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "gehennas"); }
static Action* garr_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "garr"); }
static Action* baron_geddon_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "baron geddon"); }
static Action* check_should_move_from_group(PlayerbotAI* botAI) { return new McMoveFromGroupAction(botAI); }
static Action* move_from_baron_geddon(PlayerbotAI* botAI) { return new McMoveFromBaronGeddonAction(botAI); }
static Action* shazzrah_move_away(PlayerbotAI* botAI) { return new McShazzrahMoveAwayAction(botAI); }
static Action* sulfuron_harbinger_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "sulfuron harbinger"); }
static Action* golemagg_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "golemagg the incinerator"); }
static Action* golemagg_mark_boss(PlayerbotAI* botAI) { return new McGolemaggMarkBossAction(botAI); }
static Action* golemagg_main_tank_attack_golemagg(PlayerbotAI* botAI) { return new McGolemaggMainTankAttackGolemaggAction(botAI); }
static Action* golemagg_assist_tank_attack_core_rager(PlayerbotAI* botAI) { return new McGolemaggAssistTankAttackCoreRagerAction(botAI); }
static Action* majordomo_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "majordomo executus"); }
static Action* ragnaros_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "ragnaros"); }
}; };
#endif #endif

View File

@@ -1,215 +1,43 @@
#include "RaidMcActions.h" #include "RaidMcActions.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RtiTargetValue.h"
#include "RaidMcTriggers.h"
#include "RaidMcHelpers.h"
static constexpr float LIVING_BOMB_DISTANCE = 20.0f; bool McCheckShouldMoveFromGroupAction::Execute(Event event)
static constexpr float INFERNO_DISTANCE = 20.0f;
// don't get hit by Arcane Explosion but still be in casting range
static constexpr float ARCANE_EXPLOSION_DISTANCE = 26.0f;
// dedicated tank positions; prevents assist tanks from positioning Core Ragers on steep walls on pull
static const Position GOLEMAGG_TANK_POSITION{795.7308, -994.8848, -207.18661};
static const Position CORE_RAGER_TANK_POSITION{846.6453, -1019.0639, -198.9819};
static constexpr float GOLEMAGGS_TRUST_DISTANCE = 30.0f;
static constexpr float CORE_RAGER_STEP_DISTANCE = 5.0f;
using namespace MoltenCoreHelpers;
bool McMoveFromGroupAction::Execute(Event event)
{ {
return MoveFromGroup(LIVING_BOMB_DISTANCE); if (bot->HasAura(20475)) // barron geddon's living bomb
{
if (!botAI->HasStrategy("move from group", BotState::BOT_STATE_COMBAT))
{
// add/remove from both for now as it will make it more obvious to
// player if this strat remains on after fight somehow
botAI->ChangeStrategy("+move from group", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+move from group", BOT_STATE_COMBAT);
return true;
}
}
else if (botAI->HasStrategy("move from group", BotState::BOT_STATE_COMBAT))
{
// add/remove from both for now as it will make it more obvious to
// player if this strat remains on after fight somehow
botAI->ChangeStrategy("-move from group", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("-move from group", BOT_STATE_COMBAT);
return true;
}
return false;
} }
bool McMoveFromBaronGeddonAction::Execute(Event event) bool McMoveFromBaronGeddonAction::Execute(Event event)
{ {
const float radius = 25.0f; // more than should be needed but bots keep trying to run back in
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon")) if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
{ {
float distToTravel = INFERNO_DISTANCE - bot->GetDistance2d(boss); long distToTravel = radius - bot->GetDistance(boss);
if (distToTravel > 0) if (distToTravel > 0)
{ {
// Stop current spell first // float angle = bot->GetAngle(boss) + M_PI;
bot->AttackStop(); // return Move(angle, distToTravel);
bot->InterruptNonMeleeSpells(false);
return MoveAway(boss, distToTravel); return MoveAway(boss, distToTravel);
} }
} }
return false; return false;
} }
bool McShazzrahMoveAwayAction::Execute(Event event)
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "shazzrah"))
{
float distToTravel = ARCANE_EXPLOSION_DISTANCE - bot->GetDistance2d(boss);
if (distToTravel > 0)
return MoveAway(boss, distToTravel);
}
return false;
}
bool McGolemaggMarkBossAction::Execute(Event event)
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator"))
{
if (Group* group = bot->GetGroup())
{
ObjectGuid currentSkullGuid = group->GetTargetIcon(RtiTargetValue::skullIndex);
if (currentSkullGuid.IsEmpty() || currentSkullGuid != boss->GetGUID())
{
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID());
return true;
}
}
}
return false;
}
bool McGolemaggTankAction::MoveUnitToPosition(Unit* target, const Position& tankPosition, float maxDistance,
float stepDistance)
{
if (bot->GetVictim() != target)
return Attack(target);
if (target->GetVictim() == bot)
{
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.GetPositionX(), tankPosition.GetPositionY());
if (distanceToTankPosition > maxDistance)
{
float dX = tankPosition.GetPositionX() - bot->GetPositionX();
float dY = tankPosition.GetPositionY() - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * stepDistance;
float moveY = bot->GetPositionY() + (dY / dist) * stepDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT, true,
true);
}
}
else if (botAI->DoSpecificAction("taunt spell", Event(), true))
return true;
return false;
}
bool McGolemaggTankAction::FindCoreRagers(Unit*& coreRager1, Unit*& coreRager2) const
{
coreRager1 = coreRager2 = nullptr;
for (auto const& target : AI_VALUE(GuidVector, "possible targets no los"))
{
Unit* unit = botAI->GetUnit(target);
if (unit && unit->IsAlive() && unit->GetEntry() == NPC_CORE_RAGER)
{
if (coreRager1 == nullptr)
coreRager1 = unit;
else if (coreRager2 == nullptr)
{
coreRager2 = unit;
break; // There should be no third Core Rager.
}
}
}
return coreRager1 != nullptr && coreRager2 != nullptr;
}
bool McGolemaggMainTankAttackGolemaggAction::Execute(Event event)
{
// At this point, we know we are not the last living tank in the group.
if (Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator"))
{
Unit* coreRager1;
Unit* coreRager2;
if (!FindCoreRagers(coreRager1, coreRager2))
return false; // safety check
// We only need to move if the Core Ragers still have Golemagg's Trust
if (coreRager1->HasAura(SPELL_GOLEMAGGS_TRUST) || coreRager2->HasAura(SPELL_GOLEMAGGS_TRUST))
return MoveUnitToPosition(boss, GOLEMAGG_TANK_POSITION, boss->GetCombatReach());
}
return false;
}
bool McGolemaggAssistTankAttackCoreRagerAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator");
if (!boss)
return false;
// Step 0: Filter additional assist tanks. We only need 2.
bool isFirstAssistTank = PlayerbotAI::IsAssistTankOfIndex(bot, 0, true);
bool isSecondAssistTank = PlayerbotAI::IsAssistTankOfIndex(bot, 1, true);
if (!isFirstAssistTank && !isSecondAssistTank)
return Attack(boss);
// Step 1: Find both Core Ragers
Unit* coreRager1;
Unit* coreRager2;
if (!FindCoreRagers(coreRager1, coreRager2))
return false; // safety check
// Step 2: Assign Core Rager to bot
Unit* myCoreRager = nullptr;
Unit* otherCoreRager = nullptr;
if (isFirstAssistTank)
{
myCoreRager = coreRager1;
otherCoreRager = coreRager2;
}
else // isSecondAssistTank is always true here
{
myCoreRager = coreRager2;
otherCoreRager = coreRager1;
}
// Step 3: Select the right target
if (myCoreRager->GetVictim() != bot)
{
// Step 3.1: My Core Rager isn't attacking me. Attack until it does.
if (bot->GetVictim() != myCoreRager)
return Attack(myCoreRager);
return botAI->DoSpecificAction("taunt spell", event, true);
}
Unit* otherCoreRagerVictim = otherCoreRager->GetVictim();
if (otherCoreRagerVictim) // Core Rager victim can be NULL
{
// Step 3.2: Check if the other Core Rager isn't attacking its assist tank.
Player* otherCoreRagerPlayerVictim = otherCoreRagerVictim->ToPlayer();
if (otherCoreRagerPlayerVictim &&
!PlayerbotAI::IsAssistTankOfIndex(otherCoreRagerPlayerVictim, 0, true) &&
!PlayerbotAI::IsAssistTankOfIndex(otherCoreRagerPlayerVictim, 1, true))
{
// Assume we are the only assist tank or the other assist tank is dead => pick up other Core Rager!
if (bot->GetVictim() != otherCoreRager)
return Attack(otherCoreRager);
return botAI->DoSpecificAction("taunt spell", event, true);
}
}
if (bot->GetVictim() != myCoreRager)
return Attack(myCoreRager); // Step 3.3: Attack our Core Rager in case we previously switched in 3.2.
// Step 4: Prevent Golemagg's Trust on Core Ragers
if (myCoreRager->HasAura(SPELL_GOLEMAGGS_TRUST) ||
(otherCoreRagerVictim == bot && otherCoreRager->HasAura(SPELL_GOLEMAGGS_TRUST)))
{
// Step 4.1: Move Core Ragers to dedicated tank position (only if Golemagg is far enough away from said position)
float bossDistanceToCoreRagerTankPosition = boss->GetExactDist2d(
CORE_RAGER_TANK_POSITION.GetPositionX(), CORE_RAGER_TANK_POSITION.GetPositionY());
if (bossDistanceToCoreRagerTankPosition > GOLEMAGGS_TRUST_DISTANCE)
{
float distanceToTankPosition = bot->GetExactDist2d(CORE_RAGER_TANK_POSITION.GetPositionX(),
CORE_RAGER_TANK_POSITION.GetPositionY());
if (distanceToTankPosition > CORE_RAGER_STEP_DISTANCE)
return MoveUnitToPosition(myCoreRager, CORE_RAGER_TANK_POSITION, CORE_RAGER_STEP_DISTANCE);
}
// Step 4.2: if boss is too close to tank position, or we are already there, move away from Golemagg to try to out-range Golemagg's Trust
return MoveAway(boss, CORE_RAGER_STEP_DISTANCE, true);
}
return false;
}

View File

@@ -1,16 +1,15 @@
#ifndef _PLAYERBOT_RAIDMCACTIONS_H #ifndef _PLAYERBOT_RAIDMCACTIONS_H
#define _PLAYERBOT_RAIDMCACTIONS_H #define _PLAYERBOT_RAIDMCACTIONS_H
#include "AttackAction.h"
#include "MovementActions.h" #include "MovementActions.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "Playerbots.h" #include "Playerbots.h"
class McMoveFromGroupAction : public MovementAction class McCheckShouldMoveFromGroupAction : public Action
{ {
public: public:
McMoveFromGroupAction(PlayerbotAI* botAI, std::string const name = "mc move from group") McCheckShouldMoveFromGroupAction(PlayerbotAI* botAI, std::string const name = "mc check should move from group")
: MovementAction(botAI, name) {} : Action(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
@@ -22,46 +21,4 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class McShazzrahMoveAwayAction : public MovementAction
{
public:
McShazzrahMoveAwayAction(PlayerbotAI* botAI, std::string const name = "mc shazzrah move away")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class McGolemaggMarkBossAction : public Action
{
public:
McGolemaggMarkBossAction(PlayerbotAI* botAI, std::string const name = "mc golemagg mark boss")
: Action(botAI, name) {};
bool Execute(Event event) override;
};
class McGolemaggTankAction : public AttackAction
{
public:
McGolemaggTankAction(PlayerbotAI* botAI, std::string const name)
: AttackAction(botAI, name) {}
protected:
bool MoveUnitToPosition(Unit* target, const Position& tankPosition, float maxDistance, float stepDistance = 3.0f);
bool FindCoreRagers(Unit*& coreRager1, Unit*& coreRager2) const;
};
class McGolemaggMainTankAttackGolemaggAction : public McGolemaggTankAction
{
public:
McGolemaggMainTankAttackGolemaggAction(PlayerbotAI* botAI, std::string const name = "mc golemagg main tank attack golemagg")
: McGolemaggTankAction(botAI, name) {};
bool Execute(Event event) override;
};
class McGolemaggAssistTankAttackCoreRagerAction : public McGolemaggTankAction
{
public:
McGolemaggAssistTankAttackCoreRagerAction(PlayerbotAI* botAI, std::string const name = "mc golemagg assist tank attack core rager")
: McGolemaggTankAction(botAI, name) {};
bool Execute(Event event) override;
};
#endif #endif

View File

@@ -1,22 +0,0 @@
#ifndef _PLAYERBOT_RAIDMCHELPERS_H
#define _PLAYERBOT_RAIDMCHELPERS_H
namespace MoltenCoreHelpers
{
enum MoltenCoreNPCs
{
// Golemagg
NPC_CORE_RAGER = 11672,
};
enum MoltenCoreSpells
{
// Baron Geddon
SPELL_INFERNO = 19695,
SPELL_LIVING_BOMB = 20475,
// Golemagg
SPELL_GOLEMAGGS_TRUST = 20553,
};
}
#endif

View File

@@ -1,117 +0,0 @@
#include "RaidMcMultipliers.h"
#include "Playerbots.h"
#include "ChooseTargetActions.h"
#include "GenericSpellActions.h"
#include "DruidActions.h"
#include "HunterActions.h"
#include "PaladinActions.h"
#include "ShamanActions.h"
#include "WarriorActions.h"
#include "DKActions.h"
#include "RaidMcActions.h"
#include "RaidMcHelpers.h"
using namespace MoltenCoreHelpers;
static bool IsDpsBotWithAoeAction(Player* bot, Action* action)
{
if (PlayerbotAI::IsDps(bot))
{
if (dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastConsecrationAction*>(action) ||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<CastWhirlwindAction*>(action) ||
dynamic_cast<CastMagmaTotemAction*>(action) || dynamic_cast<CastExplosiveTrapAction*>(action) ||
dynamic_cast<CastDeathAndDecayAction*>(action))
return true;
if (auto castSpellAction = dynamic_cast<CastSpellAction*>(action))
{
if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
return true;
}
}
return false;
}
float GarrDisableDpsAoeMultiplier::GetValue(Action* action)
{
if (AI_VALUE2(Unit*, "find target", "garr"))
{
if (IsDpsBotWithAoeAction(bot, action))
return 0.0f;
}
return 1.0f;
}
static bool IsAllowedGeddonMovementAction(Action* action)
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<McMoveFromGroupAction*>(action) &&
!dynamic_cast<McMoveFromBaronGeddonAction*>(action))
return false;
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return false;
return true;
}
float BaronGeddonAbilityMultiplier::GetValue(Action* action)
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
{
if (boss->HasAura(SPELL_INFERNO))
{
if (!IsAllowedGeddonMovementAction(action))
return 0.0f;
}
}
// No check for Baron Geddon, because bots may have the bomb even after Geddon died.
if (bot->HasAura(SPELL_LIVING_BOMB))
{
if (!IsAllowedGeddonMovementAction(action))
return 0.0f;
}
return 1.0f;
}
static bool IsSingleLivingTankInGroup(Player* bot)
{
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)
continue;
if (PlayerbotAI::IsTank(member))
return false;
}
}
return true;
}
float GolemaggMultiplier::GetValue(Action* action)
{
if (AI_VALUE2(Unit*, "find target", "golemagg the incinerator"))
{
if (PlayerbotAI::IsTank(bot) && IsSingleLivingTankInGroup(bot))
{
// Only one tank => Pick up Golemagg and the two Core Ragers
if (dynamic_cast<McGolemaggMainTankAttackGolemaggAction*>(action) ||
dynamic_cast<McGolemaggAssistTankAttackCoreRagerAction*>(action))
return 0.0f;
}
if (PlayerbotAI::IsAssistTank(bot))
{
// The first two assist tanks manage the Core Ragers. The remaining assist tanks attack the boss.
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
}
if (IsDpsBotWithAoeAction(bot, action))
return 0.0f;
}
return 1.0f;
}

View File

@@ -1,27 +0,0 @@
#ifndef _PLAYERBOT_RAIDMCMULTIPLIERS_H
#define _PLAYERBOT_RAIDMCMULTIPLIERS_H
#include "Multiplier.h"
class GarrDisableDpsAoeMultiplier : public Multiplier
{
public:
GarrDisableDpsAoeMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "garr disable dps aoe multiplier") {}
float GetValue(Action* action) override;
};
class BaronGeddonAbilityMultiplier : public Multiplier
{
public:
BaronGeddonAbilityMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "baron geddon ability multiplier") {}
float GetValue(Action* action) override;
};
class GolemaggMultiplier : public Multiplier
{
public:
GolemaggMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "golemagg multiplier") {}
float GetValue(Action* action) override;
};
#endif

View File

@@ -1,81 +1,13 @@
#include "RaidMcStrategy.h" #include "RaidMcStrategy.h"
#include "RaidMcMultipliers.h"
#include "Strategy.h" #include "Strategy.h"
void RaidMcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void RaidMcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
// Lucifron
triggers.push_back(
new TriggerNode("mc lucifron shadow resistance",
NextAction::array(0, new NextAction("mc lucifron shadow resistance", ACTION_RAID), nullptr)));
// Magmadar
// TODO: Fear ward / tremor totem, or general anti-fear strat development. Same as King Dred (Drak'Tharon) and faction commander (Nexus).
triggers.push_back(
new TriggerNode("mc magmadar fire resistance",
NextAction::array(0, new NextAction("mc magmadar fire resistance", ACTION_RAID), nullptr)));
// Gehennas
triggers.push_back(
new TriggerNode("mc gehennas shadow resistance",
NextAction::array(0, new NextAction("mc gehennas shadow resistance", ACTION_RAID), nullptr)));
// Garr
triggers.push_back(
new TriggerNode("mc garr fire resistance",
NextAction::array(0, new NextAction("mc garr fire resistance", ACTION_RAID), nullptr)));
// Baron Geddon
triggers.push_back(
new TriggerNode("mc baron geddon fire resistance",
NextAction::array(0, new NextAction("mc baron geddon fire resistance", ACTION_RAID), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("mc living bomb debuff", new TriggerNode("mc living bomb debuff",
NextAction::array(0, new NextAction("mc move from group", ACTION_RAID), nullptr))); NextAction::array(0, new NextAction("mc check should move from group", ACTION_RAID), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("mc baron geddon inferno", new TriggerNode("mc baron geddon inferno",
NextAction::array(0, new NextAction("mc move from baron geddon", ACTION_RAID), nullptr))); NextAction::array(0, new NextAction("mc move from baron geddon", ACTION_RAID), nullptr)));
// Shazzrah
triggers.push_back(
new TriggerNode("mc shazzrah ranged",
NextAction::array(0, new NextAction("mc shazzrah move away", ACTION_RAID), nullptr)));
// Sulfuron Harbinger
// Alternatively, shadow resistance is also possible.
triggers.push_back(
new TriggerNode("mc sulfuron harbinger fire resistance",
NextAction::array(0, new NextAction("mc sulfuron harbinger fire resistance", ACTION_RAID), nullptr)));
// Golemagg the Incinerator
triggers.push_back(
new TriggerNode("mc golemagg fire resistance",
NextAction::array(0, new NextAction("mc golemagg fire resistance", ACTION_RAID), nullptr)));
triggers.push_back(
new TriggerNode("mc golemagg mark boss",
NextAction::array(0, new NextAction("mc golemagg mark boss", ACTION_RAID), nullptr)));
triggers.push_back(
new TriggerNode("mc golemagg is main tank",
NextAction::array(0, new NextAction("mc golemagg main tank attack golemagg", ACTION_RAID), nullptr)));
triggers.push_back(
new TriggerNode("mc golemagg is assist tank",
NextAction::array(0, new NextAction("mc golemagg assist tank attack core rager", ACTION_RAID), nullptr)));
// Majordomo Executus
triggers.push_back(
new TriggerNode("mc majordomo shadow resistance",
NextAction::array(0, new NextAction("mc majordomo shadow resistance", ACTION_RAID), nullptr)));
// Ragnaros
triggers.push_back(
new TriggerNode("mc ragnaros fire resistance",
NextAction::array(0, new NextAction("mc ragnaros fire resistance", ACTION_RAID), nullptr)));
}
void RaidMcStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new GarrDisableDpsAoeMultiplier(botAI));
multipliers.push_back(new BaronGeddonAbilityMultiplier(botAI));
multipliers.push_back(new GolemaggMultiplier(botAI));
} }

View File

@@ -8,10 +8,10 @@
class RaidMcStrategy : public Strategy class RaidMcStrategy : public Strategy
{ {
public: public:
RaidMcStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} RaidMcStrategy(PlayerbotAI* ai) : Strategy(ai) {}
std::string const getName() override { return "moltencore"; } virtual std::string const getName() override { return "mc"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override; virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*> &multipliers) override; // virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
}; };
#endif #endif

View File

@@ -10,39 +10,13 @@ class RaidMcTriggerContext : public NamedObjectContext<Trigger>
public: public:
RaidMcTriggerContext() RaidMcTriggerContext()
{ {
creators["mc lucifron shadow resistance"] = &RaidMcTriggerContext::lucifron_shadow_resistance;
creators["mc magmadar fire resistance"] = &RaidMcTriggerContext::magmadar_fire_resistance;
creators["mc gehennas shadow resistance"] = &RaidMcTriggerContext::gehennas_shadow_resistance;
creators["mc garr fire resistance"] = &RaidMcTriggerContext::garr_fire_resistance;
creators["mc baron geddon fire resistance"] = &RaidMcTriggerContext::baron_geddon_fire_resistance;
creators["mc living bomb debuff"] = &RaidMcTriggerContext::living_bomb_debuff; creators["mc living bomb debuff"] = &RaidMcTriggerContext::living_bomb_debuff;
creators["mc baron geddon inferno"] = &RaidMcTriggerContext::baron_geddon_inferno; creators["mc baron geddon inferno"] = &RaidMcTriggerContext::baron_geddon_inferno;
creators["mc shazzrah ranged"] = &RaidMcTriggerContext::shazzrah_ranged;
creators["mc sulfuron harbinger fire resistance"] = &RaidMcTriggerContext::sulfuron_harbinger_fire_resistance;
creators["mc golemagg fire resistance"] = &RaidMcTriggerContext::golemagg_fire_resistance;
creators["mc golemagg mark boss"] = &RaidMcTriggerContext::golemagg_mark_boss;
creators["mc golemagg is main tank"] = &RaidMcTriggerContext::golemagg_is_main_tank;
creators["mc golemagg is assist tank"] = &RaidMcTriggerContext::golemagg_is_assist_tank;
creators["mc majordomo shadow resistance"] = &RaidMcTriggerContext::majordomo_shadow_resistance;
creators["mc ragnaros fire resistance"] = &RaidMcTriggerContext::ragnaros_fire_resistance;
} }
private: private:
static Trigger* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "lucifron"); } static Trigger* living_bomb_debuff(PlayerbotAI* ai) { return new McLivingBombDebuffTrigger(ai); }
static Trigger* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "magmadar"); } static Trigger* baron_geddon_inferno(PlayerbotAI* ai) { return new McBaronGeddonInfernoTrigger(ai); }
static Trigger* gehennas_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "gehennas"); }
static Trigger* garr_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "garr"); }
static Trigger* baron_geddon_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "baron geddon"); }
static Trigger* living_bomb_debuff(PlayerbotAI* botAI) { return new McLivingBombDebuffTrigger(botAI); }
static Trigger* baron_geddon_inferno(PlayerbotAI* botAI) { return new McBaronGeddonInfernoTrigger(botAI); }
static Trigger* shazzrah_ranged(PlayerbotAI* botAI) { return new McShazzrahRangedTrigger(botAI); }
static Trigger* sulfuron_harbinger_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "sulfuron harbinger"); }
static Trigger* golemagg_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "golemagg the incinerator"); }
static Trigger* golemagg_mark_boss(PlayerbotAI* botAI) { return new McGolemaggMarkBossTrigger(botAI); }
static Trigger* golemagg_is_main_tank(PlayerbotAI* botAI) { return new McGolemaggIsMainTankTrigger(botAI); }
static Trigger* golemagg_is_assist_tank(PlayerbotAI* botAI) { return new McGolemaggIsAssistTankTrigger(botAI); }
static Trigger* majordomo_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "majordomo executus"); }
static Trigger* ragnaros_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "ragnaros"); }
}; };
#endif #endif

View File

@@ -1,40 +1,22 @@
#include "RaidMcTriggers.h" #include "RaidMcTriggers.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "RaidMcHelpers.h"
using namespace MoltenCoreHelpers;
bool McLivingBombDebuffTrigger::IsActive() bool McLivingBombDebuffTrigger::IsActive()
{ {
// No check for Baron Geddon, because bots may have the bomb even after Geddon died. // if bot has barron geddon's living bomb, we need to add strat, otherwise we need to remove
return bot->HasAura(SPELL_LIVING_BOMB); // only do when fighting baron geddon (to avoid modifying strat set by player outside this fight)
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
{
if (boss->IsInCombat())
return bot->HasAura(20475) != botAI->HasStrategy("move from group", BotState::BOT_STATE_COMBAT);
}
return false;
} }
bool McBaronGeddonInfernoTrigger::IsActive() bool McBaronGeddonInfernoTrigger::IsActive()
{ {
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon")) if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
return boss->HasAura(SPELL_INFERNO); return boss->HasAura(19695);
return false; return false;
} }
bool McShazzrahRangedTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "shazzrah") && PlayerbotAI::IsRanged(bot);
}
bool McGolemaggMarkBossTrigger::IsActive()
{
// any tank may mark the boss
return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsTank(bot);
}
bool McGolemaggIsMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsMainTank(bot);
}
bool McGolemaggIsAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsAssistTank(bot);
}

View File

@@ -19,32 +19,4 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
class McShazzrahRangedTrigger : public Trigger
{
public:
McShazzrahRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc shazzrah ranged") {}
bool IsActive() override;
};
class McGolemaggMarkBossTrigger : public Trigger
{
public:
McGolemaggMarkBossTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg mark boss") {}
bool IsActive() override;
};
class McGolemaggIsMainTankTrigger : public Trigger
{
public:
McGolemaggIsMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg is main tank") {}
bool IsActive() override;
};
class McGolemaggIsAssistTankTrigger : public Trigger
{
public:
McGolemaggIsAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg is assist tank") {}
bool IsActive() override;
};
#endif #endif

View File

@@ -22,8 +22,8 @@ bool UnstealthTrigger::IsActive()
return botAI->HasAura("stealth", bot) && !AI_VALUE(uint8, "attacker count") && return botAI->HasAura("stealth", bot) && !AI_VALUE(uint8, "attacker count") &&
(AI_VALUE2(bool, "moving", "self target") && (AI_VALUE2(bool, "moving", "self target") &&
((botAI->GetMaster() && ((botAI->GetMaster() &&
sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "group leader"), 10.0f) && sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), 10.0f) &&
AI_VALUE2(bool, "moving", "group leader")) || AI_VALUE2(bool, "moving", "master target")) ||
!AI_VALUE(uint8, "attacker count"))); !AI_VALUE(uint8, "attacker count")));
} }

View File

@@ -213,7 +213,7 @@ PartyMemberToHealOutOfSpellRangeTrigger::PartyMemberToHealOutOfSpellRangeTrigger
bool FarFromMasterTrigger::IsActive() bool FarFromMasterTrigger::IsActive()
{ {
return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "group leader"), distance); return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), distance);
} }
bool TooCloseToCreatureTrigger::TooCloseToCreature(uint32 creatureId, float range, bool alive) bool TooCloseToCreatureTrigger::TooCloseToCreature(uint32 creatureId, float range, bool alive)

View File

@@ -33,14 +33,18 @@ GuidVector AttackersValue::Calculate()
{ {
Unit* unit = botAI->GetUnit(target); Unit* unit = botAI->GetUnit(target);
if (unit && IsValidTarget(unit, bot)) if (unit && IsValidTarget(unit, bot))
{
targets.insert(unit); targets.insert(unit);
}
} }
if (Group* group = bot->GetGroup()) if (Group* group = bot->GetGroup())
{ {
ObjectGuid skullGuid = group->GetTargetIcon(7); ObjectGuid skullGuid = group->GetTargetIcon(7);
Unit* skullTarget = botAI->GetUnit(skullGuid); Unit* skullTarget = botAI->GetUnit(skullGuid);
if (skullTarget && IsValidTarget(skullTarget, bot)) if (skullTarget && IsValidTarget(skullTarget, bot))
{
targets.insert(skullTarget); targets.insert(skullTarget);
}
} }
for (Unit* unit : targets) for (Unit* unit : targets)
@@ -57,7 +61,9 @@ GuidVector AttackersValue::Calculate()
{ {
Unit* unit = botAI->GetUnit(guid); Unit* unit = botAI->GetUnit(guid);
if (unit && unit->IsPlayer() && IsValidTarget(unit, bot)) if (unit && unit->IsPlayer() && IsValidTarget(unit, bot))
{
result.push_back(unit->GetGUID()); result.push_back(unit->GetGUID());
}
} }
} }
@@ -101,11 +107,13 @@ void AttackersValue::AddAttackersOf(Player* player, std::unordered_set<Unit*>& t
{ {
ThreatMgr* threatMgr = ref->GetSource(); ThreatMgr* threatMgr = ref->GetSource();
Unit* attacker = threatMgr->GetOwner(); Unit* attacker = threatMgr->GetOwner();
Unit* victim = attacker->GetVictim();
if (player->IsValidAttackTarget(attacker) && if (player->IsValidAttackTarget(attacker) &&
player->GetDistance2d(attacker) < sPlayerbotAIConfig->sightDistance) player->GetDistance2d(attacker) < sPlayerbotAIConfig->sightDistance)
{
targets.insert(attacker); targets.insert(attacker);
}
ref = ref->next(); ref = ref->next();
} }
} }
@@ -134,108 +142,57 @@ bool AttackersValue::hasRealThreat(Unit* attacker)
(attacker->GetThreatMgr().getCurrentVictim() || dynamic_cast<Player*>(attacker)); (attacker->GetThreatMgr().getCurrentVictim() || dynamic_cast<Player*>(attacker));
} }
bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float /*range*/) bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float range)
{ {
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); Creature* c = attacker->ToCreature();
if (!botAI) bool rti = false;
return false; if (attacker && bot->GetGroup())
rti = bot->GetGroup()->GetTargetIcon(7) == attacker->GetGUID();
// Basic check PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!attacker)
return false; bool leaderHasThreat = false;
if (attacker && bot->GetGroup() && botAI->GetMaster())
leaderHasThreat = attacker->GetThreatMgr().GetThreat(botAI->GetMaster());
bool isMemberBotGroup = false;
if (bot->GetGroup() && botAI->GetMaster())
{
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(botAI->GetMaster());
if (masterBotAI && !masterBotAI->IsRealPlayer())
isMemberBotGroup = true;
}
// bool inCannon = botAI->IsInVehicle(false, true); // bool inCannon = botAI->IsInVehicle(false, true);
// bool enemy = botAI->GetAiObjectContext()->GetValue<Unit*>("enemy player target")->Get(); // bool enemy = botAI->GetAiObjectContext()->GetValue<Unit*>("enemy player target")->Get();
// Validity checks return attacker && attacker->IsVisible() && attacker->IsInWorld() && attacker->GetMapId() == bot->GetMapId() &&
if (!attacker->IsVisible() || !attacker->IsInWorld() || attacker->GetMapId() != bot->GetMapId()) !attacker->isDead() &&
return false; !attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2) &&
// (inCannon || !attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) &&
if (attacker->isDead() || attacker->HasSpiritOfRedemptionAura()) // attacker->CanSeeOrDetect(bot) &&
return false; // !(attacker->HasUnitState(UNIT_STATE_STUNNED) && botAI->HasAura("shackle undead", attacker)) &&
// !((attacker->IsPolymorphed() || botAI->HasAura("sap", attacker) || /*attacker->IsCharmed() ||*/
// Flag checks // attacker->isFeared()) && !rti) &&
if (attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2)) /*!sServerFacade->IsInRoots(attacker) &&*/
return false; !attacker->IsFriendlyTo(bot) && !attacker->HasSpiritOfRedemptionAura() &&
// !(attacker->GetGUID().IsPet() && enemy) &&
if (attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) || attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) !(attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) &&
return false; !attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) && !attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) &&
bot->CanSeeOrDetect(attacker) &&
// Relationship checks !(sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) &&
if (attacker->IsFriendlyTo(bot)) (attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet())) &&
return false; !(attacker->IsPlayer() && !attacker->IsPvP() && !attacker->IsFFAPvP() &&
(!bot->duel || bot->duel->Opponent != attacker)) &&
// Critter exception (!c ||
if (attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) (!c->IsInEvadeMode() &&
return false; ((!isMemberBotGroup && botAI->HasStrategy("attack tagged", BOT_STATE_NON_COMBAT)) || leaderHasThreat ||
(!c->hasLootRecipient() &&
// Visibility check (!c->GetVictim() ||
if (!bot->CanSeeOrDetect(attacker)) (c->GetVictim() &&
return false; ((!c->GetVictim()->IsPlayer() || bot->IsInSameGroupWith(c->GetVictim()->ToPlayer())) ||
(botAI->GetMaster() && c->GetVictim() == botAI->GetMaster()))))) ||
// PvP prohibition checks (skip for duels) c->isTappedBy(bot))));
if ((attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet()) &&
(!bot->duel || bot->duel->Opponent != attacker) &&
(sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) ||
sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId())))
{
// This will stop aggresive pets from starting an attack.
// This will stop currently attacking pets from continuing their attack.
// This will first require the bot to change from a combat strat. It will
// not be reached if the bot only switches targets, including NPC targets.
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin();
itr != bot->m_Controlled.end(); ++itr)
{
Creature* creature = dynamic_cast<Creature*>(*itr);
if (creature && creature->GetVictim() == attacker)
{
creature->AttackStop();
if (CharmInfo* charmInfo = creature->GetCharmInfo())
charmInfo->SetIsCommandAttack(false);
}
}
return false;
}
// Unflagged player check
if (attacker->IsPlayer() && !attacker->IsPvP() && !attacker->IsFFAPvP() &&
(!bot->duel || bot->duel->Opponent != attacker))
return false;
// Creature-specific checks
Creature* c = attacker->ToCreature();
if (c)
{
if (c->IsInEvadeMode())
return false;
bool leaderHasThreat = false;
if (bot->GetGroup() && botAI->GetMaster())
leaderHasThreat = attacker->GetThreatMgr().GetThreat(botAI->GetMaster());
bool isMemberBotGroup = false;
if (bot->GetGroup() && botAI->GetMaster())
{
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(botAI->GetMaster());
if (masterBotAI && !masterBotAI->IsRealPlayer())
isMemberBotGroup = true;
}
bool canAttack = (!isMemberBotGroup && botAI->HasStrategy("attack tagged", BOT_STATE_NON_COMBAT)) ||
leaderHasThreat ||
(!c->hasLootRecipient() &&
(!c->GetVictim() ||
(c->GetVictim() &&
((!c->GetVictim()->IsPlayer() || bot->IsInSameGroupWith(c->GetVictim()->ToPlayer())) ||
(botAI->GetMaster() && c->GetVictim() == botAI->GetMaster()))))) ||
c->isTappedBy(bot);
if (!canAttack)
return false;
}
return true;
} }
bool AttackersValue::IsValidTarget(Unit* attacker, Player* bot) bool AttackersValue::IsValidTarget(Unit* attacker, Player* bot)

View File

@@ -62,7 +62,7 @@ class MeleeFormation : public FollowFormation
public: public:
MeleeFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "melee") {} MeleeFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "melee") {}
std::string const GetTargetName() override { return "group leader"; } std::string const GetTargetName() override { return "master target"; }
}; };
class QueueFormation : public FollowFormation class QueueFormation : public FollowFormation

View File

@@ -28,7 +28,7 @@ GuidVector GroupMembersValue::Calculate()
bool IsFollowingPartyValue::Calculate() bool IsFollowingPartyValue::Calculate()
{ {
if (botAI->GetGroupLeader() == bot) if (botAI->GetGroupMaster() == bot)
return true; return true;
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
@@ -39,15 +39,15 @@ bool IsFollowingPartyValue::Calculate()
bool IsNearLeaderValue::Calculate() bool IsNearLeaderValue::Calculate()
{ {
Player* groupLeader = botAI->GetGroupLeader(); Player* groupMaster = botAI->GetGroupMaster();
if (!groupLeader) if (!groupMaster)
return false; return false;
if (groupLeader == bot) if (groupMaster == bot)
return true; return true;
return sServerFacade->GetDistance2d(bot, botAI->GetGroupLeader()) < sPlayerbotAIConfig->sightDistance; return sServerFacade->GetDistance2d(bot, botAI->GetGroupMaster()) < sPlayerbotAIConfig->sightDistance;
} }
bool BoolANDValue::Calculate() bool BoolANDValue::Calculate()
@@ -154,8 +154,8 @@ bool GroupReadyValue::Calculate()
// We only wait for members that are in range otherwise we might be waiting for bots stuck in dead loops // We only wait for members that are in range otherwise we might be waiting for bots stuck in dead loops
// forever. // forever.
if (botAI->GetGroupLeader() && if (botAI->GetGroupMaster() &&
sServerFacade->GetDistance2d(member, botAI->GetGroupLeader()) > sPlayerbotAIConfig->sightDistance) sServerFacade->GetDistance2d(member, botAI->GetGroupMaster()) > sPlayerbotAIConfig->sightDistance)
continue; continue;
if (member->GetHealthPct() < sPlayerbotAIConfig->almostFullHealth) if (member->GetHealthPct() < sPlayerbotAIConfig->almostFullHealth)

View File

@@ -3,8 +3,8 @@
* and/or modify it under version 3 of the License, or (at your option), any later version. * and/or modify it under version 3 of the License, or (at your option), any later version.
*/ */
#include "GroupLeaderValue.h" #include "MasterTargetValue.h"
#include "Playerbots.h" #include "Playerbots.h"
Unit* GroupLeaderValue::Calculate() { return botAI->GetGroupLeader(); } Unit* MasterTargetValue::Calculate() { return botAI->GetGroupMaster(); }

View File

@@ -3,18 +3,18 @@
* and/or modify it under version 3 of the License, or (at your option), any later version. * and/or modify it under version 3 of the License, or (at your option), any later version.
*/ */
#ifndef _PLAYERBOT_GROUPLEADERVALUE_H #ifndef _PLAYERBOT_MASTERTARGETVALUE_H
#define _PLAYERBOT_GROUPLEADERVALUE_H #define _PLAYERBOT_MASTERTARGETVALUE_H
#include "Value.h" #include "Value.h"
class PlayerbotAI; class PlayerbotAI;
class Unit; class Unit;
class GroupLeaderValue : public UnitCalculatedValue class MasterTargetValue : public UnitCalculatedValue
{ {
public: public:
GroupLeaderValue(PlayerbotAI* botAI, std::string const name = "group leader") : UnitCalculatedValue(botAI, name) MasterTargetValue(PlayerbotAI* botAI, std::string const name = "master target") : UnitCalculatedValue(botAI, name)
{ {
} }

View File

@@ -30,7 +30,6 @@
#include "Formations.h" #include "Formations.h"
#include "GrindTargetValue.h" #include "GrindTargetValue.h"
#include "GroupValues.h" #include "GroupValues.h"
#include "GroupLeaderValue.h"
#include "GuildValues.h" #include "GuildValues.h"
#include "HasAvailableLootValue.h" #include "HasAvailableLootValue.h"
#include "HasTotemValue.h" #include "HasTotemValue.h"
@@ -52,6 +51,7 @@
#include "LootStrategyValue.h" #include "LootStrategyValue.h"
#include "MaintenanceValues.h" #include "MaintenanceValues.h"
#include "ManaSaveLevelValue.h" #include "ManaSaveLevelValue.h"
#include "MasterTargetValue.h"
#include "NearestAdsValue.h" #include "NearestAdsValue.h"
#include "NearestCorpsesValue.h" #include "NearestCorpsesValue.h"
#include "NearestFriendlyPlayersValue.h" #include "NearestFriendlyPlayersValue.h"
@@ -130,7 +130,7 @@ public:
creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect; creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect;
creators["current target"] = &ValueContext::current_target; creators["current target"] = &ValueContext::current_target;
creators["self target"] = &ValueContext::self_target; creators["self target"] = &ValueContext::self_target;
creators["group leader"] = &ValueContext::group_leader; creators["master target"] = &ValueContext::master;
creators["line target"] = &ValueContext::line_target; creators["line target"] = &ValueContext::line_target;
creators["tank target"] = &ValueContext::tank_target; creators["tank target"] = &ValueContext::tank_target;
creators["dps target"] = &ValueContext::dps_target; creators["dps target"] = &ValueContext::dps_target;
@@ -439,7 +439,7 @@ private:
static UntypedValue* current_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); } static UntypedValue* current_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
static UntypedValue* old_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); } static UntypedValue* old_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
static UntypedValue* self_target(PlayerbotAI* botAI) { return new SelfTargetValue(botAI); } static UntypedValue* self_target(PlayerbotAI* botAI) { return new SelfTargetValue(botAI); }
static UntypedValue* group_leader(PlayerbotAI* botAI) { return new GroupLeaderValue(botAI); } static UntypedValue* master(PlayerbotAI* botAI) { return new MasterTargetValue(botAI); }
static UntypedValue* line_target(PlayerbotAI* botAI) { return new LineTargetValue(botAI); } static UntypedValue* line_target(PlayerbotAI* botAI) { return new LineTargetValue(botAI); }
static UntypedValue* tank_target(PlayerbotAI* botAI) { return new TankTargetValue(botAI); } static UntypedValue* tank_target(PlayerbotAI* botAI) { return new TankTargetValue(botAI); }
static UntypedValue* dps_target(PlayerbotAI* botAI) { return new DpsTargetValue(botAI); } static UntypedValue* dps_target(PlayerbotAI* botAI) { return new DpsTargetValue(botAI); }