mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-24 14:06:22 +00:00
Compare commits
2 Commits
bb569b4d39
...
master_v16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6620def962 | ||
|
|
cddd406b1c |
@@ -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);
|
||||
@@ -365,7 +365,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
}
|
||||
|
||||
// Update the bot's group status (moved to helper function)
|
||||
UpdateAIGroupMaster();
|
||||
UpdateAIGroupAndMaster();
|
||||
|
||||
// Update internal AI
|
||||
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
|
||||
void PlayerbotAI::UpdateAIGroupMaster()
|
||||
void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
{
|
||||
if (!bot)
|
||||
return;
|
||||
@@ -420,7 +420,7 @@ void PlayerbotAI::UpdateAIGroupMaster()
|
||||
{
|
||||
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
|
||||
|
||||
if (botAI->GetMaster() == botAI->GetGroupLeader())
|
||||
if (botAI->GetMaster() == botAI->GetGroupMaster())
|
||||
botAI->TellMaster("Hello, I follow you!");
|
||||
else
|
||||
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
|
||||
@@ -431,6 +431,8 @@ void PlayerbotAI::UpdateAIGroupMaster()
|
||||
botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT);
|
||||
}
|
||||
}
|
||||
else if (!newMaster && !bot->InBattleground())
|
||||
LeaveOrDisbandGroup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1459,7 +1461,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
|
||||
strategyName = "onyxia"; // Onyxia's Lair
|
||||
break;
|
||||
case 409:
|
||||
strategyName = "moltencore"; // Molten Core
|
||||
strategyName = "mc"; // Molten Core
|
||||
break;
|
||||
case 469:
|
||||
strategyName = "bwl"; // Blackwing Lair
|
||||
@@ -1475,7 +1477,6 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
|
||||
break;
|
||||
case 544:
|
||||
strategyName = "magtheridon"; // Magtheridon's Lair
|
||||
break;
|
||||
case 565:
|
||||
strategyName = "gruulslair"; // Gruul's Lair
|
||||
break;
|
||||
@@ -2247,7 +2248,7 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* 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();
|
||||
if (!group)
|
||||
@@ -2264,9 +2265,6 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDead
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreDeadPlayers && !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2286,9 +2284,6 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDead
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreDeadPlayers && !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -4097,7 +4092,7 @@ Player* PlayerbotAI::FindNewMaster()
|
||||
if (!group)
|
||||
return nullptr;
|
||||
|
||||
Player* groupLeader = GetGroupLeader();
|
||||
Player* groupLeader = GetGroupMaster();
|
||||
PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(groupLeader);
|
||||
if (!leaderBotAI || leaderBotAI->IsRealPlayer())
|
||||
return groupLeader;
|
||||
@@ -4148,7 +4143,7 @@ bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(m
|
||||
|
||||
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
|
||||
|
||||
Player* PlayerbotAI::GetGroupLeader()
|
||||
Player* PlayerbotAI::GetGroupMaster()
|
||||
{
|
||||
if (!bot->InBattleground())
|
||||
if (Group* group = bot->GetGroup())
|
||||
|
||||
@@ -428,7 +428,7 @@ public:
|
||||
static bool IsMainTank(Player* player);
|
||||
static uint32 GetGroupTankNum(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 IsRangedDpsAssistantOfIndex(Player* player, int index);
|
||||
bool HasAggro(Unit* unit);
|
||||
@@ -540,7 +540,7 @@ public:
|
||||
// Get the group leader or the master of the bot.
|
||||
// Checks if the bot is summoned as alt of a player
|
||||
bool IsAlt();
|
||||
Player* GetGroupLeader();
|
||||
Player* GetGroupMaster();
|
||||
// Returns a semi-random (cycling) number that is fixed for each bot.
|
||||
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1);
|
||||
GrouperType GetGrouperType();
|
||||
@@ -612,7 +612,7 @@ private:
|
||||
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
|
||||
bool mixed = false);
|
||||
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
|
||||
void UpdateAIGroupMaster();
|
||||
void UpdateAIGroupAndMaster();
|
||||
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
|
||||
void HandleCommands();
|
||||
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
|
||||
|
||||
@@ -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.";
|
||||
break;
|
||||
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.";
|
||||
}
|
||||
else
|
||||
|
||||
@@ -82,12 +82,12 @@ public:
|
||||
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", {
|
||||
PLAYERHOOK_ON_LOGIN,
|
||||
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_ACHI_COMPLETE,
|
||||
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_BEFORE_TELEPORT
|
||||
}) {}
|
||||
@@ -164,17 +164,14 @@ public:
|
||||
{
|
||||
botAI->HandleCommand(type, msg, player);
|
||||
|
||||
// hotfix; otherwise the server will crash when whispering logout
|
||||
// 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 false;
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
@@ -186,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)
|
||||
{
|
||||
@@ -208,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))
|
||||
{
|
||||
@@ -222,7 +217,6 @@ public:
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
|
||||
|
||||
@@ -1480,10 +1480,10 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
|
||||
if (!sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
update = false;
|
||||
|
||||
if (player->GetGroup() && botAI->GetGroupLeader())
|
||||
if (player->GetGroup() && botAI->GetGroupMaster())
|
||||
{
|
||||
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader());
|
||||
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())
|
||||
PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
|
||||
if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
|
||||
{
|
||||
update = false;
|
||||
}
|
||||
|
||||
@@ -4099,7 +4099,6 @@ void PlayerbotFactory::InitImmersive()
|
||||
|
||||
void PlayerbotFactory::InitArenaTeam()
|
||||
{
|
||||
|
||||
if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId()))
|
||||
return;
|
||||
|
||||
@@ -4186,34 +4185,10 @@ void PlayerbotFactory::InitArenaTeam()
|
||||
|
||||
if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need?
|
||||
{
|
||||
// Add bot to arena team
|
||||
arenateam->AddMember(bot->GetGUID());
|
||||
|
||||
// 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);
|
||||
}
|
||||
arenateam->SaveToDB();
|
||||
}
|
||||
}
|
||||
|
||||
arenateams.erase(arenateams.begin() + index);
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ public:
|
||||
creators["shoot"] = &ActionContext::shoot;
|
||||
creators["follow"] = &ActionContext::follow;
|
||||
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["stay"] = &ActionContext::stay;
|
||||
creators["sit"] = &ActionContext::sit;
|
||||
@@ -318,7 +318,7 @@ private:
|
||||
static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); }
|
||||
static Action* follow(PlayerbotAI* botAI) { return new FollowAction(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_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); }
|
||||
static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); }
|
||||
|
||||
@@ -84,10 +84,9 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if bot OR target is in prohibited zone/area
|
||||
if ((target->IsPlayer() || target->IsPet()) &&
|
||||
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
|
||||
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
||||
if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
|
||||
sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId()))
|
||||
&& (target->IsPlayer() || target->IsPet()))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
||||
|
||||
@@ -311,7 +311,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target)
|
||||
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||
{
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* gmaster = botAI->GetGroupMaster();
|
||||
Player* realMaster = botAI->GetMaster();
|
||||
AiObjectContext* context = botAI->GetAiObjectContext();
|
||||
|
||||
@@ -327,30 +327,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!groupLeader || bot == groupLeader)
|
||||
if (!gmaster || bot == gmaster)
|
||||
return true;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return true;
|
||||
|
||||
if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||
if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||
return false;
|
||||
|
||||
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)
|
||||
{
|
||||
Player* player = groupLeader;
|
||||
if (groupLeader && !groupLeader->isMoving() ||
|
||||
Player* player = gmaster;
|
||||
if (gmaster && !gmaster->isMoving() ||
|
||||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
|
||||
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;
|
||||
|
||||
if (!groupLeader->isMoving() && distance < 25.0f)
|
||||
if (!gmaster->isMoving() && distance < 25.0f)
|
||||
return true;
|
||||
|
||||
if (distance < formation->GetMaxDistance())
|
||||
|
||||
@@ -180,7 +180,7 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
|
||||
void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
|
||||
{
|
||||
// Tell the master where we are going.
|
||||
if (!bot->GetGroup() || (botAI->GetGroupLeader() == bot))
|
||||
if (!bot->GetGroup() || (botAI->GetGroupMaster() == bot))
|
||||
ReportTravelTarget(newTarget, oldTarget);
|
||||
|
||||
// If we are heading to a creature/npc clear it from the ignore list.
|
||||
|
||||
@@ -70,7 +70,7 @@ bool FollowAction::isUseful()
|
||||
if (!target.empty())
|
||||
fTarget = AI_VALUE(Unit*, target);
|
||||
else
|
||||
fTarget = AI_VALUE(Unit*, "group leader");
|
||||
fTarget = AI_VALUE(Unit*, "master target");
|
||||
|
||||
if (fTarget)
|
||||
{
|
||||
@@ -114,9 +114,9 @@ bool FollowAction::CanDeadFollow(Unit* target)
|
||||
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);
|
||||
if (!canFollow)
|
||||
{
|
||||
@@ -146,22 +146,22 @@ bool FleeToGroupLeaderAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeToGroupLeaderAction::isUseful()
|
||||
bool FleeToMasterAction::isUseful()
|
||||
{
|
||||
if (!botAI->GetGroupLeader())
|
||||
if (!botAI->GetGroupMaster())
|
||||
return false;
|
||||
|
||||
if (botAI->GetGroupLeader() == bot)
|
||||
if (botAI->GetGroupMaster() == bot)
|
||||
return false;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (target && botAI->GetGroupLeader()->GetTarget() == target->GetGUID())
|
||||
if (target && botAI->GetGroupMaster()->GetTarget() == target->GetGUID())
|
||||
return false;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return false;
|
||||
|
||||
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||
Unit* fTarget = AI_VALUE(Unit*, "master target");
|
||||
|
||||
if (!CanDeadFollow(fTarget))
|
||||
return false;
|
||||
|
||||
@@ -20,10 +20,10 @@ public:
|
||||
bool CanDeadFollow(Unit* target);
|
||||
};
|
||||
|
||||
class FleeToGroupLeaderAction : public FollowAction
|
||||
class FleeToMasterAction : public FollowAction
|
||||
{
|
||||
public:
|
||||
FleeToGroupLeaderAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to group leader") {}
|
||||
FleeToMasterAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to master") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
@@ -141,7 +141,7 @@ bool InviteNearbyToGroupAction::isUseful()
|
||||
if (group->isRaidGroup() && group->IsFull())
|
||||
return false;
|
||||
|
||||
if (botAI->GetGroupLeader() != bot)
|
||||
if (botAI->GetGroupMaster() != bot)
|
||||
return false;
|
||||
|
||||
uint32 memberCount = group->GetMembersCount();
|
||||
|
||||
@@ -109,22 +109,22 @@ bool LeaveFarAwayAction::isUseful()
|
||||
if (!bot->GetGroup())
|
||||
return false;
|
||||
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* trueMaster = botAI->GetMaster();
|
||||
if (!groupLeader || (bot == groupLeader && !botAI->IsRealPlayer()))
|
||||
if (!master || (bot == master && !botAI->IsRealPlayer()))
|
||||
return false;
|
||||
|
||||
PlayerbotAI* groupLeaderBotAI = nullptr;
|
||||
if (groupLeader)
|
||||
groupLeaderBotAI = GET_PLAYERBOT_AI(groupLeader);
|
||||
if (groupLeader && !groupLeaderBotAI)
|
||||
PlayerbotAI* masterBotAI = nullptr;
|
||||
if (master)
|
||||
masterBotAI = GET_PLAYERBOT_AI(master);
|
||||
if (master && !masterBotAI)
|
||||
return false;
|
||||
|
||||
if (trueMaster && !GET_PLAYERBOT_AI(trueMaster))
|
||||
return false;
|
||||
|
||||
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;
|
||||
|
||||
if (botAI->GetGrouperType() == GrouperType::SOLO)
|
||||
@@ -138,19 +138,19 @@ bool LeaveFarAwayAction::isUseful()
|
||||
if (dCount > 4 && !botAI->HasRealPlayerMaster())
|
||||
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"))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (abs(int32(groupLeader->GetLevel() - bot->GetLevel())) > 4)
|
||||
if (abs(int32(master->GetLevel() - bot->GetLevel())) > 4)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
|
||||
WorldLocation location = *target->getPosition();
|
||||
|
||||
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())
|
||||
{
|
||||
|
||||
@@ -192,23 +192,30 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool generatePath = !bot->IsFlying() && !bot->isSwimming();
|
||||
bool disableMoveSplinePath =
|
||||
sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
|
||||
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
|
||||
bool disableMoveSplinePath = sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
|
||||
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
|
||||
if (Vehicle* vehicle = bot->GetVehicle())
|
||||
{
|
||||
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
|
||||
Unit* vehicleBase = vehicle->GetBase();
|
||||
generatePath = !vehicleBase || !vehicleBase->CanFly();
|
||||
generatePath = vehicleBase->CanFly();
|
||||
if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway
|
||||
return false;
|
||||
|
||||
float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot
|
||||
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 delay = 1000.0f * (distance / speed);
|
||||
if (lessDelay)
|
||||
@@ -234,7 +241,16 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
// bot->CastStop();
|
||||
// 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);
|
||||
if (lessDelay)
|
||||
{
|
||||
@@ -252,7 +268,9 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
Movement::PointsArray path =
|
||||
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only);
|
||||
if (modifiedZ == INVALID_HEIGHT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
float distance = bot->GetExactDist(x, y, modifiedZ);
|
||||
if (distance > 0.01f)
|
||||
{
|
||||
@@ -264,8 +282,17 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
// bot->CastStop();
|
||||
// botAI->InterruptSpell();
|
||||
// }
|
||||
MotionMaster& mm = *bot->GetMotionMaster();
|
||||
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);
|
||||
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;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// else {
|
||||
// LOG_DEBUG("playerbots", "!entry");
|
||||
// return bot->TeleportTo(movePosition.getMapId(), movePosition.getX(), movePosition.getY(),
|
||||
// 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);
|
||||
|
||||
// if (botAI->HasCheat(BotCheatMask::gold))
|
||||
// {
|
||||
// bot->SetMoney(botMoney);
|
||||
// }
|
||||
// LOG_DEBUG("playerbots", "goTaxi");
|
||||
// return goTaxi;
|
||||
// }
|
||||
@@ -960,8 +988,7 @@ bool MovementAction::IsMovingAllowed()
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING))
|
||||
// {
|
||||
// if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING)) {
|
||||
// return false;
|
||||
// }
|
||||
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
|
||||
@@ -971,95 +998,54 @@ bool MovementAction::Follow(Unit* target, float distance) { return Follow(target
|
||||
|
||||
void MovementAction::UpdateMovementState()
|
||||
{
|
||||
const bool isCurrentlyRestricted = // see if the bot is currently slowed, rooted, or otherwise unable to move
|
||||
bot->isFrozen() ||
|
||||
bot->IsPolymorphed() ||
|
||||
bot->HasRootAura() ||
|
||||
bot->HasStunAura() ||
|
||||
bot->HasConfuseAura() ||
|
||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
|
||||
int8 botInLiquidState = bot->GetLiquidData().Status;
|
||||
|
||||
// no update movement flags while movement is current restricted.
|
||||
if (!isCurrentlyRestricted && bot->IsAlive())
|
||||
if (botInLiquidState == LIQUID_MAP_IN_WATER || botInLiquidState == LIQUID_MAP_UNDER_WATER)
|
||||
{
|
||||
// state flags
|
||||
const auto master = botAI ? botAI->GetMaster() : nullptr; // real player or not
|
||||
const bool masterIsFlying = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) : true;
|
||||
const bool masterIsSwimming = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) : true;
|
||||
const auto liquidState = bot->GetLiquidData().Status; // default LIQUID_MAP_NO_WATER
|
||||
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();
|
||||
bot->SetSwim(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
bot->SetSwim(false);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Temporary speed increase in group
|
||||
// if (botAI->HasRealPlayerMaster())
|
||||
// {
|
||||
// if (botAI->HasRealPlayerMaster()) {
|
||||
// bot->SetSpeedRate(MOVE_RUN, 1.1f);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// } else {
|
||||
// bot->SetSpeedRate(MOVE_RUN, 1.0f);
|
||||
// }
|
||||
// check if target is not reachable (from Vmangos)
|
||||
@@ -1068,7 +1054,7 @@ void MovementAction::UpdateMovementState()
|
||||
// {
|
||||
// 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 distance = 5.0f;
|
||||
@@ -1102,7 +1088,7 @@ void MovementAction::UpdateMovementState()
|
||||
// {
|
||||
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
|
||||
// {
|
||||
// if (pTarget != botAI->GetGroupLeader())
|
||||
// if (pTarget != botAI->GetGroupMaster())
|
||||
// return;
|
||||
|
||||
// if (!bot->IsWithinMeleeRange(pTarget))
|
||||
@@ -1696,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
|
||||
// normal_only, float step)
|
||||
// {
|
||||
// if (!generatePath)
|
||||
// {
|
||||
// if (!generatePath) {
|
||||
// return z;
|
||||
// }
|
||||
// float min_length = 100000.0f;
|
||||
@@ -1708,12 +1693,10 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
|
||||
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
|
||||
// PathGenerator gen(bot);
|
||||
// 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();
|
||||
// current_z = modified_z;
|
||||
// if (abs(current_z - z) < 0.5f)
|
||||
// {
|
||||
// if (abs(current_z - z) < 0.5f) {
|
||||
// return current_z;
|
||||
// }
|
||||
// }
|
||||
@@ -1722,34 +1705,30 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
|
||||
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
|
||||
// PathGenerator gen(bot);
|
||||
// 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();
|
||||
// current_z = modified_z;
|
||||
// if (abs(current_z - z) < 0.5f)
|
||||
// if (abs(current_z - z) < 0.5f) {
|
||||
// return current_z;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// for (delta = range / 2 + step; delta <= range; delta += 2) {
|
||||
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
|
||||
// PathGenerator gen(bot);
|
||||
// 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();
|
||||
// current_z = modified_z;
|
||||
// if (abs(current_z - z) < 0.5f)
|
||||
// {
|
||||
// if (abs(current_z - z) < 0.5f) {
|
||||
// return current_z;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (current_z == INVALID_HEIGHT && normal_only)
|
||||
// {
|
||||
// if (current_z == INVALID_HEIGHT && normal_only) {
|
||||
// return INVALID_HEIGHT;
|
||||
// }
|
||||
// if (current_z == INVALID_HEIGHT && !normal_only)
|
||||
// {
|
||||
// if (current_z == INVALID_HEIGHT && !normal_only) {
|
||||
// return z;
|
||||
// }
|
||||
// return current_z;
|
||||
@@ -1824,46 +1803,6 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
|
||||
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)
|
||||
{
|
||||
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
|
||||
@@ -2124,8 +2063,8 @@ Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
|
||||
if (currentTarget)
|
||||
{
|
||||
// Normally, move to left or right is the best position
|
||||
bool isTanking = (!currentTarget->isFrozen()
|
||||
&& !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
|
||||
bool isTanking =
|
||||
(!currentTarget->isFrozen() && !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
|
||||
float angle = bot->GetAngle(currentTarget);
|
||||
float angleLeft = angle + (float)M_PI / 2;
|
||||
float angleRight = angle - (float)M_PI / 2;
|
||||
@@ -2541,7 +2480,9 @@ bool RearFlankAction::isUseful()
|
||||
{
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (!target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Need to double the front angle check to account for mirrored angle.
|
||||
bool inFront = target->HasInArc(2.f * minAngle, bot);
|
||||
@@ -2556,7 +2497,9 @@ bool RearFlankAction::Execute(Event event)
|
||||
{
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (!target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float angle = frand(minAngle, maxAngle);
|
||||
float baseDistance = bot->GetMeleeRange(target) * 0.5f;
|
||||
@@ -2663,7 +2606,7 @@ bool DisperseSetAction::Execute(Event event)
|
||||
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)
|
||||
{
|
||||
@@ -2850,7 +2793,7 @@ bool MoveAwayFromCreatureAction::Execute(Event event)
|
||||
|
||||
// Find all creatures with the specified Id
|
||||
std::vector<Unit*> creatures;
|
||||
for (auto const& guid : targets)
|
||||
for (const auto& guid : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(guid);
|
||||
if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId)
|
||||
|
||||
@@ -25,9 +25,7 @@ bool PetsAction::Execute(Event event)
|
||||
if (param.empty())
|
||||
{
|
||||
// If no parameter is provided, show usage instructions and return.
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_usage_error", "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", {});
|
||||
botAI->TellError(text);
|
||||
botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -54,9 +52,7 @@ bool PetsAction::Execute(Event event)
|
||||
// If no pets or guardians are found, notify and return.
|
||||
if (targets.empty())
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_no_pet_error", "You have no pet or guardian pet.", {});
|
||||
botAI->TellError(text);
|
||||
botAI->TellError("You have no pet or guardian pet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -67,54 +63,42 @@ bool PetsAction::Execute(Event event)
|
||||
if (param == "aggressive")
|
||||
{
|
||||
react = REACT_AGGRESSIVE;
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_aggressive", "aggressive", {});
|
||||
stanceText = "aggressive";
|
||||
}
|
||||
else if (param == "defensive")
|
||||
{
|
||||
react = REACT_DEFENSIVE;
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_defensive", "defensive", {});
|
||||
stanceText = "defensive";
|
||||
}
|
||||
else if (param == "passive")
|
||||
{
|
||||
react = REACT_PASSIVE;
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_passive", "passive", {});
|
||||
stanceText = "passive";
|
||||
}
|
||||
// The "stance" command simply reports the current stance of each pet/guardian.
|
||||
else if (param == "stance")
|
||||
{
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
std::string type = target->IsPet() ?
|
||||
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_pet", "pet", {}) :
|
||||
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_guardian", "guardian", {});
|
||||
std::string type = target->IsPet() ? "pet" : "guardian";
|
||||
std::string name = target->GetName();
|
||||
std::string stance;
|
||||
switch (target->GetReactState())
|
||||
{
|
||||
case REACT_AGGRESSIVE:
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_aggressive", "aggressive", {});
|
||||
stance = "aggressive";
|
||||
break;
|
||||
case REACT_DEFENSIVE:
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_defensive", "defensive", {});
|
||||
stance = "defensive";
|
||||
break;
|
||||
case REACT_PASSIVE:
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_passive", "passive", {});
|
||||
stance = "passive";
|
||||
break;
|
||||
default:
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_unknown", "unknown", {});
|
||||
stance = "unknown";
|
||||
break;
|
||||
}
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_report", "Current stance of %type \"%name\": %stance.",
|
||||
{{"type", type}, {"name", name}, {"stance", stance}});
|
||||
botAI->TellMaster(text);
|
||||
botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + ".");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -137,31 +121,17 @@ bool PetsAction::Execute(Event event)
|
||||
// If no valid target is selected, show an error and return.
|
||||
if (!targetUnit)
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_no_target_error", "No valid target selected by master.", {});
|
||||
botAI->TellError(text);
|
||||
botAI->TellError("No valid target selected by master.");
|
||||
return false;
|
||||
}
|
||||
if (!targetUnit->IsAlive())
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_target_dead_error", "Target is not alive.", {});
|
||||
botAI->TellError(text);
|
||||
botAI->TellError("Target is not alive.");
|
||||
return false;
|
||||
}
|
||||
if (!bot->IsValidAttackTarget(targetUnit))
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"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()))
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {});
|
||||
botAI->TellError(text);
|
||||
botAI->TellError("Target is not a valid attack target for the bot.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -212,17 +182,9 @@ bool PetsAction::Execute(Event event)
|
||||
}
|
||||
// Inform the master if the command succeeded or failed.
|
||||
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_attack_success", "Pet commanded to attack your target.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
botAI->TellMaster("Pet commanded to attack your target.");
|
||||
else if (!didAttack)
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {});
|
||||
botAI->TellError(text);
|
||||
}
|
||||
botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)");
|
||||
return didAttack;
|
||||
}
|
||||
// The "follow" command makes all pets/guardians follow the bot.
|
||||
@@ -230,11 +192,7 @@ bool PetsAction::Execute(Event event)
|
||||
{
|
||||
botAI->PetFollow();
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_follow_success", "Pet commanded to follow.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
botAI->TellMaster("Pet commanded to follow.");
|
||||
return true;
|
||||
}
|
||||
// The "stay" command causes all pets/guardians to stop and stay in place.
|
||||
@@ -271,20 +229,14 @@ bool PetsAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stay_success", "Pet commanded to stay.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
botAI->TellMaster("Pet commanded to stay.");
|
||||
return true;
|
||||
}
|
||||
// Unknown command: show usage instructions and return.
|
||||
else
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_unknown_command_error", "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
{{"param", param}});
|
||||
botAI->TellError(text);
|
||||
botAI->TellError("Unknown pet command: " + param +
|
||||
". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -299,12 +251,7 @@ bool PetsAction::Execute(Event event)
|
||||
|
||||
// Inform the master of the new stance if debug is enabled.
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_set_success", "Pet stance set to %stance.",
|
||||
{{"stance", stanceText}});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
botAI->TellMaster("Pet stance set to " + stanceText + ".");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ bool RandomBotUpdateAction::Execute(Event event)
|
||||
if (!sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
return false;
|
||||
|
||||
if (bot->GetGroup() && botAI->GetGroupLeader())
|
||||
if (bot->GetGroup() && botAI->GetGroupMaster())
|
||||
{
|
||||
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader());
|
||||
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())
|
||||
PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
|
||||
if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,15 +168,15 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
||||
if (!bot->GetGroup())
|
||||
return true;
|
||||
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
if (!groupLeader || groupLeader == bot)
|
||||
Player* groupMaster = botAI->GetGroupMaster();
|
||||
if (!groupMaster || groupMaster == bot)
|
||||
return true;
|
||||
|
||||
if (!botAI->HasActivePlayerMaster())
|
||||
return true;
|
||||
|
||||
if (botAI->HasActivePlayerMaster() &&
|
||||
groupLeader->GetMapId() == bot->GetMapId() &&
|
||||
groupMaster->GetMapId() == bot->GetMapId() &&
|
||||
bot->GetMap() &&
|
||||
(bot->GetMap()->IsRaid() || bot->GetMap()->IsDungeon()))
|
||||
{
|
||||
@@ -184,7 +184,7 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
||||
}
|
||||
|
||||
return sServerFacade->IsDistanceGreaterThan(
|
||||
AI_VALUE2(float, "distance", "group leader"),
|
||||
AI_VALUE2(float, "distance", "master target"),
|
||||
sPlayerbotAIConfig->sightDistance);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,4 +16,4 @@ bool ResetInstancesAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResetInstancesAction::isUseful() { return botAI->GetGroupLeader() == bot; };
|
||||
bool ResetInstancesAction::isUseful() { return botAI->GetGroupMaster() == bot; };
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
|
||||
bool ReviveFromCorpseAction::Execute(Event event)
|
||||
{
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Corpse* corpse = bot->GetCorpse();
|
||||
|
||||
// follow group Leader when group Leader revives
|
||||
// follow master when master revives
|
||||
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))
|
||||
{
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
@@ -43,10 +43,10 @@ bool ReviveFromCorpseAction::Execute(Event event)
|
||||
// time(nullptr))
|
||||
// return false;
|
||||
|
||||
if (groupLeader)
|
||||
if (master)
|
||||
{
|
||||
if (!GET_PLAYERBOT_AI(groupLeader) && groupLeader->isDead() && groupLeader->GetCorpse() &&
|
||||
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
if (!GET_PLAYERBOT_AI(master) && master->isDead() && master->GetCorpse() &&
|
||||
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
sPlayerbotAIConfig->farDistance))
|
||||
return false;
|
||||
}
|
||||
@@ -79,15 +79,15 @@ bool FindCorpseAction::Execute(Event event)
|
||||
if (bot->InBattleground())
|
||||
return false;
|
||||
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Corpse* corpse = bot->GetCorpse();
|
||||
if (!corpse)
|
||||
return false;
|
||||
|
||||
// if (groupLeader)
|
||||
// if (master)
|
||||
// {
|
||||
// if (!GET_PLAYERBOT_AI(groupLeader) &&
|
||||
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
// if (!GET_PLAYERBOT_AI(master) &&
|
||||
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
// sPlayerbotAIConfig->farDistance)) return false;
|
||||
// }
|
||||
|
||||
@@ -110,20 +110,20 @@ bool FindCorpseAction::Execute(Event event)
|
||||
WorldPosition botPos(bot);
|
||||
WorldPosition corpsePos(corpse);
|
||||
WorldPosition moveToPos = corpsePos;
|
||||
WorldPosition leaderPos(groupLeader);
|
||||
WorldPosition masterPos(master);
|
||||
|
||||
float reclaimDist = CORPSE_RECLAIM_RADIUS - 5.0f;
|
||||
float corpseDist = botPos.distance(corpsePos);
|
||||
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.
|
||||
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;
|
||||
}
|
||||
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 (corpseDist < sPlayerbotAIConfig->reactDistance)
|
||||
{
|
||||
if (moveToLeader)
|
||||
moveToPos = leaderPos;
|
||||
if (moveToMaster)
|
||||
moveToPos = masterPos;
|
||||
else
|
||||
{
|
||||
FleeManager manager(bot, reclaimDist, 0.0, urand(0, 1), moveToPos);
|
||||
@@ -215,12 +215,12 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
|
||||
if (!startZone && 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();
|
||||
if (groupLeader && groupLeader != bot)
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
if (master && master != bot)
|
||||
{
|
||||
ClosestGrave = sGraveyard->GetClosestGraveyard(groupLeader, bot->GetTeamId());
|
||||
ClosestGrave = sGraveyard->GetClosestGraveyard(master, bot->GetTeamId());
|
||||
|
||||
if (ClosestGrave)
|
||||
return ClosestGrave;
|
||||
|
||||
@@ -35,8 +35,8 @@ bool RewardAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
Unit* groupLeaderUnit = AI_VALUE(Unit*, "group leader");
|
||||
if (groupLeaderUnit && Reward(itemId, groupLeaderUnit))
|
||||
Unit* mtar = AI_VALUE(Unit*, "master target");
|
||||
if (mtar && Reward(itemId, mtar))
|
||||
return true;
|
||||
|
||||
botAI->TellError("Cannot talk to quest giver");
|
||||
|
||||
@@ -76,7 +76,7 @@ void RpgHelper::setFacing(GuidPosition guidPosition)
|
||||
|
||||
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);
|
||||
else
|
||||
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay / 5);
|
||||
|
||||
@@ -22,8 +22,8 @@ bool SecurityCheckAction::Execute(Event event)
|
||||
ItemQualities threshold = group->GetLootThreshold();
|
||||
if (method == MASTER_LOOT || method == FREE_FOR_ALL || threshold > ITEM_QUALITY_UNCOMMON)
|
||||
{
|
||||
if ((botAI->GetGroupLeader()->GetSession()->GetSecurity() == SEC_PLAYER) &&
|
||||
(!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupLeader()->GetGuildId()))
|
||||
if ((botAI->GetGroupMaster()->GetSession()->GetSecurity() == SEC_PLAYER) &&
|
||||
(!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupMaster()->GetGuildId()))
|
||||
{
|
||||
botAI->TellError("I will play with this loot type only if I'm in your guild :/");
|
||||
botAI->ChangeStrategy("+passive,+stay", BOT_STATE_NON_COMBAT);
|
||||
|
||||
@@ -22,7 +22,7 @@ bool OutOfReactRangeAction::Execute(Event event)
|
||||
|
||||
bool OutOfReactRangeAction::isUseful()
|
||||
{
|
||||
bool canFollow = Follow(AI_VALUE(Unit*, "group leader"));
|
||||
bool canFollow = Follow(AI_VALUE(Unit*, "master target"));
|
||||
if (!canFollow)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -64,7 +64,7 @@ bool MoveToDarkPortalAction::Execute(Event event)
|
||||
{
|
||||
if (bot->GetGroup())
|
||||
if (bot->GetGroup()->GetLeaderGUID() != bot->GetGUID() &&
|
||||
!GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupLeader()))
|
||||
!GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupMaster()))
|
||||
return false;
|
||||
|
||||
if (bot->GetLevel() > 57)
|
||||
|
||||
@@ -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("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("seldom", NextAction::array(0, new NextAction("reset instances", 1.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)));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
RaidStrategyContext() : NamedObjectContext<Strategy>(false, true)
|
||||
{
|
||||
creators["aq20"] = &RaidStrategyContext::aq20;
|
||||
creators["moltencore"] = &RaidStrategyContext::moltencore;
|
||||
creators["mc"] = &RaidStrategyContext::mc;
|
||||
creators["bwl"] = &RaidStrategyContext::bwl;
|
||||
creators["karazhan"] = &RaidStrategyContext::karazhan;
|
||||
creators["magtheridon"] = &RaidStrategyContext::magtheridon;
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
|
||||
private:
|
||||
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* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); }
|
||||
static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); }
|
||||
|
||||
@@ -10,39 +10,13 @@ class RaidMcActionContext : public NamedObjectContext<Action>
|
||||
public:
|
||||
RaidMcActionContext()
|
||||
{
|
||||
creators["mc lucifron shadow resistance"] = &RaidMcActionContext::lucifron_shadow_resistance;
|
||||
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 check should move from group"] = &RaidMcActionContext::check_should_move_from_group;
|
||||
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:
|
||||
static Action* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "lucifron"); }
|
||||
static Action* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "magmadar"); }
|
||||
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"); }
|
||||
static Action* check_should_move_from_group(PlayerbotAI* ai) { return new McCheckShouldMoveFromGroupAction(ai); }
|
||||
static Action* move_from_baron_geddon(PlayerbotAI* ai) { return new McMoveFromBaronGeddonAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,215 +1,43 @@
|
||||
#include "RaidMcActions.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
#include "RaidMcTriggers.h"
|
||||
#include "RaidMcHelpers.h"
|
||||
|
||||
static constexpr float LIVING_BOMB_DISTANCE = 20.0f;
|
||||
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)
|
||||
bool McCheckShouldMoveFromGroupAction::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)
|
||||
{
|
||||
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"))
|
||||
{
|
||||
float distToTravel = INFERNO_DISTANCE - bot->GetDistance2d(boss);
|
||||
long distToTravel = radius - bot->GetDistance(boss);
|
||||
if (distToTravel > 0)
|
||||
{
|
||||
// Stop current spell first
|
||||
bot->AttackStop();
|
||||
bot->InterruptNonMeleeSpells(false);
|
||||
|
||||
// float angle = bot->GetAngle(boss) + M_PI;
|
||||
// return Move(angle, distToTravel);
|
||||
return MoveAway(boss, distToTravel);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#ifndef _PLAYERBOT_RAIDMCACTIONS_H
|
||||
#define _PLAYERBOT_RAIDMCACTIONS_H
|
||||
|
||||
#include "AttackAction.h"
|
||||
#include "MovementActions.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
class McMoveFromGroupAction : public MovementAction
|
||||
class McCheckShouldMoveFromGroupAction : public Action
|
||||
{
|
||||
public:
|
||||
McMoveFromGroupAction(PlayerbotAI* botAI, std::string const name = "mc move from group")
|
||||
: MovementAction(botAI, name) {}
|
||||
McCheckShouldMoveFromGroupAction(PlayerbotAI* botAI, std::string const name = "mc check should move from group")
|
||||
: Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
@@ -22,46 +21,4 @@ public:
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,81 +1,13 @@
|
||||
#include "RaidMcStrategy.h"
|
||||
|
||||
#include "RaidMcMultipliers.h"
|
||||
#include "Strategy.h"
|
||||
|
||||
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(
|
||||
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(
|
||||
new TriggerNode("mc baron geddon inferno",
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
class RaidMcStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
RaidMcStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
|
||||
std::string const getName() override { return "moltencore"; }
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
RaidMcStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
virtual std::string const getName() override { return "mc"; }
|
||||
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
// virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -10,39 +10,13 @@ class RaidMcTriggerContext : public NamedObjectContext<Trigger>
|
||||
public:
|
||||
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 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:
|
||||
static Trigger* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "lucifron"); }
|
||||
static Trigger* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "magmadar"); }
|
||||
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"); }
|
||||
static Trigger* living_bomb_debuff(PlayerbotAI* ai) { return new McLivingBombDebuffTrigger(ai); }
|
||||
static Trigger* baron_geddon_inferno(PlayerbotAI* ai) { return new McBaronGeddonInfernoTrigger(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,40 +1,22 @@
|
||||
#include "RaidMcTriggers.h"
|
||||
|
||||
#include "SharedDefines.h"
|
||||
#include "RaidMcHelpers.h"
|
||||
|
||||
using namespace MoltenCoreHelpers;
|
||||
|
||||
bool McLivingBombDebuffTrigger::IsActive()
|
||||
{
|
||||
// No check for Baron Geddon, because bots may have the bomb even after Geddon died.
|
||||
return bot->HasAura(SPELL_LIVING_BOMB);
|
||||
// if bot has barron geddon's living bomb, we need to add strat, otherwise we need to remove
|
||||
// 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()
|
||||
{
|
||||
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
|
||||
return boss->HasAura(SPELL_INFERNO);
|
||||
return boss->HasAura(19695);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -19,32 +19,4 @@ public:
|
||||
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
|
||||
|
||||
@@ -22,8 +22,8 @@ bool UnstealthTrigger::IsActive()
|
||||
return botAI->HasAura("stealth", bot) && !AI_VALUE(uint8, "attacker count") &&
|
||||
(AI_VALUE2(bool, "moving", "self target") &&
|
||||
((botAI->GetMaster() &&
|
||||
sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "group leader"), 10.0f) &&
|
||||
AI_VALUE2(bool, "moving", "group leader")) ||
|
||||
sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), 10.0f) &&
|
||||
AI_VALUE2(bool, "moving", "master target")) ||
|
||||
!AI_VALUE(uint8, "attacker count")));
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ PartyMemberToHealOutOfSpellRangeTrigger::PartyMemberToHealOutOfSpellRangeTrigger
|
||||
|
||||
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)
|
||||
|
||||
@@ -107,6 +107,7 @@ void AttackersValue::AddAttackersOf(Player* player, std::unordered_set<Unit*>& t
|
||||
{
|
||||
ThreatMgr* threatMgr = ref->GetSource();
|
||||
Unit* attacker = threatMgr->GetOwner();
|
||||
Unit* victim = attacker->GetVictim();
|
||||
|
||||
if (player->IsValidAttackTarget(attacker) &&
|
||||
player->GetDistance2d(attacker) < sPlayerbotAIConfig->sightDistance)
|
||||
@@ -141,107 +142,57 @@ bool AttackersValue::hasRealThreat(Unit* 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);
|
||||
if (!botAI)
|
||||
return false;
|
||||
Creature* c = attacker->ToCreature();
|
||||
bool rti = false;
|
||||
if (attacker && bot->GetGroup())
|
||||
rti = bot->GetGroup()->GetTargetIcon(7) == attacker->GetGUID();
|
||||
|
||||
// Basic check
|
||||
if (!attacker)
|
||||
return false;
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
|
||||
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 enemy = botAI->GetAiObjectContext()->GetValue<Unit*>("enemy player target")->Get();
|
||||
|
||||
// Validity checks
|
||||
if (!attacker->IsVisible() || !attacker->IsInWorld() || attacker->GetMapId() != bot->GetMapId())
|
||||
return false;
|
||||
|
||||
if (attacker->isDead() || attacker->HasSpiritOfRedemptionAura())
|
||||
return false;
|
||||
|
||||
// Flag checks
|
||||
if (attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2))
|
||||
return false;
|
||||
|
||||
if (attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) || attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
|
||||
return false;
|
||||
|
||||
// Relationship checks
|
||||
if (attacker->IsFriendlyTo(bot))
|
||||
return false;
|
||||
|
||||
// Critter exception
|
||||
if (attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat())
|
||||
return false;
|
||||
|
||||
// Visibility check
|
||||
if (!bot->CanSeeOrDetect(attacker))
|
||||
return false;
|
||||
|
||||
// PvP prohibition checks
|
||||
if ((attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet()) &&
|
||||
(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;
|
||||
return attacker && attacker->IsVisible() && attacker->IsInWorld() && attacker->GetMapId() == bot->GetMapId() &&
|
||||
!attacker->isDead() &&
|
||||
!attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2) &&
|
||||
// (inCannon || !attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) &&
|
||||
// attacker->CanSeeOrDetect(bot) &&
|
||||
// !(attacker->HasUnitState(UNIT_STATE_STUNNED) && botAI->HasAura("shackle undead", attacker)) &&
|
||||
// !((attacker->IsPolymorphed() || botAI->HasAura("sap", attacker) || /*attacker->IsCharmed() ||*/
|
||||
// attacker->isFeared()) && !rti) &&
|
||||
/*!sServerFacade->IsInRoots(attacker) &&*/
|
||||
!attacker->IsFriendlyTo(bot) && !attacker->HasSpiritOfRedemptionAura() &&
|
||||
// !(attacker->GetGUID().IsPet() && enemy) &&
|
||||
!(attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) &&
|
||||
!attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) && !attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) &&
|
||||
bot->CanSeeOrDetect(attacker) &&
|
||||
!(sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) &&
|
||||
(attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet())) &&
|
||||
!(attacker->IsPlayer() && !attacker->IsPvP() && !attacker->IsFFAPvP() &&
|
||||
(!bot->duel || bot->duel->Opponent != attacker)) &&
|
||||
(!c ||
|
||||
(!c->IsInEvadeMode() &&
|
||||
((!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))));
|
||||
}
|
||||
|
||||
bool AttackersValue::IsValidTarget(Unit* attacker, Player* bot)
|
||||
|
||||
@@ -62,7 +62,7 @@ class MeleeFormation : public FollowFormation
|
||||
public:
|
||||
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
|
||||
|
||||
@@ -28,7 +28,7 @@ GuidVector GroupMembersValue::Calculate()
|
||||
|
||||
bool IsFollowingPartyValue::Calculate()
|
||||
{
|
||||
if (botAI->GetGroupLeader() == bot)
|
||||
if (botAI->GetGroupMaster() == bot)
|
||||
return true;
|
||||
|
||||
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
@@ -39,15 +39,15 @@ bool IsFollowingPartyValue::Calculate()
|
||||
|
||||
bool IsNearLeaderValue::Calculate()
|
||||
{
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* groupMaster = botAI->GetGroupMaster();
|
||||
|
||||
if (!groupLeader)
|
||||
if (!groupMaster)
|
||||
return false;
|
||||
|
||||
if (groupLeader == bot)
|
||||
if (groupMaster == bot)
|
||||
return true;
|
||||
|
||||
return sServerFacade->GetDistance2d(bot, botAI->GetGroupLeader()) < sPlayerbotAIConfig->sightDistance;
|
||||
return sServerFacade->GetDistance2d(bot, botAI->GetGroupMaster()) < sPlayerbotAIConfig->sightDistance;
|
||||
}
|
||||
|
||||
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
|
||||
// forever.
|
||||
if (botAI->GetGroupLeader() &&
|
||||
sServerFacade->GetDistance2d(member, botAI->GetGroupLeader()) > sPlayerbotAIConfig->sightDistance)
|
||||
if (botAI->GetGroupMaster() &&
|
||||
sServerFacade->GetDistance2d(member, botAI->GetGroupMaster()) > sPlayerbotAIConfig->sightDistance)
|
||||
continue;
|
||||
|
||||
if (member->GetHealthPct() < sPlayerbotAIConfig->almostFullHealth)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* 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"
|
||||
|
||||
Unit* GroupLeaderValue::Calculate() { return botAI->GetGroupLeader(); }
|
||||
Unit* MasterTargetValue::Calculate() { return botAI->GetGroupMaster(); }
|
||||
@@ -3,18 +3,18 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_GROUPLEADERVALUE_H
|
||||
#define _PLAYERBOT_GROUPLEADERVALUE_H
|
||||
#ifndef _PLAYERBOT_MASTERTARGETVALUE_H
|
||||
#define _PLAYERBOT_MASTERTARGETVALUE_H
|
||||
|
||||
#include "Value.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
class Unit;
|
||||
|
||||
class GroupLeaderValue : public UnitCalculatedValue
|
||||
class MasterTargetValue : public UnitCalculatedValue
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include "Formations.h"
|
||||
#include "GrindTargetValue.h"
|
||||
#include "GroupValues.h"
|
||||
#include "GroupLeaderValue.h"
|
||||
#include "GuildValues.h"
|
||||
#include "HasAvailableLootValue.h"
|
||||
#include "HasTotemValue.h"
|
||||
@@ -52,6 +51,7 @@
|
||||
#include "LootStrategyValue.h"
|
||||
#include "MaintenanceValues.h"
|
||||
#include "ManaSaveLevelValue.h"
|
||||
#include "MasterTargetValue.h"
|
||||
#include "NearestAdsValue.h"
|
||||
#include "NearestCorpsesValue.h"
|
||||
#include "NearestFriendlyPlayersValue.h"
|
||||
@@ -130,7 +130,7 @@ public:
|
||||
creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect;
|
||||
creators["current target"] = &ValueContext::current_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["tank target"] = &ValueContext::tank_target;
|
||||
creators["dps target"] = &ValueContext::dps_target;
|
||||
@@ -439,7 +439,7 @@ private:
|
||||
static UntypedValue* current_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* 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* tank_target(PlayerbotAI* botAI) { return new TankTargetValue(botAI); }
|
||||
static UntypedValue* dps_target(PlayerbotAI* botAI) { return new DpsTargetValue(botAI); }
|
||||
|
||||
Reference in New Issue
Block a user