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)
|
# 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)
|
# 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)
|
# 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.WanderRandom = 15
|
||||||
AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20
|
AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20
|
||||||
AiPlayerbot.RpgStatusProbWeight.GoGrind = 15
|
AiPlayerbot.RpgStatusProbWeight.GoGrind = 15
|
||||||
@ -1045,6 +1046,7 @@ AiPlayerbot.RpgStatusProbWeight.GoCamp = 10
|
|||||||
AiPlayerbot.RpgStatusProbWeight.DoQuest = 60
|
AiPlayerbot.RpgStatusProbWeight.DoQuest = 60
|
||||||
AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15
|
AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15
|
||||||
AiPlayerbot.RpgStatusProbWeight.Rest = 5
|
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
|
# 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
|
# 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 "WorldBuffAction.h"
|
||||||
#include "XpGainAction.h"
|
#include "XpGainAction.h"
|
||||||
#include "NewRpgAction.h"
|
#include "NewRpgAction.h"
|
||||||
|
#include "NewRpgOutdoorPvP.h"
|
||||||
#include "FishingAction.h"
|
#include "FishingAction.h"
|
||||||
#include "CancelChannelAction.h"
|
#include "CancelChannelAction.h"
|
||||||
#include "WaitForAttackAction.h"
|
#include "WaitForAttackAction.h"
|
||||||
@ -265,6 +266,7 @@ public:
|
|||||||
creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc;
|
creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc;
|
||||||
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
|
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
|
||||||
creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight;
|
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;
|
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_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(ai); }
|
||||||
static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(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_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); }
|
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 "LogLevelAction.h"
|
||||||
#include "LootStrategyAction.h"
|
#include "LootStrategyAction.h"
|
||||||
#include "LootRollAction.h"
|
#include "LootRollAction.h"
|
||||||
|
#include "SetFocusHealTargetsAction.h"
|
||||||
#include "MailAction.h"
|
#include "MailAction.h"
|
||||||
#include "NamedObjectContext.h"
|
#include "NamedObjectContext.h"
|
||||||
#include "NewRpgAction.h"
|
#include "NewRpgAction.h"
|
||||||
@ -201,6 +202,7 @@ public:
|
|||||||
creators["pet attack"] = &ChatActionContext::pet_attack;
|
creators["pet attack"] = &ChatActionContext::pet_attack;
|
||||||
creators["roll"] = &ChatActionContext::roll_action;
|
creators["roll"] = &ChatActionContext::roll_action;
|
||||||
creators["wait for attack time"] = &ChatActionContext::wait_for_attack_time;
|
creators["wait for attack time"] = &ChatActionContext::wait_for_attack_time;
|
||||||
|
creators["focus heal targets"] = &ChatActionContext::focus_heal_targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -314,6 +316,7 @@ private:
|
|||||||
static Action* pet_attack(PlayerbotAI* botAI) { return new PetsAction(botAI, "attack"); }
|
static Action* pet_attack(PlayerbotAI* botAI) { return new PetsAction(botAI, "attack"); }
|
||||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||||
static Action* wait_for_attack_time(PlayerbotAI* botAI) { return new SetWaitForAttackTimeAction(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
|
#endif
|
||||||
|
|||||||
@ -146,6 +146,7 @@ public:
|
|||||||
creators["pet attack"] = &ChatTriggerContext::pet_attack;
|
creators["pet attack"] = &ChatTriggerContext::pet_attack;
|
||||||
creators["roll"] = &ChatTriggerContext::roll_action;
|
creators["roll"] = &ChatTriggerContext::roll_action;
|
||||||
creators["wait for attack time"] = &ChatTriggerContext::wait_for_attack_time;
|
creators["wait for attack time"] = &ChatTriggerContext::wait_for_attack_time;
|
||||||
|
creators["focus heal"] = &ChatTriggerContext::focus_heal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -271,6 +272,7 @@ private:
|
|||||||
static Trigger* pet_attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet attack"); }
|
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* 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* 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
|
#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", { NextAction("pet", relevance) }));
|
||||||
triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", 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("roll", { NextAction("roll", relevance) }));
|
||||||
|
triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
|
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
|
||||||
@ -200,4 +201,5 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
|
|||||||
supported.push_back("pet");
|
supported.push_back("pet");
|
||||||
supported.push_back("pet attack");
|
supported.push_back("pet attack");
|
||||||
supported.push_back("wait for attack time");
|
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 "DuelStrategy.h"
|
||||||
#include "EmoteStrategy.h"
|
#include "EmoteStrategy.h"
|
||||||
#include "FleeStrategy.h"
|
#include "FleeStrategy.h"
|
||||||
|
#include "FocusTargetStrategy.h"
|
||||||
#include "FollowMasterStrategy.h"
|
#include "FollowMasterStrategy.h"
|
||||||
#include "GrindingStrategy.h"
|
#include "GrindingStrategy.h"
|
||||||
#include "GroupStrategy.h"
|
#include "GroupStrategy.h"
|
||||||
@ -126,6 +127,7 @@ public:
|
|||||||
creators["use bobber"] = &StrategyContext::bobber_strategy;
|
creators["use bobber"] = &StrategyContext::bobber_strategy;
|
||||||
creators["master fishing"] = &StrategyContext::master_fishing;
|
creators["master fishing"] = &StrategyContext::master_fishing;
|
||||||
creators["wait for attack"] = &StrategyContext::wait_for_attack;
|
creators["wait for attack"] = &StrategyContext::wait_for_attack;
|
||||||
|
creators["focus heal targets"] = &StrategyContext::focus_heal_targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -198,6 +200,7 @@ private:
|
|||||||
static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); }
|
static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); }
|
||||||
static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); }
|
static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); }
|
||||||
static Strategy* wait_for_attack(PlayerbotAI* botAI) { return new WaitForAttackStrategy(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>
|
class MovementStrategyContext : public NamedObjectContext<Strategy>
|
||||||
|
|||||||
@ -230,6 +230,7 @@ public:
|
|||||||
creators["wander npc status"] = &TriggerContext::wander_npc_status;
|
creators["wander npc status"] = &TriggerContext::wander_npc_status;
|
||||||
creators["do quest status"] = &TriggerContext::do_quest_status;
|
creators["do quest status"] = &TriggerContext::do_quest_status;
|
||||||
creators["travel flight status"] = &TriggerContext::travel_flight_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 self resurrect"] = &TriggerContext::can_self_resurrect;
|
||||||
creators["can fish"] = &TriggerContext::can_fish;
|
creators["can fish"] = &TriggerContext::can_fish;
|
||||||
creators["can use fishing bobber"] = &TriggerContext::can_use_fishing_bobber;
|
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* 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* 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* 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_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); }
|
||||||
static Trigger* can_fish(PlayerbotAI* ai) { return new CanFishTrigger(ai); }
|
static Trigger* can_fish(PlayerbotAI* ai) { return new CanFishTrigger(ai); }
|
||||||
static Trigger* can_use_fishing_bobber(PlayerbotAI* ai) { return new CanUseFishingBobberTrigger(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();
|
bool isRaid = bot->GetGroup()->isRaidGroup();
|
||||||
MinValueCalculator calc(100);
|
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())
|
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||||
{
|
{
|
||||||
Player* player = gref->GetSource();
|
Player* player = gref->GetSource();
|
||||||
@ -45,17 +75,17 @@ Unit* PartyMemberToHeal::Calculate()
|
|||||||
continue;
|
continue;
|
||||||
if (player && player->IsAlive())
|
if (player && player->IsAlive())
|
||||||
{
|
{
|
||||||
uint8 health = player->GetHealthPct();
|
float health = player->GetHealthPct();
|
||||||
if (isRaid || health < sPlayerbotAIConfig.mediumHealth || !IsTargetOfSpellCast(player, predicate))
|
if (isRaid || health < sPlayerbotAIConfig.mediumHealth || !IsTargetOfSpellCast(player, predicate))
|
||||||
{
|
{
|
||||||
uint32 probeValue = 100;
|
float probeValue = 100.0f;
|
||||||
if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance)
|
if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance)
|
||||||
{
|
{
|
||||||
probeValue = health + 30;
|
probeValue = health + 30.0f;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
probeValue = health + player->GetDistance2d(bot) / 10;
|
probeValue = health + player->GetDistance2d(bot) / 10.0f;
|
||||||
}
|
}
|
||||||
// delay Check player to here for better performance
|
// delay Check player to here for better performance
|
||||||
if (probeValue < calc.minValue && Check(player))
|
if (probeValue < calc.minValue && Check(player))
|
||||||
@ -68,10 +98,10 @@ Unit* PartyMemberToHeal::Calculate()
|
|||||||
Pet* pet = player->GetPet();
|
Pet* pet = player->GetPet();
|
||||||
if (pet && pet->IsAlive())
|
if (pet && pet->IsAlive())
|
||||||
{
|
{
|
||||||
uint8 health = ((Unit*)pet)->GetHealthPct();
|
float health = ((Unit*)pet)->GetHealthPct();
|
||||||
uint32 probeValue = 100;
|
float probeValue = 100.0f;
|
||||||
if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
|
if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
|
||||||
probeValue = health + 30;
|
probeValue = health + 30.0f;
|
||||||
// delay Check pet to here for better performance
|
// delay Check pet to here for better performance
|
||||||
if (probeValue < calc.minValue && Check(pet))
|
if (probeValue < calc.minValue && Check(pet))
|
||||||
{
|
{
|
||||||
@ -82,10 +112,10 @@ Unit* PartyMemberToHeal::Calculate()
|
|||||||
Unit* charm = player->GetCharm();
|
Unit* charm = player->GetCharm();
|
||||||
if (charm && charm->IsAlive())
|
if (charm && charm->IsAlive())
|
||||||
{
|
{
|
||||||
uint8 health = charm->GetHealthPct();
|
float health = charm->GetHealthPct();
|
||||||
uint32 probeValue = 100;
|
float probeValue = 100.0f;
|
||||||
if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
|
if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
|
||||||
probeValue = health + 30;
|
probeValue = health + 30.0f;
|
||||||
// delay Check charm to here for better performance
|
// delay Check charm to here for better performance
|
||||||
if (probeValue < calc.minValue && Check(charm))
|
if (probeValue < calc.minValue && Check(charm))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
#include "NearestGameObjects.h"
|
#include "NearestGameObjects.h"
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags;
|
std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags;
|
||||||
|
|
||||||
@ -88,8 +89,11 @@ bool PossibleRpgTargetsValue::AcceptUnit(Unit* unit)
|
|||||||
|
|
||||||
std::vector<uint32> PossibleNewRpgTargetsValue::allowedNpcFlags;
|
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)
|
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())
|
if (allowedNpcFlags.empty())
|
||||||
{
|
{
|
||||||
@ -119,6 +123,11 @@ PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float
|
|||||||
|
|
||||||
GuidVector PossibleNewRpgTargetsValue::Calculate()
|
GuidVector PossibleNewRpgTargetsValue::Calculate()
|
||||||
{
|
{
|
||||||
|
if (rpgRangeOverrideAreaIds.count(bot->GetAreaId()) && defaultRange < 200.0f)
|
||||||
|
range = 200.0f;
|
||||||
|
else
|
||||||
|
range = defaultRange;
|
||||||
|
|
||||||
std::list<Unit*> targets;
|
std::list<Unit*> targets;
|
||||||
FindUnits(targets);
|
FindUnits(targets);
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,8 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
void FindUnits(std::list<Unit*>& targets) override;
|
void FindUnits(std::list<Unit*>& targets) override;
|
||||||
bool AcceptUnit(Unit* unit) override;
|
bool AcceptUnit(Unit* unit) override;
|
||||||
|
private:
|
||||||
|
float defaultRange;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PossibleNewRpgGameObjectsValue : public ObjectGuidListCalculatedValue
|
class PossibleNewRpgGameObjectsValue : public ObjectGuidListCalculatedValue
|
||||||
|
|||||||
@ -140,4 +140,11 @@ public:
|
|||||||
public:
|
public:
|
||||||
Unit* Calculate();
|
Unit* Calculate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FocusHealTargetValue : public ManualSetValue<std::list<ObjectGuid>>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FocusHealTargetValue(PlayerbotAI* botAI) : ManualSetValue<std::list<ObjectGuid>>(botAI, {}, "focus heal targets") {}
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -241,6 +241,7 @@ public:
|
|||||||
creators["travel target"] = &ValueContext::travel_target;
|
creators["travel target"] = &ValueContext::travel_target;
|
||||||
creators["talk target"] = &ValueContext::talk_target;
|
creators["talk target"] = &ValueContext::talk_target;
|
||||||
creators["pull target"] = &ValueContext::pull_target;
|
creators["pull target"] = &ValueContext::pull_target;
|
||||||
|
creators["focus heal targets"] = &ValueContext::focus_heal_targets;
|
||||||
creators["group"] = &ValueContext::group;
|
creators["group"] = &ValueContext::group;
|
||||||
creators["range"] = &ValueContext::range;
|
creators["range"] = &ValueContext::range;
|
||||||
creators["inside target"] = &ValueContext::inside_target;
|
creators["inside target"] = &ValueContext::inside_target;
|
||||||
@ -497,6 +498,7 @@ private:
|
|||||||
static UntypedValue* next_rpg_action(PlayerbotAI* botAI) { return new NextRpgActionValue(botAI); }
|
static UntypedValue* next_rpg_action(PlayerbotAI* botAI) { return new NextRpgActionValue(botAI); }
|
||||||
static UntypedValue* travel_target(PlayerbotAI* botAI) { return new TravelTargetValue(botAI); }
|
static UntypedValue* travel_target(PlayerbotAI* botAI) { return new TravelTargetValue(botAI); }
|
||||||
static UntypedValue* pull_target(PlayerbotAI* botAI) { return new PullTargetValue(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_master(PlayerbotAI* botAI) { return new BgMasterValue(botAI); }
|
||||||
static UntypedValue* bg_role(PlayerbotAI* botAI) { return new BgRoleValue(botAI); }
|
static UntypedValue* bg_role(PlayerbotAI* botAI) { return new BgRoleValue(botAI); }
|
||||||
|
|||||||
@ -23,6 +23,16 @@ std::vector<NextAction> CastAbolishPoisonOnPartyAction::getAlternatives()
|
|||||||
CastSpellAction::getPrerequisites());
|
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()
|
Value<Unit*>* CastEntanglingRootsCcAction::GetTargetValue()
|
||||||
{
|
{
|
||||||
return context->GetValue<Unit*>("cc target", "entangling roots");
|
return context->GetValue<Unit*>("cc target", "entangling roots");
|
||||||
|
|||||||
@ -128,6 +128,14 @@ public:
|
|||||||
CastThornsOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "thorns", false) {}
|
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
|
class CastOmenOfClarityAction : public CastBuffSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -79,6 +79,7 @@ public:
|
|||||||
DruidTriggerFactoryInternal()
|
DruidTriggerFactoryInternal()
|
||||||
{
|
{
|
||||||
creators["omen of clarity"] = &DruidTriggerFactoryInternal::omen_of_clarity;
|
creators["omen of clarity"] = &DruidTriggerFactoryInternal::omen_of_clarity;
|
||||||
|
creators["clearcasting"] = &DruidTriggerFactoryInternal::clearcasting;
|
||||||
creators["thorns"] = &DruidTriggerFactoryInternal::thorns;
|
creators["thorns"] = &DruidTriggerFactoryInternal::thorns;
|
||||||
creators["thorns on party"] = &DruidTriggerFactoryInternal::thorns_on_party;
|
creators["thorns on party"] = &DruidTriggerFactoryInternal::thorns_on_party;
|
||||||
creators["thorns on main tank"] = &DruidTriggerFactoryInternal::thorns_on_main_tank;
|
creators["thorns on main tank"] = &DruidTriggerFactoryInternal::thorns_on_main_tank;
|
||||||
@ -117,6 +118,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static Trigger* natures_swiftness(PlayerbotAI* botAI) { return new NaturesSwiftnessTrigger(botAI); }
|
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_solar(PlayerbotAI* botAI) { return new EclipseSolarTrigger(botAI); }
|
||||||
static Trigger* eclipse_lunar(PlayerbotAI* botAI) { return new EclipseLunarTrigger(botAI); }
|
static Trigger* eclipse_lunar(PlayerbotAI* botAI) { return new EclipseLunarTrigger(botAI); }
|
||||||
static Trigger* thorns(PlayerbotAI* botAI) { return new ThornsTrigger(botAI); }
|
static Trigger* thorns(PlayerbotAI* botAI) { return new ThornsTrigger(botAI); }
|
||||||
@ -209,6 +211,7 @@ public:
|
|||||||
creators["thorns"] = &DruidAiObjectContextInternal::thorns;
|
creators["thorns"] = &DruidAiObjectContextInternal::thorns;
|
||||||
creators["thorns on party"] = &DruidAiObjectContextInternal::thorns_on_party;
|
creators["thorns on party"] = &DruidAiObjectContextInternal::thorns_on_party;
|
||||||
creators["thorns on main tank"] = &DruidAiObjectContextInternal::thorns_on_main_tank;
|
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"] = &DruidAiObjectContextInternal::cure_poison;
|
||||||
creators["cure poison on party"] = &DruidAiObjectContextInternal::cure_poison_on_party;
|
creators["cure poison on party"] = &DruidAiObjectContextInternal::cure_poison_on_party;
|
||||||
creators["abolish poison"] = &DruidAiObjectContextInternal::abolish_poison;
|
creators["abolish poison"] = &DruidAiObjectContextInternal::abolish_poison;
|
||||||
@ -304,6 +307,7 @@ private:
|
|||||||
static Action* thorns(PlayerbotAI* botAI) { return new CastThornsAction(botAI); }
|
static Action* thorns(PlayerbotAI* botAI) { return new CastThornsAction(botAI); }
|
||||||
static Action* thorns_on_party(PlayerbotAI* botAI) { return new CastThornsOnPartyAction(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* 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(PlayerbotAI* botAI) { return new CastCurePoisonAction(botAI); }
|
||||||
static Action* cure_poison_on_party(PlayerbotAI* botAI) { return new CastCurePoisonOnPartyAction(botAI); }
|
static Action* cure_poison_on_party(PlayerbotAI* botAI) { return new CastCurePoisonOnPartyAction(botAI); }
|
||||||
static Action* abolish_poison(PlayerbotAI* botAI) { return new CastAbolishPoisonAction(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",
|
new TriggerNode("party member critical health",
|
||||||
{ NextAction("nature's swiftness", ACTION_CRITICAL_HEAL + 4) }));
|
{ 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(
|
triggers.push_back(new TriggerNode(
|
||||||
"group heal setting",
|
"group heal setting",
|
||||||
{
|
{
|
||||||
|
|||||||
@ -61,6 +61,12 @@ public:
|
|||||||
OmenOfClarityTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "omen of clarity") {}
|
OmenOfClarityTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "omen of clarity") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ClearcastingTrigger : public HasAuraTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ClearcastingTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "clearcasting") {}
|
||||||
|
};
|
||||||
|
|
||||||
class RakeTrigger : public DebuffTrigger
|
class RakeTrigger : public DebuffTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -62,7 +62,7 @@ bool NewRpgStatusUpdateAction::Execute(Event /*event*/)
|
|||||||
{
|
{
|
||||||
case RPG_IDLE:
|
case RPG_IDLE:
|
||||||
return RandomChangeStatus({RPG_GO_CAMP, RPG_GO_GRIND, RPG_WANDER_RANDOM, RPG_WANDER_NPC, RPG_DO_QUEST,
|
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:
|
case RPG_GO_GRIND:
|
||||||
{
|
{
|
||||||
@ -140,6 +140,15 @@ bool NewRpgStatusUpdateAction::Execute(Event /*event*/)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case RPG_OUTDOOR_PVP:
|
||||||
|
{
|
||||||
|
if (info.HasStatusPersisted(statusOutDoorPvPDuration))
|
||||||
|
{
|
||||||
|
info.ChangeToIdle();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,10 +47,11 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
// static NewRpgStatusTransitionProb transitionMat;
|
// static NewRpgStatusTransitionProb transitionMat;
|
||||||
const int32 statusWanderNpcDuration = 5 * 60 * 1000;
|
const int32 statusWanderNpcDuration = 5 * MINUTE * IN_MILLISECONDS ;
|
||||||
const int32 statusWanderRandomDuration = 5 * 60 * 1000;
|
const int32 statusWanderRandomDuration = 5 * MINUTE * IN_MILLISECONDS ;
|
||||||
const int32 statusRestDuration = 30 * 1000;
|
const int32 statusRestDuration = 30 * IN_MILLISECONDS ;
|
||||||
const int32 statusDoQuestDuration = 30 * 60 * 1000;
|
const int32 statusDoQuestDuration = 30 * MINUTE * IN_MILLISECONDS ;
|
||||||
|
const int32 statusOutDoorPvPDuration = HOUR * IN_MILLISECONDS ;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NewRpgGoGrindAction : public NewRpgBaseAction
|
class NewRpgGoGrindAction : public NewRpgBaseAction
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include "NewRpgStrategy.h"
|
#include "NewRpgStrategy.h"
|
||||||
#include "Object.h"
|
#include "Object.h"
|
||||||
#include "ObjectAccessor.h"
|
#include "ObjectAccessor.h"
|
||||||
|
#include "OutdoorPvPMgr.h"
|
||||||
#include "ObjectDefines.h"
|
#include "ObjectDefines.h"
|
||||||
#include "ObjectGuid.h"
|
#include "ObjectGuid.h"
|
||||||
#include "ObjectMgr.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);
|
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))
|
if (IsWaitingForLastMove(priority))
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
Map* map = bot->GetMap();
|
Map* map = bot->GetMap();
|
||||||
const float x = bot->GetPositionX();
|
const float x = bot->GetPositionX();
|
||||||
@ -1160,6 +1159,11 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector<NewRpgStatus> candidateSta
|
|||||||
bot->SetStandState(UNIT_STAND_STATE_SIT);
|
bot->SetStandState(UNIT_STAND_STATE_SIT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case RPG_OUTDOOR_PVP:
|
||||||
|
{
|
||||||
|
botAI->rpgInfo.ChangeToOutdoorPvp();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
botAI->rpgInfo.ChangeToRest();
|
botAI->rpgInfo.ChangeToRest();
|
||||||
@ -1220,6 +1224,17 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status)
|
|||||||
std::vector<uint32> path;
|
std::vector<uint32> path;
|
||||||
return SelectRandomFlightTaxiNode(flightMaster, 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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ protected:
|
|||||||
/* MOVEMENT RELATED */
|
/* MOVEMENT RELATED */
|
||||||
bool MoveFarTo(WorldPosition dest);
|
bool MoveFarTo(WorldPosition dest);
|
||||||
bool MoveWorldObjectTo(ObjectGuid guid, float distance = INTERACTION_DISTANCE);
|
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);
|
bool ForceToWait(uint32 duration, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
|
|
||||||
/* QUEST RELATED CHECK */
|
/* 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;
|
data = flight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NewRpgInfo::ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId)
|
||||||
|
{
|
||||||
|
startT = getMSTime();
|
||||||
|
OutdoorPvP pvp;
|
||||||
|
pvp.capturePointSpawnId = capturePointSpawnId;
|
||||||
|
data = pvp;
|
||||||
|
}
|
||||||
|
|
||||||
void NewRpgInfo::ChangeToRest()
|
void NewRpgInfo::ChangeToRest()
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
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, Rest>) return RPG_REST;
|
||||||
if constexpr (std::is_same_v<T, DoQuest>) return RPG_DO_QUEST;
|
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, TravelFlight>) return RPG_TRAVEL_FLIGHT;
|
||||||
|
if constexpr (std::is_same_v<T, OutdoorPvP>) return RPG_OUTDOOR_PVP;
|
||||||
return RPG_IDLE;
|
return RPG_IDLE;
|
||||||
}, data);
|
}, data);
|
||||||
}
|
}
|
||||||
@ -153,6 +162,14 @@ std::string NewRpgInfo::ToString()
|
|||||||
out << "\ntoNode: " << arg.path[arg.path.size() - 1];
|
out << "\ntoNode: " << arg.path[arg.path.size() - 1];
|
||||||
out << "\ninFlight: " << arg.inFlight;
|
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
|
else
|
||||||
out << "UNKNOWN";
|
out << "UNKNOWN";
|
||||||
}, data);
|
}, data);
|
||||||
|
|||||||
@ -58,6 +58,11 @@ struct NewRpgInfo
|
|||||||
{
|
{
|
||||||
Rest() = default;
|
Rest() = default;
|
||||||
};
|
};
|
||||||
|
// RPG_OUTDOOR_PVP
|
||||||
|
struct OutdoorPvP
|
||||||
|
{
|
||||||
|
ObjectGuid::LowType capturePointSpawnId{0};
|
||||||
|
};
|
||||||
struct Idle
|
struct Idle
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
@ -79,7 +84,8 @@ struct NewRpgInfo
|
|||||||
WanderRandom,
|
WanderRandom,
|
||||||
DoQuest,
|
DoQuest,
|
||||||
Rest,
|
Rest,
|
||||||
TravelFlight
|
TravelFlight,
|
||||||
|
OutdoorPvP
|
||||||
>;
|
>;
|
||||||
RpgData data;
|
RpgData data;
|
||||||
|
|
||||||
@ -91,6 +97,7 @@ struct NewRpgInfo
|
|||||||
void ChangeToWanderRandom();
|
void ChangeToWanderRandom();
|
||||||
void ChangeToDoQuest(uint32 questId, const Quest* quest);
|
void ChangeToDoQuest(uint32 questId, const Quest* quest);
|
||||||
void ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector<uint32> path);
|
void ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector<uint32> path);
|
||||||
|
void ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId = 0);
|
||||||
void ChangeToRest();
|
void ChangeToRest();
|
||||||
void ChangeToIdle();
|
void ChangeToIdle();
|
||||||
bool CanChangeTo(NewRpgStatus status);
|
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*>&)
|
void NewRpgStrategy::InitMultipliers(std::vector<Multiplier*>&)
|
||||||
|
|||||||
@ -54,6 +54,7 @@
|
|||||||
#include "Unit.h"
|
#include "Unit.h"
|
||||||
#include "UpdateTime.h"
|
#include "UpdateTime.h"
|
||||||
#include "Vehicle.h"
|
#include "Vehicle.h"
|
||||||
|
#include "../../../../src/server/scripts/Spells/spell_dk.cpp"
|
||||||
|
|
||||||
const int SPELL_TITAN_GRIP = 49152;
|
const int SPELL_TITAN_GRIP = 49152;
|
||||||
|
|
||||||
@ -2163,7 +2164,7 @@ bool PlayerbotAI::IsTank(Player* player, bool bySpec)
|
|||||||
switch (player->getClass())
|
switch (player->getClass())
|
||||||
{
|
{
|
||||||
case CLASS_DEATH_KNIGHT:
|
case CLASS_DEATH_KNIGHT:
|
||||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
if (tab == DEATH_KNIGHT_TAB_BLOOD || player->HasAura(SPELL_DK_FROST_PRESENCE))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2861,10 +2861,10 @@ void RandomPlayerbotMgr::PrintStats()
|
|||||||
LOG_INFO("playerbots", "Bots rpg status:");
|
LOG_INFO("playerbots", "Bots rpg status:");
|
||||||
LOG_INFO("playerbots",
|
LOG_INFO("playerbots",
|
||||||
" Idle: {}, Rest: {}, GoGrind: {}, GoCamp: {}, MoveRandom: {}, MoveNpc: {}, DoQuest: {}, "
|
" Idle: {}, Rest: {}, GoGrind: {}, GoCamp: {}, MoveRandom: {}, MoveNpc: {}, DoQuest: {}, "
|
||||||
"TravelFlight: {}",
|
"TravelFlight: {}, OutdoorPvP: {}",
|
||||||
rpgStatusCount[RPG_IDLE], rpgStatusCount[RPG_REST], rpgStatusCount[RPG_GO_GRIND],
|
rpgStatusCount[RPG_IDLE], rpgStatusCount[RPG_REST], rpgStatusCount[RPG_GO_GRIND],
|
||||||
rpgStatusCount[RPG_GO_CAMP], rpgStatusCount[RPG_WANDER_RANDOM], rpgStatusCount[RPG_WANDER_NPC],
|
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", "Bots total quests:");
|
||||||
LOG_INFO("playerbots", " Accepted: {}, Rewarded: {}, Dropped: {}", rpgStasticTotal.questAccepted,
|
LOG_INFO("playerbots", " Accepted: {}, Rewarded: {}, Dropped: {}", rpgStasticTotal.questAccepted,
|
||||||
|
|||||||
@ -20,6 +20,13 @@
|
|||||||
#include "StatsCollector.h"
|
#include "StatsCollector.h"
|
||||||
#include "Unit.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)
|
StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
|
||||||
{
|
{
|
||||||
if (PlayerbotAI::IsHeal(player))
|
if (PlayerbotAI::IsHeal(player))
|
||||||
@ -454,6 +461,16 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player)
|
|||||||
if (player->HasAura(51885))
|
if (player->HasAura(51885))
|
||||||
stats_weights_[STATS_TYPE_INTELLECT] += 1.1f;
|
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)
|
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_DO_QUEST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.DoQuest", 60);
|
||||||
RpgStatusProbWeight[RPG_TRAVEL_FLIGHT] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.TravelFlight", 15);
|
RpgStatusProbWeight[RPG_TRAVEL_FLIGHT] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.TravelFlight", 15);
|
||||||
RpgStatusProbWeight[RPG_REST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.Rest", 5);
|
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);
|
syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false);
|
||||||
randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", false);
|
randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", false);
|
||||||
|
|||||||
@ -56,7 +56,8 @@ enum NewRpgStatus : int
|
|||||||
RPG_TRAVEL_FLIGHT = 6,
|
RPG_TRAVEL_FLIGHT = 6,
|
||||||
// Taking a break
|
// Taking a break
|
||||||
RPG_REST = 7,
|
RPG_REST = 7,
|
||||||
RPG_STATUS_END = 8
|
RPG_OUTDOOR_PVP = 8,
|
||||||
|
RPG_STATUS_END = 9
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MAX_SPECNO 20
|
#define MAX_SPECNO 20
|
||||||
|
|||||||
@ -53,8 +53,9 @@ void ServerFacade::SetFacingTo(Player* bot, WorldObject* wo, bool force)
|
|||||||
bot->SetOrientation(angle);
|
bot->SetOrientation(angle);
|
||||||
|
|
||||||
if (!bot->IsRooted())
|
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)
|
Unit* ServerFacade::GetChaseTarget(Unit* target)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user