mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
commit
f275f72957
@ -1038,6 +1038,7 @@ AiPlayerbot.EnableNewRpgStrategy = 1
|
||||
# DoQuest (Default: 60 Select quest from the quest log and head to the location to attempt completion)
|
||||
# TravelFlight (Default: 15 Go to the nearest flightmaster and fly to a level-appropriate area)
|
||||
# Rest (Default: 5 Take a break for a while and do nothing)
|
||||
# OutdoorPvp (Default: 10 Participate in outdoor PvP capture points if already in an outdoor PvP zone)
|
||||
AiPlayerbot.RpgStatusProbWeight.WanderRandom = 15
|
||||
AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20
|
||||
AiPlayerbot.RpgStatusProbWeight.GoGrind = 15
|
||||
@ -1045,6 +1046,7 @@ AiPlayerbot.RpgStatusProbWeight.GoCamp = 10
|
||||
AiPlayerbot.RpgStatusProbWeight.DoQuest = 60
|
||||
AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15
|
||||
AiPlayerbot.RpgStatusProbWeight.Rest = 5
|
||||
AiPlayerbot.RpgStatusProbWeight.OutdoorPvp = 10
|
||||
|
||||
# Bots' minimum and maximum level when teleporting in and out of a zone, according to the new RPG strategy
|
||||
# Format: AiPlayerbot.ZoneBracket.zoneID = minLevel,maxLevel
|
||||
|
||||
@ -0,0 +1,240 @@
|
||||
-- #########################################################
|
||||
-- Playerbots - Add focus heal command texts
|
||||
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
|
||||
-- zhTW, esES, esMX, ruRU)
|
||||
-- #########################################################
|
||||
|
||||
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||
'focus_heal_not_healer',
|
||||
'focus_heal_provide_names',
|
||||
'focus_heal_no_targets',
|
||||
'focus_heal_current_targets',
|
||||
'focus_heal_cleared',
|
||||
'focus_heal_add_remove_syntax',
|
||||
'focus_heal_not_in_group',
|
||||
'focus_heal_not_in_group_with',
|
||||
'focus_heal_added',
|
||||
'focus_heal_removed'
|
||||
);
|
||||
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||
'focus_heal_not_healer',
|
||||
'focus_heal_provide_names',
|
||||
'focus_heal_no_targets',
|
||||
'focus_heal_current_targets',
|
||||
'focus_heal_cleared',
|
||||
'focus_heal_add_remove_syntax',
|
||||
'focus_heal_not_in_group',
|
||||
'focus_heal_not_in_group_with',
|
||||
'focus_heal_added',
|
||||
'focus_heal_removed'
|
||||
);
|
||||
|
||||
-- focus_heal_not_healer
|
||||
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 (
|
||||
1745,
|
||||
'focus_heal_not_healer',
|
||||
'I''m not a healer or offhealer (please change my strats to heal or offheal)',
|
||||
0, 0,
|
||||
'저는 힐러나 오프힐러가 아닙니다 (전략을 heal 또는 offheal로 변경해주세요)',
|
||||
'Je ne suis pas un soigneur ou un soigneur secondaire (veuillez changer mes strats en heal ou offheal)',
|
||||
'Ich bin kein Heiler oder Nebenheiler (bitte ändere meine Strategien auf heal oder offheal)',
|
||||
'我不是治疗者或副治疗者(请将我的策略更改为 heal 或 offheal)',
|
||||
'我不是治療者或副治療者(請將我的策略更改為 heal 或 offheal)',
|
||||
'No soy un sanador ni un sanador secundario (por favor cambia mis estrategias a heal o offheal)',
|
||||
'No soy un sanador ni un sanador secundario (por favor cambia mis estrategias a heal o offheal)',
|
||||
'Я не лекарь и не побочный лекарь (пожалуйста, измените мои стратегии на heal или offheal)');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_not_healer', 100);
|
||||
|
||||
-- focus_heal_provide_names
|
||||
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 (
|
||||
1746,
|
||||
'focus_heal_provide_names',
|
||||
'Please provide one or more player names',
|
||||
0, 0,
|
||||
'하나 이상의 플레이어 이름을 제공해주세요',
|
||||
'Veuillez fournir un ou plusieurs noms de joueurs',
|
||||
'Bitte geben Sie einen oder mehrere Spielernamen an',
|
||||
'请提供一个或多个玩家名称',
|
||||
'請提供一個或多個玩家名稱',
|
||||
'Por favor proporciona uno o más nombres de jugadores',
|
||||
'Por favor proporciona uno o más nombres de jugadores',
|
||||
'Пожалуйста, укажите одно или несколько имён игроков');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_provide_names', 100);
|
||||
|
||||
-- focus_heal_no_targets
|
||||
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 (
|
||||
1747,
|
||||
'focus_heal_no_targets',
|
||||
'I don''t have any focus heal targets',
|
||||
0, 0,
|
||||
'지정된 집중 치유 대상이 없습니다',
|
||||
'Je n''ai aucune cible de soin prioritaire',
|
||||
'Ich habe keine fokussierten Heilziele',
|
||||
'我没有任何集中治疗目标',
|
||||
'我沒有任何集中治療目標',
|
||||
'No tengo ningún objetivo de sanación prioritario',
|
||||
'No tengo ningún objetivo de sanación prioritario',
|
||||
'У меня нет целей приоритетного лечения');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_no_targets', 100);
|
||||
|
||||
-- focus_heal_current_targets: %targets is replaced with comma-separated player names
|
||||
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 (
|
||||
1748,
|
||||
'focus_heal_current_targets',
|
||||
'My focus heal targets are %targets',
|
||||
0, 0,
|
||||
'나의 집중 치유 대상: %targets',
|
||||
'Mes cibles de soin prioritaire sont %targets',
|
||||
'Meine fokussierten Heilziele sind %targets',
|
||||
'我的集中治疗目标是 %targets',
|
||||
'我的集中治療目標是 %targets',
|
||||
'Mis objetivos de sanación prioritarios son %targets',
|
||||
'Mis objetivos de sanación prioritarios son %targets',
|
||||
'Мои цели приоритетного лечения: %targets');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_current_targets', 100);
|
||||
|
||||
-- focus_heal_cleared
|
||||
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 (
|
||||
1749,
|
||||
'focus_heal_cleared',
|
||||
'Removed focus heal targets',
|
||||
0, 0,
|
||||
'집중 치유 대상을 제거했습니다',
|
||||
'Cibles de soin prioritaire supprimées',
|
||||
'Fokussierte Heilziele entfernt',
|
||||
'已移除集中治疗目标',
|
||||
'已移除集中治療目標',
|
||||
'Objetivos de sanación prioritarios eliminados',
|
||||
'Objetivos de sanación prioritarios eliminados',
|
||||
'Цели приоритетного лечения удалены');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_cleared', 100);
|
||||
|
||||
-- focus_heal_add_remove_syntax
|
||||
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 (
|
||||
1750,
|
||||
'focus_heal_add_remove_syntax',
|
||||
'Please specify a + for add or - to remove a target',
|
||||
0, 0,
|
||||
'대상을 추가하려면 +, 제거하려면 -를 지정해주세요',
|
||||
'Veuillez spécifier + pour ajouter ou - pour retirer une cible',
|
||||
'Bitte geben Sie + zum Hinzufügen oder - zum Entfernen eines Ziels an',
|
||||
'请指定 + 添加或 - 移除目标',
|
||||
'請指定 + 添加或 - 移除目標',
|
||||
'Por favor especifica + para agregar o - para eliminar un objetivo',
|
||||
'Por favor especifica + para agregar o - para eliminar un objetivo',
|
||||
'Пожалуйста, укажите + для добавления или - для удаления цели');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_add_remove_syntax', 100);
|
||||
|
||||
-- focus_heal_not_in_group
|
||||
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 (
|
||||
1751,
|
||||
'focus_heal_not_in_group',
|
||||
'I''m not in a group',
|
||||
0, 0,
|
||||
'저는 파티에 속해있지 않습니다',
|
||||
'Je ne suis pas dans un groupe',
|
||||
'Ich bin in keiner Gruppe',
|
||||
'我不在队伍中',
|
||||
'我不在隊伍中',
|
||||
'No estoy en un grupo',
|
||||
'No estoy en un grupo',
|
||||
'Я не в группе');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_not_in_group', 100);
|
||||
|
||||
-- focus_heal_not_in_group_with: %player_name is replaced with the target player's name
|
||||
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 (
|
||||
1752,
|
||||
'focus_heal_not_in_group_with',
|
||||
'I''m not in a group with %player_name',
|
||||
0, 0,
|
||||
'%player_name 와(과) 같은 파티에 없습니다',
|
||||
'Je ne suis pas dans un groupe avec %player_name',
|
||||
'Ich bin nicht in einer Gruppe mit %player_name',
|
||||
'我与 %player_name 不在同一队伍中',
|
||||
'我與 %player_name 不在同一隊伍中',
|
||||
'No estoy en un grupo con %player_name',
|
||||
'No estoy en un grupo con %player_name',
|
||||
'Я не в группе с %player_name');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_not_in_group_with', 100);
|
||||
|
||||
-- focus_heal_added: %player_name is replaced with the added player's name
|
||||
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 (
|
||||
1753,
|
||||
'focus_heal_added',
|
||||
'Added %player_name to focus heal targets',
|
||||
0, 0,
|
||||
'%player_name 을(를) 집중 치유 대상에 추가했습니다',
|
||||
'%player_name ajouté aux cibles de soin prioritaire',
|
||||
'%player_name zu den fokussierten Heilzielen hinzugefügt',
|
||||
'已将 %player_name 添加到集中治疗目标',
|
||||
'已將 %player_name 添加到集中治療目標',
|
||||
'%player_name agregado a los objetivos de sanación prioritarios',
|
||||
'%player_name agregado a los objetivos de sanación prioritarios',
|
||||
'%player_name добавлен в цели приоритетного лечения');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_added', 100);
|
||||
|
||||
-- focus_heal_removed: %player_name is replaced with the removed player's name
|
||||
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 (
|
||||
1754,
|
||||
'focus_heal_removed',
|
||||
'Removed %player_name from focus heal targets',
|
||||
0, 0,
|
||||
'%player_name 을(를) 집중 치유 대상에서 제거했습니다',
|
||||
'%player_name retiré des cibles de soin prioritaire',
|
||||
'%player_name aus den fokussierten Heilzielen entfernt',
|
||||
'已将 %player_name 从集中治疗目标中移除',
|
||||
'已將 %player_name 從集中治療目標中移除',
|
||||
'%player_name eliminado de los objetivos de sanación prioritarios',
|
||||
'%player_name eliminado de los objetivos de sanación prioritarios',
|
||||
'%player_name удалён из целей приоритетного лечения');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_removed', 100);
|
||||
@ -63,6 +63,7 @@
|
||||
#include "WorldBuffAction.h"
|
||||
#include "XpGainAction.h"
|
||||
#include "NewRpgAction.h"
|
||||
#include "NewRpgOutdoorPvP.h"
|
||||
#include "FishingAction.h"
|
||||
#include "CancelChannelAction.h"
|
||||
#include "WaitForAttackAction.h"
|
||||
@ -265,6 +266,7 @@ public:
|
||||
creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc;
|
||||
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
|
||||
creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight;
|
||||
creators["new rpg outdoor pvp"] = &ActionContext::new_rpg_outdoor_pvp;
|
||||
creators["wait for attack keep safe distance"] = &ActionContext::wait_for_attack_keep_safe_distance;
|
||||
}
|
||||
|
||||
@ -462,6 +464,7 @@ private:
|
||||
static Action* new_rpg_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(ai); }
|
||||
static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); }
|
||||
static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); }
|
||||
static Action* new_rpg_outdoor_pvp(PlayerbotAI* ai) { return new NewRpgOutdoorPvpAction(ai); }
|
||||
static Action* wait_for_attack_keep_safe_distance(PlayerbotAI* ai) { return new WaitForAttackKeepSafeDistanceAction(ai); }
|
||||
};
|
||||
|
||||
|
||||
219
src/Ai/Base/Actions/SetFocusHealTargetsAction.cpp
Normal file
219
src/Ai/Base/Actions/SetFocusHealTargetsAction.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "SetFocusHealTargetsAction.h"
|
||||
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotTextMgr.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
static std::string LowercaseString(std::string const& str)
|
||||
{
|
||||
std::string result = str;
|
||||
std::transform(result.begin(), result.end(), result.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return result;
|
||||
}
|
||||
|
||||
static Player* FindGroupPlayerByName(Player* player, std::string const& playerName)
|
||||
{
|
||||
if (!player)
|
||||
return nullptr;
|
||||
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
return nullptr;
|
||||
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (member)
|
||||
{
|
||||
std::string memberName = member->GetName();
|
||||
if (LowercaseString(memberName) == playerName)
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool SetFocusHealTargetsAction::Execute(Event event)
|
||||
{
|
||||
if (!botAI->IsHeal(bot) && !botAI->HasStrategy("offheal", BOT_STATE_COMBAT))
|
||||
{
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_not_healer",
|
||||
"I'm not a healer or offhealer (please change my strats to heal or offheal)",
|
||||
{});
|
||||
botAI->TellMasterNoFacing(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string const param = LowercaseString(event.getParam());
|
||||
if (param.empty())
|
||||
{
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_provide_names",
|
||||
"Please provide one or more player names",
|
||||
{});
|
||||
botAI->TellMasterNoFacing(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::list<ObjectGuid> focusHealTargets =
|
||||
AI_VALUE(std::list<ObjectGuid>, "focus heal targets");
|
||||
|
||||
// Query current focus targets
|
||||
if (param.find('?') != std::string::npos)
|
||||
{
|
||||
if (focusHealTargets.empty())
|
||||
{
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_no_targets",
|
||||
"I don't have any focus heal targets",
|
||||
{});
|
||||
botAI->TellMasterNoFacing(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream targetNames;
|
||||
for (auto it = focusHealTargets.begin(); it != focusHealTargets.end(); ++it)
|
||||
{
|
||||
Unit* target = botAI->GetUnit(*it);
|
||||
if (target)
|
||||
{
|
||||
if (it != focusHealTargets.begin())
|
||||
targetNames << ", ";
|
||||
targetNames << target->GetName();
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> placeholders;
|
||||
placeholders["%targets"] = targetNames.str();
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_current_targets",
|
||||
"My focus heal targets are %targets",
|
||||
placeholders);
|
||||
botAI->TellMasterNoFacing(text);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clear all targets
|
||||
if (param == "none" || param == "unset" || param == "clear")
|
||||
{
|
||||
focusHealTargets.clear();
|
||||
SET_AI_VALUE(std::list<ObjectGuid>, "focus heal targets", focusHealTargets);
|
||||
botAI->ChangeStrategy("-focus heal targets", BOT_STATE_COMBAT);
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_cleared",
|
||||
"Removed focus heal targets",
|
||||
{});
|
||||
botAI->TellMasterNoFacing(text);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse multiple targets separated by commas
|
||||
std::vector<std::string> targetNames;
|
||||
if (param.find(',') != std::string::npos)
|
||||
{
|
||||
std::string targetName;
|
||||
std::stringstream ss(param);
|
||||
while (std::getline(ss, targetName, ','))
|
||||
targetNames.push_back(targetName);
|
||||
}
|
||||
else
|
||||
targetNames.push_back(param);
|
||||
|
||||
if (targetNames.empty())
|
||||
{
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_provide_names",
|
||||
"Please provide one or more player names",
|
||||
{});
|
||||
botAI->TellMasterNoFacing(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->GetGroup())
|
||||
{
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_not_in_group",
|
||||
"I'm not in a group",
|
||||
{});
|
||||
botAI->TellMasterNoFacing(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (std::string const& targetName : targetNames)
|
||||
{
|
||||
bool const add = targetName.find("+") != std::string::npos;
|
||||
bool const remove = targetName.find("-") != std::string::npos;
|
||||
if (!add && !remove)
|
||||
{
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_add_remove_syntax",
|
||||
"Please specify a + for add or - to remove a target",
|
||||
{});
|
||||
botAI->TellMasterNoFacing(text);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string const playerName = targetName.substr(1);
|
||||
Player* target = FindGroupPlayerByName(bot, playerName);
|
||||
if (!target)
|
||||
{
|
||||
std::map<std::string, std::string> placeholders;
|
||||
placeholders["%player_name"] = playerName;
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_not_in_group_with",
|
||||
"I'm not in a group with %player_name",
|
||||
placeholders);
|
||||
botAI->TellMasterNoFacing(text);
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectGuid const& targetGuid = target->GetGUID();
|
||||
if (add)
|
||||
{
|
||||
if (std::find(focusHealTargets.begin(), focusHealTargets.end(), targetGuid) ==
|
||||
focusHealTargets.end())
|
||||
focusHealTargets.push_back(targetGuid);
|
||||
|
||||
std::map<std::string, std::string> placeholders;
|
||||
placeholders["%player_name"] = playerName;
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_added",
|
||||
"Added %player_name to focus heal targets",
|
||||
placeholders);
|
||||
botAI->TellMasterNoFacing(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
focusHealTargets.remove(targetGuid);
|
||||
std::map<std::string, std::string> placeholders;
|
||||
placeholders["%player_name"] = playerName;
|
||||
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||
"focus_heal_removed",
|
||||
"Removed %player_name from focus heal targets",
|
||||
placeholders);
|
||||
botAI->TellMasterNoFacing(text);
|
||||
}
|
||||
}
|
||||
|
||||
SET_AI_VALUE(std::list<ObjectGuid>, "focus heal targets", focusHealTargets);
|
||||
|
||||
if (focusHealTargets.empty())
|
||||
botAI->ChangeStrategy("-focus heal targets", BOT_STATE_COMBAT);
|
||||
else
|
||||
botAI->ChangeStrategy("+focus heal targets", BOT_STATE_COMBAT);
|
||||
|
||||
return true;
|
||||
}
|
||||
21
src/Ai/Base/Actions/SetFocusHealTargetsAction.h
Normal file
21
src/Ai/Base/Actions/SetFocusHealTargetsAction.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_SETFOCUSHEALTARGETSACTION_H
|
||||
#define _PLAYERBOT_SETFOCUSHEALTARGETSACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class SetFocusHealTargetsAction : public Action
|
||||
{
|
||||
public:
|
||||
SetFocusHealTargetsAction(PlayerbotAI* botAI) : Action(botAI, "focus heal targets") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -37,6 +37,7 @@
|
||||
#include "LogLevelAction.h"
|
||||
#include "LootStrategyAction.h"
|
||||
#include "LootRollAction.h"
|
||||
#include "SetFocusHealTargetsAction.h"
|
||||
#include "MailAction.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "NewRpgAction.h"
|
||||
@ -201,6 +202,7 @@ public:
|
||||
creators["pet attack"] = &ChatActionContext::pet_attack;
|
||||
creators["roll"] = &ChatActionContext::roll_action;
|
||||
creators["wait for attack time"] = &ChatActionContext::wait_for_attack_time;
|
||||
creators["focus heal targets"] = &ChatActionContext::focus_heal_targets;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -314,6 +316,7 @@ private:
|
||||
static Action* pet_attack(PlayerbotAI* botAI) { return new PetsAction(botAI, "attack"); }
|
||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||
static Action* wait_for_attack_time(PlayerbotAI* botAI) { return new SetWaitForAttackTimeAction(botAI); }
|
||||
static Action* focus_heal_targets(PlayerbotAI* botAI) { return new SetFocusHealTargetsAction(botAI); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -146,6 +146,7 @@ public:
|
||||
creators["pet attack"] = &ChatTriggerContext::pet_attack;
|
||||
creators["roll"] = &ChatTriggerContext::roll_action;
|
||||
creators["wait for attack time"] = &ChatTriggerContext::wait_for_attack_time;
|
||||
creators["focus heal"] = &ChatTriggerContext::focus_heal;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -271,6 +272,7 @@ private:
|
||||
static Trigger* pet_attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet attack"); }
|
||||
static Trigger* roll_action(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "roll"); }
|
||||
static Trigger* wait_for_attack_time(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wait for attack time"); }
|
||||
static Trigger* focus_heal(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "focus heal"); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -107,6 +107,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
||||
triggers.push_back(new TriggerNode("pet", { NextAction("pet", relevance) }));
|
||||
triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) }));
|
||||
triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) }));
|
||||
triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) }));
|
||||
}
|
||||
|
||||
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
|
||||
@ -200,4 +201,5 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
|
||||
supported.push_back("pet");
|
||||
supported.push_back("pet attack");
|
||||
supported.push_back("wait for attack time");
|
||||
supported.push_back("focus heal");
|
||||
}
|
||||
|
||||
20
src/Ai/Base/Strategy/FocusTargetStrategy.h
Normal file
20
src/Ai/Base/Strategy/FocusTargetStrategy.h
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_FOCUSTARGETSTRATEGY_H
|
||||
#define _PLAYERBOT_FOCUSTARGETSTRATEGY_H
|
||||
|
||||
#include "Strategy.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class FocusHealTargetsStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
FocusHealTargetsStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
|
||||
std::string const getName() override { return "focus heal targets"; }
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -19,6 +19,7 @@
|
||||
#include "DuelStrategy.h"
|
||||
#include "EmoteStrategy.h"
|
||||
#include "FleeStrategy.h"
|
||||
#include "FocusTargetStrategy.h"
|
||||
#include "FollowMasterStrategy.h"
|
||||
#include "GrindingStrategy.h"
|
||||
#include "GroupStrategy.h"
|
||||
@ -126,6 +127,7 @@ public:
|
||||
creators["use bobber"] = &StrategyContext::bobber_strategy;
|
||||
creators["master fishing"] = &StrategyContext::master_fishing;
|
||||
creators["wait for attack"] = &StrategyContext::wait_for_attack;
|
||||
creators["focus heal targets"] = &StrategyContext::focus_heal_targets;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -198,6 +200,7 @@ private:
|
||||
static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); }
|
||||
static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); }
|
||||
static Strategy* wait_for_attack(PlayerbotAI* botAI) { return new WaitForAttackStrategy(botAI); }
|
||||
static Strategy* focus_heal_targets(PlayerbotAI* botAI) { return new FocusHealTargetsStrategy(botAI); }
|
||||
};
|
||||
|
||||
class MovementStrategyContext : public NamedObjectContext<Strategy>
|
||||
|
||||
@ -230,6 +230,7 @@ public:
|
||||
creators["wander npc status"] = &TriggerContext::wander_npc_status;
|
||||
creators["do quest status"] = &TriggerContext::do_quest_status;
|
||||
creators["travel flight status"] = &TriggerContext::travel_flight_status;
|
||||
creators["outdoor pvp status"] = &TriggerContext::outdoor_pvp_status;
|
||||
creators["can self resurrect"] = &TriggerContext::can_self_resurrect;
|
||||
creators["can fish"] = &TriggerContext::can_fish;
|
||||
creators["can use fishing bobber"] = &TriggerContext::can_use_fishing_bobber;
|
||||
@ -436,6 +437,7 @@ private:
|
||||
static Trigger* wander_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_WANDER_NPC); }
|
||||
static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); }
|
||||
static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); }
|
||||
static Trigger* outdoor_pvp_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_OUTDOOR_PVP); }
|
||||
static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); }
|
||||
static Trigger* can_fish(PlayerbotAI* ai) { return new CanFishTrigger(ai); }
|
||||
static Trigger* can_use_fishing_bobber(PlayerbotAI* ai) { return new CanUseFishingBobberTrigger(ai); }
|
||||
|
||||
@ -38,6 +38,36 @@ Unit* PartyMemberToHeal::Calculate()
|
||||
bool isRaid = bot->GetGroup()->isRaidGroup();
|
||||
MinValueCalculator calc(100);
|
||||
|
||||
// If focus heal targets strategy is active, only heal those targets
|
||||
if (botAI->HasStrategy("focus heal targets", BOT_STATE_COMBAT))
|
||||
{
|
||||
std::list<ObjectGuid> const focusHealTargets =
|
||||
AI_VALUE(std::list<ObjectGuid>, "focus heal targets");
|
||||
|
||||
for (ObjectGuid const& focusHealTarget : focusHealTargets)
|
||||
{
|
||||
Player* player = ObjectAccessor::FindPlayer(focusHealTarget);
|
||||
if (!player || !player->IsInWorld() || !player->IsAlive() || !player->IsInSameGroupWith(bot))
|
||||
continue;
|
||||
|
||||
float health = player->GetHealthPct();
|
||||
if (isRaid || health < sPlayerbotAIConfig.mediumHealth ||
|
||||
!IsTargetOfSpellCast(player, predicate))
|
||||
{
|
||||
float probeValue = 100.0f;
|
||||
if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance)
|
||||
probeValue = health + 30.0f;
|
||||
else
|
||||
probeValue = health + player->GetDistance2d(bot) / 10.0f;
|
||||
|
||||
if (probeValue < calc.minValue && Check(player))
|
||||
calc.probe(probeValue, player);
|
||||
}
|
||||
}
|
||||
|
||||
return (Unit*)calc.param;
|
||||
}
|
||||
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* player = gref->GetSource();
|
||||
@ -45,17 +75,17 @@ Unit* PartyMemberToHeal::Calculate()
|
||||
continue;
|
||||
if (player && player->IsAlive())
|
||||
{
|
||||
uint8 health = player->GetHealthPct();
|
||||
float health = player->GetHealthPct();
|
||||
if (isRaid || health < sPlayerbotAIConfig.mediumHealth || !IsTargetOfSpellCast(player, predicate))
|
||||
{
|
||||
uint32 probeValue = 100;
|
||||
float probeValue = 100.0f;
|
||||
if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance)
|
||||
{
|
||||
probeValue = health + 30;
|
||||
probeValue = health + 30.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
probeValue = health + player->GetDistance2d(bot) / 10;
|
||||
probeValue = health + player->GetDistance2d(bot) / 10.0f;
|
||||
}
|
||||
// delay Check player to here for better performance
|
||||
if (probeValue < calc.minValue && Check(player))
|
||||
@ -68,10 +98,10 @@ Unit* PartyMemberToHeal::Calculate()
|
||||
Pet* pet = player->GetPet();
|
||||
if (pet && pet->IsAlive())
|
||||
{
|
||||
uint8 health = ((Unit*)pet)->GetHealthPct();
|
||||
uint32 probeValue = 100;
|
||||
float health = ((Unit*)pet)->GetHealthPct();
|
||||
float probeValue = 100.0f;
|
||||
if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
|
||||
probeValue = health + 30;
|
||||
probeValue = health + 30.0f;
|
||||
// delay Check pet to here for better performance
|
||||
if (probeValue < calc.minValue && Check(pet))
|
||||
{
|
||||
@ -82,10 +112,10 @@ Unit* PartyMemberToHeal::Calculate()
|
||||
Unit* charm = player->GetCharm();
|
||||
if (charm && charm->IsAlive())
|
||||
{
|
||||
uint8 health = charm->GetHealthPct();
|
||||
uint32 probeValue = 100;
|
||||
float health = charm->GetHealthPct();
|
||||
float probeValue = 100.0f;
|
||||
if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
|
||||
probeValue = health + 30;
|
||||
probeValue = health + 30.0f;
|
||||
// delay Check charm to here for better performance
|
||||
if (probeValue < calc.minValue && Check(charm))
|
||||
{
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#include "ServerFacade.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "NearestGameObjects.h"
|
||||
#include <unordered_set>
|
||||
|
||||
std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags;
|
||||
|
||||
@ -88,8 +89,11 @@ bool PossibleRpgTargetsValue::AcceptUnit(Unit* unit)
|
||||
|
||||
std::vector<uint32> PossibleNewRpgTargetsValue::allowedNpcFlags;
|
||||
|
||||
// Sparse starting zones where the default scan range is insufficient for WANDER_NPC (requires >= 3 NPCs)
|
||||
static const std::unordered_set<uint32> rpgRangeOverrideAreaIds = { 3526 /* Ammen Vale */, 2117 /* Deathknell */ };
|
||||
|
||||
PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float range)
|
||||
: NearestUnitsValue(botAI, "possible new rpg targets", range, true)
|
||||
: NearestUnitsValue(botAI, "possible new rpg targets", range, true), defaultRange(range)
|
||||
{
|
||||
if (allowedNpcFlags.empty())
|
||||
{
|
||||
@ -119,6 +123,11 @@ PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float
|
||||
|
||||
GuidVector PossibleNewRpgTargetsValue::Calculate()
|
||||
{
|
||||
if (rpgRangeOverrideAreaIds.count(bot->GetAreaId()) && defaultRange < 200.0f)
|
||||
range = 200.0f;
|
||||
else
|
||||
range = defaultRange;
|
||||
|
||||
std::list<Unit*> targets;
|
||||
FindUnits(targets);
|
||||
|
||||
|
||||
@ -35,6 +35,8 @@ public:
|
||||
protected:
|
||||
void FindUnits(std::list<Unit*>& targets) override;
|
||||
bool AcceptUnit(Unit* unit) override;
|
||||
private:
|
||||
float defaultRange;
|
||||
};
|
||||
|
||||
class PossibleNewRpgGameObjectsValue : public ObjectGuidListCalculatedValue
|
||||
|
||||
@ -140,4 +140,11 @@ public:
|
||||
public:
|
||||
Unit* Calculate();
|
||||
};
|
||||
|
||||
class FocusHealTargetValue : public ManualSetValue<std::list<ObjectGuid>>
|
||||
{
|
||||
public:
|
||||
FocusHealTargetValue(PlayerbotAI* botAI) : ManualSetValue<std::list<ObjectGuid>>(botAI, {}, "focus heal targets") {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -241,6 +241,7 @@ public:
|
||||
creators["travel target"] = &ValueContext::travel_target;
|
||||
creators["talk target"] = &ValueContext::talk_target;
|
||||
creators["pull target"] = &ValueContext::pull_target;
|
||||
creators["focus heal targets"] = &ValueContext::focus_heal_targets;
|
||||
creators["group"] = &ValueContext::group;
|
||||
creators["range"] = &ValueContext::range;
|
||||
creators["inside target"] = &ValueContext::inside_target;
|
||||
@ -497,6 +498,7 @@ private:
|
||||
static UntypedValue* next_rpg_action(PlayerbotAI* botAI) { return new NextRpgActionValue(botAI); }
|
||||
static UntypedValue* travel_target(PlayerbotAI* botAI) { return new TravelTargetValue(botAI); }
|
||||
static UntypedValue* pull_target(PlayerbotAI* botAI) { return new PullTargetValue(botAI); }
|
||||
static UntypedValue* focus_heal_targets(PlayerbotAI* botAI) { return new FocusHealTargetValue(botAI); }
|
||||
|
||||
static UntypedValue* bg_master(PlayerbotAI* botAI) { return new BgMasterValue(botAI); }
|
||||
static UntypedValue* bg_role(PlayerbotAI* botAI) { return new BgRoleValue(botAI); }
|
||||
|
||||
@ -23,6 +23,16 @@ std::vector<NextAction> CastAbolishPoisonOnPartyAction::getAlternatives()
|
||||
CastSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
bool CastLifebloomOnMainTankAction::isUseful()
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target || !target->IsAlive() || !CastSpellAction::isUseful())
|
||||
return false;
|
||||
|
||||
Aura* lifebloom = botAI->GetAura("lifebloom", target, true, true);
|
||||
return !lifebloom || lifebloom->GetStackAmount() < 3 || lifebloom->GetDuration() < 2000;
|
||||
}
|
||||
|
||||
Value<Unit*>* CastEntanglingRootsCcAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("cc target", "entangling roots");
|
||||
|
||||
@ -128,6 +128,14 @@ public:
|
||||
CastThornsOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "thorns", false) {}
|
||||
};
|
||||
|
||||
class CastLifebloomOnMainTankAction : public BuffOnMainTankAction
|
||||
{
|
||||
public:
|
||||
CastLifebloomOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "lifebloom", true) {}
|
||||
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class CastOmenOfClarityAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
|
||||
@ -79,6 +79,7 @@ public:
|
||||
DruidTriggerFactoryInternal()
|
||||
{
|
||||
creators["omen of clarity"] = &DruidTriggerFactoryInternal::omen_of_clarity;
|
||||
creators["clearcasting"] = &DruidTriggerFactoryInternal::clearcasting;
|
||||
creators["thorns"] = &DruidTriggerFactoryInternal::thorns;
|
||||
creators["thorns on party"] = &DruidTriggerFactoryInternal::thorns_on_party;
|
||||
creators["thorns on main tank"] = &DruidTriggerFactoryInternal::thorns_on_main_tank;
|
||||
@ -117,6 +118,7 @@ public:
|
||||
|
||||
private:
|
||||
static Trigger* natures_swiftness(PlayerbotAI* botAI) { return new NaturesSwiftnessTrigger(botAI); }
|
||||
static Trigger* clearcasting(PlayerbotAI* botAI) { return new ClearcastingTrigger(botAI); }
|
||||
static Trigger* eclipse_solar(PlayerbotAI* botAI) { return new EclipseSolarTrigger(botAI); }
|
||||
static Trigger* eclipse_lunar(PlayerbotAI* botAI) { return new EclipseLunarTrigger(botAI); }
|
||||
static Trigger* thorns(PlayerbotAI* botAI) { return new ThornsTrigger(botAI); }
|
||||
@ -209,6 +211,7 @@ public:
|
||||
creators["thorns"] = &DruidAiObjectContextInternal::thorns;
|
||||
creators["thorns on party"] = &DruidAiObjectContextInternal::thorns_on_party;
|
||||
creators["thorns on main tank"] = &DruidAiObjectContextInternal::thorns_on_main_tank;
|
||||
creators["lifebloom on main tank"] = &DruidAiObjectContextInternal::lifebloom_on_main_tank;
|
||||
creators["cure poison"] = &DruidAiObjectContextInternal::cure_poison;
|
||||
creators["cure poison on party"] = &DruidAiObjectContextInternal::cure_poison_on_party;
|
||||
creators["abolish poison"] = &DruidAiObjectContextInternal::abolish_poison;
|
||||
@ -304,6 +307,7 @@ private:
|
||||
static Action* thorns(PlayerbotAI* botAI) { return new CastThornsAction(botAI); }
|
||||
static Action* thorns_on_party(PlayerbotAI* botAI) { return new CastThornsOnPartyAction(botAI); }
|
||||
static Action* thorns_on_main_tank(PlayerbotAI* botAI) { return new CastThornsOnMainTankAction(botAI); }
|
||||
static Action* lifebloom_on_main_tank(PlayerbotAI* botAI) { return new CastLifebloomOnMainTankAction(botAI); }
|
||||
static Action* cure_poison(PlayerbotAI* botAI) { return new CastCurePoisonAction(botAI); }
|
||||
static Action* cure_poison_on_party(PlayerbotAI* botAI) { return new CastCurePoisonOnPartyAction(botAI); }
|
||||
static Action* abolish_poison(PlayerbotAI* botAI) { return new CastAbolishPoisonAction(botAI); }
|
||||
|
||||
@ -56,6 +56,9 @@ void HealDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
new TriggerNode("party member critical health",
|
||||
{ NextAction("nature's swiftness", ACTION_CRITICAL_HEAL + 4) }));
|
||||
|
||||
triggers.push_back(new TriggerNode("clearcasting",
|
||||
{ NextAction("lifebloom on main tank", ACTION_CRITICAL_HEAL - 1) }));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"group heal setting",
|
||||
{
|
||||
|
||||
@ -61,6 +61,12 @@ public:
|
||||
OmenOfClarityTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "omen of clarity") {}
|
||||
};
|
||||
|
||||
class ClearcastingTrigger : public HasAuraTrigger
|
||||
{
|
||||
public:
|
||||
ClearcastingTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "clearcasting") {}
|
||||
};
|
||||
|
||||
class RakeTrigger : public DebuffTrigger
|
||||
{
|
||||
public:
|
||||
|
||||
@ -62,7 +62,7 @@ bool NewRpgStatusUpdateAction::Execute(Event /*event*/)
|
||||
{
|
||||
case RPG_IDLE:
|
||||
return RandomChangeStatus({RPG_GO_CAMP, RPG_GO_GRIND, RPG_WANDER_RANDOM, RPG_WANDER_NPC, RPG_DO_QUEST,
|
||||
RPG_TRAVEL_FLIGHT, RPG_REST});
|
||||
RPG_TRAVEL_FLIGHT, RPG_REST, RPG_OUTDOOR_PVP});
|
||||
|
||||
case RPG_GO_GRIND:
|
||||
{
|
||||
@ -140,6 +140,15 @@ bool NewRpgStatusUpdateAction::Execute(Event /*event*/)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RPG_OUTDOOR_PVP:
|
||||
{
|
||||
if (info.HasStatusPersisted(statusOutDoorPvPDuration))
|
||||
{
|
||||
info.ChangeToIdle();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@ -47,10 +47,11 @@ public:
|
||||
|
||||
protected:
|
||||
// static NewRpgStatusTransitionProb transitionMat;
|
||||
const int32 statusWanderNpcDuration = 5 * 60 * 1000;
|
||||
const int32 statusWanderRandomDuration = 5 * 60 * 1000;
|
||||
const int32 statusRestDuration = 30 * 1000;
|
||||
const int32 statusDoQuestDuration = 30 * 60 * 1000;
|
||||
const int32 statusWanderNpcDuration = 5 * MINUTE * IN_MILLISECONDS ;
|
||||
const int32 statusWanderRandomDuration = 5 * MINUTE * IN_MILLISECONDS ;
|
||||
const int32 statusRestDuration = 30 * IN_MILLISECONDS ;
|
||||
const int32 statusDoQuestDuration = 30 * MINUTE * IN_MILLISECONDS ;
|
||||
const int32 statusOutDoorPvPDuration = HOUR * IN_MILLISECONDS ;
|
||||
};
|
||||
|
||||
class NewRpgGoGrindAction : public NewRpgBaseAction
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "NewRpgStrategy.h"
|
||||
#include "Object.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "OutdoorPvPMgr.h"
|
||||
#include "ObjectDefines.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "ObjectMgr.h"
|
||||
@ -222,12 +223,10 @@ bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance)
|
||||
return MoveTo(mapId, x, y, z, false, false, false, true);
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority)
|
||||
bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority, WorldObject* center)
|
||||
{
|
||||
if (IsWaitingForLastMove(priority))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Map* map = bot->GetMap();
|
||||
const float x = bot->GetPositionX();
|
||||
@ -1160,6 +1159,11 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector<NewRpgStatus> candidateSta
|
||||
bot->SetStandState(UNIT_STAND_STATE_SIT);
|
||||
return true;
|
||||
}
|
||||
case RPG_OUTDOOR_PVP:
|
||||
{
|
||||
botAI->rpgInfo.ChangeToOutdoorPvp();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
{
|
||||
botAI->rpgInfo.ChangeToRest();
|
||||
@ -1220,6 +1224,17 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status)
|
||||
std::vector<uint32> path;
|
||||
return SelectRandomFlightTaxiNode(flightMaster, path);
|
||||
}
|
||||
case RPG_OUTDOOR_PVP:
|
||||
{
|
||||
if (!bot->IsPvP())
|
||||
return false;
|
||||
uint32 zoneId = bot->GetZoneId();
|
||||
if (zoneId == AREA_NAGRAND)
|
||||
return false;
|
||||
|
||||
OutdoorPvP* outdoorPvP = sOutdoorPvPMgr->GetOutdoorPvPToZoneId(zoneId);
|
||||
return outdoorPvP != nullptr;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ protected:
|
||||
/* MOVEMENT RELATED */
|
||||
bool MoveFarTo(WorldPosition dest);
|
||||
bool MoveWorldObjectTo(ObjectGuid guid, float distance = INTERACTION_DISTANCE);
|
||||
bool MoveRandomNear(float moveStep = 50.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
bool MoveRandomNear(float moveStep = 50.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, WorldObject* center = nullptr);
|
||||
bool ForceToWait(uint32 duration, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||
|
||||
/* QUEST RELATED CHECK */
|
||||
|
||||
128
src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.cpp
Normal file
128
src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include "NewRpgOutdoorPvP.h"
|
||||
#include "OutdoorPvP.h"
|
||||
#include "OutdoorPvPMgr.h"
|
||||
|
||||
bool NewRpgOutdoorPvpAction::Execute(Event event)
|
||||
{
|
||||
if (!bot->IsPvP())
|
||||
{
|
||||
botAI->rpgInfo.ChangeToIdle();
|
||||
return false;
|
||||
}
|
||||
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL) || !bot->IsOutdoorPvPActive())
|
||||
return false;
|
||||
|
||||
uint32 zoneId = bot->GetZoneId();
|
||||
OutdoorPvP* outdoorPvP = sOutdoorPvPMgr->GetOutdoorPvPToZoneId(zoneId);
|
||||
if (!outdoorPvP || zoneId == AREA_NAGRAND)
|
||||
{
|
||||
botAI->rpgInfo.ChangeToIdle();
|
||||
return false;
|
||||
}
|
||||
|
||||
OutdoorPvP::OPvPCapturePointMap const& capturePointMap = outdoorPvP->GetCapturePoints();
|
||||
|
||||
NewRpgInfo& info = botAI->rpgInfo;
|
||||
auto* dataPtr = std::get_if<NewRpgInfo::OutdoorPvP>(&info.data);
|
||||
if (!dataPtr)
|
||||
return false;
|
||||
auto& data = *dataPtr;
|
||||
// Re-resolve stored spawn ID from the capture point map each tick (avoids dangling pointers)
|
||||
OPvPCapturePoint* objective = nullptr;
|
||||
if (data.capturePointSpawnId && !capturePointMap.empty())
|
||||
{
|
||||
auto it = capturePointMap.find(data.capturePointSpawnId);
|
||||
if (it != capturePointMap.end())
|
||||
{
|
||||
OPvPCapturePoint* capturePoint = it->second;
|
||||
if (capturePoint && capturePoint->_capturePoint)
|
||||
{
|
||||
float threshold = capturePoint->GetMinValue();
|
||||
float slider = capturePoint->GetSlider();
|
||||
uint8 faction = bot->GetTeamId();
|
||||
LOG_DEBUG("playerbots", "[NEW RPG] Bot {} with faction {} is evaluating existing RPG objective {} with threshold {} and slider value {}", bot->GetName(), faction, capturePoint->_capturePoint->GetName(), threshold, slider);
|
||||
if ((faction == TEAM_HORDE && slider >= -threshold) ||
|
||||
(faction == TEAM_ALLIANCE && slider <= threshold))
|
||||
objective = capturePoint;
|
||||
}
|
||||
}
|
||||
if (!objective)
|
||||
data.capturePointSpawnId = 0;
|
||||
}
|
||||
|
||||
if (!objective)
|
||||
{
|
||||
objective = SelectNewObjective(capturePointMap);
|
||||
if (!objective)
|
||||
{
|
||||
botAI->rpgInfo.ChangeToIdle();
|
||||
return true;
|
||||
}
|
||||
data.capturePointSpawnId = objective->m_capturePointSpawnId;
|
||||
LOG_DEBUG("playerbots","[NEW RPG] Bot {} selected OutDoorPvP target capturePointSpawnId {}", bot->GetName(), data.capturePointSpawnId);
|
||||
}
|
||||
|
||||
GameObject* objectiveGO = objective->_capturePoint;
|
||||
if (!objectiveGO)
|
||||
return false;
|
||||
|
||||
if (objectiveGO->GetGoType() != GAMEOBJECT_TYPE_CAPTURE_POINT)
|
||||
return false;
|
||||
|
||||
float radius = objectiveGO->GetGOInfo()->capturePoint.radius / 2.0f;
|
||||
if (!objectiveGO->IsWithinDistInMap(bot, radius))
|
||||
return MoveFarTo(WorldPosition(objectiveGO));
|
||||
|
||||
return PatrolCapturePoint(objectiveGO, radius);
|
||||
}
|
||||
|
||||
OPvPCapturePoint* NewRpgOutdoorPvpAction::SelectNewObjective(OutdoorPvP::OPvPCapturePointMap const& capturePointMap)
|
||||
{
|
||||
OPvPCapturePoint* objective = nullptr;
|
||||
uint8 faction = bot->GetTeamId();
|
||||
std::vector<OPvPCapturePoint*> candidateObjectives;
|
||||
|
||||
if (capturePointMap.empty())
|
||||
{
|
||||
botAI->rpgInfo.ChangeToIdle();
|
||||
return objective;
|
||||
}
|
||||
for (auto const& [guid, point] : capturePointMap)
|
||||
{
|
||||
GameObject* capturePointObject = point->_capturePoint;
|
||||
if (!capturePointObject)
|
||||
continue;
|
||||
|
||||
float threshold = point->GetMinValue();
|
||||
float slider = point->GetSlider();
|
||||
if (faction == TEAM_HORDE && slider > -threshold)
|
||||
candidateObjectives.push_back(point);
|
||||
else if (faction == TEAM_ALLIANCE && slider < threshold)
|
||||
candidateObjectives.push_back(point);
|
||||
}
|
||||
if (candidateObjectives.empty())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[New RPG] Bot {} found no valid outdoor PVP objectives to capture", bot->GetName());
|
||||
botAI->rpgInfo.ChangeToIdle();
|
||||
return objective;
|
||||
}
|
||||
int randomIndex = urand(0, candidateObjectives.size() - 1);
|
||||
objective = candidateObjectives[randomIndex];
|
||||
return objective;
|
||||
}
|
||||
|
||||
bool NewRpgOutdoorPvpAction::PatrolCapturePoint(GameObject* objectiveGO, float radius)
|
||||
{
|
||||
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
|
||||
return false;
|
||||
|
||||
// Randomly pause at the current spot before picking a new patrol point
|
||||
if (urand(0, 2) == 0)
|
||||
return ForceToWait(urand(3000, 6000));
|
||||
|
||||
float patrolRadius = radius * 0.8f;
|
||||
if (MoveRandomNear(patrolRadius, MovementPriority::MOVEMENT_NORMAL, objectiveGO))
|
||||
return true;
|
||||
|
||||
return ForceToWait(urand(3000, 6000));
|
||||
}
|
||||
19
src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.h
Normal file
19
src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef PLAYERBOT_NEWRPGOUTDOORPVP_H
|
||||
#define PLAYERBOT_NEWRPGOUTDOORPVP_H
|
||||
|
||||
#include "NewRpgBaseAction.h"
|
||||
#include "OutdoorPvP.h"
|
||||
|
||||
class NewRpgOutdoorPvpAction : public NewRpgBaseAction
|
||||
{
|
||||
public:
|
||||
NewRpgOutdoorPvpAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg outdoor pvp") {}
|
||||
|
||||
virtual bool Execute(Event event) override;
|
||||
OPvPCapturePoint* SelectNewObjective(OutdoorPvP::OPvPCapturePointMap const& capturePointMap);
|
||||
|
||||
private:
|
||||
bool PatrolCapturePoint(GameObject* objectiveGO, float radius);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -47,6 +47,14 @@ void NewRpgInfo::ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector<u
|
||||
data = flight;
|
||||
}
|
||||
|
||||
void NewRpgInfo::ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId)
|
||||
{
|
||||
startT = getMSTime();
|
||||
OutdoorPvP pvp;
|
||||
pvp.capturePointSpawnId = capturePointSpawnId;
|
||||
data = pvp;
|
||||
}
|
||||
|
||||
void NewRpgInfo::ChangeToRest()
|
||||
{
|
||||
startT = getMSTime();
|
||||
@ -90,6 +98,7 @@ NewRpgStatus NewRpgInfo::GetStatus()
|
||||
if constexpr (std::is_same_v<T, Rest>) return RPG_REST;
|
||||
if constexpr (std::is_same_v<T, DoQuest>) return RPG_DO_QUEST;
|
||||
if constexpr (std::is_same_v<T, TravelFlight>) return RPG_TRAVEL_FLIGHT;
|
||||
if constexpr (std::is_same_v<T, OutdoorPvP>) return RPG_OUTDOOR_PVP;
|
||||
return RPG_IDLE;
|
||||
}, data);
|
||||
}
|
||||
@ -153,6 +162,14 @@ std::string NewRpgInfo::ToString()
|
||||
out << "\ntoNode: " << arg.path[arg.path.size() - 1];
|
||||
out << "\ninFlight: " << arg.inFlight;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, OutdoorPvP>)
|
||||
{
|
||||
out << "OUTDOOR_PVP";
|
||||
if (!arg.capturePointSpawnId)
|
||||
out << "\nNo capture point assigned.";
|
||||
else
|
||||
out << "\ncapturePointSpawnId: " << arg.capturePointSpawnId;
|
||||
}
|
||||
else
|
||||
out << "UNKNOWN";
|
||||
}, data);
|
||||
|
||||
@ -58,6 +58,11 @@ struct NewRpgInfo
|
||||
{
|
||||
Rest() = default;
|
||||
};
|
||||
// RPG_OUTDOOR_PVP
|
||||
struct OutdoorPvP
|
||||
{
|
||||
ObjectGuid::LowType capturePointSpawnId{0};
|
||||
};
|
||||
struct Idle
|
||||
{
|
||||
};
|
||||
@ -79,7 +84,8 @@ struct NewRpgInfo
|
||||
WanderRandom,
|
||||
DoQuest,
|
||||
Rest,
|
||||
TravelFlight
|
||||
TravelFlight,
|
||||
OutdoorPvP
|
||||
>;
|
||||
RpgData data;
|
||||
|
||||
@ -91,6 +97,7 @@ struct NewRpgInfo
|
||||
void ChangeToWanderRandom();
|
||||
void ChangeToDoQuest(uint32 questId, const Quest* quest);
|
||||
void ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector<uint32> path);
|
||||
void ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId = 0);
|
||||
void ChangeToRest();
|
||||
void ChangeToIdle();
|
||||
bool CanChangeTo(NewRpgStatus status);
|
||||
|
||||
@ -65,6 +65,14 @@ void NewRpgStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"outdoor pvp status",
|
||||
{
|
||||
NextAction("new rpg outdoor pvp", 3.0f)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void NewRpgStrategy::InitMultipliers(std::vector<Multiplier*>&)
|
||||
|
||||
@ -54,6 +54,7 @@
|
||||
#include "Unit.h"
|
||||
#include "UpdateTime.h"
|
||||
#include "Vehicle.h"
|
||||
#include "../../../../src/server/scripts/Spells/spell_dk.cpp"
|
||||
|
||||
const int SPELL_TITAN_GRIP = 49152;
|
||||
|
||||
@ -2163,7 +2164,7 @@ bool PlayerbotAI::IsTank(Player* player, bool bySpec)
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD || player->HasAura(SPELL_DK_FROST_PRESENCE))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2861,10 +2861,10 @@ void RandomPlayerbotMgr::PrintStats()
|
||||
LOG_INFO("playerbots", "Bots rpg status:");
|
||||
LOG_INFO("playerbots",
|
||||
" Idle: {}, Rest: {}, GoGrind: {}, GoCamp: {}, MoveRandom: {}, MoveNpc: {}, DoQuest: {}, "
|
||||
"TravelFlight: {}",
|
||||
"TravelFlight: {}, OutdoorPvP: {}",
|
||||
rpgStatusCount[RPG_IDLE], rpgStatusCount[RPG_REST], rpgStatusCount[RPG_GO_GRIND],
|
||||
rpgStatusCount[RPG_GO_CAMP], rpgStatusCount[RPG_WANDER_RANDOM], rpgStatusCount[RPG_WANDER_NPC],
|
||||
rpgStatusCount[RPG_DO_QUEST], rpgStatusCount[RPG_TRAVEL_FLIGHT]);
|
||||
rpgStatusCount[RPG_DO_QUEST], rpgStatusCount[RPG_TRAVEL_FLIGHT], rpgStatusCount[RPG_OUTDOOR_PVP]);
|
||||
|
||||
LOG_INFO("playerbots", "Bots total quests:");
|
||||
LOG_INFO("playerbots", " Accepted: {}, Rewarded: {}, Dropped: {}", rpgStasticTotal.questAccepted,
|
||||
|
||||
@ -20,6 +20,13 @@
|
||||
#include "StatsCollector.h"
|
||||
#include "Unit.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr uint32 SPELL_MOLTEN_ARMOR_RANK_1 = 30482;
|
||||
constexpr uint32 SPELL_MOLTEN_ARMOR_RANK_2 = 43045;
|
||||
constexpr uint32 SPELL_MOLTEN_ARMOR_RANK_3 = 43046;
|
||||
}
|
||||
|
||||
StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
|
||||
{
|
||||
if (PlayerbotAI::IsHeal(player))
|
||||
@ -454,6 +461,16 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player)
|
||||
if (player->HasAura(51885))
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 1.1f;
|
||||
}
|
||||
else if (cls == CLASS_MAGE)
|
||||
{
|
||||
if (!player->HasSpell(SPELL_MOLTEN_ARMOR_RANK_1)
|
||||
&& !player->HasSpell(SPELL_MOLTEN_ARMOR_RANK_2)
|
||||
&& !player->HasSpell(SPELL_MOLTEN_ARMOR_RANK_3))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 0.2f;
|
||||
stats_weights_[STATS_TYPE_SPIRIT] -= 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatsWeightCalculator::CalculateItemSetMod(Player* player, ItemTemplate const* proto)
|
||||
|
||||
@ -652,6 +652,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
RpgStatusProbWeight[RPG_DO_QUEST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.DoQuest", 60);
|
||||
RpgStatusProbWeight[RPG_TRAVEL_FLIGHT] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.TravelFlight", 15);
|
||||
RpgStatusProbWeight[RPG_REST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.Rest", 5);
|
||||
RpgStatusProbWeight[RPG_OUTDOOR_PVP] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.OutdoorPvp", 10);
|
||||
|
||||
syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false);
|
||||
randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", false);
|
||||
|
||||
@ -56,7 +56,8 @@ enum NewRpgStatus : int
|
||||
RPG_TRAVEL_FLIGHT = 6,
|
||||
// Taking a break
|
||||
RPG_REST = 7,
|
||||
RPG_STATUS_END = 8
|
||||
RPG_OUTDOOR_PVP = 8,
|
||||
RPG_STATUS_END = 9
|
||||
};
|
||||
|
||||
#define MAX_SPECNO 20
|
||||
|
||||
@ -53,8 +53,9 @@ void ServerFacade::SetFacingTo(Player* bot, WorldObject* wo, bool force)
|
||||
bot->SetOrientation(angle);
|
||||
|
||||
if (!bot->IsRooted())
|
||||
bot->SendMovementFlagUpdate();
|
||||
// }
|
||||
// enforce (bool self) true otherwhise when using real-client with self-bot wont
|
||||
// recieve update; e.g. will not face the target when using (mostly ranged) attack
|
||||
bot->SendMovementFlagUpdate(true);
|
||||
}
|
||||
|
||||
Unit* ServerFacade::GetChaseTarget(Unit* target)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user