mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 09:07:19 +00:00
Compare commits
41 Commits
master_v16
...
02e8465a3b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02e8465a3b | ||
|
|
f53a8704eb | ||
|
|
e5525958c8 | ||
|
|
9ae457d069 | ||
|
|
c9e98a6b4e | ||
|
|
3d9623f119 | ||
|
|
c9cc4324d3 | ||
|
|
b13fb7d12a | ||
|
|
962fdeb3d1 | ||
|
|
83c6977de5 | ||
|
|
686fe513b2 | ||
|
|
61402e83a1 | ||
|
|
b16789fa54 | ||
|
|
8f638b6a66 | ||
|
|
33f5e733dc | ||
|
|
9917863ca1 | ||
|
|
2317652d72 | ||
|
|
1fcd6c5cda | ||
|
|
88016789ba | ||
|
|
6be860c967 | ||
|
|
9971622093 | ||
|
|
895df9b197 | ||
|
|
467b63b840 | ||
|
|
66f5f597bb | ||
|
|
cafbd4681e | ||
|
|
f5c84ee7ff | ||
|
|
b6f882886d | ||
|
|
c1222da8b0 | ||
|
|
00cb177c86 | ||
|
|
5f697e806e | ||
|
|
934e73ae20 | ||
|
|
f4b4d8967f | ||
|
|
910b8a9c53 | ||
|
|
bb569b4d39 | ||
|
|
dde16674c3 | ||
|
|
e5b2791053 | ||
|
|
353c29dfc4 | ||
|
|
52c3e96641 | ||
|
|
38e2d8584b | ||
|
|
d5dbc4ddd7 | ||
|
|
2424f73bc4 |
@@ -25,6 +25,7 @@
|
||||
# CHEATS
|
||||
# SPELLS
|
||||
# FLIGHTPATH
|
||||
# PROFESSIONS
|
||||
# RANDOMBOT-SPECIFIC SETTINGS
|
||||
# GENERAL
|
||||
# LEVELS
|
||||
@@ -44,7 +45,7 @@
|
||||
# HUNTER
|
||||
# ROGUE
|
||||
# PRIEST
|
||||
# DEATHKNIGHT
|
||||
# DEATH KNIGHT
|
||||
# SHAMAN
|
||||
# MAGE
|
||||
# WARLOCK
|
||||
@@ -55,7 +56,7 @@
|
||||
# HUNTER
|
||||
# ROGUE
|
||||
# PRIEST
|
||||
# DEATHKNIGHT
|
||||
# DEATH KNIGHT
|
||||
# SHAMAN
|
||||
# MAGE
|
||||
# WARLOCK
|
||||
@@ -579,6 +580,24 @@ AiPlayerbot.BotTaxiGapJitterMs = 100
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# PROFESSIONS
|
||||
# Random bots currently do not get professions.
|
||||
#
|
||||
|
||||
# EnableFishingWithMaster automatically adds the 'master fishing' strategy to bots that can fish that can.
|
||||
# Default: 1 (Enabled)
|
||||
AiPlayerbot.EnableFishingWithMaster = 1
|
||||
#FishingDistance sets how far a bot without a master will search for water, while FishingDistanceFromMaster limits it to a closer range, and overrides the following distance to the same value. EndFishingWithMaster sets the distance from water a bot needs to have to automatically drop the 'master fishing' strategy.
|
||||
AiPlayerbot.FishingDistanceFromMaster = 10.0
|
||||
AiPlayerbot.FishingDistance = 40.0
|
||||
AiPlayerbot.EndFishingWithMaster = 30.0
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
#######################################
|
||||
# #
|
||||
# RANDOMBOT-SPECIFIC SETTINGS #
|
||||
@@ -630,7 +649,7 @@ AiPlayerbot.RandomBotHordeRatio = 50
|
||||
AiPlayerbot.DisableDeathKnightLogin = 0
|
||||
|
||||
# Enable simulated expansion limitation for talents and glyphs
|
||||
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
|
||||
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
|
||||
# and 7 rows plus the middle talent of the 8th row for bots from level 61 until level 71
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.LimitTalentsExpansion = 0
|
||||
@@ -1185,7 +1204,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
|
||||
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
|
||||
|
||||
# PvP Restricted Areas (bots don't pvp)
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"
|
||||
|
||||
# Improve reaction speeds in battlegrounds and arenas (may cause lag)
|
||||
AiPlayerbot.FastReactInBG = 1
|
||||
@@ -1455,7 +1474,7 @@ AiPlayerbot.PremadeSpecLink.5.5.80 = 50332031003--005323241223112003102311351
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# DEATHKNIGHT
|
||||
# DEATH KNIGHT
|
||||
#
|
||||
#
|
||||
|
||||
@@ -1778,7 +1797,7 @@ AiPlayerbot.RandomClassSpecIndex.5.2 = 2
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# DEATHKNIGHT
|
||||
# DEATH KNIGHT
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||
'pet_usage_error',
|
||||
'pet_no_pet_error',
|
||||
'pet_stance_report',
|
||||
'pet_no_target_error',
|
||||
'pet_target_dead_error',
|
||||
'pet_invalid_target_error',
|
||||
'pet_pvp_prohibited_error',
|
||||
'pet_attack_success',
|
||||
'pet_attack_failed',
|
||||
'pet_follow_success',
|
||||
'pet_stay_success',
|
||||
'pet_unknown_command_error',
|
||||
'pet_stance_set_success',
|
||||
'pet_type_pet',
|
||||
'pet_type_guardian',
|
||||
'pet_stance_aggressive',
|
||||
'pet_stance_defensive',
|
||||
'pet_stance_passive',
|
||||
'pet_stance_unknown'
|
||||
);
|
||||
|
||||
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||
'pet_usage_error',
|
||||
'pet_no_pet_error',
|
||||
'pet_stance_report',
|
||||
'pet_no_target_error',
|
||||
'pet_target_dead_error',
|
||||
'pet_invalid_target_error',
|
||||
'pet_pvp_prohibited_error',
|
||||
'pet_attack_success',
|
||||
'pet_attack_failed',
|
||||
'pet_follow_success',
|
||||
'pet_stay_success',
|
||||
'pet_unknown_command_error',
|
||||
'pet_stance_set_success',
|
||||
'pet_type_pet',
|
||||
'pet_type_guardian',
|
||||
'pet_stance_aggressive',
|
||||
'pet_stance_defensive',
|
||||
'pet_stance_passive',
|
||||
'pet_stance_unknown'
|
||||
);
|
||||
|
||||
INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES
|
||||
(1717, 'pet_usage_error', "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
|
||||
"사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
|
||||
|
||||
(1718, 'pet_no_pet_error', "You have no pet or guardian pet.", 0, 0,
|
||||
"펫이나 수호자 펫이 없습니다.",
|
||||
"Vous n'avez pas de familier ou gardien.",
|
||||
"Du hast kein Tier oder Wächter.",
|
||||
"你没有宠物或守护者宠物。",
|
||||
"你沒有寵物或守護者寵物。",
|
||||
"No tienes mascota o mascota guardián.",
|
||||
"No tienes mascota o mascota guardián.",
|
||||
"У вас нет питомца или защитника."),
|
||||
|
||||
(1719, 'pet_stance_report', "Current stance of %type \"%name\": %stance.", 0, 0,
|
||||
"%type \"%name\"의 현재 태세: %stance.",
|
||||
"Position actuelle du %type \"%name\": %stance.",
|
||||
"Aktuelle Haltung des %type \"%name\": %stance.",
|
||||
"%type \"%name\" 的当前姿态: %stance。",
|
||||
"%type \"%name\" 的當前姿態: %stance。",
|
||||
"Postura actual del %type \"%name\": %stance.",
|
||||
"Postura actual del %type \"%name\": %stance.",
|
||||
"Текущая позиция %type \"%name\": %stance."),
|
||||
|
||||
(1720, 'pet_no_target_error', "No valid target selected by master.", 0, 0,
|
||||
"주인이 유효한 대상을 선택하지 않았습니다.",
|
||||
"Aucune cible valide sélectionnée par le maître.",
|
||||
"Kein gültiges Ziel vom Meister ausgewählt.",
|
||||
"主人未选择有效目标。",
|
||||
"主人未選擇有效目標。",
|
||||
"No hay objetivo válido seleccionado por el maestro.",
|
||||
"No hay objetivo válido seleccionado por el maestro.",
|
||||
"Хозяин не выбрал действительную цель."),
|
||||
|
||||
(1721, 'pet_target_dead_error', "Target is not alive.", 0, 0,
|
||||
"대상이 살아있지 않습니다.",
|
||||
"La cible n'est pas vivante.",
|
||||
"Das Ziel ist nicht am Leben.",
|
||||
"目标未存活。",
|
||||
"目標未存活。",
|
||||
"El objetivo no está vivo.",
|
||||
"El objetivo no está vivo.",
|
||||
"Цель не жива."),
|
||||
|
||||
(1722, 'pet_invalid_target_error', "Target is not a valid attack target for the bot.", 0, 0,
|
||||
"대상이 봇에게 유효한 공격 대상이 아닙니다.",
|
||||
"La cible n'est pas une cible d'attaque valide pour le bot.",
|
||||
"Das Ziel ist kein gültiges Angriffsziel für den Bot.",
|
||||
"目标不是机器人的有效攻击目标。",
|
||||
"目標不是機器人的有效攻擊目標。",
|
||||
"El objetivo no es un objetivo de ataque válido para el bot.",
|
||||
"El objetivo no es un objetivo de ataque válido para el bot.",
|
||||
"Цель не является допустимой целью атаки для бота."),
|
||||
|
||||
(1723, 'pet_pvp_prohibited_error', "I cannot command my pet to attack players in PvP prohibited areas.", 0, 0,
|
||||
"PvP 금지 지역에서는 펫에게 플레이어 공격 명령을 내릴 수 없습니다.",
|
||||
"Je ne peux pas commander à mon familier d'attaquer des joueurs dans les zones où le PvP est interdit.",
|
||||
"Ich kann meinem Tier nicht befehlen, Spieler in PvP-verbotenen Gebieten anzugreifen.",
|
||||
"我不能命令我的宠物在禁止PvP的区域攻击玩家。",
|
||||
"我不能命令我的寵物在禁止PvP的區域攻擊玩家。",
|
||||
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
|
||||
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
|
||||
"Я не могу приказать своему питомцу атаковать игроков в зонах, где PvP запрещено."),
|
||||
|
||||
(1724, 'pet_attack_success', "Pet commanded to attack your target.", 0, 0,
|
||||
"펫이 당신의 대상을 공격하도록 명령했습니다.",
|
||||
"Le familier a reçu l'ordre d'attaquer votre cible.",
|
||||
"Tier wurde befohlen, dein Ziel anzugreifen.",
|
||||
"宠物已命令攻击你的目标。",
|
||||
"寵物已命令攻擊你的目標。",
|
||||
"Mascota ordenada a atacar tu objetivo.",
|
||||
"Mascota ordenada a atacar tu objetivo.",
|
||||
"Питомцу приказано атаковать вашу цель."),
|
||||
|
||||
(1725, 'pet_attack_failed', "Pet did not attack. (Already attacking or unable to attack target)", 0, 0,
|
||||
"펫이 공격하지 않았습니다. (이미 공격 중이거나 대상 공격 불가)",
|
||||
"Le familier n'a pas attaqué. (Attaque déjà en cours ou impossible d'attaquer la cible)",
|
||||
"Tier hat nicht angegriffen. (Greift bereits an oder kann Ziel nicht angreifen)",
|
||||
"宠物未攻击。(已在攻击或无法攻击目标)",
|
||||
"寵物未攻擊。(已在攻擊或無法攻擊目標)",
|
||||
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
|
||||
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
|
||||
"Питомец не атаковал. (Уже атакует или не может атаковать цель)"),
|
||||
|
||||
(1726, 'pet_follow_success', "Pet commanded to follow.", 0, 0,
|
||||
"펫이 따라오도록 명령했습니다.",
|
||||
"Le familier a reçu l'ordre de suivre.",
|
||||
"Tier wurde befohlen zu folgen.",
|
||||
"宠物已命令跟随。",
|
||||
"寵物已命令跟隨。",
|
||||
"Mascota ordenada a seguir.",
|
||||
"Mascota ordenada a seguir.",
|
||||
"Питомцу приказано следовать."),
|
||||
|
||||
(1727, 'pet_stay_success', "Pet commanded to stay.", 0, 0,
|
||||
"펫이 머물도록 명령했습니다.",
|
||||
"Le familier a reçu l'ordre de rester.",
|
||||
"Tier wurde befohlen zu bleiben.",
|
||||
"宠物已命令停留。",
|
||||
"寵物已命令停留。",
|
||||
"Mascota ordenada a quedarse.",
|
||||
"Mascota ordenada a quedarse.",
|
||||
"Питомцу приказано остаться."),
|
||||
|
||||
(1728, 'pet_unknown_command_error', "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
|
||||
"알 수 없는 펫 명령: %param. 사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Commande de familier inconnue: %param. Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Unbekannter Tierbefehl: %param. Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"未知宠物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"未知寵物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Неизвестная команда питомца: %param. Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
|
||||
|
||||
(1729, 'pet_stance_set_success', "Pet stance set to %stance.", 0, 0,
|
||||
"펫 태세가 %stance(으)로 설정되었습니다.",
|
||||
"Position du familier définie sur %stance.",
|
||||
"Tierhaltung auf %stance gesetzt.",
|
||||
"宠物姿态设置为 %stance。",
|
||||
"寵物姿態設置為 %stance。",
|
||||
"Postura de mascota establecida en %stance.",
|
||||
"Postura de mascota establecida en %stance.",
|
||||
"Позиция питомца установлена на %stance."),
|
||||
|
||||
(1730, 'pet_type_pet', "pet", 0, 0,
|
||||
"펫",
|
||||
"familier",
|
||||
"Tier",
|
||||
"宠物",
|
||||
"寵物",
|
||||
"mascota",
|
||||
"mascota",
|
||||
"питомец"),
|
||||
|
||||
(1731, 'pet_type_guardian', "guardian", 0, 0,
|
||||
"수호자",
|
||||
"gardien",
|
||||
"Wächter",
|
||||
"守护者",
|
||||
"守護者",
|
||||
"guardián",
|
||||
"guardián",
|
||||
"защитник"),
|
||||
|
||||
(1732, 'pet_stance_aggressive', "aggressive", 0, 0,
|
||||
"공격적",
|
||||
"agressif",
|
||||
"aggressiv",
|
||||
"进攻",
|
||||
"進攻",
|
||||
"agresivo",
|
||||
"agresivo",
|
||||
"агрессивная"),
|
||||
|
||||
(1733, 'pet_stance_defensive', "defensive", 0, 0,
|
||||
"방어적",
|
||||
"défensif",
|
||||
"defensiv",
|
||||
"防御",
|
||||
"防禦",
|
||||
"defensivo",
|
||||
"defensivo",
|
||||
"защитная"),
|
||||
|
||||
(1734, 'pet_stance_passive', "passive", 0, 0,
|
||||
"수동적",
|
||||
"passif",
|
||||
"passiv",
|
||||
"被动",
|
||||
"被動",
|
||||
"pasivo",
|
||||
"pasivo",
|
||||
"пассивная"),
|
||||
|
||||
(1735, 'pet_stance_unknown', "unknown", 0, 0,
|
||||
"알 수 없음",
|
||||
"inconnu",
|
||||
"unbekannt",
|
||||
"未知",
|
||||
"未知",
|
||||
"desconocido",
|
||||
"desconocido",
|
||||
"неизвестная");
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||
('pet_usage_error', 100),
|
||||
('pet_no_pet_error', 100),
|
||||
('pet_stance_report', 100),
|
||||
('pet_no_target_error', 100),
|
||||
('pet_target_dead_error', 100),
|
||||
('pet_invalid_target_error', 100),
|
||||
('pet_pvp_prohibited_error', 100),
|
||||
('pet_attack_success', 100),
|
||||
('pet_attack_failed', 100),
|
||||
('pet_follow_success', 100),
|
||||
('pet_stay_success', 100),
|
||||
('pet_unknown_command_error', 100),
|
||||
('pet_stance_set_success', 100),
|
||||
('pet_type_pet', 100),
|
||||
('pet_type_guardian', 100),
|
||||
('pet_stance_aggressive', 100),
|
||||
('pet_stance_defensive', 100),
|
||||
('pet_stance_passive', 100),
|
||||
('pet_stance_unknown', 100);
|
||||
@@ -0,0 +1,15 @@
|
||||
DELETE FROM ai_playerbot_texts WHERE name IN ('no_fishing_pole_error');
|
||||
DELETE FROM ai_playerbot_texts_chance WHERE name IN ('no_fishing_pole_error');
|
||||
|
||||
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
|
||||
(1736, 'no_fishing_pole_error', "I don't have a Fishing Pole", 0, 0,
|
||||
"낚싯대가 없습니다",
|
||||
"Je n’ai pas de canne à pêche",
|
||||
"Ich habe keine Angelrute",
|
||||
"我沒有釣魚竿",
|
||||
"我没有钓鱼竿",
|
||||
"No tengo una caña de pescar",
|
||||
"No tengo una caña de pescar",
|
||||
"У меня нет удочки");
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('no_fishing_pole_error', 100);
|
||||
@@ -140,37 +140,37 @@ BotRoles AiFactory::GetPlayerRoles(Player* player)
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_PRIEST:
|
||||
if (tab == 2)
|
||||
if (tab == PRIEST_TAB_SHADOW)
|
||||
role = BOT_ROLE_DPS;
|
||||
else
|
||||
role = BOT_ROLE_HEALER;
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (tab == 2)
|
||||
if (tab == SHAMAN_TAB_RESTORATION)
|
||||
role = BOT_ROLE_HEALER;
|
||||
else
|
||||
role = BOT_ROLE_DPS;
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
if (tab == WARRIOR_TAB_PROTECTION)
|
||||
role = BOT_ROLE_TANK;
|
||||
else
|
||||
role = BOT_ROLE_DPS;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (tab == 0)
|
||||
if (tab == PALADIN_TAB_HOLY)
|
||||
role = BOT_ROLE_HEALER;
|
||||
else if (tab == 1)
|
||||
else if (tab == PALADIN_TAB_PROTECTION)
|
||||
role = BOT_ROLE_TANK;
|
||||
else if (tab == 2)
|
||||
else if (tab == PALADIN_TAB_RETRIBUTION)
|
||||
role = BOT_ROLE_DPS;
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (tab == 0)
|
||||
if (tab == DRUID_TAB_BALANCE)
|
||||
role = BOT_ROLE_DPS;
|
||||
else if (tab == 1)
|
||||
else if (tab == DRUID_TAB_FERAL)
|
||||
role = (BotRoles)(BOT_ROLE_TANK | BOT_ROLE_DPS);
|
||||
else if (tab == 2)
|
||||
else if (tab == DRUID_TAB_RESTORATION)
|
||||
role = BOT_ROLE_HEALER;
|
||||
break;
|
||||
default:
|
||||
@@ -188,84 +188,83 @@ std::string AiFactory::GetPlayerSpecName(Player* player)
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_PRIEST:
|
||||
if (tab == 2)
|
||||
if (tab == PRIEST_TAB_SHADOW)
|
||||
specName = "shadow";
|
||||
else if (tab == 1)
|
||||
else if (tab == PRIEST_TAB_HOLY)
|
||||
specName = "holy";
|
||||
else
|
||||
specName = "disc";
|
||||
;
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (tab == 2)
|
||||
if (tab == SHAMAN_TAB_RESTORATION)
|
||||
specName = "resto";
|
||||
else if (tab == 1)
|
||||
else if (tab == SHAMAN_TAB_ENHANCEMENT)
|
||||
specName = "enhance";
|
||||
else
|
||||
specName = "elem";
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
if (tab == WARRIOR_TAB_PROTECTION)
|
||||
specName = "prot";
|
||||
else if (tab == 1)
|
||||
else if (tab == WARRIOR_TAB_FURY)
|
||||
specName = "fury";
|
||||
else
|
||||
specName = "arms";
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (tab == 0)
|
||||
if (tab == PALADIN_TAB_HOLY)
|
||||
specName = "holy";
|
||||
else if (tab == 1)
|
||||
else if (tab == PALADIN_TAB_PROTECTION)
|
||||
specName = "prot";
|
||||
else if (tab == 2)
|
||||
else if (tab == PALADIN_TAB_RETRIBUTION)
|
||||
specName = "retrib";
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (tab == 0)
|
||||
if (tab == DRUID_TAB_BALANCE)
|
||||
specName = "balance";
|
||||
else if (tab == 1)
|
||||
else if (tab == DRUID_TAB_FERAL)
|
||||
specName = "feraldps";
|
||||
else if (tab == 2)
|
||||
else if (tab == DRUID_TAB_RESTORATION)
|
||||
specName = "resto";
|
||||
break;
|
||||
case CLASS_ROGUE:
|
||||
if (tab == 0)
|
||||
if (tab == ROGUE_TAB_ASSASSINATION)
|
||||
specName = "assas";
|
||||
else if (tab == 1)
|
||||
else if (tab == ROGUE_TAB_COMBAT)
|
||||
specName = "combat";
|
||||
else if (tab == 2)
|
||||
else if (tab == ROGUE_TAB_SUBTLETY)
|
||||
specName = "subtle";
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
if (tab == 0)
|
||||
if (tab == HUNTER_TAB_BEAST_MASTERY)
|
||||
specName = "beast";
|
||||
else if (tab == 1)
|
||||
else if (tab == HUNTER_TAB_MARKSMANSHIP)
|
||||
specName = "marks";
|
||||
else if (tab == 2)
|
||||
else if (tab == HUNTER_TAB_SURVIVAL)
|
||||
specName = "surv";
|
||||
break;
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
specName = "blooddps";
|
||||
else if (tab == 1)
|
||||
else if (tab == DEATH_KNIGHT_TAB_FROST)
|
||||
specName = "frostdps";
|
||||
else if (tab == 2)
|
||||
else if (tab == DEATH_KNIGHT_TAB_UNHOLY)
|
||||
specName = "unholydps";
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
if (tab == 0)
|
||||
if (tab == MAGE_TAB_ARCANE)
|
||||
specName = "arcane";
|
||||
else if (tab == 1)
|
||||
else if (tab == MAGE_TAB_FIRE)
|
||||
specName = "fire";
|
||||
else if (tab == 2)
|
||||
else if (tab == MAGE_TAB_FROST)
|
||||
specName = "frost";
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
if (tab == 0)
|
||||
if (tab == WARLOCK_TAB_AFFLICTION)
|
||||
specName = "afflic";
|
||||
else if (tab == 1)
|
||||
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
||||
specName = "demo";
|
||||
else if (tab == 2)
|
||||
else if (tab == WARLOCK_TAB_DESTRUCTION)
|
||||
specName = "destro";
|
||||
break;
|
||||
default:
|
||||
@@ -280,147 +279,124 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
uint8 tab = GetPlayerSpecTab(player);
|
||||
|
||||
if (!player->InBattleground())
|
||||
{
|
||||
engine->addStrategiesNoInit("racials", "chat", "default", "cast time", "potions", "duel", "boost", nullptr);
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster())
|
||||
{
|
||||
engine->addStrategy("avoid aoe", false);
|
||||
}
|
||||
|
||||
engine->addStrategy("formation", false);
|
||||
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_PRIEST:
|
||||
if (tab == 2)
|
||||
{
|
||||
if (tab == PRIEST_TAB_SHADOW)
|
||||
engine->addStrategiesNoInit("dps", "shadow debuff", "shadow aoe", nullptr);
|
||||
}
|
||||
else if (tab == PRIEST_TAB_DISCIPLINE)
|
||||
{
|
||||
engine->addStrategiesNoInit("heal", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("holy heal", nullptr);
|
||||
}
|
||||
|
||||
engine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
if (tab == 0) // Arcane
|
||||
if (tab == MAGE_TAB_ARCANE)
|
||||
engine->addStrategiesNoInit("arcane", nullptr);
|
||||
else if (tab == 1) // Fire
|
||||
else if (tab == MAGE_TAB_FIRE)
|
||||
{
|
||||
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
|
||||
{
|
||||
engine->addStrategiesNoInit("frostfire", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("fire", nullptr);
|
||||
}
|
||||
}
|
||||
else // Frost
|
||||
else
|
||||
engine->addStrategiesNoInit("frost", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
if (tab == WARRIOR_TAB_PROTECTION)
|
||||
engine->addStrategiesNoInit("tank", "tank assist", "aoe", nullptr);
|
||||
else if (tab == 0 || !player->HasSpell(1680)) // Whirlwind
|
||||
engine->addStrategiesNoInit("arms", "aoe", "dps assist", /*"behind",*/ nullptr);
|
||||
else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(1680)) // Whirlwind
|
||||
engine->addStrategiesNoInit("arms", "aoe", "dps assist", nullptr);
|
||||
else
|
||||
engine->addStrategiesNoInit("fury", "aoe", "dps assist", /*"behind",*/ nullptr);
|
||||
engine->addStrategiesNoInit("fury", "aoe", "dps assist", nullptr);
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (tab == 0) // Elemental
|
||||
if (tab == SHAMAN_TAB_ELEMENTAL)
|
||||
engine->addStrategiesNoInit("ele", "stoneskin", "wrath", "mana spring", "wrath of air", nullptr);
|
||||
else if (tab == 2) // Restoration
|
||||
else if (tab == SHAMAN_TAB_RESTORATION)
|
||||
engine->addStrategiesNoInit("resto", "stoneskin", "flametongue", "mana spring", "wrath of air", nullptr);
|
||||
else // Enhancement
|
||||
else
|
||||
engine->addStrategiesNoInit("enh", "strength of earth", "magma", "healing stream", "windfury", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("dps assist", "cure", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (tab == 1)
|
||||
if (tab == PALADIN_TAB_PROTECTION)
|
||||
engine->addStrategiesNoInit("tank", "tank assist", "bthreat", "barmor", "cure", nullptr);
|
||||
else if (tab == 0)
|
||||
else if (tab == PALADIN_TAB_HOLY)
|
||||
engine->addStrategiesNoInit("heal", "dps assist", "cure", "bcast", nullptr);
|
||||
else
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", "baoe", nullptr);
|
||||
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (tab == 0)
|
||||
if (tab == DRUID_TAB_BALANCE)
|
||||
{
|
||||
engine->addStrategiesNoInit("caster", "cure", "caster aoe", "dps assist", nullptr);
|
||||
engine->addStrategy("caster debuff", false);
|
||||
}
|
||||
else if (tab == 2)
|
||||
else if (tab == DRUID_TAB_RESTORATION)
|
||||
engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr);
|
||||
else
|
||||
{
|
||||
if (player->HasSpell(768) /*cat form*/&& !player->HasAura(16931) /*thick hide*/)
|
||||
{
|
||||
if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/)
|
||||
engine->addStrategiesNoInit("cat", "dps assist", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("bear", "tank assist", nullptr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
if (tab == 0) // Beast Mastery
|
||||
if (tab == HUNTER_TAB_BEAST_MASTERY)
|
||||
engine->addStrategiesNoInit("bm", nullptr);
|
||||
else if (tab == 1) // Marksmanship
|
||||
else if (tab == HUNTER_TAB_MARKSMANSHIP)
|
||||
engine->addStrategiesNoInit("mm", nullptr);
|
||||
else if (tab == 2) // Survival
|
||||
else
|
||||
engine->addStrategiesNoInit("surv", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_ROGUE:
|
||||
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)
|
||||
{
|
||||
engine->addStrategiesNoInit("melee", "dps assist", "aoe", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr);
|
||||
}
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
if (tab == 0) // Affliction
|
||||
if (tab == WARLOCK_TAB_AFFLICTION)
|
||||
engine->addStrategiesNoInit("affli", "curse of agony", nullptr);
|
||||
else if (tab == 1) // Demonology
|
||||
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
||||
engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr);
|
||||
else if (tab == 2) // Destruction
|
||||
else
|
||||
engine->addStrategiesNoInit("destro", "curse of elements", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
||||
break;
|
||||
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
engine->addStrategiesNoInit("blood", "tank assist", nullptr);
|
||||
else if (tab == 1)
|
||||
else if (tab == DEATH_KNIGHT_TAB_FROST)
|
||||
engine->addStrategiesNoInit("frost", "frost aoe", "dps assist", nullptr);
|
||||
else
|
||||
engine->addStrategiesNoInit("unholy", "unholy aoe", "dps assist", nullptr);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (PlayerbotAI::IsTank(player, true))
|
||||
{
|
||||
engine->addStrategy("tank face", false);
|
||||
}
|
||||
|
||||
if (PlayerbotAI::IsMelee(player, true) && PlayerbotAI::IsDps(player, true))
|
||||
{
|
||||
engine->addStrategy("behind", false);
|
||||
}
|
||||
|
||||
if (PlayerbotAI::IsHeal(player, true))
|
||||
{
|
||||
if (sPlayerbotAIConfig->autoSaveMana)
|
||||
@@ -428,6 +404,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
if (!sPlayerbotAIConfig->IsRestrictedHealerDPSMap(player->GetMapId()))
|
||||
engine->addStrategy("healer dps", false);
|
||||
}
|
||||
|
||||
if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
if (!player->GetGroup())
|
||||
@@ -436,15 +413,13 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
engine->addStrategy("boost", false);
|
||||
engine->addStrategy("dps assist", false);
|
||||
engine->removeStrategy("threat", false);
|
||||
// engine-
|
||||
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_PRIEST:
|
||||
{
|
||||
if (tab != PRIEST_TAB_SHADOW)
|
||||
{
|
||||
engine->addStrategiesNoInit("holy dps", "shadow debuff", "shadow aoe", nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLASS_DRUID:
|
||||
@@ -459,17 +434,13 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
case CLASS_SHAMAN:
|
||||
{
|
||||
if (tab == SHAMAN_TAB_RESTORATION)
|
||||
{
|
||||
engine->addStrategiesNoInit("caster", "caster aoe", "bmana", nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLASS_PALADIN:
|
||||
{
|
||||
if (tab == PALADIN_TAB_HOLY)
|
||||
{
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "baoe", nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -478,13 +449,9 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
}
|
||||
}
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
engine->ChangeStrategy(sPlayerbotAIConfig->randomBotCombatStrategies);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->ChangeStrategy(sPlayerbotAIConfig->combatStrategies);
|
||||
}
|
||||
|
||||
// Battleground switch
|
||||
if (player->InBattleground() && player->GetBattleground())
|
||||
@@ -511,23 +478,15 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
if (player->InArena())
|
||||
{
|
||||
engine->addStrategy("arena", false);
|
||||
engine->addStrategiesNoInit("boost", "racials", "chat", "default", "aoe", "cast time", "dps assist", nullptr);
|
||||
}
|
||||
else
|
||||
engine->addStrategiesNoInit("boost", "racials", "chat", "default", "aoe", "potions", "cast time", "dps assist", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("boost", "racials", "chat", "default", "aoe", "potions", "cast time", "dps assist",
|
||||
nullptr);
|
||||
engine->removeStrategy("custom::say", false);
|
||||
engine->removeStrategy("flee", false);
|
||||
engine->removeStrategy("threat", false);
|
||||
engine->addStrategy("boost", false);
|
||||
|
||||
// if ((player->getClass() == CLASS_DRUID && tab == 2) || (player->getClass() == CLASS_SHAMAN && tab == 2))
|
||||
// engine->addStrategiesNoInit("caster", "caster aoe", nullptr);
|
||||
|
||||
// if (player->getClass() == CLASS_DRUID && tab == 1)
|
||||
// engine->addStrategiesNoInit(/*"behind",*/ "dps", nullptr);
|
||||
|
||||
// if (player->getClass() == CLASS_ROGUE)
|
||||
// engine->addStrategiesNoInit(/*"behind",*/ "stealth", nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,19 +508,15 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (tab == 1)
|
||||
if (tab == PALADIN_TAB_PROTECTION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("bthreat", "tank assist", "barmor", nullptr);
|
||||
if (player->GetLevel() >= 20)
|
||||
{
|
||||
nonCombatEngine->addStrategy("bhealth", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
nonCombatEngine->addStrategy("bdps", false);
|
||||
}
|
||||
}
|
||||
else if (tab == 0)
|
||||
else if (tab == PALADIN_TAB_HOLY)
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "bmana", "bcast", nullptr);
|
||||
else
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "bdps", "baoe", nullptr);
|
||||
@@ -572,7 +527,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
nonCombatEngine->addStrategiesNoInit("bdps", "dps assist", "pet", nullptr);
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (tab == 0 || tab == 2)
|
||||
if (tab == SHAMAN_TAB_ELEMENTAL || tab == SHAMAN_TAB_RESTORATION)
|
||||
nonCombatEngine->addStrategy("bmana", false);
|
||||
else
|
||||
nonCombatEngine->addStrategy("bdps", false);
|
||||
@@ -588,43 +543,34 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (tab == 1)
|
||||
if (tab == DRUID_TAB_FERAL)
|
||||
{
|
||||
if (player->GetLevel() >= 20 && !player->HasAura(16931) /*thick hide*/)
|
||||
{
|
||||
nonCombatEngine->addStrategy("dps assist", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
nonCombatEngine->addStrategy("tank assist", false);
|
||||
}
|
||||
}
|
||||
else
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
if (tab == WARRIOR_TAB_PROTECTION)
|
||||
nonCombatEngine->addStrategy("tank assist", false);
|
||||
else
|
||||
nonCombatEngine->addStrategy("dps assist", false);
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
if (tab == WARLOCK_TAB_AFFLICTION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("felhunter", "spellstone", nullptr);
|
||||
}
|
||||
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("felguard", "spellstone", nullptr);
|
||||
}
|
||||
else if (tab == WARLOCK_TAB_DESTRUCTION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("imp", "firestone", nullptr);
|
||||
}
|
||||
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "ss self", nullptr);
|
||||
break;
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
nonCombatEngine->addStrategy("tank assist", false);
|
||||
else
|
||||
nonCombatEngine->addStrategy("dps assist", false);
|
||||
@@ -641,9 +587,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->autoSaveMana && PlayerbotAI::IsHeal(player, true))
|
||||
{
|
||||
nonCombatEngine->addStrategy("save mana", false);
|
||||
}
|
||||
|
||||
if ((sRandomPlayerbotMgr->IsRandomBot(player)) && !player->InBattleground())
|
||||
{
|
||||
@@ -669,18 +613,14 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
nonCombatEngine->addStrategy("grind", false);
|
||||
|
||||
if (sPlayerbotAIConfig->enableNewRpgStrategy)
|
||||
{
|
||||
nonCombatEngine->addStrategy("new rpg", false);
|
||||
}
|
||||
else if (sPlayerbotAIConfig->autoDoQuests)
|
||||
{
|
||||
// nonCombatEngine->addStrategy("travel");
|
||||
nonCombatEngine->addStrategy("rpg", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
nonCombatEngine->addStrategy("move random", false);
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->randomBotJoinBG)
|
||||
nonCombatEngine->addStrategy("bg", false);
|
||||
@@ -729,11 +669,8 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig->nonCombatStrategies);
|
||||
}
|
||||
// nonCombatEngine->addStrategy("battleground");
|
||||
// nonCombatEngine->addStrategy("warsong");
|
||||
|
||||
// Battleground switch
|
||||
if (player->InBattleground() && player->GetBattleground())
|
||||
{
|
||||
@@ -790,9 +727,7 @@ void AiFactory::AddDefaultDeadStrategies(Player* player, PlayerbotAI* const faca
|
||||
deadEngine->addStrategiesNoInit("dead", "stay", "chat", "default", "follow", nullptr);
|
||||
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player) && !player->GetGroup())
|
||||
{
|
||||
deadEngine->removeStrategy("follow", false);
|
||||
}
|
||||
}
|
||||
|
||||
Engine* AiFactory::createDeadEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext)
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PointMovementGenerator.h"
|
||||
#include "PositionValue.h"
|
||||
@@ -242,8 +243,8 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
nextAICheckDelay = 0;
|
||||
|
||||
// Early return if bot is in invalid state
|
||||
if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() ||
|
||||
bot->IsDuringRemoveFromWorld())
|
||||
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
|
||||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
|
||||
return;
|
||||
|
||||
// Handle cheat options (set bot health and power if cheats are enabled)
|
||||
@@ -365,7 +366,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
}
|
||||
|
||||
// Update the bot's group status (moved to helper function)
|
||||
UpdateAIGroupAndMaster();
|
||||
UpdateAIGroupMaster();
|
||||
|
||||
// Update internal AI
|
||||
UpdateAIInternal(elapsed, minimal);
|
||||
@@ -373,7 +374,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
}
|
||||
|
||||
// Helper function for UpdateAI to check group membership and handle removal if necessary
|
||||
void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
void PlayerbotAI::UpdateAIGroupMaster()
|
||||
{
|
||||
if (!bot)
|
||||
return;
|
||||
@@ -420,7 +421,7 @@ void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
{
|
||||
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
|
||||
|
||||
if (botAI->GetMaster() == botAI->GetGroupMaster())
|
||||
if (botAI->GetMaster() == botAI->GetGroupLeader())
|
||||
botAI->TellMaster("Hello, I follow you!");
|
||||
else
|
||||
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
|
||||
@@ -431,14 +432,12 @@ void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT);
|
||||
}
|
||||
}
|
||||
else if (!newMaster && !bot->InBattleground())
|
||||
LeaveOrDisbandGroup();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal)
|
||||
{
|
||||
if (bot->IsBeingTeleported() || !bot->IsInWorld())
|
||||
if (!bot || bot->IsBeingTeleported() || !bot->IsInWorld())
|
||||
return;
|
||||
|
||||
std::string const mapString = WorldPosition(bot).isOverworld() ? std::to_string(bot->GetMapId()) : "I";
|
||||
@@ -517,23 +516,37 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
|
||||
void PlayerbotAI::HandleCommands()
|
||||
{
|
||||
ExternalEventHelper helper(aiObjectContext);
|
||||
|
||||
for (auto it = chatCommands.begin(); it != chatCommands.end();)
|
||||
{
|
||||
time_t& checkTime = it->GetTime();
|
||||
if (checkTime && time(0) < checkTime)
|
||||
if (checkTime && time(nullptr) < checkTime)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& command = it->GetCommand();
|
||||
Player* owner = it->GetOwner();
|
||||
if (!owner)
|
||||
{
|
||||
it = chatCommands.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& command = it->GetCommand();
|
||||
if (command.empty())
|
||||
{
|
||||
it = chatCommands.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!helper.ParseChatCommand(command, owner) && it->GetType() == CHAT_MSG_WHISPER)
|
||||
{
|
||||
// ostringstream out; out << "Unknown command " << command;
|
||||
// TellPlayer(out);
|
||||
// helper.ParseChatCommand("help");
|
||||
}
|
||||
|
||||
it = chatCommands.erase(it);
|
||||
}
|
||||
}
|
||||
@@ -541,6 +554,9 @@ void PlayerbotAI::HandleCommands()
|
||||
std::map<std::string, ChatMsg> chatMap;
|
||||
void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang)
|
||||
{
|
||||
if (!bot)
|
||||
return;
|
||||
|
||||
std::string filtered = text;
|
||||
|
||||
if (!IsAllowedCommand(filtered) && !GetSecurity()->CheckLevelFor(PlayerbotSecurityLevel::PLAYERBOT_SECURITY_INVITE,
|
||||
@@ -712,45 +728,82 @@ void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fr
|
||||
|
||||
void PlayerbotAI::HandleTeleportAck()
|
||||
{
|
||||
if (!bot || !bot->GetSession())
|
||||
return;
|
||||
|
||||
// only for bots
|
||||
if (IsRealPlayer())
|
||||
return;
|
||||
|
||||
bot->GetMotionMaster()->Clear(true);
|
||||
bot->StopMoving();
|
||||
if (bot->IsBeingTeleportedNear())
|
||||
{
|
||||
// Temporary fix for instance can not enter
|
||||
if (!bot->IsInWorld())
|
||||
{
|
||||
bot->GetMap()->AddPlayerToMap(bot);
|
||||
}
|
||||
while (bot->IsInWorld() && bot->IsBeingTeleportedNear())
|
||||
{
|
||||
Player* plMover = bot->m_mover->ToPlayer();
|
||||
if (!plMover)
|
||||
return;
|
||||
WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 20);
|
||||
p << plMover->GetPackGUID();
|
||||
p << (uint32)0; // supposed to be flags? not used currently
|
||||
p << (uint32)0; // time - not currently used
|
||||
bot->GetSession()->HandleMoveTeleportAck(p);
|
||||
};
|
||||
}
|
||||
/*
|
||||
* FAR TELEPORT (worldport / map change)
|
||||
* Player may NOT be in world or grid here.
|
||||
* Handle this FIRST.
|
||||
*/
|
||||
if (bot->IsBeingTeleportedFar())
|
||||
{
|
||||
while (bot->IsBeingTeleportedFar())
|
||||
bot->GetSession()->HandleMoveWorldportAck();
|
||||
|
||||
// after worldport ACK the player should be in a valid map
|
||||
if (!bot->GetMap())
|
||||
{
|
||||
bot->GetSession()->HandleMoveWorldportAck();
|
||||
LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString());
|
||||
return;
|
||||
}
|
||||
// SetNextCheckDelay(urand(2000, 5000));
|
||||
|
||||
// apply instance-related strategies after map attach
|
||||
if (sPlayerbotAIConfig->applyInstanceStrategies)
|
||||
ApplyInstanceStrategies(bot->GetMapId(), true);
|
||||
|
||||
if (sPlayerbotAIConfig->restrictHealerDPS)
|
||||
EvaluateHealerDpsStrategy();
|
||||
|
||||
// reset AI state after teleport
|
||||
Reset(true);
|
||||
|
||||
// clear movement only AFTER teleport is finalized and bot is in world
|
||||
if (bot->IsInWorld() && bot->GetMotionMaster())
|
||||
{
|
||||
bot->GetMotionMaster()->Clear(true);
|
||||
bot->StopMoving();
|
||||
}
|
||||
|
||||
// simulate far teleport latency (cmangos-style)
|
||||
SetNextCheckDelay(urand(2000, 5000));
|
||||
return;
|
||||
}
|
||||
|
||||
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
|
||||
/*
|
||||
* NEAR TELEPORT (same map / instance)
|
||||
* Player MUST be in world (and in grid).
|
||||
*/
|
||||
if (bot->IsBeingTeleportedNear())
|
||||
{
|
||||
if (!bot->IsInWorld())
|
||||
return;
|
||||
|
||||
Player* plMover = bot->m_mover ? bot->m_mover->ToPlayer() : nullptr;
|
||||
if (!plMover)
|
||||
return;
|
||||
|
||||
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
|
||||
p << plMover->GetPackGUID();
|
||||
p << uint32(0); // flags
|
||||
p << uint32(0); // time
|
||||
|
||||
bot->GetSession()->HandleMoveTeleportAck(p);
|
||||
|
||||
// clear movement after successful relocation
|
||||
if (bot->GetMotionMaster())
|
||||
{
|
||||
bot->GetMotionMaster()->Clear(true);
|
||||
bot->StopMoving();
|
||||
}
|
||||
|
||||
// simulate near teleport latency
|
||||
SetNextCheckDelay(urand(1000, 2000));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotAI::Reset(bool full)
|
||||
@@ -912,7 +965,6 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro
|
||||
fromPlayer->SendDirectMessage(&data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsAllowedCommand(filtered) &&
|
||||
(!GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER, fromPlayer)))
|
||||
return;
|
||||
@@ -990,10 +1042,10 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
{
|
||||
if (packet.empty())
|
||||
return;
|
||||
|
||||
if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (packet.GetOpcode())
|
||||
{
|
||||
case SMSG_SPELL_FAILURE:
|
||||
@@ -1161,7 +1213,26 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
|
||||
return;
|
||||
}
|
||||
case SMSG_MOVE_KNOCK_BACK: // handle knockbacks
|
||||
case SMSG_FORCE_MOVE_ROOT: // CMSG_FORCE_MOVE_ROOT_ACK
|
||||
case SMSG_FORCE_MOVE_UNROOT: // CMSG_FORCE_MOVE_UNROOT_ACK
|
||||
{
|
||||
// Quick fix for CMSG_FORCE_MOVE_ROOT_ACK and CMSG_FORCE_MOVE_UNROOT_ACK:
|
||||
// this should resolve issues with MOVEMENTFLAG_ROOT being permanently set
|
||||
// when rooted during lost client control (charm + root effects)
|
||||
// @see https://github.com/azerothcore/azerothcore-wotlk/pull/23147
|
||||
bool forceRoot = (packet.GetOpcode() == SMSG_FORCE_MOVE_ROOT);
|
||||
if (forceRoot)
|
||||
{
|
||||
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_MASK_MOVING_FLY);
|
||||
bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_ROOT);
|
||||
bot->StopMoving();
|
||||
}
|
||||
else
|
||||
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ROOT);
|
||||
|
||||
return;
|
||||
}
|
||||
case SMSG_MOVE_KNOCK_BACK: // CMSG_MOVE_KNOCK_BACK_ACK
|
||||
{
|
||||
WorldPacket p(packet);
|
||||
p.rpos(0);
|
||||
@@ -1249,7 +1320,12 @@ void PlayerbotAI::SpellInterrupted(uint32 spellid)
|
||||
Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type);
|
||||
if (!spell)
|
||||
continue;
|
||||
if (spell->GetSpellInfo()->Id == spellid)
|
||||
|
||||
SpellInfo const* spellInfo = spell->GetSpellInfo();
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (spellInfo->Id == spellid)
|
||||
bot->InterruptSpell((CurrentSpellTypes)type);
|
||||
}
|
||||
// LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get();
|
||||
@@ -1333,10 +1409,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
bool isBotAlive = bot->IsAlive();
|
||||
if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive)
|
||||
{
|
||||
bot->StopMoving();
|
||||
bot->GetMotionMaster()->Clear();
|
||||
bot->GetMotionMaster()->MoveIdle();
|
||||
|
||||
// Death Count to prevent skeleton piles
|
||||
// Player* master = GetMaster(); // warning here - whipowill
|
||||
if (!HasActivePlayerMaster() && !bot->InBattleground())
|
||||
@@ -1354,9 +1426,11 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
return;
|
||||
}
|
||||
|
||||
// Change engine if just ressed
|
||||
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
|
||||
// Change engine if just ressed (no movement update when rooted)
|
||||
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive && !bot->IsRooted())
|
||||
{
|
||||
bot->SendMovementFlagUpdate();
|
||||
|
||||
ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
return;
|
||||
}
|
||||
@@ -1386,9 +1460,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
else if (bot->isAFK())
|
||||
bot->ToggleAFK();
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
PlayerbotAI* masterBotAI = nullptr;
|
||||
|
||||
if (master && master->IsInWorld())
|
||||
{
|
||||
float distance = sServerFacade->GetDistance2d(bot, master);
|
||||
@@ -1461,7 +1532,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
|
||||
strategyName = "onyxia"; // Onyxia's Lair
|
||||
break;
|
||||
case 409:
|
||||
strategyName = "mc"; // Molten Core
|
||||
strategyName = "moltencore"; // Molten Core
|
||||
break;
|
||||
case 469:
|
||||
strategyName = "bwl"; // Blackwing Lair
|
||||
@@ -1472,11 +1543,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
|
||||
case 532:
|
||||
strategyName = "karazhan"; // Karazhan
|
||||
break;
|
||||
case 533:
|
||||
strategyName = "naxx"; // Naxxramas
|
||||
break;
|
||||
case 544:
|
||||
strategyName = "magtheridon"; // Magtheridon's Lair
|
||||
break;
|
||||
case 565:
|
||||
strategyName = "gruulslair"; // Gruul's Lair
|
||||
break;
|
||||
@@ -1698,6 +1767,7 @@ bool PlayerbotAI::IsRanged(Player* player, bool bySpec)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1791,10 +1861,9 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
|
||||
|
||||
bool PlayerbotAI::HasAggro(Unit* unit)
|
||||
{
|
||||
if (!unit)
|
||||
{
|
||||
if (!IsValidUnit(unit))
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isMT = IsMainTank(bot);
|
||||
Unit* victim = unit->GetVictim();
|
||||
if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && IsTank(victim->ToPlayer()))))
|
||||
@@ -2002,7 +2071,7 @@ bool PlayerbotAI::IsTank(Player* player, bool bySpec)
|
||||
switch (player->getClass())
|
||||
{
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == DEATHKNIGHT_TAB_BLOOD)
|
||||
if (tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2110,7 +2179,7 @@ bool PlayerbotAI::IsDps(Player* player, bool bySpec)
|
||||
}
|
||||
break;
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab != DEATHKNIGHT_TAB_BLOOD)
|
||||
if (tab != DEATH_KNIGHT_TAB_BLOOD)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2248,7 +2317,7 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player)
|
||||
|
||||
bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); }
|
||||
|
||||
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers)
|
||||
{
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
@@ -2265,6 +2334,9 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreDeadPlayers && !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2284,6 +2356,9 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreDeadPlayers && !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2764,7 +2839,12 @@ bool PlayerbotAI::TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel
|
||||
|
||||
bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel)
|
||||
{
|
||||
if (!master || !TellMasterNoFacing(text, securityLevel))
|
||||
if (!master)
|
||||
{
|
||||
if (sPlayerbotAIConfig->randomBotSayWithoutMaster)
|
||||
return TellMasterNoFacing(text, securityLevel);
|
||||
}
|
||||
if (!TellMasterNoFacing(text, securityLevel))
|
||||
return false;
|
||||
|
||||
if (!bot->isMoving() && !bot->IsInCombat() && bot->GetMapId() == master->GetMapId() &&
|
||||
@@ -2781,6 +2861,9 @@ bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel secu
|
||||
|
||||
bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
|
||||
{
|
||||
if (!unit || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld())
|
||||
return false;
|
||||
|
||||
if (!aurEff)
|
||||
return false;
|
||||
|
||||
@@ -2788,6 +2871,8 @@ bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
|
||||
return true;
|
||||
|
||||
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
|
||||
if (!spellInfo)
|
||||
return false;
|
||||
|
||||
uint32 stacks = aurEff->GetBase()->GetStackAmount();
|
||||
if (stacks >= spellInfo->StackAmount)
|
||||
@@ -2803,7 +2888,7 @@ bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
|
||||
bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, bool checkIsOwner, int maxAuraAmount,
|
||||
bool checkDuration)
|
||||
{
|
||||
if (!unit)
|
||||
if (!IsValidUnit(unit))
|
||||
return false;
|
||||
|
||||
std::wstring wnamepart;
|
||||
@@ -2899,7 +2984,7 @@ bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit)
|
||||
|
||||
Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack)
|
||||
{
|
||||
if (!unit)
|
||||
if (!IsValidUnit(unit))
|
||||
return nullptr;
|
||||
|
||||
std::wstring wnamepart;
|
||||
@@ -2917,6 +3002,9 @@ Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner
|
||||
for (AuraEffect const* aurEff : auras)
|
||||
{
|
||||
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
std::string const& auraName = spellInfo->SpellName[0];
|
||||
|
||||
// Directly skip if name mismatch (both length and content)
|
||||
@@ -2997,6 +3085,9 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
|
||||
if (!target)
|
||||
target = bot;
|
||||
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
if (Pet* pet = bot->GetPet())
|
||||
if (pet->HasSpell(spellid))
|
||||
return true;
|
||||
@@ -3258,6 +3349,9 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool c
|
||||
|
||||
bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarget)
|
||||
{
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
bool result = CastSpell(aiObjectContext->GetValue<uint32>("spell id", name)->Get(), target, itemTarget);
|
||||
if (result)
|
||||
{
|
||||
@@ -3270,15 +3364,19 @@ bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarg
|
||||
bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
{
|
||||
if (!spellId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!target)
|
||||
target = bot;
|
||||
|
||||
Pet* pet = bot->GetPet();
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!spellInfo)
|
||||
return false;
|
||||
|
||||
Pet* pet = bot->GetPet();
|
||||
if (pet && pet->HasSpell(spellId))
|
||||
{
|
||||
// List of spell IDs for which we do NOT want to toggle auto-cast or send message
|
||||
@@ -3681,6 +3779,9 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
|
||||
if (!spellId)
|
||||
return false;
|
||||
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
Vehicle* vehicle = bot->GetVehicle();
|
||||
if (!vehicle)
|
||||
return false;
|
||||
@@ -3691,12 +3792,12 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
|
||||
return false;
|
||||
|
||||
Unit* vehicleBase = vehicle->GetBase();
|
||||
|
||||
Unit* spellTarget = target;
|
||||
|
||||
if (!spellTarget)
|
||||
spellTarget = vehicleBase;
|
||||
|
||||
if (!spellTarget)
|
||||
if (!IsValidUnit(spellTarget))
|
||||
return false;
|
||||
|
||||
if (vehicleBase->HasSpellCooldown(spellId))
|
||||
@@ -3763,6 +3864,9 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
|
||||
if (!spellId)
|
||||
return false;
|
||||
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
Vehicle* vehicle = bot->GetVehicle();
|
||||
if (!vehicle)
|
||||
return false;
|
||||
@@ -3773,12 +3877,12 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
|
||||
return false;
|
||||
|
||||
Unit* vehicleBase = vehicle->GetBase();
|
||||
|
||||
Unit* spellTarget = target;
|
||||
|
||||
if (!spellTarget)
|
||||
spellTarget = vehicleBase;
|
||||
|
||||
if (!spellTarget)
|
||||
if (!IsValidUnit(spellTarget))
|
||||
return false;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
@@ -3931,9 +4035,13 @@ bool PlayerbotAI::IsInVehicle(bool canControl, bool canCast, bool canAttack, boo
|
||||
|
||||
void PlayerbotAI::WaitForSpellCast(Spell* spell)
|
||||
{
|
||||
if (!spell)
|
||||
return;
|
||||
|
||||
SpellInfo const* spellInfo = spell->GetSpellInfo();
|
||||
uint32 castTime = spell->GetCastTime();
|
||||
if (spellInfo->IsChanneled())
|
||||
|
||||
if (spellInfo && spellInfo->IsChanneled())
|
||||
{
|
||||
int32 duration = spellInfo->GetDuration();
|
||||
bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration);
|
||||
@@ -3981,6 +4089,9 @@ void PlayerbotAI::RemoveAura(std::string const name)
|
||||
|
||||
bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const spell)
|
||||
{
|
||||
if (!IsValidUnit(target))
|
||||
return false;
|
||||
|
||||
uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", spell)->Get();
|
||||
if (!spellid || !target->IsNonMeleeSpellCast(true))
|
||||
return false;
|
||||
@@ -4009,17 +4120,25 @@ bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const sp
|
||||
|
||||
bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
|
||||
{
|
||||
if (!target->IsInWorld())
|
||||
{
|
||||
if (!IsValidUnit(target) || !target->IsAlive())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidPlayer(bot))
|
||||
return false;
|
||||
|
||||
bool isFriend = bot->IsFriendlyTo(target);
|
||||
|
||||
Unit::VisibleAuraMap const* visibleAuras = target->GetVisibleAuras();
|
||||
if (!visibleAuras)
|
||||
return false;
|
||||
|
||||
for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr)
|
||||
{
|
||||
Aura* aura = itr->second->GetBase();
|
||||
if (!itr->second)
|
||||
continue;
|
||||
|
||||
if (aura->IsPassive())
|
||||
Aura* aura = itr->second->GetBase();
|
||||
if (!aura || aura->IsPassive() || aura->IsRemoved())
|
||||
continue;
|
||||
|
||||
if (sPlayerbotAIConfig->dispelAuraDuration && aura->GetDuration() &&
|
||||
@@ -4027,6 +4146,8 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = aura->GetSpellInfo();
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
bool isPositiveSpell = spellInfo->IsPositive();
|
||||
if (isPositiveSpell && isFriend)
|
||||
@@ -4038,6 +4159,7 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
|
||||
if (canDispel(spellInfo, dispelType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4092,7 +4214,7 @@ Player* PlayerbotAI::FindNewMaster()
|
||||
if (!group)
|
||||
return nullptr;
|
||||
|
||||
Player* groupLeader = GetGroupMaster();
|
||||
Player* groupLeader = GetGroupLeader();
|
||||
PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(groupLeader);
|
||||
if (!leaderBotAI || leaderBotAI->IsRealPlayer())
|
||||
return groupLeader;
|
||||
@@ -4101,8 +4223,7 @@ Player* PlayerbotAI::FindNewMaster()
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (!member || member == bot || !member->IsInWorld() ||
|
||||
!member->IsInSameRaidWith(bot))
|
||||
if (!member || member == bot || !member->IsInWorld() || !member->IsInSameRaidWith(bot))
|
||||
continue;
|
||||
|
||||
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
|
||||
@@ -4143,7 +4264,7 @@ bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(m
|
||||
|
||||
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
|
||||
|
||||
Player* PlayerbotAI::GetGroupMaster()
|
||||
Player* PlayerbotAI::GetGroupLeader()
|
||||
{
|
||||
if (!bot->InBattleground())
|
||||
if (Group* group = bot->GetGroup())
|
||||
@@ -4337,6 +4458,11 @@ inline bool ZoneHasRealPlayers(Player* bot)
|
||||
|
||||
bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
{
|
||||
// Early return if bot is in invalid state
|
||||
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
|
||||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
|
||||
return false;
|
||||
|
||||
// when botActiveAlone is 100% and smartScale disabled
|
||||
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
|
||||
{
|
||||
@@ -4427,10 +4553,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if ((!member || !member->IsInWorld()) && member->GetMapId() != bot->GetMapId())
|
||||
{
|
||||
if (!member || !member->IsInWorld() || member->GetMapId() != bot->GetMapId())
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member == bot)
|
||||
{
|
||||
@@ -4481,23 +4605,23 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
// HasFriend
|
||||
if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend)
|
||||
{
|
||||
if (!bot || !bot->IsInWorld() || !bot->GetGUID())
|
||||
// shouldnt be needed analyse in future
|
||||
if (!bot->GetGUID())
|
||||
return false;
|
||||
|
||||
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
|
||||
{
|
||||
if (!player || !player->IsInWorld())
|
||||
if (!player || !player->GetSession() || !player->IsInWorld() || player->IsDuringRemoveFromWorld() ||
|
||||
player->GetSession()->isLogingOut())
|
||||
continue;
|
||||
|
||||
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
|
||||
if (!connectedPlayer)
|
||||
PlayerbotAI* playerAI = GET_PLAYERBOT_AI(player);
|
||||
if (!playerAI || !playerAI->IsRealPlayer())
|
||||
continue;
|
||||
|
||||
// if a real player has the bot as a friend
|
||||
PlayerSocial* social = player->GetSocial();
|
||||
if (!social)
|
||||
continue;
|
||||
|
||||
if (social->HasFriend(bot->GetGUID()))
|
||||
if (social && social->HasFriend(bot->GetGUID()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -4511,7 +4635,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
}
|
||||
}
|
||||
|
||||
// Bots don't need to move using PathGenerator.
|
||||
// Bots don't need react to PathGenerator activities
|
||||
if (activityType == DETAILED_MOVE_ACTIVITY)
|
||||
{
|
||||
return false;
|
||||
@@ -4547,15 +4671,25 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
|
||||
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
|
||||
{
|
||||
if (!allowActiveCheckTimer[activityType])
|
||||
allowActiveCheckTimer[activityType] = time(nullptr);
|
||||
const int activityIndex = static_cast<int>(activityType);
|
||||
|
||||
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityType] + 5))
|
||||
return allowActive[activityType];
|
||||
// Unknown/out-of-range avoid blocking, added logging for further analysing should not happen in the first place.
|
||||
if (activityIndex <= 0 || activityIndex >= MAX_ACTIVITY_TYPE)
|
||||
{
|
||||
LOG_ERROR("playerbots", "AllowActivity received invalid activity type value: {}", activityIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!allowActiveCheckTimer[activityIndex])
|
||||
allowActiveCheckTimer[activityIndex] = time(nullptr);
|
||||
|
||||
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityIndex] + 5))
|
||||
return allowActive[activityIndex];
|
||||
|
||||
const bool allowed = AllowActive(activityType);
|
||||
allowActive[activityIndex] = allowed;
|
||||
allowActiveCheckTimer[activityIndex] = time(nullptr);
|
||||
|
||||
bool allowed = AllowActive(activityType);
|
||||
allowActive[activityType] = allowed;
|
||||
allowActiveCheckTimer[activityType] = time(nullptr);
|
||||
return allowed;
|
||||
}
|
||||
|
||||
@@ -5339,15 +5473,13 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
if (!item_template)
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
|
||||
};
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
|
||||
};
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
||||
|
||||
Item* stone = nullptr;
|
||||
ItemTemplate const* pProto = weapon->GetTemplate();
|
||||
@@ -5383,7 +5515,6 @@ static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
|
||||
Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
{
|
||||
|
||||
if (!weapon)
|
||||
return nullptr;
|
||||
|
||||
@@ -5392,12 +5523,12 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedManaOilIds = {
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL, BRILLIANT_WIZARD_OIL,
|
||||
SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
|
||||
Item* oil = nullptr;
|
||||
int botClass = bot->getClass();
|
||||
@@ -5413,22 +5544,22 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (specTab == 0) // Balance
|
||||
if (specTab == 0) // Balance
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 1) // Feral
|
||||
else if (specTab == 1) // Feral
|
||||
prioritizedOils = nullptr;
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (specTab == 1) // Protection
|
||||
if (specTab == 1) // Protection
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 2) // Retribution
|
||||
else if (specTab == 2) // Retribution
|
||||
prioritizedOils = nullptr;
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
default:
|
||||
@@ -5659,7 +5790,7 @@ void PlayerbotAI::ImbueItem(Item* item) { ImbueItem(item, TARGET_FLAG_NONE, Obje
|
||||
// item on unit
|
||||
void PlayerbotAI::ImbueItem(Item* item, Unit* target)
|
||||
{
|
||||
if (!target)
|
||||
if (!IsValidUnit(target))
|
||||
return;
|
||||
|
||||
ImbueItem(item, TARGET_FLAG_UNIT, target->GetGUID());
|
||||
@@ -5801,30 +5932,38 @@ int32 PlayerbotAI::GetNearGroupMemberCount(float dis)
|
||||
|
||||
bool PlayerbotAI::CanMove()
|
||||
{
|
||||
// do not allow if not vehicle driver
|
||||
if (IsInVehicle() && !IsInVehicle(true))
|
||||
// Most common checks: confused, stunned, fleeing, jumping, charging. All these
|
||||
// states are set when handling certain aura effects. We don't check against
|
||||
// UNIT_STATE_ROOT here, because this state is used by vehicles.
|
||||
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
|
||||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
// Death state (w/o spirit release) and Spirit of Redemption aura (priest)
|
||||
if ((bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || bot->HasSpiritOfRedemptionAura())
|
||||
return false;
|
||||
|
||||
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
|
||||
}
|
||||
|
||||
bool PlayerbotAI::IsRealGuild(uint32 guildId)
|
||||
{
|
||||
Guild* guild = sGuildMgr->GetGuildById(guildId);
|
||||
if (!guild)
|
||||
{
|
||||
// Common CC effects, ordered by frequency: rooted > charmed > frozen > polymorphed.
|
||||
// NOTE: Can't find proper way to check if bot is rooted or charmed w/o additional
|
||||
// vehicle check -- when a passenger is added, they become rooted and charmed.
|
||||
if (!bot->GetVehicle() && (bot->IsRooted() || bot->IsCharmed()))
|
||||
return false;
|
||||
}
|
||||
uint32 leaderAccount = sCharacterCache->GetCharacterAccountIdByGuid(guild->GetLeaderGUID());
|
||||
if (!leaderAccount)
|
||||
if (bot->isFrozen() || bot->IsPolymorphed())
|
||||
return false;
|
||||
|
||||
return !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount));
|
||||
// Check for the MM controlled slot types: feared, confused, fleeing, etc.
|
||||
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
|
||||
return false;
|
||||
|
||||
// Traveling state: taxi flight and being teleported (relatively rare)
|
||||
if (bot->IsInFlight() || bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
|
||||
bot->IsBeingTeleported())
|
||||
return false;
|
||||
|
||||
// Vehicle state: is in the vehicle and can control it (rare, content-specific)
|
||||
if ((bot->GetVehicle() && !IsInVehicle(true)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayerbotAI::IsInRealGuild()
|
||||
@@ -5832,7 +5971,7 @@ bool PlayerbotAI::IsInRealGuild()
|
||||
if (!bot->GetGuildId())
|
||||
return false;
|
||||
|
||||
return IsRealGuild(bot->GetGuildId());
|
||||
return sPlayerbotGuildMgr->IsRealGuild(bot->GetGuildId());
|
||||
}
|
||||
|
||||
void PlayerbotAI::QueueChatResponse(const ChatQueuedReply chatReply) { chatReplies.push_back(std::move(chatReply)); }
|
||||
|
||||
@@ -276,7 +276,7 @@ enum BotRoles : uint8
|
||||
|
||||
enum HUNTER_TABS
|
||||
{
|
||||
HUNTER_TAB_BEASTMASTERY,
|
||||
HUNTER_TAB_BEAST_MASTERY,
|
||||
HUNTER_TAB_MARKSMANSHIP,
|
||||
HUNTER_TAB_SURVIVAL,
|
||||
};
|
||||
@@ -295,11 +295,11 @@ enum PRIEST_TABS
|
||||
PRIEST_TAB_SHADOW,
|
||||
};
|
||||
|
||||
enum DEATHKNIGHT_TABS
|
||||
enum DEATH_KNIGHT_TABS
|
||||
{
|
||||
DEATHKNIGHT_TAB_BLOOD,
|
||||
DEATHKNIGHT_TAB_FROST,
|
||||
DEATHKNIGHT_TAB_UNHOLY,
|
||||
DEATH_KNIGHT_TAB_BLOOD,
|
||||
DEATH_KNIGHT_TAB_FROST,
|
||||
DEATH_KNIGHT_TAB_UNHOLY,
|
||||
};
|
||||
|
||||
enum DRUID_TABS
|
||||
@@ -428,7 +428,7 @@ public:
|
||||
static bool IsMainTank(Player* player);
|
||||
static uint32 GetGroupTankNum(Player* player);
|
||||
static bool IsAssistTank(Player* player);
|
||||
static bool IsAssistTankOfIndex(Player* player, int index);
|
||||
static bool IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers = false);
|
||||
static bool IsHealAssistantOfIndex(Player* player, int index);
|
||||
static bool IsRangedDpsAssistantOfIndex(Player* player, int index);
|
||||
bool HasAggro(Unit* unit);
|
||||
@@ -540,7 +540,7 @@ public:
|
||||
// Get the group leader or the master of the bot.
|
||||
// Checks if the bot is summoned as alt of a player
|
||||
bool IsAlt();
|
||||
Player* GetGroupMaster();
|
||||
Player* GetGroupLeader();
|
||||
// Returns a semi-random (cycling) number that is fixed for each bot.
|
||||
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1);
|
||||
GrouperType GetGrouperType();
|
||||
@@ -579,7 +579,6 @@ public:
|
||||
void ResetJumpDestination() { jumpDestination = Position(); }
|
||||
|
||||
bool CanMove();
|
||||
static bool IsRealGuild(uint32 guildId);
|
||||
bool IsInRealGuild();
|
||||
static std::vector<std::string> dispel_whitelist;
|
||||
bool EqualLowercaseName(std::string s1, std::string s2);
|
||||
@@ -612,12 +611,20 @@ private:
|
||||
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
|
||||
bool mixed = false);
|
||||
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
|
||||
void UpdateAIGroupAndMaster();
|
||||
void UpdateAIGroupMaster();
|
||||
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
|
||||
void HandleCommands();
|
||||
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
|
||||
bool _isBotInitializing = false;
|
||||
|
||||
inline bool IsValidUnit(const Unit* unit) const
|
||||
{
|
||||
return unit && unit->IsInWorld() && !unit->IsDuringRemoveFromWorld();
|
||||
}
|
||||
inline bool IsValidPlayer(const Player* player) const
|
||||
{
|
||||
return player && player->GetSession() && player->IsInWorld() && !player->IsDuringRemoveFromWorld() &&
|
||||
!player->IsBeingTeleported();
|
||||
}
|
||||
protected:
|
||||
Player* bot;
|
||||
Player* master;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "PlayerbotDungeonSuggestionMgr.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "RandomItemMgr.h"
|
||||
#include "RandomPlayerbotFactory.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
@@ -165,7 +166,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
pvpProhibitedZoneIds);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds",
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"),
|
||||
pvpProhibitedAreaIds);
|
||||
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
|
||||
LoadList<std::vector<uint32>>(
|
||||
@@ -222,6 +223,11 @@ bool PlayerbotAIConfig::Initialize()
|
||||
|
||||
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
|
||||
|
||||
//////////////////////////// Professions
|
||||
fishingDistanceFromMaster = sConfigMgr->GetOption<float>("AiPlayerbot.FishingDistanceFromMaster", 10.0f);
|
||||
endFishingWithMaster = sConfigMgr->GetOption<float>("AiPlayerbot.EndFishingWithMaster", 30.0f);
|
||||
fishingDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FishingDistance", 40.0f);
|
||||
enableFishingWithMaster = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableFishingWithMaster", true);
|
||||
//////////////////////////// CHAT
|
||||
enableBroadcasts = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableBroadcasts", true);
|
||||
randomBotTalk = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotTalk", false);
|
||||
@@ -661,6 +667,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sRandomPlayerbotMgr->Init();
|
||||
}
|
||||
|
||||
sPlayerbotGuildMgr->Init();
|
||||
sRandomItemMgr->Init();
|
||||
sRandomItemMgr->InitAfterAhBot();
|
||||
sPlayerbotTextMgr->LoadBotTexts();
|
||||
|
||||
@@ -145,6 +145,10 @@ public:
|
||||
// Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff. Default: 30
|
||||
int32 rpWarningCooldown;
|
||||
|
||||
// Professions
|
||||
bool enableFishingWithMaster;
|
||||
float fishingDistanceFromMaster, fishingDistance, endFishingWithMaster;
|
||||
|
||||
// chat
|
||||
bool randomBotTalk;
|
||||
bool randomBotEmote;
|
||||
@@ -269,7 +273,6 @@ public:
|
||||
bool deleteRandomBotAccounts;
|
||||
uint32 randomBotGuildCount, randomBotGuildSizeMax;
|
||||
bool deleteRandomBotGuilds;
|
||||
std::vector<uint32> randomBotGuilds;
|
||||
std::vector<uint32> pvpProhibitedZoneIds;
|
||||
std::vector<uint32> pvpProhibitedAreaIds;
|
||||
bool fastReactInBG;
|
||||
|
||||
322
src/PlayerbotGuildMgr.cpp
Normal file
322
src/PlayerbotGuildMgr.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Guild.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
|
||||
PlayerbotGuildMgr::PlayerbotGuildMgr(){}
|
||||
|
||||
void PlayerbotGuildMgr::Init()
|
||||
{
|
||||
_guildCache.clear();
|
||||
if (sPlayerbotAIConfig->deleteRandomBotGuilds)
|
||||
DeleteBotGuilds();
|
||||
|
||||
LoadGuildNames();
|
||||
ValidateGuildCache();
|
||||
}
|
||||
|
||||
bool PlayerbotGuildMgr::CreateGuild(Player* player, std::string guildName)
|
||||
{
|
||||
Guild* guild = new Guild();
|
||||
if (!guild->Create(player, guildName))
|
||||
{
|
||||
LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName,
|
||||
player->GetName());
|
||||
delete guild;
|
||||
return false;
|
||||
}
|
||||
sGuildMgr->AddGuild(guild);
|
||||
|
||||
LOG_DEBUG("playerbots", "Guild created: id={} name='{}'", guild->GetId(), guildName);
|
||||
SetGuildEmblem(guild->GetId());
|
||||
|
||||
GuildCache entry;
|
||||
entry.name = guildName;
|
||||
entry.memberCount = 1;
|
||||
entry.status = 1;
|
||||
entry.maxMembers = sPlayerbotAIConfig->randomBotGuildSizeMax;
|
||||
entry.faction = player->GetTeamId();
|
||||
|
||||
_guildCache[guild->GetId()] = entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayerbotGuildMgr::SetGuildEmblem(uint32 guildId)
|
||||
{
|
||||
Guild* guild = sGuildMgr->GetGuildById(guildId);
|
||||
if (!guild)
|
||||
return false;
|
||||
|
||||
// create random emblem
|
||||
uint32 st, cl, br, bc, bg;
|
||||
bg = urand(0, 51);
|
||||
bc = urand(0, 17);
|
||||
cl = urand(0, 17);
|
||||
br = urand(0, 7);
|
||||
st = urand(0, 180);
|
||||
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] new guild id={} random -> style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), st, cl, br, bc, bg);
|
||||
|
||||
// populate guild table with a random tabard design
|
||||
CharacterDatabase.Execute(
|
||||
"UPDATE guild SET EmblemStyle={}, EmblemColor={}, BorderStyle={}, BorderColor={}, BackgroundColor={} "
|
||||
"WHERE guildid={}",
|
||||
st, cl, br, bc, bg, guild->GetId());
|
||||
LOG_DEBUG("playerbots", "[TABARD] UPDATE done for guild id={}", guild->GetId());
|
||||
|
||||
// Immediate reading for log
|
||||
if (QueryResult qr = CharacterDatabase.Query(
|
||||
"SELECT EmblemStyle,EmblemColor,BorderStyle,BorderColor,BackgroundColor FROM guild WHERE guildid={}",
|
||||
guild->GetId()))
|
||||
{
|
||||
Field* f = qr->Fetch();
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] DB check guild id={} => style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), f[0].Get<uint8>(), f[1].Get<uint8>(), f[2].Get<uint8>(), f[3].Get<uint8>(), f[4].Get<uint8>());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string PlayerbotGuildMgr::AssignToGuild(Player* player)
|
||||
{
|
||||
if (!player)
|
||||
return "";
|
||||
|
||||
uint8_t playerFaction = player->GetTeamId();
|
||||
std::vector<GuildCache*> partiallyfilledguilds;
|
||||
partiallyfilledguilds.reserve(_guildCache.size());
|
||||
|
||||
for (auto& keyValue : _guildCache)
|
||||
{
|
||||
GuildCache& cached = keyValue.second;
|
||||
if (!cached.hasRealPlayer && cached.status == 1 && cached.faction == playerFaction)
|
||||
partiallyfilledguilds.push_back(&cached);
|
||||
}
|
||||
|
||||
if (!partiallyfilledguilds.empty())
|
||||
{
|
||||
size_t idx = static_cast<size_t>(urand(0, static_cast<int>(partiallyfilledguilds.size()) - 1));
|
||||
return (partiallyfilledguilds[idx]->name);
|
||||
}
|
||||
|
||||
size_t count = std::count_if(
|
||||
_guildCache.begin(), _guildCache.end(),
|
||||
[](const std::pair<const uint32, GuildCache>& pair)
|
||||
{
|
||||
return !pair.second.hasRealPlayer;
|
||||
}
|
||||
);
|
||||
|
||||
if (count < sPlayerbotAIConfig->randomBotGuildCount)
|
||||
{
|
||||
for (auto& key : _shuffled_guild_keys)
|
||||
{
|
||||
if (_guildNames[key])
|
||||
{
|
||||
LOG_INFO("playerbots","Assigning player [{}] to guild [{}]", player->GetName(), key);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
LOG_ERROR("playerbots","No available guild names left.");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::OnGuildUpdate(Guild* guild)
|
||||
{
|
||||
auto it = _guildCache.find(guild->GetId());
|
||||
if (it == _guildCache.end())
|
||||
return;
|
||||
|
||||
GuildCache& entry = it->second;
|
||||
entry.memberCount = guild->GetMemberCount();
|
||||
if (entry.memberCount < entry.maxMembers)
|
||||
entry.status = 1;
|
||||
else if (entry.memberCount >= entry.maxMembers)
|
||||
entry.status = 2; // Full
|
||||
std::string guildName = guild->GetName();
|
||||
for (auto& it : _guildNames)
|
||||
{
|
||||
if (it.first == guildName)
|
||||
{
|
||||
it.second = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::ResetGuildCache()
|
||||
{
|
||||
for (auto it = _guildCache.begin(); it != _guildCache.end();)
|
||||
{
|
||||
GuildCache& cached = it->second;
|
||||
cached.memberCount = 0;
|
||||
cached.faction = 2;
|
||||
cached.status = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::LoadGuildNames()
|
||||
{
|
||||
LOG_INFO("playerbots", "Loading guild names from playerbots_guild_names...");
|
||||
|
||||
QueryResult result = CharacterDatabase.Query("SELECT name_id, name FROM playerbots_guild_names");
|
||||
|
||||
if (!result)
|
||||
{
|
||||
LOG_ERROR("playerbots", "No entries found in playerbots_guild_names. List is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
_guildNames[fields[1].Get<std::string>()] = true;
|
||||
} while (result->NextRow());
|
||||
|
||||
for (auto& pair : _guildNames)
|
||||
_shuffled_guild_keys.push_back(pair.first);
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
|
||||
std::shuffle(_shuffled_guild_keys.begin(), _shuffled_guild_keys.end(), g);
|
||||
LOG_INFO("playerbots", "Loaded {} guild entries from playerbots_guild_names table.", _guildNames.size());
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::ValidateGuildCache()
|
||||
{
|
||||
QueryResult result = CharacterDatabase.Query("SELECT guildid, name FROM guild");
|
||||
if (!result)
|
||||
{
|
||||
LOG_ERROR("playerbots", "No guilds found in database, resetting guild cache");
|
||||
ResetGuildCache();
|
||||
return;
|
||||
}
|
||||
|
||||
std::unordered_map<uint32, std::string> dbGuilds;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 guildId = fields[0].Get<uint32>();
|
||||
std::string guildName = fields[1].Get<std::string>();
|
||||
dbGuilds[guildId] = guildName;
|
||||
} while (result->NextRow());
|
||||
|
||||
for (auto it = dbGuilds.begin(); it != dbGuilds.end(); it++)
|
||||
{
|
||||
uint32 guildId = it->first;
|
||||
GuildCache cache;
|
||||
cache.name = it->second;
|
||||
cache.maxMembers = sPlayerbotAIConfig->randomBotGuildSizeMax;
|
||||
|
||||
Guild* guild = sGuildMgr ->GetGuildById(guildId);
|
||||
if (!guild)
|
||||
continue;
|
||||
|
||||
cache.memberCount = guild->GetMemberCount();
|
||||
ObjectGuid leaderGuid = guild->GetLeaderGUID();
|
||||
CharacterCacheEntry const* leaderEntry = sCharacterCache->GetCharacterCacheByGuid(leaderGuid);
|
||||
uint32 leaderAccount = leaderEntry->AccountId;
|
||||
cache.hasRealPlayer = !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount));
|
||||
cache.faction = Player::TeamIdForRace(leaderEntry->Race);
|
||||
if (cache.memberCount == 0)
|
||||
cache.status = 0; // empty
|
||||
else if (cache.memberCount < cache.maxMembers)
|
||||
cache.status = 1; // partially filled
|
||||
else
|
||||
cache.status = 2; // full
|
||||
|
||||
_guildCache.insert_or_assign(guildId, cache);
|
||||
for (auto& it : _guildNames)
|
||||
{
|
||||
if (it.first == cache.name)
|
||||
{
|
||||
it.second = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::DeleteBotGuilds()
|
||||
{
|
||||
LOG_INFO("playerbots", "Deleting random bot guilds...");
|
||||
std::vector<uint32> randomBots;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BOT);
|
||||
stmt->SetData(0, "add");
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 bot = fields[0].Get<uint32>();
|
||||
randomBots.push_back(bot);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
for (std::vector<uint32>::iterator i = randomBots.begin(); i != randomBots.end(); ++i)
|
||||
{
|
||||
if (Guild* guild = sGuildMgr->GetGuildByLeader(ObjectGuid::Create<HighGuid::Player>(*i)))
|
||||
guild->Disband();
|
||||
}
|
||||
LOG_INFO("playerbots", "Random bot guilds deleted");
|
||||
}
|
||||
|
||||
bool PlayerbotGuildMgr::IsRealGuild(Player* bot)
|
||||
{
|
||||
if (!bot)
|
||||
return false;
|
||||
uint32 guildId = bot->GetGuildId();
|
||||
if (!guildId)
|
||||
return false;
|
||||
|
||||
return IsRealGuild(guildId);
|
||||
}
|
||||
|
||||
bool PlayerbotGuildMgr::IsRealGuild(uint32 guildId)
|
||||
{
|
||||
if (!guildId)
|
||||
return false;
|
||||
|
||||
auto it = _guildCache.find(guildId);
|
||||
if (it == _guildCache.end())
|
||||
return false;
|
||||
|
||||
return it->second.hasRealPlayer;
|
||||
}
|
||||
|
||||
class BotGuildCacheWorldScript : public WorldScript
|
||||
{
|
||||
public:
|
||||
|
||||
BotGuildCacheWorldScript() : WorldScript("BotGuildCacheWorldScript"), _validateTimer(0){}
|
||||
|
||||
void OnUpdate(uint32 diff) override
|
||||
{
|
||||
_validateTimer += diff;
|
||||
|
||||
if (_validateTimer >= _validateInterval) // Validate every hour
|
||||
{
|
||||
_validateTimer = 0;
|
||||
sPlayerbotGuildMgr->ValidateGuildCache();
|
||||
LOG_INFO("playerbots", "Scheduled guild cache validation");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 _validateInterval = HOUR*IN_MILLISECONDS;
|
||||
uint32 _validateTimer;
|
||||
};
|
||||
|
||||
void PlayerBotsGuildValidationScript()
|
||||
{
|
||||
new BotGuildCacheWorldScript();
|
||||
}
|
||||
52
src/PlayerbotGuildMgr.h
Normal file
52
src/PlayerbotGuildMgr.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef _PLAYERBOT_PLAYERBOTGUILDMGR_H
|
||||
#define _PLAYERBOT_PLAYERBOTGUILDMGR_H
|
||||
|
||||
#include "Guild.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class PlayerbotGuildMgr
|
||||
{
|
||||
public:
|
||||
static PlayerbotGuildMgr* instance()
|
||||
{
|
||||
static PlayerbotGuildMgr instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void Init();
|
||||
std::string AssignToGuild(Player* player);
|
||||
void LoadGuildNames();
|
||||
void ValidateGuildCache();
|
||||
void ResetGuildCache();
|
||||
bool CreateGuild(Player* player, std::string guildName);
|
||||
void OnGuildUpdate (Guild* guild);
|
||||
bool SetGuildEmblem(uint32 guildId);
|
||||
void DeleteBotGuilds();
|
||||
bool IsRealGuild(uint32 guildId);
|
||||
bool IsRealGuild(Player* bot);
|
||||
|
||||
private:
|
||||
PlayerbotGuildMgr();
|
||||
std::unordered_map<std::string, bool> _guildNames;
|
||||
|
||||
struct GuildCache
|
||||
{
|
||||
std::string name;
|
||||
uint8 status;
|
||||
uint32 maxMembers = 0;
|
||||
uint32 memberCount = 0;
|
||||
uint8 faction = 0;
|
||||
bool hasRealPlayer = false;
|
||||
};
|
||||
std::unordered_map<uint32 , GuildCache> _guildCache;
|
||||
std::vector<std::string> _shuffled_guild_keys;
|
||||
};
|
||||
|
||||
void PlayerBotsGuildValidationScript();
|
||||
|
||||
#define sPlayerbotGuildMgr PlayerbotGuildMgr::instance()
|
||||
|
||||
#endif
|
||||
@@ -9,9 +9,10 @@
|
||||
#include <cstring>
|
||||
#include <istream>
|
||||
#include <string>
|
||||
#include <openssl/sha.h>
|
||||
#include <unordered_set>
|
||||
#include <openssl/sha.h>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ChannelMgr.h"
|
||||
#include "CharacterCache.h"
|
||||
@@ -31,15 +32,13 @@
|
||||
#include "PlayerbotSecurity.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "WorldSession.h"
|
||||
#include "ChannelMgr.h"
|
||||
#include "BroadcastHelper.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
#include "DatabaseEnv.h" // Added for gender choice
|
||||
#include <algorithm> // Added for gender choice
|
||||
#include "DatabaseEnv.h"
|
||||
|
||||
class BotInitGuard
|
||||
{
|
||||
@@ -68,6 +67,7 @@ private:
|
||||
};
|
||||
|
||||
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
|
||||
std::unordered_set<ObjectGuid> PlayerbotHolder::botLoading;
|
||||
|
||||
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
|
||||
class PlayerbotLoginQueryHolder : public LoginQueryHolder
|
||||
@@ -76,13 +76,12 @@ private:
|
||||
uint32 masterAccountId;
|
||||
PlayerbotHolder* playerbotHolder;
|
||||
public:
|
||||
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
|
||||
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
|
||||
PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid)
|
||||
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount)
|
||||
{
|
||||
}
|
||||
|
||||
uint32 GetMasterAccountId() const { return masterAccountId; }
|
||||
PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; }
|
||||
};
|
||||
|
||||
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
|
||||
@@ -143,7 +142,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
||||
return;
|
||||
}
|
||||
std::shared_ptr<PlayerbotLoginQueryHolder> holder =
|
||||
std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
|
||||
std::make_shared<PlayerbotLoginQueryHolder>(masterAccountId, accountId, playerGuid);
|
||||
if (!holder->Initialize())
|
||||
{
|
||||
return;
|
||||
@@ -153,8 +152,27 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
||||
|
||||
// Always login in with world session to avoid race condition
|
||||
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
|
||||
.AfterComplete([this](SQLQueryHolderBase const& holder)
|
||||
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
|
||||
.AfterComplete(
|
||||
[](SQLQueryHolderBase const& queryHolder)
|
||||
{
|
||||
PlayerbotLoginQueryHolder const& holder = static_cast<PlayerbotLoginQueryHolder const&>(queryHolder);
|
||||
PlayerbotHolder* mgr = sRandomPlayerbotMgr; // could be null
|
||||
uint32 masterAccountId = holder.GetMasterAccountId();
|
||||
|
||||
if (masterAccountId)
|
||||
{
|
||||
// verify and find current world session of master
|
||||
WorldSession* masterSession = sWorldSessionMgr->FindSession(masterAccountId);
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterPlayer)
|
||||
mgr = GET_PLAYERBOT_MGR(masterPlayer);
|
||||
}
|
||||
|
||||
if (mgr)
|
||||
mgr->HandlePlayerBotLoginCallback(holder);
|
||||
else
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
});
|
||||
}
|
||||
|
||||
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
|
||||
@@ -169,8 +187,9 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
uint32 botAccountId = holder.GetAccountId();
|
||||
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
|
||||
// allows channels to work as intended)
|
||||
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
WorldSession* botSession =
|
||||
new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
|
||||
sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
|
||||
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
|
||||
|
||||
@@ -181,26 +200,27 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
|
||||
botSession->LogoutPlayer(true);
|
||||
delete botSession;
|
||||
botLoading.erase(holder.GetGuid());
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 masterAccount = holder.GetMasterAccountId();
|
||||
WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr;
|
||||
uint32 masterAccountId = holder.GetMasterAccountId();
|
||||
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr;
|
||||
|
||||
// Check if masterSession->GetPlayer() is valid
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterSession && !masterPlayer)
|
||||
{
|
||||
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
|
||||
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}",
|
||||
masterAccountId);
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->OnPlayerLogin(bot);
|
||||
|
||||
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), this);
|
||||
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), masterAccountId);
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||
|
||||
botLoading.erase(holder.GetGuid());
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
}
|
||||
|
||||
void PlayerbotHolder::UpdateSessions()
|
||||
@@ -1174,7 +1194,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
if (ObjectAccessor::FindConnectedPlayer(guid))
|
||||
continue;
|
||||
uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid);
|
||||
if (guildId && PlayerbotAI::IsRealGuild(guildId))
|
||||
if (guildId && sPlayerbotGuildMgr->IsRealGuild(guildId))
|
||||
continue;
|
||||
AddPlayerBot(guid, master->GetSession()->GetAccountId());
|
||||
messages.push_back("Add class " + std::string(charname));
|
||||
|
||||
@@ -60,7 +60,7 @@ protected:
|
||||
virtual void OnBotLoginInternal(Player* const bot) = 0;
|
||||
|
||||
PlayerBotMap playerBots;
|
||||
std::unordered_set<ObjectGuid> botLoading;
|
||||
static std::unordered_set<ObjectGuid> botLoading;
|
||||
};
|
||||
|
||||
class PlayerbotMgr : public PlayerbotHolder
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Group.h"
|
||||
#include "GroupMgr.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "PlayerbotOperation.h"
|
||||
#include "Player.h"
|
||||
@@ -16,6 +17,8 @@
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "WorldSession.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
|
||||
// Group invite operation
|
||||
class GroupInviteOperation : public PlayerbotOperation
|
||||
@@ -468,18 +471,31 @@ private:
|
||||
class OnBotLoginOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
OnBotLoginOperation(ObjectGuid botGuid, PlayerbotHolder* holder)
|
||||
: m_botGuid(botGuid), m_holder(holder)
|
||||
OnBotLoginOperation(ObjectGuid botGuid, uint32 masterAccountId)
|
||||
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
// find and verify bot still exists
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||
if (!bot || !m_holder)
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
m_holder->OnBotLogin(bot);
|
||||
PlayerbotHolder* holder = sRandomPlayerbotMgr;
|
||||
if (m_masterAccountId)
|
||||
{
|
||||
WorldSession* masterSession = sWorldSessionMgr->FindSession(m_masterAccountId);
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterPlayer)
|
||||
holder = GET_PLAYERBOT_MGR(masterPlayer);
|
||||
}
|
||||
|
||||
if (!holder)
|
||||
return false;
|
||||
|
||||
holder->OnBotLogin(bot);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -487,14 +503,11 @@ public:
|
||||
uint32 GetPriority() const override { return 100; }
|
||||
std::string GetName() const override { return "OnBotLogin"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr;
|
||||
}
|
||||
bool IsValid() const override { return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr; }
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
PlayerbotHolder* m_holder;
|
||||
uint32 m_masterAccountId = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,14 +17,28 @@ PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot)
|
||||
|
||||
PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup)
|
||||
{
|
||||
// Basic pointer validity checks
|
||||
if (!bot || !from || !from->GetSession())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NONE;
|
||||
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
// GMs always have full access
|
||||
if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
if (!botAI)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NONE;
|
||||
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
if (botAI->IsOpposing(from))
|
||||
{
|
||||
if (reason)
|
||||
@@ -35,6 +49,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
|
||||
if (sPlayerbotAIConfig->IsInRandomAccountList(account))
|
||||
{
|
||||
// (duplicate check in case of faction change)
|
||||
if (botAI->IsOpposing(from))
|
||||
{
|
||||
if (reason)
|
||||
@@ -43,27 +58,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
// if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
|
||||
// {
|
||||
// if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
// {
|
||||
// if (reason)
|
||||
// *reason = PLAYERBOT_DENY_LFG;
|
||||
Group* fromGroup = from->GetGroup();
|
||||
Group* botGroup = bot->GetGroup();
|
||||
|
||||
// return PLAYERBOT_SECURITY_TALK;
|
||||
// }
|
||||
// }
|
||||
|
||||
Group* group = from->GetGroup();
|
||||
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() == from)
|
||||
if (fromGroup && botGroup && fromGroup == botGroup && !ignoreGroup)
|
||||
{
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
}
|
||||
if (botAI->GetMaster() == from)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() != from)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NOT_YOURS;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
@@ -75,27 +80,34 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->groupInvitationPermission <= 1 && (int32)bot->GetLevel() - (int8)from->GetLevel() > 5)
|
||||
if (sPlayerbotAIConfig->groupInvitationPermission <= 1)
|
||||
{
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
int32 levelDiff = int32(bot->GetLevel()) - int32(from->GetLevel());
|
||||
if (levelDiff > 5)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_LOW_LEVEL;
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_LOW_LEVEL;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 botGS = (int32)botAI->GetEquipGearScore(bot/*, false, false*/);
|
||||
int32 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/);
|
||||
if (sPlayerbotAIConfig->gearscorecheck)
|
||||
int32 botGS = static_cast<int32>(botAI->GetEquipGearScore(bot));
|
||||
int32 fromGS = static_cast<int32>(botAI->GetEquipGearScore(from));
|
||||
|
||||
if (sPlayerbotAIConfig->gearscorecheck && botGS && bot->GetLevel() > 15 && botGS > fromGS)
|
||||
{
|
||||
if (botGS && bot->GetLevel() > 15 && botGS > fromGS &&
|
||||
static_cast<float>(100 * (botGS - fromGS) / botGS) >=
|
||||
static_cast<float>(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel()))
|
||||
uint32 diffPct = uint32(100 * (botGS - fromGS) / botGS);
|
||||
uint32 reqPct = uint32(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel());
|
||||
|
||||
if (diffPct >= reqPct)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_GEARSCORE;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}
|
||||
@@ -111,35 +123,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
}
|
||||
}
|
||||
|
||||
/*if (bot->isDead())
|
||||
// If the bot is not in the group, we offer an invite
|
||||
botGroup = bot->GetGroup();
|
||||
if (!botGroup)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_DEAD;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}*/
|
||||
|
||||
group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
/*if (bot->GetMapId() != from->GetMapId() || bot->GetDistance(from) > sPlayerbotAIConfig->whisperDistance)
|
||||
{
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_FAR;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_INVITE;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
if (!ignoreGroup && group->IsFull())
|
||||
if (!ignoreGroup && botGroup->IsFull())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_FULL_GROUP;
|
||||
@@ -147,27 +141,22 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID())
|
||||
if (!ignoreGroup && botGroup->GetLeaderGUID() != bot->GetGUID())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NOT_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_IS_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
// The bot is the group leader, you can invite the initiator
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_INVITE;
|
||||
*reason = PLAYERBOT_DENY_IS_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
// Non-random bots: only their master has full access
|
||||
if (botAI->GetMaster() == from)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
@@ -179,8 +168,13 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
|
||||
bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup)
|
||||
{
|
||||
// If something is wrong with the pointers, we silently refuse
|
||||
if (!bot || !from || !from->GetSession())
|
||||
return false;
|
||||
|
||||
DenyReason reason = PLAYERBOT_DENY_NONE;
|
||||
PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup);
|
||||
|
||||
if (realLevel >= level || from == bot)
|
||||
return true;
|
||||
|
||||
@@ -189,11 +183,17 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
return false;
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
Player* master = botAI->GetMaster();
|
||||
if (master && botAI && botAI->IsOpposing(master) && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
||||
if (!botAI)
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (master && botAI->IsOpposing(master))
|
||||
if (WorldSession* session = master->GetSession())
|
||||
if (session->GetSecurity() < SEC_GAMEMASTER)
|
||||
return false;
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
switch (realLevel)
|
||||
{
|
||||
case PLAYERBOT_SECURITY_DENY_ALL:
|
||||
@@ -206,19 +206,20 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
out << "I'll do it later";
|
||||
break;
|
||||
case PLAYERBOT_DENY_LOW_LEVEL:
|
||||
out << "You are too low level: |cffff0000" << (uint32)from->GetLevel() << "|cffffffff/|cff00ff00"
|
||||
<< (uint32)bot->GetLevel();
|
||||
out << "You are too low level: |cffff0000" << uint32(from->GetLevel()) << "|cffffffff/|cff00ff00"
|
||||
<< uint32(bot->GetLevel());
|
||||
break;
|
||||
case PLAYERBOT_DENY_GEARSCORE:
|
||||
{
|
||||
int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/);
|
||||
int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/);
|
||||
int botGS = int(botAI->GetEquipGearScore(bot));
|
||||
int fromGS = int(botAI->GetEquipGearScore(from));
|
||||
int diff = (100 * (botGS - fromGS) / botGS);
|
||||
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
|
||||
|
||||
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS
|
||||
<< " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_NOT_YOURS:
|
||||
out << "I have a master already";
|
||||
break;
|
||||
@@ -237,13 +238,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
case PLAYERBOT_DENY_FAR:
|
||||
{
|
||||
out << "You must be closer to invite me to your group. I am in ";
|
||||
|
||||
if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId()))
|
||||
{
|
||||
out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)";
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_FULL_GROUP:
|
||||
out << "I am in a full group. Will do it later";
|
||||
break;
|
||||
@@ -251,15 +249,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
out << "I am currently leading a group. I can invite you if you want.";
|
||||
break;
|
||||
case PLAYERBOT_DENY_NOT_LEADER:
|
||||
if (botAI->GetGroupMaster())
|
||||
{
|
||||
out << "I am in a group with " << botAI->GetGroupMaster()->GetName()
|
||||
<< ". You can ask him for invite.";
|
||||
}
|
||||
if (Player* leader = botAI->GetGroupLeader())
|
||||
out << "I am in a group with " << leader->GetName() << ". You can ask him for invite.";
|
||||
else
|
||||
{
|
||||
out << "I am in a group with someone else. You can ask him for invite.";
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_BG:
|
||||
out << "I am in a queue for BG. Will do it later";
|
||||
@@ -283,10 +276,14 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
std::string const text = out.str();
|
||||
ObjectGuid guid = from->GetGUID();
|
||||
time_t lastSaid = whispers[guid][text];
|
||||
|
||||
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
|
||||
{
|
||||
whispers[guid][text] = time(nullptr);
|
||||
bot->Whisper(text, LANG_UNIVERSAL, from);
|
||||
|
||||
// Additional protection against crashes during logout
|
||||
if (bot->IsInWorld() && from->IsInWorld())
|
||||
bot->Whisper(text, LANG_UNIVERSAL, from);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "Metric.h"
|
||||
#include "PlayerScript.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "PlayerbotSpellCache.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
@@ -82,12 +84,12 @@ public:
|
||||
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", {
|
||||
PLAYERHOOK_ON_LOGIN,
|
||||
PLAYERHOOK_ON_AFTER_UPDATE,
|
||||
PLAYERHOOK_ON_CHAT,
|
||||
PLAYERHOOK_ON_CHAT_WITH_CHANNEL,
|
||||
PLAYERHOOK_ON_CHAT_WITH_GROUP,
|
||||
PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS,
|
||||
PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_GROUP_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_GUILD_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_CHANNEL_CHAT,
|
||||
PLAYERHOOK_ON_GIVE_EXP,
|
||||
PLAYERHOOK_ON_BEFORE_TELEPORT
|
||||
}) {}
|
||||
@@ -123,24 +125,49 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeTeleport(Player* player, uint32 mapid, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
|
||||
bool OnPlayerBeforeTeleport(Player* /*player*/, uint32 /*mapid*/, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
|
||||
{
|
||||
// Only apply to bots to prevent affecting real players
|
||||
if (!player || !player->GetSession()->IsBot())
|
||||
/* for now commmented out until proven its actually required
|
||||
* havent seen any proof CleanVisibilityReferences() is needed
|
||||
|
||||
// If the player is not safe to touch, do nothing
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
// If changing maps, proactively clean visibility references to prevent
|
||||
// stale pointers in other players' visibility maps during the teleport.
|
||||
// This fixes a race condition where:
|
||||
// 1. Bot A teleports and its visible objects start getting cleaned up
|
||||
// 2. Bot B is simultaneously updating visibility and tries to access objects in Bot A's old visibility map
|
||||
// 3. Those objects may already be freed, causing a segmentation fault
|
||||
if (player->GetMapId() != mapid && player->IsInWorld())
|
||||
{
|
||||
player->GetObjectVisibilityContainer().CleanVisibilityReferences();
|
||||
}
|
||||
// If same map or not in world do nothing
|
||||
if (!player->IsInWorld() || player->GetMapId() == mapid)
|
||||
return true;
|
||||
|
||||
return true; // Allow teleport to continue
|
||||
// If real player do nothing
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(player);
|
||||
if (!ai || ai->IsRealPlayer())
|
||||
return true;
|
||||
|
||||
// Cross-map bot teleport: defer visibility reference cleanup.
|
||||
// CleanVisibilityReferences() erases this bot's GUID from other objects' visibility containers.
|
||||
// This is intentionally done via the event queue (instead of directly here) because erasing
|
||||
// from other players' visibility maps inside the teleport call stack can hit unsafe re-entrancy
|
||||
// or iterator invalidation while visibility updates are in progress
|
||||
ObjectGuid guid = player->GetGUID();
|
||||
player->m_Events.AddEventAtOffset(
|
||||
[guid, mapid]()
|
||||
{
|
||||
// do nothing, if the player is not safe to touch
|
||||
Player* p = ObjectAccessor::FindPlayer(guid);
|
||||
if (!p || !p->IsInWorld() || p->IsDuringRemoveFromWorld())
|
||||
return;
|
||||
|
||||
// do nothing if we are already on the target map
|
||||
if (p->GetMapId() == mapid)
|
||||
return;
|
||||
|
||||
p->GetObjectVisibilityContainer().CleanVisibilityReferences();
|
||||
},
|
||||
Milliseconds(0));
|
||||
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerAfterUpdate(Player* player, uint32 diff) override
|
||||
@@ -164,14 +191,17 @@ public:
|
||||
{
|
||||
botAI->HandleCommand(type, msg, player);
|
||||
|
||||
return false;
|
||||
// hotfix; otherwise the server will crash when whispering logout
|
||||
// https://github.com/mod-playerbots/mod-playerbots/pull/1838
|
||||
// TODO: find the root cause and solve it. (does not happen in party chat)
|
||||
if (msg == "logout")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
{
|
||||
@@ -183,9 +213,10 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg) override
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Guild* guild) override
|
||||
{
|
||||
if (type == CHAT_MSG_GUILD)
|
||||
{
|
||||
@@ -204,9 +235,10 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
|
||||
{
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
@@ -217,6 +249,7 @@ public:
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
|
||||
@@ -331,6 +364,9 @@ public:
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
|
||||
sPlayerbotSpellCache->Initialize();
|
||||
|
||||
LOG_INFO("server.loading", "Playerbots World Thread Processor initialized");
|
||||
}
|
||||
|
||||
@@ -467,6 +503,8 @@ public:
|
||||
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
|
||||
};
|
||||
|
||||
void AddPlayerbotsSecureLoginScripts();
|
||||
|
||||
void AddPlayerbotsScripts()
|
||||
{
|
||||
new PlayerbotsDatabaseScript();
|
||||
@@ -476,6 +514,7 @@ void AddPlayerbotsScripts()
|
||||
new PlayerbotsWorldScript();
|
||||
new PlayerbotsScript();
|
||||
new PlayerBotsBGScript();
|
||||
|
||||
AddPlayerbotsSecureLoginScripts();
|
||||
AddSC_playerbots_commandscript();
|
||||
PlayerBotsGuildValidationScript();
|
||||
}
|
||||
|
||||
82
src/PlayerbotsSecureLogin.cpp
Normal file
82
src/PlayerbotsSecureLogin.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "ScriptMgr.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
static Player* FindOnlineAltbotByGuid(ObjectGuid guid)
|
||||
{
|
||||
if (!guid)
|
||||
return nullptr;
|
||||
|
||||
Player* p = ObjectAccessor::FindPlayer(guid);
|
||||
if (!p)
|
||||
return nullptr;
|
||||
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(p);
|
||||
if (!ai || ai->IsRealPlayer())
|
||||
return nullptr;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static void ForceLogoutViaPlayerbotHolder(Player* target)
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(target);
|
||||
if (!ai)
|
||||
return;
|
||||
|
||||
if (Player* master = ai->GetMaster())
|
||||
{
|
||||
if (PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(master))
|
||||
{
|
||||
mgr->LogoutPlayerBot(target->GetGUID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sRandomPlayerbotMgr)
|
||||
{
|
||||
sRandomPlayerbotMgr->LogoutPlayerBot(target->GetGUID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerbotsSecureLoginServerScript : public ServerScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsSecureLoginServerScript()
|
||||
: ServerScript("PlayerbotsSecureLoginServerScript", { SERVERHOOK_CAN_PACKET_RECEIVE }) {}
|
||||
|
||||
bool CanPacketReceive(WorldSession* /*session*/, WorldPacket& packet) override
|
||||
{
|
||||
if (packet.GetOpcode() != CMSG_PLAYER_LOGIN)
|
||||
return true;
|
||||
|
||||
auto const oldPos = packet.rpos();
|
||||
ObjectGuid loginGuid;
|
||||
packet >> loginGuid;
|
||||
packet.rpos(oldPos);
|
||||
|
||||
if (!loginGuid)
|
||||
return true;
|
||||
|
||||
Player* existingAltbot = FindOnlineAltbotByGuid(loginGuid);
|
||||
if (existingAltbot)
|
||||
ForceLogoutViaPlayerbotHolder(existingAltbot);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void AddPlayerbotsSecureLoginScripts()
|
||||
{
|
||||
new PlayerbotsSecureLoginServerScript();
|
||||
}
|
||||
@@ -2834,22 +2834,20 @@ inline bool ContainsInternal(ItemTemplate const* proto, uint32 skillId)
|
||||
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
||||
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
|
||||
{
|
||||
if (itr->second.trainer_type != TRAINER_TYPE_TRADESKILLS)
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(itr->first);
|
||||
|
||||
if (!trainer)
|
||||
continue;
|
||||
|
||||
uint32 trainerId = itr->second.Entry;
|
||||
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
|
||||
if (!trainer_spells)
|
||||
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill)
|
||||
continue;
|
||||
|
||||
for (TrainerSpellMap::const_iterator iter = trainer_spells->spellList.begin();
|
||||
iter != trainer_spells->spellList.end(); ++iter)
|
||||
for (auto& spell : trainer->GetSpells())
|
||||
{
|
||||
TrainerSpell const* tSpell = &iter->second;
|
||||
if (!tSpell || tSpell->reqSkill != skillId)
|
||||
if (spell.ReqSkillLine != skillId)
|
||||
continue;
|
||||
|
||||
if (IsCraftedBy(proto, tSpell->spell))
|
||||
if (IsCraftedBy(proto, spell.SpellId))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "GuildMgr.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "SocialMgr.h"
|
||||
@@ -754,187 +755,6 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars);
|
||||
}
|
||||
|
||||
void RandomPlayerbotFactory::CreateRandomGuilds()
|
||||
{
|
||||
std::vector<uint32> randomBots;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BOT);
|
||||
stmt->SetData(0, "add");
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 bot = fields[0].Get<uint32>();
|
||||
randomBots.push_back(bot);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->deleteRandomBotGuilds)
|
||||
{
|
||||
LOG_INFO("playerbots", "Deleting random bot guilds...");
|
||||
for (std::vector<uint32>::iterator i = randomBots.begin(); i != randomBots.end(); ++i)
|
||||
{
|
||||
if (Guild* guild = sGuildMgr->GetGuildByLeader(ObjectGuid::Create<HighGuid::Player>(*i)))
|
||||
guild->Disband();
|
||||
}
|
||||
|
||||
LOG_INFO("playerbots", "Random bot guilds deleted");
|
||||
}
|
||||
|
||||
std::unordered_set<uint32> botAccounts;
|
||||
botAccounts.reserve(sPlayerbotAIConfig->randomBotAccounts.size());
|
||||
for (uint32 acc : sPlayerbotAIConfig->randomBotAccounts)
|
||||
botAccounts.insert(acc);
|
||||
|
||||
// Recount bot guilds directly from the database (does not depend on connected bots)
|
||||
uint32 guildNumber = 0;
|
||||
sPlayerbotAIConfig->randomBotGuilds.clear();
|
||||
sPlayerbotAIConfig->randomBotGuilds.shrink_to_fit(); // avoids accumulating old capacity
|
||||
|
||||
if (!botAccounts.empty())
|
||||
{
|
||||
if (QueryResult res = CharacterDatabase.Query(
|
||||
// We only retrieve what is necessary (guildid, leader account)
|
||||
"SELECT g.guildid, c.account "
|
||||
"FROM guild g JOIN characters c ON g.leaderguid = c.guid"))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* f = res->Fetch();
|
||||
const uint32 guildId = f[0].Get<uint32>();
|
||||
const uint32 accountId = f[1].Get<uint32>();
|
||||
|
||||
// Determine if guild leader's account is a bot account.
|
||||
if (botAccounts.find(accountId) != botAccounts.end())
|
||||
{
|
||||
++guildNumber;
|
||||
sPlayerbotAIConfig->randomBotGuilds.push_back(guildId);
|
||||
}
|
||||
} while (res->NextRow());
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("playerbots", "{}/{} random bot guilds exist in guild table",guildNumber, sPlayerbotAIConfig->randomBotGuildCount);
|
||||
if (guildNumber >= sPlayerbotAIConfig->randomBotGuildCount)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "No new random guilds required");
|
||||
return;
|
||||
}
|
||||
|
||||
// We list the available leaders (online bots, not in guilds)
|
||||
GuidVector availableLeaders;
|
||||
availableLeaders.reserve(randomBots.size()); // limit reallocs
|
||||
for (const uint32 botLowGuid : randomBots)
|
||||
{
|
||||
ObjectGuid leader = ObjectGuid::Create<HighGuid::Player>(botLowGuid);
|
||||
if (sGuildMgr->GetGuildByLeader(leader))
|
||||
{
|
||||
// already GuildLeader -> ignored
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Player* player = ObjectAccessor::FindPlayer(leader))
|
||||
{
|
||||
if (!player->GetGuildId())
|
||||
availableLeaders.push_back(leader);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("playerbots", "{} available leaders for new guilds found", availableLeaders.size());
|
||||
|
||||
// Create up to randomBotGuildCount by counting only EFFECTIVE creations
|
||||
uint32 createdThisRun = 0;
|
||||
for (; guildNumber < sPlayerbotAIConfig->randomBotGuildCount; /* ++guildNumber -> done only if creation */)
|
||||
{
|
||||
std::string const guildName = CreateRandomGuildName();
|
||||
if (guildName.empty())
|
||||
break; // no more names available in playerbots_guild_names
|
||||
|
||||
if (sGuildMgr->GetGuildByName(guildName))
|
||||
continue; // name already taken, skip
|
||||
|
||||
if (availableLeaders.empty())
|
||||
{
|
||||
LOG_ERROR("playerbots", "No leaders for random guilds available");
|
||||
break; // no more leaders: we can no longer progress without distorting the counter
|
||||
}
|
||||
|
||||
uint32 index = urand(0, availableLeaders.size() - 1);
|
||||
ObjectGuid leader = availableLeaders[index];
|
||||
availableLeaders.erase(availableLeaders.begin() + index); // Removes the chosen leader to avoid re-selecting it repeatedly
|
||||
|
||||
Player* player = ObjectAccessor::FindPlayer(leader);
|
||||
if (!player)
|
||||
{
|
||||
LOG_ERROR("playerbots", "ObjectAccessor Cannot find player to set leader for guild {} . Skipped...",
|
||||
guildName.c_str());
|
||||
// we will try with other leaders in the next round (guildNumber is not incremented)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player->GetGuildId())
|
||||
{
|
||||
// leader already in guild -> we don't advance the counter, we move on to the next one
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_DEBUG("playerbots", "Creating guild name='{}' leader='{}'...", guildName.c_str(), player->GetName().c_str());
|
||||
|
||||
Guild* guild = new Guild();
|
||||
if (!guild->Create(player, guildName))
|
||||
{
|
||||
LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName.c_str(),
|
||||
player->GetName().c_str());
|
||||
delete guild;
|
||||
continue;
|
||||
}
|
||||
|
||||
sGuildMgr->AddGuild(guild);
|
||||
|
||||
LOG_DEBUG("playerbots", "Guild created: id={} name='{}'", guild->GetId(), guildName.c_str());
|
||||
|
||||
// create random emblem
|
||||
uint32 st, cl, br, bc, bg;
|
||||
bg = urand(0, 51);
|
||||
bc = urand(0, 17);
|
||||
cl = urand(0, 17);
|
||||
br = urand(0, 7);
|
||||
st = urand(0, 180);
|
||||
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] new guild id={} random -> style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), st, cl, br, bc, bg);
|
||||
|
||||
// populate guild table with a random tabard design
|
||||
CharacterDatabase.Execute(
|
||||
"UPDATE guild SET EmblemStyle={}, EmblemColor={}, BorderStyle={}, BorderColor={}, BackgroundColor={} "
|
||||
"WHERE guildid={}",
|
||||
st, cl, br, bc, bg, guild->GetId());
|
||||
LOG_DEBUG("playerbots", "[TABARD] UPDATE done for guild id={}", guild->GetId());
|
||||
|
||||
// Immediate reading for log
|
||||
if (QueryResult qr = CharacterDatabase.Query(
|
||||
"SELECT EmblemStyle,EmblemColor,BorderStyle,BorderColor,BackgroundColor FROM guild WHERE guildid={}",
|
||||
guild->GetId()))
|
||||
{
|
||||
Field* f = qr->Fetch();
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] DB check guild id={} => style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), f[0].Get<uint8>(), f[1].Get<uint8>(), f[2].Get<uint8>(), f[3].Get<uint8>(), f[4].Get<uint8>());
|
||||
}
|
||||
|
||||
sPlayerbotAIConfig->randomBotGuilds.push_back(guild->GetId());
|
||||
// The guild is only counted if it is actually created
|
||||
++guildNumber;
|
||||
++createdThisRun;
|
||||
}
|
||||
|
||||
// Shows the true total and how many were created during this run
|
||||
LOG_INFO("playerbots", "{} random bot guilds created this run)", createdThisRun);
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotFactory::CreateRandomGuildName()
|
||||
{
|
||||
std::string guildName = "";
|
||||
|
||||
@@ -51,7 +51,6 @@ public:
|
||||
|
||||
Player* CreateRandomBot(WorldSession* session, uint8 cls, std::unordered_map<NameRaceAndGender, std::vector<std::string>>& names);
|
||||
static void CreateRandomBots();
|
||||
static void CreateRandomGuilds();
|
||||
static void CreateRandomArenaTeams(ArenaType slot, uint32 count);
|
||||
static std::string const CreateRandomGuildName();
|
||||
static uint32 CalculateTotalAccountCount();
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Define.h"
|
||||
#include "FleeManager.h"
|
||||
#include "GameTime.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "GuildMgr.h"
|
||||
@@ -670,9 +669,9 @@ void RandomPlayerbotMgr::AssignAccountTypes()
|
||||
uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts;
|
||||
uint32 assigned = 0;
|
||||
|
||||
for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--)
|
||||
for (size_t idx = allRandomBotAccounts.size(); idx-- > 0 && assigned < toAssign;)
|
||||
{
|
||||
uint32 accountId = allRandomBotAccounts[i];
|
||||
uint32 accountId = allRandomBotAccounts[idx];
|
||||
if (currentAssignments[accountId] == 0) // Unassigned
|
||||
{
|
||||
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId);
|
||||
@@ -1425,7 +1424,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
|
||||
LOG_DEBUG("playerbots", "Bot #{}: log out", bot);
|
||||
|
||||
SetEventValue(bot, "add", 0, 0);
|
||||
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
|
||||
currentBots.remove(bot);
|
||||
|
||||
if (player)
|
||||
LogoutPlayerBot(botGUID);
|
||||
@@ -1480,10 +1479,10 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
|
||||
if (!sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
update = false;
|
||||
|
||||
if (player->GetGroup() && botAI->GetGroupMaster())
|
||||
if (player->GetGroup() && botAI->GetGroupLeader())
|
||||
{
|
||||
PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
|
||||
if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
|
||||
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader());
|
||||
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())
|
||||
{
|
||||
update = false;
|
||||
}
|
||||
@@ -1655,6 +1654,10 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
|
||||
if (bot->IsBeingTeleported() || !bot->IsInWorld())
|
||||
return;
|
||||
|
||||
// no teleport / movement update when rooted.
|
||||
if (bot->IsRooted())
|
||||
return;
|
||||
|
||||
// ignore when in queue for battle grounds.
|
||||
if (bot->InBattlegroundQueue())
|
||||
return;
|
||||
@@ -2712,69 +2715,73 @@ std::vector<uint32> RandomPlayerbotMgr::GetBgBots(uint32 bracket)
|
||||
return std::move(BgBots);
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event)
|
||||
CachedEvent* RandomPlayerbotMgr::FindEvent(uint32 bot, std::string const& event)
|
||||
{
|
||||
// load all events at once on first event load
|
||||
if (eventCache[bot].empty())
|
||||
BotEventCache& cache = eventCache[bot];
|
||||
|
||||
// Load once
|
||||
if (!cache.loaded)
|
||||
{
|
||||
cache.events.clear();
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT);
|
||||
stmt->SetData(0, 0);
|
||||
stmt->SetData(1, bot);
|
||||
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
std::string const eventName = fields[0].Get<std::string>();
|
||||
|
||||
CachedEvent e;
|
||||
e.value = fields[1].Get<uint32>();
|
||||
e.lastChangeTime = fields[2].Get<uint32>();
|
||||
e.validIn = fields[3].Get<uint32>();
|
||||
e.data = fields[4].Get<std::string>();
|
||||
eventCache[bot][eventName] = std::move(e);
|
||||
|
||||
cache.events.emplace(fields[0].Get<std::string>(), std::move(e));
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
cache.loaded = true;
|
||||
}
|
||||
|
||||
CachedEvent& e = eventCache[bot][event];
|
||||
/*if (e.IsEmpty())
|
||||
auto it = cache.events.find(event);
|
||||
if (it == cache.events.end())
|
||||
return nullptr;
|
||||
|
||||
CachedEvent& e = it->second;
|
||||
|
||||
// remove expired events
|
||||
if (e.validIn && (NowSeconds() - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
|
||||
{
|
||||
QueryResult results = PlayerbotsDatabase.Query("SELECT `value`, `time`, validIn, `data` FROM
|
||||
playerbots_random_bots WHERE owner = 0 AND bot = {} AND event = {}", bot, event.c_str());
|
||||
|
||||
if (results)
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
e.value = fields[0].Get<uint32>();
|
||||
e.lastChangeTime = fields[1].Get<uint32>();
|
||||
e.validIn = fields[2].Get<uint32>();
|
||||
e.data = fields[3].Get<std::string>();
|
||||
}
|
||||
cache.events.erase(it);
|
||||
return nullptr;
|
||||
}
|
||||
*/
|
||||
|
||||
if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
|
||||
e.value = 0;
|
||||
|
||||
return e.value;
|
||||
return &e;
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const event)
|
||||
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const& event)
|
||||
{
|
||||
std::string data = "";
|
||||
if (GetEventValue(bot, event))
|
||||
{
|
||||
CachedEvent e = eventCache[bot][event];
|
||||
data = e.data;
|
||||
}
|
||||
if (CachedEvent* e = FindEvent(bot, event))
|
||||
return e->value;
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
|
||||
std::string const data)
|
||||
std::string RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const& event)
|
||||
{
|
||||
if (CachedEvent* e = FindEvent(bot, event))
|
||||
return e->data;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
|
||||
std::string const& data)
|
||||
{
|
||||
PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction();
|
||||
|
||||
@@ -2790,43 +2797,55 @@ uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, ui
|
||||
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS);
|
||||
stmt->SetData(0, 0);
|
||||
stmt->SetData(1, bot);
|
||||
stmt->SetData(2, static_cast<uint32>(GameTime::GetGameTime().count()));
|
||||
stmt->SetData(2, NowSeconds());
|
||||
stmt->SetData(3, validIn);
|
||||
stmt->SetData(4, event.c_str());
|
||||
stmt->SetData(5, value);
|
||||
if (data != "")
|
||||
{
|
||||
|
||||
if (!data.empty())
|
||||
stmt->SetData(6, data.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt->SetData(6);
|
||||
}
|
||||
stmt->SetData(6); // NULL
|
||||
|
||||
trans->Append(stmt);
|
||||
}
|
||||
|
||||
PlayerbotsDatabase.CommitTransaction(trans);
|
||||
|
||||
CachedEvent e(value, (uint32)time(nullptr), validIn, data);
|
||||
eventCache[bot][event] = std::move(e);
|
||||
// Update in-memory cache
|
||||
BotEventCache& cache = eventCache[bot];
|
||||
cache.loaded = true;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
cache.events.erase(event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CachedEvent& e = cache.events[event]; // create-on-write is OK here
|
||||
e.value = value;
|
||||
e.lastChangeTime = NowSeconds();
|
||||
e.validIn = validIn;
|
||||
e.data = data;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const type) { return GetEventValue(bot, type); }
|
||||
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const& type) { return GetEventValue(bot, type); }
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const type)
|
||||
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const& type)
|
||||
{
|
||||
return GetValue(bot->GetGUID().GetCounter(), type);
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotMgr::GetData(uint32 bot, std::string const type) { return GetEventData(bot, type); }
|
||||
std::string RandomPlayerbotMgr::GetData(uint32 bot, std::string const& type) { return GetEventData(bot, type); }
|
||||
|
||||
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const type, uint32 value, std::string const data)
|
||||
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data)
|
||||
{
|
||||
SetEventValue(bot, type, value, sPlayerbotAIConfig->maxRandomBotInWorldTime, data);
|
||||
}
|
||||
|
||||
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const type, uint32 value, std::string const data)
|
||||
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data)
|
||||
{
|
||||
SetValue(bot->GetGUID().GetCounter(), type, value, data);
|
||||
}
|
||||
@@ -3115,7 +3134,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot)
|
||||
{
|
||||
SetEventValue(bot, "add", 0, 0);
|
||||
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
|
||||
currentBots.remove(bot);
|
||||
}
|
||||
|
||||
Player* RandomPlayerbotMgr::GetRandomPlayer()
|
||||
@@ -3497,7 +3516,8 @@ void RandomPlayerbotMgr::Remove(Player* bot)
|
||||
stmt->SetData(1, owner.GetCounter());
|
||||
PlayerbotsDatabase.Execute(stmt);
|
||||
|
||||
eventCache[owner.GetCounter()].clear();
|
||||
uint32 botId = owner.GetCounter();
|
||||
eventCache.erase(botId);
|
||||
|
||||
LogoutPlayerBot(owner);
|
||||
}
|
||||
@@ -3514,7 +3534,7 @@ CreatureData const* RandomPlayerbotMgr::GetCreatureDataByEntry(uint32 entry)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
|
||||
ObjectGuid RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
|
||||
{
|
||||
ObjectGuid battleMasterGUID = ObjectGuid::Empty;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "NewRpgInfo.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "GameTime.h"
|
||||
|
||||
struct BattlegroundInfo
|
||||
{
|
||||
@@ -45,25 +46,20 @@ class ChatHandler;
|
||||
class PerformanceMonitorOperation;
|
||||
class WorldLocation;
|
||||
|
||||
class CachedEvent
|
||||
struct CachedEvent
|
||||
{
|
||||
public:
|
||||
CachedEvent() : value(0), lastChangeTime(0), validIn(0), data("") {}
|
||||
CachedEvent(const CachedEvent& other)
|
||||
: value(other.value), lastChangeTime(other.lastChangeTime), validIn(other.validIn), data(other.data)
|
||||
{
|
||||
}
|
||||
CachedEvent(uint32 value, uint32 lastChangeTime, uint32 validIn, std::string const data = "")
|
||||
: value(value), lastChangeTime(lastChangeTime), validIn(validIn), data(data)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsEmpty() { return !lastChangeTime; }
|
||||
|
||||
uint32 value;
|
||||
uint32 lastChangeTime;
|
||||
uint32 validIn;
|
||||
uint32 value = 0;
|
||||
uint32 lastChangeTime = 0;
|
||||
uint32 validIn = 0;
|
||||
std::string data;
|
||||
|
||||
bool IsEmpty() const { return !lastChangeTime; }
|
||||
};
|
||||
|
||||
struct BotEventCache
|
||||
{
|
||||
bool loaded = false;
|
||||
std::unordered_map<std::string, CachedEvent> events;
|
||||
};
|
||||
|
||||
// https://gist.github.com/bradley219/5373998
|
||||
@@ -139,13 +135,13 @@ public:
|
||||
void Revive(Player* player);
|
||||
void ChangeStrategy(Player* player);
|
||||
void ChangeStrategyOnce(Player* player);
|
||||
uint32 GetValue(Player* bot, std::string const type);
|
||||
uint32 GetValue(uint32 bot, std::string const type);
|
||||
std::string const GetData(uint32 bot, std::string const type);
|
||||
void SetValue(uint32 bot, std::string const type, uint32 value, std::string const data = "");
|
||||
void SetValue(Player* bot, std::string const type, uint32 value, std::string const data = "");
|
||||
uint32 GetValue(Player* bot, std::string const& type);
|
||||
uint32 GetValue(uint32 bot, std::string const& type);
|
||||
std::string GetData(uint32 bot, std::string const& type);
|
||||
void SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data = "");
|
||||
void SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data = "");
|
||||
void Remove(Player* bot);
|
||||
ObjectGuid const GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
|
||||
ObjectGuid GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
|
||||
CreatureData const* GetCreatureDataByEntry(uint32 entry);
|
||||
void LoadBattleMastersCache();
|
||||
std::map<uint32, std::map<uint32, BattlegroundInfo>> BattlegroundData;
|
||||
@@ -203,10 +199,11 @@ private:
|
||||
bool _isBotInitializing = true;
|
||||
bool _isBotLogging = true;
|
||||
NewRpgStatistic rpgStasticTotal;
|
||||
uint32 GetEventValue(uint32 bot, std::string const event);
|
||||
std::string const GetEventData(uint32 bot, std::string const event);
|
||||
uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
|
||||
std::string const data = "");
|
||||
CachedEvent* FindEvent(uint32 bot, std::string const& event);
|
||||
uint32 GetEventValue(uint32 bot, std::string const& event);
|
||||
std::string GetEventData(uint32 bot, std::string const& event);
|
||||
uint32 SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
|
||||
std::string const& data = "");
|
||||
void GetBots();
|
||||
std::vector<uint32> GetBgBots(uint32 bracket);
|
||||
time_t BgCheckTimer;
|
||||
@@ -228,7 +225,7 @@ private:
|
||||
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
|
||||
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
|
||||
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache;
|
||||
std::map<uint32, std::map<std::string, CachedEvent>> eventCache;
|
||||
std::unordered_map<uint32, BotEventCache> eventCache;
|
||||
std::list<uint32> currentBots;
|
||||
uint32 bgBotsCount;
|
||||
uint32 playersLevel;
|
||||
@@ -238,6 +235,7 @@ private:
|
||||
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
|
||||
|
||||
//void ScaleBotActivity(); // Deprecated function
|
||||
static inline uint32 NowSeconds() { return static_cast<uint32>(GameTime::GetGameTime().count()); }
|
||||
};
|
||||
|
||||
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()
|
||||
|
||||
@@ -41,13 +41,17 @@ bool ServerFacade::IsDistanceLessOrEqualThan(float dist1, float dist2) { return
|
||||
|
||||
void ServerFacade::SetFacingTo(Player* bot, WorldObject* wo, bool force)
|
||||
{
|
||||
if (!bot)
|
||||
return;
|
||||
|
||||
float angle = bot->GetAngle(wo);
|
||||
// if (!force && bot->isMoving())
|
||||
// bot->SetFacingTo(bot->GetAngle(wo));
|
||||
// else
|
||||
// {
|
||||
bot->SetOrientation(angle);
|
||||
bot->SendMovementFlagUpdate();
|
||||
if (!bot->IsRooted())
|
||||
bot->SendMovementFlagUpdate();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -218,6 +218,11 @@ bool WorldPosition::isUnderWater()
|
||||
: false;
|
||||
};
|
||||
|
||||
bool WorldPosition::IsValid()
|
||||
{
|
||||
return !(GetMapId() == MAPID_INVALID && GetPositionX() == 0 && GetPositionY() == 0 && GetPositionZ() == 0);
|
||||
}
|
||||
|
||||
WorldPosition WorldPosition::relPoint(WorldPosition* center)
|
||||
{
|
||||
return WorldPosition(GetMapId(), GetPositionX() - center->GetPositionX(), GetPositionY() - center->GetPositionY(),
|
||||
@@ -3404,13 +3409,14 @@ void TravelMgr::LoadQuestTravelTable()
|
||||
{
|
||||
Strategy* strat = con->GetStrategy(stratName);
|
||||
|
||||
if (strat->getDefaultActions())
|
||||
for (uint32 i = 0; i < NextAction::size(strat->getDefaultActions()); i++)
|
||||
{
|
||||
NextAction* nextAction = strat->getDefaultActions()[i];
|
||||
const std::vector<NextAction> defaultActions = strat->getDefaultActions();
|
||||
|
||||
if (defaultActions.size() > 0)
|
||||
{
|
||||
for (NextAction nextAction : defaultActions)
|
||||
{
|
||||
std::ostringstream aout;
|
||||
aout << nextAction->getRelevance() << "," << nextAction->getName()
|
||||
aout << nextAction.getRelevance() << "," << nextAction.getName()
|
||||
<< ",,S:" << stratName;
|
||||
|
||||
if (actions.find(aout.str().c_str()) != actions.end())
|
||||
@@ -3422,27 +3428,24 @@ void TravelMgr::LoadQuestTravelTable()
|
||||
|
||||
actions.insert_or_assign(aout.str().c_str(), classSpecLevel);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TriggerNode*> triggers;
|
||||
strat->InitTriggers(triggers);
|
||||
for (auto& triggerNode : triggers)
|
||||
{
|
||||
// out << " TN:" << triggerNode->getName();
|
||||
|
||||
for (TriggerNode*& triggerNode : triggers)
|
||||
{
|
||||
if (Trigger* trigger = con->GetTrigger(triggerNode->getName()))
|
||||
{
|
||||
triggerNode->setTrigger(trigger);
|
||||
|
||||
NextAction** nextActions = triggerNode->getHandlers();
|
||||
std::vector<NextAction> nextActions = triggerNode->getHandlers();
|
||||
|
||||
for (uint32 i = 0; i < NextAction::size(nextActions); i++)
|
||||
// for (uint32_t i = 0; i < nextActions.size(); ++i)
|
||||
for (NextAction nextAction : nextActions)
|
||||
{
|
||||
NextAction* nextAction = nextActions[i];
|
||||
// out << " A:" << nextAction->getName() << "(" <<
|
||||
// nextAction->getRelevance() << ")";
|
||||
|
||||
std::ostringstream aout;
|
||||
aout << nextAction->getRelevance() << "," << nextAction->getName()
|
||||
aout << nextAction.getRelevance() << "," << nextAction.getName()
|
||||
<< "," << triggerNode->getName() << "," << stratName;
|
||||
|
||||
if (actions.find(aout.str().c_str()) != actions.end())
|
||||
|
||||
@@ -141,6 +141,7 @@ public:
|
||||
bool isOverworld();
|
||||
bool isInWater();
|
||||
bool isUnderWater();
|
||||
bool IsValid();
|
||||
|
||||
WorldPosition relPoint(WorldPosition* center);
|
||||
WorldPosition offset(WorldPosition* center);
|
||||
|
||||
45
src/database/PlayerbotSpellCache.cpp
Normal file
45
src/database/PlayerbotSpellCache.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "PlayerbotSpellCache.h"
|
||||
|
||||
void PlayerbotSpellCache::Initialize()
|
||||
{
|
||||
LOG_INFO("playerbots",
|
||||
"Playerbots: ListSpellsAction caches initialized");
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
skillSpells[skillLine->Spell] = skillLine;
|
||||
}
|
||||
|
||||
// Fill the vendorItems cache once from the world database.
|
||||
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
|
||||
if (results)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
int32 entry = fields[0].Get<int32>();
|
||||
if (entry <= 0)
|
||||
continue;
|
||||
|
||||
vendorItems.insert(static_cast<uint32>(entry));
|
||||
}
|
||||
while (results->NextRow());
|
||||
}
|
||||
|
||||
LOG_DEBUG("playerbots",
|
||||
"ListSpellsAction: initialized caches (skillSpells={}, vendorItems={}).",
|
||||
skillSpells.size(), vendorItems.size());
|
||||
}
|
||||
|
||||
SkillLineAbilityEntry const* PlayerbotSpellCache::GetSkillLine(uint32 spellId) const
|
||||
{
|
||||
auto itr = skillSpells.find(spellId);
|
||||
if (itr != skillSpells.end())
|
||||
return itr->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PlayerbotSpellCache::IsItemBuyable(uint32 itemId) const
|
||||
{
|
||||
return vendorItems.find(itemId) != vendorItems.end();
|
||||
}
|
||||
34
src/database/PlayerbotSpellCache.h
Normal file
34
src/database/PlayerbotSpellCache.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_PLAYERBOTSPELLCACHE_H
|
||||
#define _PLAYERBOT_PLAYERBOTSPELLCACHE_H
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
class PlayerbotSpellCache
|
||||
{
|
||||
public:
|
||||
static PlayerbotSpellCache* Instance()
|
||||
{
|
||||
static PlayerbotSpellCache instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void Initialize(); // call once on startup
|
||||
|
||||
SkillLineAbilityEntry const* GetSkillLine(uint32 spellId) const;
|
||||
bool IsItemBuyable(uint32 itemId) const;
|
||||
|
||||
private:
|
||||
PlayerbotSpellCache() = default;
|
||||
|
||||
std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
|
||||
std::set<uint32> vendorItems;
|
||||
};
|
||||
|
||||
#define sPlayerbotSpellCache PlayerbotSpellCache::Instance()
|
||||
|
||||
#endif
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "PlayerbotGuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "QuestDef.h"
|
||||
#include "RandomItemMgr.h"
|
||||
@@ -1102,8 +1103,6 @@ void PlayerbotFactory::ResetQuests()
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotFactory::InitSpells() { InitAvailableSpells(); }
|
||||
|
||||
void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_template /*true*/, bool reset /*false*/)
|
||||
{
|
||||
uint32 specTab;
|
||||
@@ -2525,66 +2524,35 @@ void PlayerbotFactory::InitAvailableSpells()
|
||||
for (CreatureTemplateContainer::const_iterator i = creatureTemplateContainer->begin();
|
||||
i != creatureTemplateContainer->end(); ++i)
|
||||
{
|
||||
CreatureTemplate const& co = i->second;
|
||||
if (co.trainer_type != TRAINER_TYPE_TRADESKILLS && co.trainer_type != TRAINER_TYPE_CLASS)
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(i->first);
|
||||
|
||||
if (!trainer)
|
||||
continue;
|
||||
|
||||
if (co.trainer_type == TRAINER_TYPE_CLASS && co.trainer_class != bot->getClass())
|
||||
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill &&
|
||||
trainer->GetTrainerType() != Trainer::Type::Class)
|
||||
continue;
|
||||
|
||||
uint32 trainerId = co.Entry;
|
||||
trainerIdCache[bot->getClass()].push_back(trainerId);
|
||||
if (trainer->GetTrainerType() == Trainer::Type::Class &&
|
||||
!trainer->IsTrainerValidForPlayer(bot))
|
||||
continue;
|
||||
|
||||
trainerIdCache[bot->getClass()].push_back(i->first);
|
||||
}
|
||||
}
|
||||
for (uint32 trainerId : trainerIdCache[bot->getClass()])
|
||||
{
|
||||
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
|
||||
if (!trainer_spells)
|
||||
trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(trainerId);
|
||||
|
||||
if (!trainer_spells)
|
||||
continue;
|
||||
|
||||
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
|
||||
itr != trainer_spells->spellList.end(); ++itr)
|
||||
for (auto& spell : trainer->GetSpells())
|
||||
{
|
||||
TrainerSpell const* tSpell = &itr->second;
|
||||
|
||||
if (!tSpell)
|
||||
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
|
||||
continue;
|
||||
|
||||
if (tSpell->learnedSpell[0] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[0]))
|
||||
continue;
|
||||
|
||||
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
|
||||
if (state != TRAINER_SPELL_GREEN)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell->spell);
|
||||
bool learn = true;
|
||||
for (uint8 j = 0; j < 3; ++j)
|
||||
{
|
||||
if (!tSpell->learnedSpell[j] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[j]))
|
||||
continue;
|
||||
|
||||
if (spellInfo->Effects[j].Effect == SPELL_EFFECT_PROFICIENCY ||
|
||||
(spellInfo->Effects[j].Effect == SPELL_EFFECT_SKILL_STEP &&
|
||||
spellInfo->Effects[j].MiscValue != SKILL_RIDING) ||
|
||||
spellInfo->Effects[j].Effect == SPELL_EFFECT_DUAL_WIELD)
|
||||
{
|
||||
learn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!learn)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tSpell->IsCastable())
|
||||
bot->CastSpell(bot, tSpell->spell, true);
|
||||
if (spell.IsCastable())
|
||||
bot->CastSpell(bot, spell.SpellId, true);
|
||||
else
|
||||
bot->learnSpell(tSpell->learnedSpell[0], false);
|
||||
bot->learnSpell(spell.SpellId, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3965,45 +3933,33 @@ void PlayerbotFactory::InitInventoryEquip()
|
||||
void PlayerbotFactory::InitGuild()
|
||||
{
|
||||
if (bot->GetGuildId())
|
||||
return;
|
||||
|
||||
// bot->SaveToDB(false, false);
|
||||
|
||||
// add guild tabard
|
||||
if (bot->GetGuildId() && !bot->HasItemCount(5976, 1))
|
||||
StoreItem(5976, 1);
|
||||
|
||||
if (sPlayerbotAIConfig->randomBotGuilds.empty())
|
||||
RandomPlayerbotFactory::CreateRandomGuilds();
|
||||
|
||||
std::vector<uint32> guilds;
|
||||
for (std::vector<uint32>::iterator i = sPlayerbotAIConfig->randomBotGuilds.begin();
|
||||
i != sPlayerbotAIConfig->randomBotGuilds.end(); ++i)
|
||||
guilds.push_back(*i);
|
||||
|
||||
if (guilds.empty())
|
||||
{
|
||||
LOG_ERROR("playerbots", "No random guilds available");
|
||||
if (!bot->HasItemCount(5976, 1) && bot->GetLevel() > 9)
|
||||
StoreItem(5976, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index = urand(0, guilds.size() - 1);
|
||||
uint32 guildId = guilds[index];
|
||||
Guild* guild = sGuildMgr->GetGuildById(guildId);
|
||||
std::string guildName = sPlayerbotGuildMgr->AssignToGuild(bot);
|
||||
if (guildName.empty())
|
||||
return;
|
||||
|
||||
Guild* guild = sGuildMgr->GetGuildByName(guildName);
|
||||
if (!guild)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Invalid guild {}", guildId);
|
||||
if (!sPlayerbotGuildMgr->CreateGuild(bot, guildName))
|
||||
LOG_ERROR("playerbots","Failed to create guild {} for bot {}", guildName, bot->GetName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (guild->GetMemberSize() < urand(10, sPlayerbotAIConfig->randomBotGuildSizeMax))
|
||||
guild->AddMember(bot->GetGUID(), urand(GR_OFFICER, GR_INITIATE));
|
||||
|
||||
else
|
||||
{
|
||||
if (guild->AddMember(bot->GetGUID(),urand(GR_OFFICER, GR_INITIATE)))
|
||||
sPlayerbotGuildMgr->OnGuildUpdate(guild);
|
||||
else
|
||||
LOG_ERROR("playerbots","Bot {} failed to join guild {}.", bot->GetName(), guildName);
|
||||
}
|
||||
// add guild tabard
|
||||
if (bot->GetGuildId() && bot->GetLevel() > 9 && urand(0, 4) && !bot->HasItemCount(5976, 1))
|
||||
StoreItem(5976, 1);
|
||||
|
||||
// bot->SaveToDB(false, false);
|
||||
}
|
||||
|
||||
void PlayerbotFactory::InitImmersive()
|
||||
@@ -4099,6 +4055,7 @@ void PlayerbotFactory::InitImmersive()
|
||||
|
||||
void PlayerbotFactory::InitArenaTeam()
|
||||
{
|
||||
|
||||
if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId()))
|
||||
return;
|
||||
|
||||
@@ -4185,10 +4142,34 @@ void PlayerbotFactory::InitArenaTeam()
|
||||
|
||||
if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need?
|
||||
{
|
||||
// Add bot to arena team
|
||||
arenateam->AddMember(bot->GetGUID());
|
||||
arenateam->SaveToDB();
|
||||
|
||||
// Only synchronize ratings once the team is full (avoid redundant work)
|
||||
// The captain was added with incorrect ratings when the team was created,
|
||||
// so we fix everyone's ratings once the roster is complete
|
||||
if (arenateam->GetMembersSize() >= (uint32)arenateam->GetType())
|
||||
{
|
||||
uint32 teamRating = arenateam->GetRating();
|
||||
|
||||
// Use SetRatingForAll to align all members with team rating
|
||||
arenateam->SetRatingForAll(teamRating);
|
||||
|
||||
// For bot-only teams, keep MMR synchronized with team rating
|
||||
// This ensures matchmaking reflects the artificial team strength (1000-2000 range)
|
||||
// instead of being influenced by the global CONFIG_ARENA_START_MATCHMAKER_RATING
|
||||
for (auto& member : arenateam->GetMembers())
|
||||
{
|
||||
// Set MMR to match personal rating (which already matches team rating)
|
||||
member.MatchMakerRating = member.PersonalRating;
|
||||
member.MaxMMR = std::max(member.MaxMMR, member.PersonalRating);
|
||||
}
|
||||
// Force save all member data to database
|
||||
arenateam->SaveToDB(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arenateams.erase(arenateams.begin() + index);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,6 @@ private:
|
||||
void InitTradeSkills();
|
||||
void UpdateTradeSkills();
|
||||
void SetRandomSkill(uint16 id);
|
||||
void InitSpells();
|
||||
void ClearSpells();
|
||||
void ClearSkills();
|
||||
void InitTalents(uint32 specNo);
|
||||
|
||||
@@ -37,7 +37,7 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
|
||||
tab = AiFactory::GetPlayerSpecTab(player);
|
||||
collector_ = std::make_unique<StatsCollector>(type_, cls);
|
||||
|
||||
if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
|
||||
if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY)
|
||||
hitOverflowType_ = CollectorType::SPELL;
|
||||
else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)
|
||||
hitOverflowType_ = CollectorType::SPELL;
|
||||
@@ -193,7 +193,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
|
||||
stats_weights_[STATS_TYPE_RANGED_DPS] += 0.01f;
|
||||
|
||||
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTERY || tab == HUNTER_TAB_SURVIVAL))
|
||||
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEAST_MASTERY || tab == HUNTER_TAB_SURVIVAL))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 2.5f;
|
||||
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
|
||||
@@ -249,7 +249,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.1f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
|
||||
}
|
||||
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY) // fury
|
||||
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.8f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.6f;
|
||||
@@ -261,7 +261,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
|
||||
}
|
||||
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) // arm
|
||||
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.3f;
|
||||
@@ -273,7 +273,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 1.4f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
|
||||
}
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) // frost dk
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.7f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.8f;
|
||||
@@ -285,7 +285,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
|
||||
}
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 0.9f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f;
|
||||
@@ -297,7 +297,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 1.5f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
|
||||
}
|
||||
else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) // retribution
|
||||
else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f;
|
||||
@@ -311,7 +311,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 9.0f;
|
||||
}
|
||||
else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)) // enhancement
|
||||
else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 1.4f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 1.1f;
|
||||
@@ -325,9 +325,10 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f;
|
||||
}
|
||||
else if (cls == CLASS_WARLOCK || (cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
|
||||
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow
|
||||
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance
|
||||
else if (cls == CLASS_WARLOCK ||
|
||||
(cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
|
||||
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) ||
|
||||
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 0.3f;
|
||||
stats_weights_[STATS_TYPE_SPIRIT] += 0.6f;
|
||||
@@ -355,8 +356,8 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_CRIT] += 0.8f;
|
||||
stats_weights_[STATS_TYPE_HASTE] += 1.0f;
|
||||
}
|
||||
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy
|
||||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) // heal
|
||||
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) ||
|
||||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 0.9f;
|
||||
stats_weights_[STATS_TYPE_SPIRIT] += 0.15f;
|
||||
@@ -365,7 +366,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_CRIT] += 0.6f;
|
||||
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
|
||||
}
|
||||
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
|
||||
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) ||
|
||||
(cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 0.8f;
|
||||
@@ -396,7 +397,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f;
|
||||
}
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD)
|
||||
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD)
|
||||
{
|
||||
stats_weights_[STATS_TYPE_AGILITY] += 2.0f;
|
||||
stats_weights_[STATS_TYPE_STRENGTH] += 1.0f;
|
||||
@@ -539,7 +540,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
// spec without double hand
|
||||
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield
|
||||
if (((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
|
||||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) ||
|
||||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST) ||
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() &&
|
||||
player_->CanDualWield()) ||
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
|
||||
@@ -556,7 +557,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) ||
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) ||
|
||||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) ||
|
||||
(cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) ||
|
||||
(cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD) ||
|
||||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield()))
|
||||
{
|
||||
weight_ *= 0.1;
|
||||
|
||||
@@ -8,90 +8,6 @@
|
||||
#include "Playerbots.h"
|
||||
#include "Timer.h"
|
||||
|
||||
uint32 NextAction::size(NextAction** actions)
|
||||
{
|
||||
if (!actions)
|
||||
return 0;
|
||||
|
||||
uint32 size = 0;
|
||||
for (size = 0; actions[size];)
|
||||
++size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
NextAction** NextAction::clone(NextAction** actions)
|
||||
{
|
||||
if (!actions)
|
||||
return nullptr;
|
||||
|
||||
uint32 size = NextAction::size(actions);
|
||||
|
||||
NextAction** res = new NextAction*[size + 1];
|
||||
for (uint32 i = 0; i < size; i++)
|
||||
res[i] = new NextAction(*actions[i]);
|
||||
|
||||
res[size] = nullptr;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
NextAction** NextAction::merge(NextAction** left, NextAction** right)
|
||||
{
|
||||
uint32 leftSize = NextAction::size(left);
|
||||
uint32 rightSize = NextAction::size(right);
|
||||
|
||||
NextAction** res = new NextAction*[leftSize + rightSize + 1];
|
||||
|
||||
for (uint32 i = 0; i < leftSize; i++)
|
||||
res[i] = new NextAction(*left[i]);
|
||||
|
||||
for (uint32 i = 0; i < rightSize; i++)
|
||||
res[leftSize + i] = new NextAction(*right[i]);
|
||||
|
||||
res[leftSize + rightSize] = nullptr;
|
||||
|
||||
NextAction::destroy(left);
|
||||
NextAction::destroy(right);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
NextAction** NextAction::array(uint32 nil, ...)
|
||||
{
|
||||
va_list vl;
|
||||
va_start(vl, nil);
|
||||
|
||||
uint32 size = 0;
|
||||
NextAction* cur = nullptr;
|
||||
do
|
||||
{
|
||||
cur = va_arg(vl, NextAction*);
|
||||
++size;
|
||||
} while (cur);
|
||||
|
||||
va_end(vl);
|
||||
|
||||
NextAction** res = new NextAction*[size];
|
||||
va_start(vl, nil);
|
||||
for (uint32 i = 0; i < size; i++)
|
||||
res[i] = va_arg(vl, NextAction*);
|
||||
va_end(vl);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void NextAction::destroy(NextAction** actions)
|
||||
{
|
||||
if (!actions)
|
||||
return;
|
||||
|
||||
for (uint32 i = 0; actions[i]; i++)
|
||||
delete actions[i];
|
||||
|
||||
delete[] actions;
|
||||
}
|
||||
|
||||
Value<Unit*>* Action::GetTargetValue() { return context->GetValue<Unit*>(GetTargetName()); }
|
||||
|
||||
Unit* Action::GetTarget() { return GetTargetValue()->Get(); }
|
||||
@@ -101,4 +17,4 @@ ActionBasket::ActionBasket(ActionNode* action, float relevance, bool skipPrerequ
|
||||
{
|
||||
}
|
||||
|
||||
bool ActionBasket::isExpired(uint32 msecs) { return getMSTime() - created >= msecs; }
|
||||
bool ActionBasket::isExpired(uint32_t msecs) { return getMSTime() - created >= msecs; }
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ACTION_H
|
||||
#define _PLAYERBOT_ACTION_H
|
||||
#pragma once
|
||||
|
||||
#include "AiObject.h"
|
||||
#include "Common.h"
|
||||
@@ -24,15 +23,26 @@ public:
|
||||
std::string const getName() { return name; }
|
||||
float getRelevance() { return relevance; }
|
||||
|
||||
static uint32 size(NextAction** actions);
|
||||
static NextAction** clone(NextAction** actions);
|
||||
static NextAction** merge(NextAction** what, NextAction** with);
|
||||
static NextAction** array(uint32 nil, ...);
|
||||
static void destroy(NextAction** actions);
|
||||
static std::vector<NextAction> merge(std::vector<NextAction> const& what, std::vector<NextAction> const& with)
|
||||
{
|
||||
std::vector<NextAction> result = {};
|
||||
|
||||
for (NextAction const& action : what)
|
||||
{
|
||||
result.push_back(action);
|
||||
}
|
||||
|
||||
for (NextAction const& action : with)
|
||||
{
|
||||
result.push_back(action);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
private:
|
||||
float relevance;
|
||||
std::string const name;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class Action : public AiNamedObject
|
||||
@@ -52,9 +62,9 @@ public:
|
||||
virtual bool Execute([[maybe_unused]] Event event) { return true; }
|
||||
virtual bool isPossible() { return true; }
|
||||
virtual bool isUseful() { return true; }
|
||||
virtual NextAction** getPrerequisites() { return nullptr; }
|
||||
virtual NextAction** getAlternatives() { return nullptr; }
|
||||
virtual NextAction** getContinuers() { return nullptr; }
|
||||
virtual std::vector<NextAction> getPrerequisites() { return {}; }
|
||||
virtual std::vector<NextAction> getAlternatives() { return {}; }
|
||||
virtual std::vector<NextAction> getContinuers() { return {}; }
|
||||
virtual ActionThreatType getThreatType() { return ActionThreatType::None; }
|
||||
void Update() {}
|
||||
void Reset() {}
|
||||
@@ -73,39 +83,44 @@ protected:
|
||||
class ActionNode
|
||||
{
|
||||
public:
|
||||
ActionNode(std::string const name, NextAction** prerequisites = nullptr, NextAction** alternatives = nullptr,
|
||||
NextAction** continuers = nullptr)
|
||||
: name(name), action(nullptr), continuers(continuers), alternatives(alternatives), prerequisites(prerequisites)
|
||||
{
|
||||
} // reorder arguments - whipowill
|
||||
ActionNode(
|
||||
std::string name,
|
||||
std::vector<NextAction> prerequisites = {},
|
||||
std::vector<NextAction> alternatives = {},
|
||||
std::vector<NextAction> continuers = {}
|
||||
) :
|
||||
name(std::move(name)),
|
||||
action(nullptr),
|
||||
continuers(continuers),
|
||||
alternatives(alternatives),
|
||||
prerequisites(prerequisites)
|
||||
{}
|
||||
|
||||
virtual ~ActionNode()
|
||||
{
|
||||
NextAction::destroy(prerequisites);
|
||||
NextAction::destroy(alternatives);
|
||||
NextAction::destroy(continuers);
|
||||
}
|
||||
virtual ~ActionNode() = default;
|
||||
|
||||
Action* getAction() { return action; }
|
||||
void setAction(Action* action) { this->action = action; }
|
||||
std::string const getName() { return name; }
|
||||
const std::string getName() { return name; }
|
||||
|
||||
NextAction** getContinuers() { return NextAction::merge(NextAction::clone(continuers), action->getContinuers()); }
|
||||
NextAction** getAlternatives()
|
||||
std::vector<NextAction> getContinuers()
|
||||
{
|
||||
return NextAction::merge(NextAction::clone(alternatives), action->getAlternatives());
|
||||
return NextAction::merge(this->continuers, action->getContinuers());
|
||||
}
|
||||
NextAction** getPrerequisites()
|
||||
std::vector<NextAction> getAlternatives()
|
||||
{
|
||||
return NextAction::merge(NextAction::clone(prerequisites), action->getPrerequisites());
|
||||
return NextAction::merge(this->alternatives, action->getAlternatives());
|
||||
}
|
||||
std::vector<NextAction> getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(this->prerequisites, action->getPrerequisites());
|
||||
}
|
||||
|
||||
private:
|
||||
std::string const name;
|
||||
const std::string name;
|
||||
Action* action;
|
||||
NextAction** continuers;
|
||||
NextAction** alternatives;
|
||||
NextAction** prerequisites;
|
||||
std::vector<NextAction> continuers;
|
||||
std::vector<NextAction> alternatives;
|
||||
std::vector<NextAction> prerequisites;
|
||||
};
|
||||
|
||||
class ActionBasket
|
||||
@@ -121,14 +136,12 @@ public:
|
||||
bool isSkipPrerequisites() { return skipPrerequisites; }
|
||||
void AmendRelevance(float k) { relevance *= k; }
|
||||
void setRelevance(float relevance) { this->relevance = relevance; }
|
||||
bool isExpired(uint32 msecs);
|
||||
bool isExpired(uint32_t msecs);
|
||||
|
||||
private:
|
||||
ActionNode* action;
|
||||
float relevance;
|
||||
bool skipPrerequisites;
|
||||
Event event;
|
||||
uint32 created;
|
||||
uint32_t created;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -42,9 +42,6 @@ protected:
|
||||
// TRIGGERS
|
||||
//
|
||||
|
||||
#define NEXT_TRIGGERS(name, relevance) \
|
||||
virtual NextAction* getNextAction() { return new NextAction(name, relevance); }
|
||||
|
||||
#define BEGIN_TRIGGER(clazz, super) \
|
||||
class clazz : public super \
|
||||
{ \
|
||||
@@ -78,14 +75,6 @@ protected:
|
||||
clazz(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, spell) {} \
|
||||
}
|
||||
|
||||
#define BUFF_PARTY_TRIGGER_A(clazz, spell) \
|
||||
class clazz : public BuffOnPartyTrigger \
|
||||
{ \
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, spell) {} \
|
||||
bool IsActive() override; \
|
||||
}
|
||||
|
||||
#define DEBUFF_TRIGGER(clazz, spell) \
|
||||
class clazz : public DebuffTrigger \
|
||||
{ \
|
||||
@@ -296,14 +285,6 @@ protected:
|
||||
clazz(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, spell) {} \
|
||||
}
|
||||
|
||||
#define HEAL_ACTION_U(clazz, spell, useful) \
|
||||
class clazz : public CastHealingSpellAction \
|
||||
{ \
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, spell) {} \
|
||||
bool isUseful() override { return useful; } \
|
||||
}
|
||||
|
||||
#define HEAL_PARTY_ACTION(clazz, spell, estAmount, manaEfficiency) \
|
||||
class clazz : public HealPartyMemberAction \
|
||||
{ \
|
||||
@@ -404,14 +385,6 @@ protected:
|
||||
clazz(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, spell, range) {} \
|
||||
}
|
||||
|
||||
#define REACH_ACTION_U(clazz, spell, range, useful) \
|
||||
class clazz : public CastReachTargetSpellAction \
|
||||
{ \
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, spell, range) {} \
|
||||
bool isUseful() override { return useful; } \
|
||||
}
|
||||
|
||||
#define ENEMY_HEALER_ACTION(clazz, spell) \
|
||||
class clazz : public CastSpellOnEnemyHealerAction \
|
||||
{ \
|
||||
@@ -440,10 +413,6 @@ protected:
|
||||
clazz(PlayerbotAI* botAI) : CastProtectSpellAction(botAI, spell) {} \
|
||||
}
|
||||
|
||||
#define END_RANGED_SPELL_ACTION() \
|
||||
} \
|
||||
;
|
||||
|
||||
#define BEGIN_SPELL_ACTION(clazz, name) \
|
||||
class clazz : public CastSpellAction \
|
||||
{ \
|
||||
@@ -472,42 +441,4 @@ protected:
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, name) {}
|
||||
|
||||
#define END_RANGED_SPELL_ACTION() \
|
||||
} \
|
||||
;
|
||||
|
||||
#define BEGIN_BUFF_ON_PARTY_ACTION(clazz, name) \
|
||||
class clazz : public BuffOnPartyAction \
|
||||
{ \
|
||||
public: \
|
||||
clazz(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, name) {}
|
||||
|
||||
//
|
||||
// Action node
|
||||
//
|
||||
|
||||
// node_name , action, prerequisite
|
||||
#define ACTION_NODE_P(name, spell, pre) \
|
||||
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \
|
||||
{ \
|
||||
return new ActionNode(spell, /*P*/ NextAction::array(0, new NextAction(pre), nullptr), /*A*/ nullptr, \
|
||||
/*C*/ nullptr); \
|
||||
}
|
||||
|
||||
// node_name , action, alternative
|
||||
#define ACTION_NODE_A(name, spell, alt) \
|
||||
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \
|
||||
{ \
|
||||
return new ActionNode(spell, /*P*/ nullptr, /*A*/ NextAction::array(0, new NextAction(alt), nullptr), \
|
||||
/*C*/ nullptr); \
|
||||
}
|
||||
|
||||
// node_name , action, continuer
|
||||
#define ACTION_NODE_C(name, spell, con) \
|
||||
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \
|
||||
{ \
|
||||
return new ActionNode(spell, /*P*/ nullptr, /*A*/ nullptr, \
|
||||
/*C*/ NextAction::array(0, new NextAction(con), nullptr)); \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -43,8 +43,6 @@
|
||||
#include "raids/magtheridon/RaidMagtheridonTriggerContext.h"
|
||||
#include "raids/gruulslair/RaidGruulsLairActionContext.h"
|
||||
#include "raids/gruulslair/RaidGruulsLairTriggerContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxActionContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoEActionContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
|
||||
#include "raids/vaultofarchavon/RaidVoAActionContext.h"
|
||||
@@ -117,7 +115,6 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
|
||||
actionContexts.Add(new RaidKarazhanActionContext());
|
||||
actionContexts.Add(new RaidMagtheridonActionContext());
|
||||
actionContexts.Add(new RaidGruulsLairActionContext());
|
||||
actionContexts.Add(new RaidNaxxActionContext());
|
||||
actionContexts.Add(new RaidOsActionContext());
|
||||
actionContexts.Add(new RaidEoEActionContext());
|
||||
actionContexts.Add(new RaidVoAActionContext());
|
||||
@@ -152,7 +149,6 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
|
||||
triggerContexts.Add(new RaidKarazhanTriggerContext());
|
||||
triggerContexts.Add(new RaidMagtheridonTriggerContext());
|
||||
triggerContexts.Add(new RaidGruulsLairTriggerContext());
|
||||
triggerContexts.Add(new RaidNaxxTriggerContext());
|
||||
triggerContexts.Add(new RaidOsTriggerContext());
|
||||
triggerContexts.Add(new RaidEoETriggerContext());
|
||||
triggerContexts.Add(new RaidVoATriggerContext());
|
||||
|
||||
@@ -6,36 +6,40 @@
|
||||
#include "CustomStrategy.h"
|
||||
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
std::map<std::string, std::string> CustomStrategy::actionLinesCache;
|
||||
|
||||
NextAction* toNextAction(std::string const action)
|
||||
NextAction toNextAction(std::string const action)
|
||||
{
|
||||
std::vector<std::string> tokens = split(action, '!');
|
||||
if (tokens.size() == 2 && !tokens[0].empty())
|
||||
return new NextAction(tokens[0], atof(tokens[1].c_str()));
|
||||
else if (tokens.size() == 1 && !tokens[0].empty())
|
||||
return new NextAction(tokens[0], ACTION_NORMAL);
|
||||
|
||||
if (tokens[0].empty())
|
||||
throw std::invalid_argument("Invalid action");
|
||||
|
||||
if (tokens.size() == 2)
|
||||
return NextAction(tokens[0], atof(tokens[1].c_str()));
|
||||
|
||||
if (tokens.size() == 1)
|
||||
return NextAction(tokens[0], ACTION_NORMAL);
|
||||
|
||||
LOG_ERROR("playerbots", "Invalid action {}", action.c_str());
|
||||
return nullptr;
|
||||
|
||||
throw std::invalid_argument("Invalid action");
|
||||
}
|
||||
|
||||
NextAction** toNextActionArray(std::string const actions)
|
||||
std::vector<NextAction> toNextActionArray(const std::string actions)
|
||||
{
|
||||
std::vector<std::string> tokens = split(actions, ',');
|
||||
NextAction** res = new NextAction*[tokens.size() + 1];
|
||||
const std::vector<std::string> tokens = split(actions, ',');
|
||||
std::vector<NextAction> res = {};
|
||||
|
||||
uint32 index = 0;
|
||||
for (std::vector<std::string>::iterator i = tokens.begin(); i != tokens.end(); ++i)
|
||||
for (const std::string token : tokens)
|
||||
{
|
||||
if (NextAction* na = toNextAction(*i))
|
||||
res[index++] = na;
|
||||
res.push_back(toNextAction(token));
|
||||
}
|
||||
|
||||
res[index++] = nullptr;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -258,48 +258,45 @@ ActionNode* Engine::CreateActionNode(std::string const name)
|
||||
return node;
|
||||
|
||||
return new ActionNode(name,
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
bool Engine::MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event,
|
||||
char const* pushType)
|
||||
bool Engine::MultiplyAndPush(
|
||||
std::vector<NextAction> actions,
|
||||
float forceRelevance,
|
||||
bool skipPrerequisites,
|
||||
Event event,
|
||||
char const* pushType
|
||||
)
|
||||
{
|
||||
bool pushed = false;
|
||||
if (actions)
|
||||
|
||||
for (NextAction nextAction : actions)
|
||||
{
|
||||
for (uint32 j = 0; actions[j]; j++)
|
||||
ActionNode* action = this->CreateActionNode(nextAction.getName());
|
||||
|
||||
this->InitializeAction(action);
|
||||
|
||||
float k = nextAction.getRelevance();
|
||||
|
||||
if (forceRelevance > 0.0f)
|
||||
{
|
||||
if (NextAction* nextAction = actions[j])
|
||||
{
|
||||
ActionNode* action = CreateActionNode(nextAction->getName());
|
||||
InitializeAction(action);
|
||||
|
||||
float k = nextAction->getRelevance();
|
||||
if (forceRelevance > 0.0f)
|
||||
{
|
||||
k = forceRelevance;
|
||||
}
|
||||
|
||||
if (k > 0)
|
||||
{
|
||||
LogAction("PUSH:%s - %f (%s)", action->getName().c_str(), k, pushType);
|
||||
queue.Push(new ActionBasket(action, k, skipPrerequisites, event));
|
||||
pushed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete action;
|
||||
}
|
||||
|
||||
delete nextAction;
|
||||
}
|
||||
else
|
||||
break;
|
||||
k = forceRelevance;
|
||||
}
|
||||
|
||||
delete[] actions;
|
||||
if (k > 0)
|
||||
{
|
||||
this->LogAction("PUSH:%s - %f (%s)", action->getName().c_str(), k, pushType);
|
||||
queue.Push(new ActionBasket(action, k, skipPrerequisites, event));
|
||||
pushed = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
delete action;
|
||||
|
||||
}
|
||||
|
||||
return pushed;
|
||||
@@ -530,10 +527,10 @@ std::vector<std::string> Engine::GetStrategies()
|
||||
|
||||
void Engine::PushAgain(ActionNode* actionNode, float relevance, Event event)
|
||||
{
|
||||
NextAction** nextAction = new NextAction*[2];
|
||||
nextAction[0] = new NextAction(actionNode->getName(), relevance);
|
||||
nextAction[1] = nullptr;
|
||||
std::vector<NextAction> nextAction = { NextAction(actionNode->getName(), relevance) };
|
||||
|
||||
MultiplyAndPush(nextAction, relevance, true, event, "again");
|
||||
|
||||
delete actionNode;
|
||||
}
|
||||
|
||||
@@ -563,6 +560,13 @@ bool Engine::ListenAndExecute(Action* action, Event event)
|
||||
{
|
||||
bool actionExecuted = false;
|
||||
|
||||
if (action == nullptr)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Action is nullptr");
|
||||
|
||||
return actionExecuted;
|
||||
}
|
||||
|
||||
if (actionExecutionListeners.Before(action, event))
|
||||
{
|
||||
actionExecuted = actionExecutionListeners.AllowExecution(action, event) ? action->Execute(event) : true;
|
||||
|
||||
@@ -90,7 +90,7 @@ public:
|
||||
bool testMode;
|
||||
|
||||
private:
|
||||
bool MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event,
|
||||
bool MultiplyAndPush(std::vector<NextAction> actions, float forceRelevance, bool skipPrerequisites, Event event,
|
||||
const char* pushType);
|
||||
void Reset();
|
||||
void ProcessTriggers(bool minimal);
|
||||
|
||||
@@ -28,90 +28,112 @@ public:
|
||||
private:
|
||||
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("melee",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"melee",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* healthstone([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("healthstone",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("healing potion"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"healthstone",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("healing potion") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* follow_master_random([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("be near",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("follow"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"be near",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("follow") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* attack_anything([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("attack anything",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"attack anything",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* move_random([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("move random",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("stay line"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"move random",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("stay line") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* move_to_loot([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("move to loot",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"move to loot",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* food([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("food",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"food",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* drink([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("drink",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"drink",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* mana_potion([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("mana potion",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"mana potion",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* healing_potion([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("healing potion",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("food"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"healing potion",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("food") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* flee([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("flee",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"flee",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
Strategy(PlayerbotAI* botAI);
|
||||
virtual ~Strategy() {}
|
||||
|
||||
virtual NextAction** getDefaultActions() { return nullptr; }
|
||||
virtual std::vector<NextAction> getDefaultActions() { return {}; }
|
||||
virtual void InitTriggers([[maybe_unused]] std::vector<TriggerNode*>& triggers) {}
|
||||
virtual void InitMultipliers([[maybe_unused]] std::vector<Multiplier*>& multipliers) {}
|
||||
virtual std::string const getName() = 0;
|
||||
|
||||
@@ -120,6 +120,8 @@ public:
|
||||
creators["formation"] = &StrategyContext::combat_formation;
|
||||
creators["move from group"] = &StrategyContext::move_from_group;
|
||||
creators["worldbuff"] = &StrategyContext::world_buff;
|
||||
creators["use bobber"] = &StrategyContext::bobber_strategy;
|
||||
creators["master fishing"] = &StrategyContext::master_fishing;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -188,6 +190,8 @@ private:
|
||||
static Strategy* combat_formation(PlayerbotAI* botAI) { return new CombatFormationStrategy(botAI); }
|
||||
static Strategy* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupStrategy(botAI); }
|
||||
static Strategy* world_buff(PlayerbotAI* botAI) { return new WorldBuffStrategy(botAI); }
|
||||
static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); }
|
||||
static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); }
|
||||
};
|
||||
|
||||
class MovementStrategyContext : public NamedObjectContext<Strategy>
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_TRIGGER_H
|
||||
#define _PLAYERBOT_TRIGGER_H
|
||||
#pragma once
|
||||
|
||||
#include "Action.h"
|
||||
#include "Common.h"
|
||||
@@ -15,7 +14,11 @@ class Unit;
|
||||
class Trigger : public AiNamedObject
|
||||
{
|
||||
public:
|
||||
Trigger(PlayerbotAI* botAI, std::string const name = "trigger", int32 checkInterval = 1);
|
||||
Trigger(
|
||||
PlayerbotAI* botAI,
|
||||
const std::string name = "trigger",
|
||||
int32_t checkInterval = 1
|
||||
);
|
||||
|
||||
virtual ~Trigger() {}
|
||||
|
||||
@@ -23,7 +26,7 @@ public:
|
||||
virtual void ExternalEvent([[maybe_unused]] std::string const param, [[maybe_unused]] Player* owner = nullptr) {}
|
||||
virtual void ExternalEvent([[maybe_unused]] WorldPacket& packet, [[maybe_unused]] Player* owner = nullptr) {}
|
||||
virtual bool IsActive() { return false; }
|
||||
virtual NextAction** getHandlers() { return nullptr; }
|
||||
virtual std::vector<NextAction> getHandlers() { return {}; }
|
||||
void Update() {}
|
||||
virtual void Reset() {}
|
||||
virtual Unit* GetTarget();
|
||||
@@ -33,32 +36,49 @@ public:
|
||||
bool needCheck(uint32 now);
|
||||
|
||||
protected:
|
||||
int32 checkInterval;
|
||||
uint32 lastCheckTime;
|
||||
int32_t checkInterval;
|
||||
uint32_t lastCheckTime;
|
||||
};
|
||||
|
||||
class TriggerNode
|
||||
{
|
||||
public:
|
||||
TriggerNode(std::string const name, NextAction** handlers = nullptr)
|
||||
: trigger(nullptr), handlers(handlers), name(name)
|
||||
{
|
||||
} // reorder args - whipowill
|
||||
|
||||
virtual ~TriggerNode() { NextAction::destroy(handlers); }
|
||||
TriggerNode(
|
||||
const std::string& name,
|
||||
std::vector<NextAction> handlers = {}
|
||||
) :
|
||||
trigger(nullptr),
|
||||
handlers(std::move(handlers)),
|
||||
name(name)
|
||||
{}
|
||||
|
||||
Trigger* getTrigger() { return trigger; }
|
||||
void setTrigger(Trigger* trigger) { this->trigger = trigger; }
|
||||
std::string const getName() { return name; }
|
||||
const std::string getName() { return name; }
|
||||
|
||||
NextAction** getHandlers() { return NextAction::merge(NextAction::clone(handlers), trigger->getHandlers()); }
|
||||
std::vector<NextAction> getHandlers()
|
||||
{
|
||||
std::vector<NextAction> result = this->handlers;
|
||||
|
||||
float getFirstRelevance() { return handlers[0] ? handlers[0]->getRelevance() : -1; }
|
||||
if (trigger != nullptr)
|
||||
{
|
||||
std::vector<NextAction> extra = trigger->getHandlers();
|
||||
result.insert(result.end(), extra.begin(), extra.end());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float getFirstRelevance()
|
||||
{
|
||||
if (this->handlers.size() > 0)
|
||||
return this->handlers[0].getRelevance();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private:
|
||||
Trigger* trigger;
|
||||
NextAction** handlers;
|
||||
std::string const name;
|
||||
std::vector<NextAction> handlers;
|
||||
const std::string name;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -59,7 +59,7 @@ bool AcceptInvitationAction::Execute(Event event)
|
||||
|
||||
if (sPlayerbotAIConfig->summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig->sightDistance)
|
||||
{
|
||||
Teleport(inviter, bot);
|
||||
Teleport(inviter, bot, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,5 @@ bool AcceptResurrectAction::Execute(Event event)
|
||||
packet << uint8(1); // accept
|
||||
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
|
||||
|
||||
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
#include "OutfitAction.h"
|
||||
#include "PositionAction.h"
|
||||
#include "DropQuestAction.h"
|
||||
#include "RaidNaxxActions.h"
|
||||
#include "RandomBotUpdateAction.h"
|
||||
#include "ReachTargetActions.h"
|
||||
#include "ReleaseSpiritAction.h"
|
||||
@@ -64,6 +63,7 @@
|
||||
#include "WorldBuffAction.h"
|
||||
#include "XpGainAction.h"
|
||||
#include "NewRpgAction.h"
|
||||
#include "FishingAction.h"
|
||||
#include "CancelChannelAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
@@ -121,7 +121,7 @@ public:
|
||||
creators["shoot"] = &ActionContext::shoot;
|
||||
creators["follow"] = &ActionContext::follow;
|
||||
creators["move from group"] = &ActionContext::move_from_group;
|
||||
creators["flee to master"] = &ActionContext::flee_to_master;
|
||||
creators["flee to group leader"] = &ActionContext::flee_to_group_leader;
|
||||
creators["runaway"] = &ActionContext::runaway;
|
||||
creators["stay"] = &ActionContext::stay;
|
||||
creators["sit"] = &ActionContext::sit;
|
||||
@@ -191,6 +191,11 @@ public:
|
||||
creators["buy tabard"] = &ActionContext::buy_tabard;
|
||||
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
|
||||
creators["clean quest log"] = &ActionContext::clean_quest_log;
|
||||
creators["move near water"] = &ActionContext::move_near_water;
|
||||
creators["go fishing"] = &ActionContext::go_fishing;
|
||||
creators["use fishing bobber"] = &ActionContext::use_fishing_bobber;
|
||||
creators["end master fishing"] = &ActionContext::end_master_fishing;
|
||||
creators["remove bobber strategy"] = &ActionContext::remove_bobber_strategy;
|
||||
creators["roll"] = &ActionContext::roll_action;
|
||||
creators["cancel channel"] = &ActionContext::cancel_channel;
|
||||
|
||||
@@ -318,7 +323,7 @@ private:
|
||||
static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); }
|
||||
static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); }
|
||||
static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); }
|
||||
static Action* flee_to_master(PlayerbotAI* botAI) { return new FleeToMasterAction(botAI); }
|
||||
static Action* flee_to_group_leader(PlayerbotAI* botAI) { return new FleeToGroupLeaderAction(botAI); }
|
||||
static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); }
|
||||
static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); }
|
||||
static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); }
|
||||
@@ -380,6 +385,11 @@ private:
|
||||
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
|
||||
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(botAI); }
|
||||
static Action* clean_quest_log(PlayerbotAI* botAI) { return new CleanQuestLogAction(botAI); }
|
||||
static Action* move_near_water(PlayerbotAI* botAI) { return new MoveNearWaterAction(botAI); }
|
||||
static Action* go_fishing(PlayerbotAI* botAI) { return new FishingAction(botAI);}
|
||||
static Action* use_fishing_bobber(PlayerbotAI* botAI) { return new UseBobberAction(botAI);}
|
||||
static Action* end_master_fishing(PlayerbotAI* botAI) { return new EndMasterFishingAction(botAI); }
|
||||
static Action* remove_bobber_strategy(PlayerbotAI* botAI) { return new RemoveBobberStrategyAction(botAI); }
|
||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||
|
||||
// BG Tactics
|
||||
|
||||
@@ -15,20 +15,19 @@
|
||||
#include "SharedDefines.h"
|
||||
#include "Unit.h"
|
||||
|
||||
bool AttackAction::Execute(Event event)
|
||||
bool AttackAction::Execute(Event /*event*/)
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!target->IsInWorld())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Attack(target);
|
||||
}
|
||||
|
||||
bool AttackMyTargetAction::Execute(Event event)
|
||||
bool AttackMyTargetAction::Execute(Event /*event*/)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
@@ -51,7 +50,7 @@ bool AttackMyTargetAction::Execute(Event event)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
||||
{
|
||||
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
|
||||
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
|
||||
@@ -81,12 +80,15 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
|
||||
sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId()))
|
||||
&& (target->IsPlayer() || target->IsPet()))
|
||||
// Check if bot OR target is in prohibited zone/area (skip for duels)
|
||||
if ((target->IsPlayer() || target->IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != target) &&
|
||||
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
|
||||
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
||||
@@ -98,6 +100,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,6 +108,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -112,6 +116,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,6 +124,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -153,10 +159,9 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
bot->StopMoving();
|
||||
}
|
||||
|
||||
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
||||
{
|
||||
if (botAI->CanMove() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
||||
sServerFacade->SetFacingTo(bot, target);
|
||||
}
|
||||
|
||||
botAI->ChangeEngine(BOT_STATE_COMBAT);
|
||||
|
||||
bot->Attack(target, shouldMelee);
|
||||
@@ -186,4 +191,4 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
|
||||
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
|
||||
|
||||
bool AttackDuelOpponentAction::Execute(Event event) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||
|
||||
@@ -224,42 +224,36 @@ bool BuyAction::Execute(Event event)
|
||||
|
||||
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
|
||||
{
|
||||
uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1);
|
||||
|
||||
if (!tItems)
|
||||
if (!tItems || !proto)
|
||||
return false;
|
||||
|
||||
uint32 itemId = proto->ItemId;
|
||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++)
|
||||
uint32 oldCount = bot->GetItemCount(itemId, false);
|
||||
|
||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot)
|
||||
{
|
||||
if (tItems->GetItem(slot)->item == itemId)
|
||||
if (tItems->GetItem(slot)->item != itemId)
|
||||
continue;
|
||||
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(10000000);
|
||||
|
||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(botMoney);
|
||||
|
||||
uint32 newCount = bot->GetItemCount(itemId, false);
|
||||
if (newCount > oldCount)
|
||||
{
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
bot->SetMoney(10000000);
|
||||
}
|
||||
|
||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
bot->SetMoney(botMoney);
|
||||
}
|
||||
|
||||
if (oldCount <
|
||||
AI_VALUE2(
|
||||
uint32, "item count",
|
||||
proto->Name1)) // BuyItem Always returns false (unless unique) so we have to check the item counts.
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
std::ostringstream out;
|
||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -185,7 +185,6 @@ public:
|
||||
creators["guild remove"] = &ChatActionContext::guild_remove;
|
||||
creators["guild leave"] = &ChatActionContext::guild_leave;
|
||||
creators["rtsc"] = &ChatActionContext::rtsc;
|
||||
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
|
||||
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
|
||||
creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps;
|
||||
creators["join"] = &ChatActionContext::join;
|
||||
@@ -298,7 +297,6 @@ private:
|
||||
static Action* guild_remove(PlayerbotAI* botAI) { return new GuildRemoveAction(botAI); }
|
||||
static Action* guild_leave(PlayerbotAI* botAI) { return new GuildLeaveAction(botAI); }
|
||||
static Action* rtsc(PlayerbotAI* botAI) { return new RTSCAction(botAI); }
|
||||
static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); }
|
||||
static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
|
||||
static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); }
|
||||
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
|
||||
|
||||
@@ -241,20 +241,6 @@ bool MaxDpsChatShortcutAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NaxxChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
botAI->Reset();
|
||||
botAI->ChangeStrategy("+naxx", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("+naxx", BOT_STATE_COMBAT);
|
||||
botAI->TellMasterNoFacing("Add Naxx Strategies!");
|
||||
// bot->Say("Add Naxx Strategies!", LANG_UNIVERSAL);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BwlChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
|
||||
@@ -85,13 +85,6 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class NaxxChatShortcutAction : public Action
|
||||
{
|
||||
public:
|
||||
NaxxChatShortcutAction(PlayerbotAI* ai) : Action(ai, "naxx chat shortcut") {}
|
||||
virtual bool Execute(Event event);
|
||||
};
|
||||
|
||||
class BwlChatShortcutAction : public Action
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -78,20 +78,17 @@ float ChooseRpgTargetAction::getMaxRelevance(GuidPosition guidP)
|
||||
if (!trigger->IsActive())
|
||||
continue;
|
||||
|
||||
NextAction** nextActions = triggerNode->getHandlers();
|
||||
std::vector<NextAction> nextActions = triggerNode->getHandlers();
|
||||
|
||||
bool isRpg = false;
|
||||
|
||||
for (int32 i = 0; i < NextAction::size(nextActions); i++)
|
||||
for (NextAction nextAction : nextActions)
|
||||
{
|
||||
NextAction* nextAction = nextActions[i];
|
||||
|
||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction->getName());
|
||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName());
|
||||
|
||||
if (dynamic_cast<RpgEnabled*>(action))
|
||||
isRpg = true;
|
||||
}
|
||||
NextAction::destroy(nextActions);
|
||||
|
||||
if (isRpg)
|
||||
{
|
||||
@@ -311,7 +308,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target)
|
||||
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||
{
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
Player* gmaster = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* realMaster = botAI->GetMaster();
|
||||
AiObjectContext* context = botAI->GetAiObjectContext();
|
||||
|
||||
@@ -327,30 +324,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gmaster || bot == gmaster)
|
||||
if (!groupLeader || bot == groupLeader)
|
||||
return true;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return true;
|
||||
|
||||
if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||
if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||
return false;
|
||||
|
||||
Formation* formation = AI_VALUE(Formation*, "formation");
|
||||
float distance = gmaster->GetDistance2d(pos.getX(), pos.getY());
|
||||
float distance = groupLeader->GetDistance2d(pos.getX(), pos.getY());
|
||||
|
||||
if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
|
||||
{
|
||||
Player* player = gmaster;
|
||||
if (gmaster && !gmaster->isMoving() ||
|
||||
Player* player = groupLeader;
|
||||
if (groupLeader && !groupLeader->isMoving() ||
|
||||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((inDungeon || !gmaster->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == gmaster && distance > 5.0f)
|
||||
if ((inDungeon || !groupLeader->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == groupLeader && distance > 5.0f)
|
||||
return false;
|
||||
|
||||
if (!gmaster->isMoving() && distance < 25.0f)
|
||||
if (!groupLeader->isMoving() && distance < 25.0f)
|
||||
return true;
|
||||
|
||||
if (distance < formation->GetMaxDistance())
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "LootObjectStack.h"
|
||||
#include "NewRpgStrategy.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
#include "PossibleRpgTargetsValue.h"
|
||||
#include "PvpTriggers.h"
|
||||
#include "ServerFacade.h"
|
||||
@@ -87,9 +88,7 @@ bool DropTargetAction::Execute(Event event)
|
||||
{
|
||||
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
|
||||
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
||||
{
|
||||
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
|
||||
}
|
||||
}
|
||||
bot->AttackStop();
|
||||
|
||||
@@ -142,6 +141,23 @@ bool AttackRtiTargetAction::Execute(Event event)
|
||||
{
|
||||
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
|
||||
|
||||
// Fallback: if the "rti target" value did not resolve a valid unit yet,
|
||||
// try to resolve the raid icon directly from the group.
|
||||
if (!rtiTarget)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
std::string const rti = AI_VALUE(std::string, "rti");
|
||||
int32 const index = RtiTargetValue::GetRtiIndex(rti);
|
||||
if (index >= 0)
|
||||
{
|
||||
ObjectGuid const guid = group->GetTargetIcon(index);
|
||||
if (!guid.IsEmpty())
|
||||
rtiTarget = botAI->GetUnit(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
|
||||
{
|
||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
|
||||
@@ -153,9 +169,7 @@ bool AttackRtiTargetAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
botAI->TellError("I dont see my rti attack target");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
|
||||
void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
|
||||
{
|
||||
// Tell the master where we are going.
|
||||
if (!bot->GetGroup() || (botAI->GetGroupMaster() == bot))
|
||||
if (!bot->GetGroup() || (botAI->GetGroupLeader() == bot))
|
||||
ReportTravelTarget(newTarget, oldTarget);
|
||||
|
||||
// If we are heading to a creature/npc clear it from the ignore list.
|
||||
|
||||
@@ -344,6 +344,27 @@ bool EquipUpgradesAction::Execute(Event event)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.GetSource() == "item push result")
|
||||
{
|
||||
WorldPacket p(event.getPacket());
|
||||
p.rpos(0);
|
||||
ObjectGuid playerGuid;
|
||||
uint32 received, created, sendChatMessage, itemSlot, itemId;
|
||||
uint8 bagSlot;
|
||||
|
||||
p >> playerGuid;
|
||||
p >> received;
|
||||
p >> created;
|
||||
p >> sendChatMessage;
|
||||
p >> bagSlot;
|
||||
p >> itemSlot;
|
||||
p >> itemId;
|
||||
|
||||
ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (item->Class == ITEM_CLASS_TRADE_GOODS && item->SubClass == ITEM_SUBCLASS_MEAT)
|
||||
return false;
|
||||
}
|
||||
|
||||
CollectItemsVisitor visitor;
|
||||
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
|
||||
|
||||
|
||||
493
src/strategy/actions/FishingAction.cpp
Normal file
493
src/strategy/actions/FishingAction.cpp
Normal file
@@ -0,0 +1,493 @@
|
||||
/*
|
||||
* 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 "FishingAction.h"
|
||||
#include "FishValues.h"
|
||||
#include "Event.h"
|
||||
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "ItemPackets.h"
|
||||
#include "LastMovementValue.h"
|
||||
#include "Map.h"
|
||||
#include "MovementActions.h"
|
||||
#include "Object.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotTextMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Position.h"
|
||||
|
||||
uint32 const FISHING_SPELL = 7620;
|
||||
uint32 const FISHING_POLE = 6256;
|
||||
uint32 const FISHING_BOBBER = 35591;
|
||||
float const MIN_DISTANCE_TO_WATER = 10.0f; // Minimum spell distance
|
||||
float const MAX_DISTANCE_TO_WATER = 20.0f; // Maximum spell distance
|
||||
float const HEIGHT_ABOVE_WATER_TOLERANCE = 1.0f; // Can stand in up to 1 unit of water and still fish.
|
||||
float const SEARCH_INCREMENT = 2.5f;
|
||||
float const HEIGHT_SEARCH_BUFFER = 10.0f; // Height buffer to prevent potentially missing the model the bot is standing on.
|
||||
float const SEARCH_LAND_BUFFER = 0.5f;
|
||||
uint32 const FISHING_LOCATION_TIMEOUT = 180000; //Three minutes
|
||||
|
||||
static bool IsFishingPole(Item* const item)
|
||||
{
|
||||
if (!item)
|
||||
return false;
|
||||
const ItemTemplate* proto = item->GetTemplate();
|
||||
return proto && proto->Class == ITEM_CLASS_WEAPON &&
|
||||
proto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE;
|
||||
}
|
||||
|
||||
float HasFishableWaterOrLand(float x, float y, float z, Map* map, uint32 phaseMask, bool checkForLand=false)
|
||||
{
|
||||
if (!map)
|
||||
return INVALID_HEIGHT;
|
||||
|
||||
LiquidData const& liq = map->GetLiquidData(phaseMask, x, y, z+HEIGHT_ABOVE_WATER_TOLERANCE, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
|
||||
float ground = map->GetHeight(phaseMask, x, y, z + HEIGHT_SEARCH_BUFFER, true);
|
||||
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER)
|
||||
{
|
||||
if (checkForLand)
|
||||
return ground;
|
||||
return INVALID_HEIGHT;
|
||||
}
|
||||
if (checkForLand)
|
||||
{
|
||||
if (ground > liq.Level - HEIGHT_ABOVE_WATER_TOLERANCE)
|
||||
return ground;
|
||||
return INVALID_HEIGHT;
|
||||
}
|
||||
|
||||
if (liq.Level + HEIGHT_ABOVE_WATER_TOLERANCE > ground)
|
||||
{
|
||||
if (abs(liq.DepthLevel) < 0.5f) // too shallow to fish in.
|
||||
return INVALID_HEIGHT;
|
||||
return liq.Level;
|
||||
}
|
||||
return INVALID_HEIGHT;
|
||||
}
|
||||
|
||||
bool HasLosToWater(Player* bot, float wx, float wy, float waterZ)
|
||||
{
|
||||
float z = bot->GetCollisionHeight() + bot->GetPositionZ();
|
||||
return bot->GetMap()->isInLineOfSight(
|
||||
bot->GetPositionX(), bot->GetPositionY(), z,
|
||||
wx, wy, waterZ,
|
||||
bot->GetPhaseMask(),
|
||||
LINEOFSIGHT_ALL_CHECKS,
|
||||
VMAP::ModelIgnoreFlags::Nothing);
|
||||
}
|
||||
|
||||
WorldPosition FindLandFromPosition(PlayerbotAI* botAI, float startDistance, float endDistance, float increment, float orientation, WorldPosition targetPos, float fishingSearchWindow, bool checkLOS = true)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
Map* map = bot->GetMap();
|
||||
uint32 phaseMask = bot->GetPhaseMask();
|
||||
Player* master = botAI->GetMaster();
|
||||
|
||||
float targetX = targetPos.GetPositionX();
|
||||
float targetY = targetPos.GetPositionY();
|
||||
float targetZ = targetPos.GetPositionZ();
|
||||
|
||||
for (float dist = startDistance; dist <= endDistance; dist += increment)
|
||||
{
|
||||
//step backwards from position to bot to find edge of shore.
|
||||
float checkX = targetX - dist * cos(orientation);
|
||||
float checkY = targetY - dist * sin(orientation);
|
||||
|
||||
float groundZ = map->GetHeight(phaseMask, checkX, checkY, targetZ + HEIGHT_SEARCH_BUFFER, true);
|
||||
|
||||
if (groundZ == INVALID_HEIGHT)
|
||||
continue;
|
||||
|
||||
LiquidData const& liq = map->GetLiquidData(phaseMask, checkX, checkY, targetZ, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
|
||||
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER || groundZ > liq.DepthLevel + HEIGHT_ABOVE_WATER_TOLERANCE)
|
||||
{
|
||||
if (checkLOS)
|
||||
{
|
||||
bool hasLOS = map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing);
|
||||
if (!hasLOS)
|
||||
continue;
|
||||
}
|
||||
// Add a distance check for the position to prevent the bot from moving out of range to the master.
|
||||
if (master && botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
|
||||
continue;
|
||||
|
||||
return WorldPosition(bot->GetMapId(), checkX, checkY, groundZ);
|
||||
}
|
||||
}
|
||||
|
||||
return WorldPosition();
|
||||
}
|
||||
|
||||
WorldPosition FindLandRadialFromPosition (PlayerbotAI* botAI, WorldPosition targetPos, float startDistance, float endDistance, float increment, float fishingSearchWindow, int angles = 16)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
const int numDirections = angles;
|
||||
std::vector<WorldPosition> boundaryPoints;
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master)
|
||||
return WorldPosition();
|
||||
|
||||
Map* map = bot->GetMap();
|
||||
uint32 phaseMask = bot->GetPhaseMask();
|
||||
|
||||
float targetX = targetPos.GetPositionX();
|
||||
float targetY = targetPos.GetPositionY();
|
||||
float targetZ = targetPos.GetPositionZ();
|
||||
|
||||
for (float dist = startDistance; dist <= endDistance; dist += increment)
|
||||
{
|
||||
for (int i = 0; i < numDirections; ++i)
|
||||
{
|
||||
float angle = (2.0f * M_PI * i) / numDirections;
|
||||
float checkX = targetX - cos(angle) * dist;
|
||||
float checkY = targetY - sin(angle) * dist;
|
||||
|
||||
float groundZ = HasFishableWaterOrLand(checkX, checkY, targetZ, map, phaseMask, true);
|
||||
|
||||
if (groundZ == INVALID_HEIGHT)
|
||||
continue;
|
||||
|
||||
if (map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
|
||||
continue;
|
||||
|
||||
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, groundZ));
|
||||
}
|
||||
|
||||
if (!boundaryPoints.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
if (boundaryPoints.empty())
|
||||
return WorldPosition();
|
||||
|
||||
if (boundaryPoints.size() == 1)
|
||||
return boundaryPoints[0];
|
||||
|
||||
float minDistance = FLT_MAX;
|
||||
WorldLocation closestPoint = WorldPosition();
|
||||
for (auto const& pos : boundaryPoints)
|
||||
{
|
||||
float distance = bot->GetExactDist2d(&pos);
|
||||
if (distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
closestPoint = pos;
|
||||
}
|
||||
}
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS, int numDirections)
|
||||
{
|
||||
std::vector<WorldPosition> boundaryPoints;
|
||||
|
||||
float dist = minDistance;
|
||||
while (dist <= maxDistance)
|
||||
{
|
||||
for (int i = 0; i < numDirections; ++i)
|
||||
{
|
||||
float angle = (2.0f * M_PI * i) / numDirections;
|
||||
float checkX = x + cos(angle) * dist;
|
||||
float checkY = y + sin(angle) * dist;
|
||||
|
||||
float waterZ = HasFishableWaterOrLand(checkX, checkY, z, map, phaseMask);
|
||||
|
||||
if (waterZ == INVALID_HEIGHT)
|
||||
continue;
|
||||
|
||||
if (checkLOS && !HasLosToWater(bot, checkX, checkY, waterZ))
|
||||
continue;
|
||||
|
||||
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, waterZ));
|
||||
}
|
||||
|
||||
if (!boundaryPoints.empty())
|
||||
break;
|
||||
|
||||
dist += increment;
|
||||
}
|
||||
|
||||
if (boundaryPoints.empty())
|
||||
return WorldPosition();
|
||||
|
||||
if (boundaryPoints.size() == 1)
|
||||
return boundaryPoints[0];
|
||||
// return the central point in the identified positions in to try to be perpendicular to the shore.
|
||||
return boundaryPoints[boundaryPoints.size() / 2];
|
||||
}
|
||||
|
||||
WorldPosition FindFishingHole(PlayerbotAI* botAI)
|
||||
{
|
||||
Player* player = botAI->GetBot();
|
||||
GuidVector gos = PAI_VALUE(GuidVector, "nearest game objects no los");
|
||||
GameObject* nearestFishingHole = nullptr;
|
||||
float minDist = std::numeric_limits<float>::max();
|
||||
for (auto const& guid : gos)
|
||||
{
|
||||
GameObject* go = botAI->GetGameObject(guid);
|
||||
if (!go)
|
||||
continue;
|
||||
if (go->GetGoType() == GAMEOBJECT_TYPE_FISHINGHOLE)
|
||||
{
|
||||
float dist = player->GetDistance2d(go);
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
nearestFishingHole = go;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestFishingHole)
|
||||
return WorldPosition(nearestFishingHole->GetMapId(), nearestFishingHole->GetPositionX(), nearestFishingHole->GetPositionY(), nearestFishingHole->GetPositionZ());
|
||||
|
||||
return WorldPosition();
|
||||
}
|
||||
|
||||
bool MoveNearWaterAction::Execute(Event event)
|
||||
{
|
||||
WorldPosition landSpot = AI_VALUE(WorldPosition, "fishing spot");
|
||||
if (landSpot.IsValid())
|
||||
return MoveTo(landSpot.GetMapId(), landSpot.GetPositionX(), landSpot.GetPositionY(), landSpot.GetPositionZ());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveNearWaterAction::isUseful()
|
||||
{
|
||||
if (!AI_VALUE(bool, "can fish"))
|
||||
return false;
|
||||
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||
WorldPosition pos = fishingSpotValueObject->Get();
|
||||
return !pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) || pos != bot->GetPosition();
|
||||
}
|
||||
|
||||
bool MoveNearWaterAction::isPossible()
|
||||
{
|
||||
Player* master = botAI->GetMaster();
|
||||
float fishingSearchWindow;
|
||||
|
||||
if (master)
|
||||
fishingSearchWindow = sPlayerbotAIConfig->fishingDistanceFromMaster;
|
||||
else
|
||||
fishingSearchWindow = sPlayerbotAIConfig->fishingDistance;
|
||||
|
||||
WorldPosition fishingHole = FindFishingHole(botAI);
|
||||
|
||||
if (fishingHole.IsValid())
|
||||
{
|
||||
float distance = bot->GetExactDist2d(&fishingHole);
|
||||
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
|
||||
// Water spot is in range, and we have LOS to it. Set bot position to fishing spot and do not move
|
||||
if (distance >= MIN_DISTANCE_TO_WATER &&
|
||||
distance <= MAX_DISTANCE_TO_WATER && hasLOS)
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot", WorldPosition(WorldPosition(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())));
|
||||
return false;
|
||||
}
|
||||
// Water spot is out of range, lets look for a spot to move to for the fishing hole.
|
||||
if (distance > MAX_DISTANCE_TO_WATER || distance < MIN_DISTANCE_TO_WATER)
|
||||
{
|
||||
float angle = bot->GetAngle(fishingHole.GetPositionX(), fishingHole.GetPositionY());
|
||||
WorldPosition landSpot = FindLandRadialFromPosition(botAI, fishingHole, MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, fishingSearchWindow, 32);
|
||||
if (landSpot.IsValid())
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Lets find some water where we can fish.
|
||||
WorldPosition water = FindWaterRadial(
|
||||
bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
bot->GetMap(), bot->GetPhaseMask(),
|
||||
MIN_DISTANCE_TO_WATER,
|
||||
fishingSearchWindow + MAX_DISTANCE_TO_WATER,
|
||||
SEARCH_INCREMENT, false);
|
||||
|
||||
if (!water.IsValid())
|
||||
return false;
|
||||
|
||||
bool hasLOS = bot->IsWithinLOS(water.GetPositionX(), water.GetPositionY(), water.GetPositionZ());
|
||||
float angle = bot->GetAngle(water.GetPositionX(), water.GetPositionY());
|
||||
WorldPosition landSpot =
|
||||
FindLandFromPosition(botAI, 0.0f, MAX_DISTANCE_TO_WATER, 1.0f, angle, water, fishingSearchWindow, false);
|
||||
|
||||
if (landSpot.IsValid())
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EquipFishingPoleAction::Execute(Event event)
|
||||
{
|
||||
if (!_pole)
|
||||
return false;
|
||||
|
||||
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
eqPacket << _pole->GetGUID() << uint8(EQUIPMENT_SLOT_MAINHAND);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EquipFishingPoleAction::isUseful()
|
||||
{
|
||||
Item* mainHand = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||
if (IsFishingPole(mainHand))
|
||||
return false;
|
||||
|
||||
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
|
||||
{
|
||||
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||
{
|
||||
if (IsFishingPole(item))
|
||||
{
|
||||
_pole = item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
|
||||
{
|
||||
if (Bag* pBag = bot->GetBagByPos(bag))
|
||||
{
|
||||
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
|
||||
{
|
||||
if (Item* item = pBag->GetItemByPos(j))
|
||||
{
|
||||
if (IsFishingPole(item))
|
||||
{
|
||||
_pole = item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
{
|
||||
bot->StoreNewItemInBestSlots(FISHING_POLE, 1); // Try to get a fishing pole
|
||||
return true;
|
||||
}
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
std::string masterName = master->GetName();
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"no_fishing_pole_error", "I don't have a Fishing Pole",{});
|
||||
botAI->Whisper(text, masterName);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FishingAction::Execute(Event event)
|
||||
{
|
||||
WorldPosition target = WorldPosition();
|
||||
WorldPosition fishingHole = FindFishingHole(botAI);
|
||||
if (fishingHole.IsValid())
|
||||
{
|
||||
Position pos = fishingHole;
|
||||
float distance = bot->GetExactDist2d(&pos);
|
||||
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
|
||||
if (distance < MAX_DISTANCE_TO_WATER &&
|
||||
distance > MIN_DISTANCE_TO_WATER && hasLOS)
|
||||
target = fishingHole;
|
||||
}
|
||||
if (!target.IsValid())
|
||||
{
|
||||
target = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), bot->GetMap(), bot->GetPhaseMask(),
|
||||
MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, true, 32);
|
||||
if (!target.IsValid())
|
||||
return false;
|
||||
}
|
||||
Position pos = target;
|
||||
|
||||
if (!bot->HasInArc(1.0, &pos, 1.0))
|
||||
{
|
||||
float angle = bot->GetAngle(pos.GetPositionX(), pos.GetPositionY());
|
||||
bot->SetOrientation(angle);
|
||||
if (!bot->IsRooted())
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
|
||||
EquipFishingPoleAction equipAction(botAI);
|
||||
if (equipAction.isUseful())
|
||||
return equipAction.Execute(event);
|
||||
|
||||
botAI->CastSpell(FISHING_SPELL, bot);
|
||||
botAI->ChangeStrategy("+use bobber", BOT_STATE_NON_COMBAT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FishingAction::isUseful()
|
||||
{
|
||||
if (!AI_VALUE(bool, "can fish"))
|
||||
return false;
|
||||
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||
WorldPosition pos = fishingSpotValueObject->Get();
|
||||
|
||||
return pos.IsValid() && !fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) && pos == bot->GetPosition();
|
||||
}
|
||||
|
||||
bool UseBobberAction::isUseful()
|
||||
{
|
||||
return AI_VALUE(bool, "can use fishing bobber");
|
||||
}
|
||||
|
||||
bool UseBobberAction::Execute(Event event)
|
||||
{
|
||||
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los");
|
||||
for (auto const& guid : gos)
|
||||
{
|
||||
if (GameObject* go = botAI->GetGameObject(guid))
|
||||
{
|
||||
if (go->GetEntry() != FISHING_BOBBER)
|
||||
continue;
|
||||
if (go->GetOwnerGUID() != bot->GetGUID())
|
||||
continue;
|
||||
if (go->getLootState() == GO_READY)
|
||||
{
|
||||
go->Use(bot);
|
||||
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EndMasterFishingAction::Execute(Event event)
|
||||
{
|
||||
botAI->ChangeStrategy("-master fishing", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EndMasterFishingAction::isUseful()
|
||||
{
|
||||
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||
WorldPosition pos = fishingSpotValueObject->Get();
|
||||
if (pos.IsValid() && !fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) && pos == bot->GetPosition())
|
||||
return false;
|
||||
|
||||
WorldPosition nearWater = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
bot->GetMap(), bot->GetPhaseMask(), MIN_DISTANCE_TO_WATER, sPlayerbotAIConfig->endFishingWithMaster, 10.0f);
|
||||
return !nearWater.IsValid();
|
||||
}
|
||||
|
||||
bool RemoveBobberStrategyAction::Execute(Event event)
|
||||
{
|
||||
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
71
src/strategy/actions/FishingAction.h
Normal file
71
src/strategy/actions/FishingAction.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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_FISHINGACTION_H
|
||||
#define _PLAYERBOT_FISHINGACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "MovementActions.h"
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
extern const uint32 FISHING_SPELL;
|
||||
extern const uint32 FISHING_POLE;
|
||||
extern const uint32 FISHING_BOBBER;
|
||||
|
||||
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS=false, int numDirections = 16);
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class FishingAction : public Action
|
||||
{
|
||||
public:
|
||||
FishingAction(PlayerbotAI* botAI) : Action(botAI, "go fishing"){}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class EquipFishingPoleAction : public Action
|
||||
{
|
||||
public:
|
||||
EquipFishingPoleAction(PlayerbotAI* botAI) : Action(botAI, "equip fishing pole") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
private:
|
||||
Item* _pole = nullptr;
|
||||
};
|
||||
|
||||
class MoveNearWaterAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MoveNearWaterAction(PlayerbotAI* botAI): MovementAction(botAI, "move near water") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
bool isPossible() override;
|
||||
};
|
||||
|
||||
class UseBobberAction : public Action
|
||||
{
|
||||
public:
|
||||
UseBobberAction(PlayerbotAI* botAI) : Action(botAI, "use fishing bobber") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class EndMasterFishingAction : public Action
|
||||
{
|
||||
public:
|
||||
EndMasterFishingAction(PlayerbotAI* botAI) : Action(botAI, "end master fishing") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class RemoveBobberStrategyAction : public Action
|
||||
{
|
||||
public:
|
||||
RemoveBobberStrategyAction(PlayerbotAI* botAI) : Action(botAI, "remove bobber strategy") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
#endif
|
||||
@@ -70,7 +70,7 @@ bool FollowAction::isUseful()
|
||||
if (!target.empty())
|
||||
fTarget = AI_VALUE(Unit*, target);
|
||||
else
|
||||
fTarget = AI_VALUE(Unit*, "master target");
|
||||
fTarget = AI_VALUE(Unit*, "group leader");
|
||||
|
||||
if (fTarget)
|
||||
{
|
||||
@@ -97,6 +97,8 @@ bool FollowAction::isUseful()
|
||||
|
||||
distance = bot->GetDistance(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ());
|
||||
}
|
||||
if (botAI->HasStrategy("master fishing", BOT_STATE_NON_COMBAT))
|
||||
return sServerFacade->IsDistanceGreaterThan(distance, sPlayerbotAIConfig->fishingDistanceFromMaster);
|
||||
|
||||
return sServerFacade->IsDistanceGreaterThan(distance, formation->GetMaxDistance());
|
||||
}
|
||||
@@ -114,9 +116,9 @@ bool FollowAction::CanDeadFollow(Unit* target)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeToMasterAction::Execute(Event event)
|
||||
bool FleeToGroupLeaderAction::Execute(Event event)
|
||||
{
|
||||
Unit* fTarget = AI_VALUE(Unit*, "master target");
|
||||
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||
bool canFollow = Follow(fTarget);
|
||||
if (!canFollow)
|
||||
{
|
||||
@@ -146,22 +148,22 @@ bool FleeToMasterAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeToMasterAction::isUseful()
|
||||
bool FleeToGroupLeaderAction::isUseful()
|
||||
{
|
||||
if (!botAI->GetGroupMaster())
|
||||
if (!botAI->GetGroupLeader())
|
||||
return false;
|
||||
|
||||
if (botAI->GetGroupMaster() == bot)
|
||||
if (botAI->GetGroupLeader() == bot)
|
||||
return false;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (target && botAI->GetGroupMaster()->GetTarget() == target->GetGUID())
|
||||
if (target && botAI->GetGroupLeader()->GetTarget() == target->GetGUID())
|
||||
return false;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return false;
|
||||
|
||||
Unit* fTarget = AI_VALUE(Unit*, "master target");
|
||||
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||
|
||||
if (!CanDeadFollow(fTarget))
|
||||
return false;
|
||||
|
||||
@@ -20,10 +20,10 @@ public:
|
||||
bool CanDeadFollow(Unit* target);
|
||||
};
|
||||
|
||||
class FleeToMasterAction : public FollowAction
|
||||
class FleeToGroupLeaderAction : public FollowAction
|
||||
{
|
||||
public:
|
||||
FleeToMasterAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to master") {}
|
||||
FleeToGroupLeaderAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to group leader") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
@@ -265,11 +265,6 @@ CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "s
|
||||
}
|
||||
}
|
||||
|
||||
NextAction** CastSpellAction::getPrerequisites()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("attacker without aura", spell);
|
||||
|
||||
@@ -27,7 +27,11 @@ public:
|
||||
bool isUseful() override;
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::Single; }
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string const getSpell() { return spell; }
|
||||
|
||||
protected:
|
||||
@@ -193,10 +197,12 @@ public:
|
||||
ResurrectPartyMemberAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
|
||||
|
||||
std::string const GetTargetName() override { return "party member to resurrect"; }
|
||||
NextAction** getPrerequisites() override
|
||||
std::vector<NextAction> getPrerequisites() override
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("reach party member to resurrect"), NULL),
|
||||
Action::getPrerequisites());
|
||||
return NextAction::merge(
|
||||
{ NextAction("reach party member to resurrect") },
|
||||
Action::getPrerequisites()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ bool InviteNearbyToGroupAction::isUseful()
|
||||
if (group->isRaidGroup() && group->IsFull())
|
||||
return false;
|
||||
|
||||
if (botAI->GetGroupMaster() != bot)
|
||||
if (botAI->GetGroupLeader() != bot)
|
||||
return false;
|
||||
|
||||
uint32 memberCount = group->GetMembersCount();
|
||||
|
||||
@@ -109,22 +109,22 @@ bool LeaveFarAwayAction::isUseful()
|
||||
if (!bot->GetGroup())
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* trueMaster = botAI->GetMaster();
|
||||
if (!master || (bot == master && !botAI->IsRealPlayer()))
|
||||
if (!groupLeader || (bot == groupLeader && !botAI->IsRealPlayer()))
|
||||
return false;
|
||||
|
||||
PlayerbotAI* masterBotAI = nullptr;
|
||||
if (master)
|
||||
masterBotAI = GET_PLAYERBOT_AI(master);
|
||||
if (master && !masterBotAI)
|
||||
PlayerbotAI* groupLeaderBotAI = nullptr;
|
||||
if (groupLeader)
|
||||
groupLeaderBotAI = GET_PLAYERBOT_AI(groupLeader);
|
||||
if (groupLeader && !groupLeaderBotAI)
|
||||
return false;
|
||||
|
||||
if (trueMaster && !GET_PLAYERBOT_AI(trueMaster))
|
||||
return false;
|
||||
|
||||
if (botAI->IsAlt() &&
|
||||
(!masterBotAI || masterBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player master.
|
||||
(!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player groupLeader.
|
||||
return false;
|
||||
|
||||
if (botAI->GetGrouperType() == GrouperType::SOLO)
|
||||
@@ -138,19 +138,19 @@ bool LeaveFarAwayAction::isUseful()
|
||||
if (dCount > 4 && !botAI->HasRealPlayerMaster())
|
||||
return true;
|
||||
|
||||
if (bot->GetGuildId() == master->GetGuildId())
|
||||
if (bot->GetGuildId() == groupLeader->GetGuildId())
|
||||
{
|
||||
if (bot->GetLevel() > master->GetLevel() + 5)
|
||||
if (bot->GetLevel() > groupLeader->GetLevel() + 5)
|
||||
{
|
||||
if (AI_VALUE(bool, "should get money"))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (abs(int32(master->GetLevel() - bot->GetLevel())) > 4)
|
||||
if (abs(int32(groupLeader->GetLevel() - bot->GetLevel())) > 4)
|
||||
return true;
|
||||
|
||||
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance2d(master) >= 2 * sPlayerbotAIConfig->rpgDistance)
|
||||
if (bot->GetMapId() != groupLeader->GetMapId() || bot->GetDistance2d(groupLeader) >= 2 * sPlayerbotAIConfig->rpgDistance)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,90 +7,43 @@
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotSpellCache.h"
|
||||
|
||||
std::map<uint32, SkillLineAbilityEntry const*> ListSpellsAction::skillSpells;
|
||||
std::set<uint32> ListSpellsAction::vendorItems;
|
||||
using SpellListEntry = std::pair<uint32, std::string>;
|
||||
|
||||
bool CompareSpells(const std::pair<uint32, std::string>& s1, const std::pair<uint32, std::string>& s2)
|
||||
// CHANGE: Simplified and cheap comparator used in MapUpdater worker thread.
|
||||
// It now avoids scanning the entire SkillLineAbilityStore for each comparison
|
||||
// and only relies on spell school and spell name to keep sorting fast and bounded.
|
||||
// lhs = the left element, rhs = the right element.
|
||||
static bool CompareSpells(SpellListEntry const& lhSpell, SpellListEntry const& rhSpell)
|
||||
{
|
||||
SpellInfo const* si1 = sSpellMgr->GetSpellInfo(s1.first);
|
||||
SpellInfo const* si2 = sSpellMgr->GetSpellInfo(s2.first);
|
||||
if (!si1 || !si2)
|
||||
SpellInfo const* lhSpellInfo = sSpellMgr->GetSpellInfo(lhSpell.first);
|
||||
SpellInfo const* rhSpellInfo = sSpellMgr->GetSpellInfo(rhSpell.first);
|
||||
|
||||
if (!lhSpellInfo || !rhSpellInfo)
|
||||
{
|
||||
LOG_ERROR("playerbots", "SpellInfo missing. {} {}", s1.first, s2.first);
|
||||
return false;
|
||||
}
|
||||
uint32 p1 = si1->SchoolMask * 20000;
|
||||
uint32 p2 = si2->SchoolMask * 20000;
|
||||
|
||||
uint32 skill1 = 0, skill2 = 0;
|
||||
uint32 skillValue1 = 0, skillValue2 = 0;
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
{
|
||||
if (skillLine->Spell == s1.first)
|
||||
{
|
||||
skill1 = skillLine->SkillLine;
|
||||
skillValue1 = skillLine->TrivialSkillLineRankLow;
|
||||
}
|
||||
|
||||
if (skillLine->Spell == s2.first)
|
||||
{
|
||||
skill2 = skillLine->SkillLine;
|
||||
skillValue2 = skillLine->TrivialSkillLineRankLow;
|
||||
}
|
||||
}
|
||||
|
||||
if (skill1 && skill2)
|
||||
break;
|
||||
LOG_ERROR("playerbots", "SpellInfo missing for spell {} or {}", lhSpell.first, rhSpell.first);
|
||||
// Fallback: order by spell id to keep comparator strict and deterministic.
|
||||
return lhSpell.first < rhSpell.first;
|
||||
}
|
||||
|
||||
p1 += skill1 * 500;
|
||||
p2 += skill2 * 500;
|
||||
uint32 lhsKey = lhSpellInfo->SchoolMask;
|
||||
uint32 rhsKey = rhSpellInfo->SchoolMask;
|
||||
|
||||
p1 += skillValue1;
|
||||
p2 += skillValue2;
|
||||
|
||||
if (p1 == p2)
|
||||
if (lhsKey == rhsKey)
|
||||
{
|
||||
return strcmp(si1->SpellName[0], si2->SpellName[0]) > 0;
|
||||
}
|
||||
// Defensive check: if DBC data is broken and spell names are nullptr,
|
||||
// fall back to id ordering instead of risking a crash in std::strcmp.
|
||||
if (!lhSpellInfo->SpellName[0] || !rhSpellInfo->SpellName[0])
|
||||
return lhSpell.first < rhSpell.first;
|
||||
|
||||
return p1 > p2;
|
||||
return std::strcmp(lhSpellInfo->SpellName[0], rhSpellInfo->SpellName[0]) > 0;
|
||||
}
|
||||
return lhsKey > rhsKey;
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::string filter)
|
||||
{
|
||||
if (skillSpells.empty())
|
||||
{
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
skillSpells[skillLine->Spell] = skillLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendorItems.empty())
|
||||
{
|
||||
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
|
||||
if (results)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
int32 entry = fields[0].Get<int32>();
|
||||
if (entry <= 0)
|
||||
continue;
|
||||
|
||||
vendorItems.insert(entry);
|
||||
} while (results->NextRow());
|
||||
}
|
||||
}
|
||||
|
||||
std::ostringstream posOut;
|
||||
std::ostringstream negOut;
|
||||
|
||||
uint32 skill = 0;
|
||||
|
||||
std::vector<std::string> ss = split(filter, ' ');
|
||||
@@ -99,13 +52,15 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
skill = chat->parseSkill(ss[0]);
|
||||
if (skill != SKILL_NONE)
|
||||
{
|
||||
filter = ss.size() > 1 ? filter = ss[1] : "";
|
||||
filter = ss.size() > 1 ? ss[1] : "";
|
||||
}
|
||||
|
||||
if (ss[0] == "first" && ss[1] == "aid")
|
||||
// Guard access to ss[1]/ss[2] to avoid out-of-bounds
|
||||
// when the player only types "first" without "aid".
|
||||
if (ss[0] == "first" && ss.size() > 1 && ss[1] == "aid")
|
||||
{
|
||||
skill = SKILL_FIRST_AID;
|
||||
filter = ss.size() > 2 ? filter = ss[2] : "";
|
||||
filter = ss.size() > 2 ? ss[2] : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,26 +70,57 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
|
||||
uint32 minLevel = 0;
|
||||
uint32 maxLevel = 0;
|
||||
if (filter.find("-") != std::string::npos)
|
||||
if (filter.find('-') != std::string::npos)
|
||||
{
|
||||
std::vector<std::string> ff = split(filter, '-');
|
||||
minLevel = atoi(ff[0].c_str());
|
||||
maxLevel = atoi(ff[1].c_str());
|
||||
filter = "";
|
||||
if (ff.size() >= 2)
|
||||
{
|
||||
minLevel = std::atoi(ff[0].c_str());
|
||||
maxLevel = std::atoi(ff[1].c_str());
|
||||
if (minLevel > maxLevel)
|
||||
std::swap(minLevel, maxLevel);
|
||||
}
|
||||
filter.clear();
|
||||
}
|
||||
|
||||
bool craftableOnly = false;
|
||||
if (filter.find("+") != std::string::npos)
|
||||
bool canCraftNow = false;
|
||||
if (filter.find('+') != std::string::npos)
|
||||
{
|
||||
craftableOnly = true;
|
||||
canCraftNow = true;
|
||||
|
||||
// Support "+<skill>" syntax (e.g. "spells +tailoring" or "spells tailoring+").
|
||||
// If no explicit skill was detected yet, try to parse the filter (without '+')
|
||||
// as a profession/skill name so that craftable-only filters still work with skills.
|
||||
if (skill == SKILL_NONE)
|
||||
{
|
||||
std::string skillFilter = filter;
|
||||
|
||||
// Remove '+' before trying to interpret the first token as a skill name.
|
||||
skillFilter.erase(remove(skillFilter.begin(), skillFilter.end(), '+'), skillFilter.end());
|
||||
|
||||
std::vector<std::string> skillTokens = split(skillFilter, ' ');
|
||||
if (!skillTokens.empty())
|
||||
{
|
||||
uint32 parsedSkill = chat->parseSkill(skillTokens[0]);
|
||||
if (parsedSkill != SKILL_NONE)
|
||||
{
|
||||
skill = parsedSkill;
|
||||
|
||||
// Any remaining text after the skill token becomes the "name" filter
|
||||
// (e.g. "spells +tailoring cloth" -> skill = tailoring, filter = "cloth").
|
||||
filter = skillTokens.size() > 1 ? skillTokens[1] : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally remove '+' from the filter that will be used for name/range parsing.
|
||||
filter.erase(remove(filter.begin(), filter.end(), '+'), filter.end());
|
||||
}
|
||||
|
||||
uint32 slot = chat->parseSlot(filter);
|
||||
if (slot != EQUIPMENT_SLOT_END)
|
||||
filter = "";
|
||||
filter.clear();
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> spells;
|
||||
std::vector<SpellListEntry> spells;
|
||||
for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr)
|
||||
{
|
||||
if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active)
|
||||
@@ -150,7 +136,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
if (spellInfo->IsPassive())
|
||||
continue;
|
||||
|
||||
SkillLineAbilityEntry const* skillLine = skillSpells[itr->first];
|
||||
SkillLineAbilityEntry const* skillLine = sPlayerbotSpellCache->GetSkillLine(itr->first);
|
||||
if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill))
|
||||
continue;
|
||||
|
||||
@@ -162,7 +148,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
continue;
|
||||
|
||||
bool first = true;
|
||||
int32 craftCount = -1;
|
||||
int32 craftsPossible = -1;
|
||||
std::ostringstream materials;
|
||||
for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x)
|
||||
{
|
||||
@@ -189,12 +175,12 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
|
||||
FindItemByIdVisitor visitor(itemid);
|
||||
uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor);
|
||||
bool buyable = (vendorItems.find(itemid) != vendorItems.end());
|
||||
bool buyable = sPlayerbotSpellCache->IsItemBuyable(itemid);
|
||||
if (!buyable)
|
||||
{
|
||||
uint32 craftable = reagentsInInventory / reagentsRequired;
|
||||
if (craftCount < 0 || craftCount > craftable)
|
||||
craftCount = craftable;
|
||||
if (craftsPossible < 0 || craftsPossible > static_cast<int32>(craftable))
|
||||
craftsPossible = static_cast<int32>(craftable);
|
||||
}
|
||||
|
||||
if (reagentsInInventory)
|
||||
@@ -205,8 +191,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
}
|
||||
}
|
||||
|
||||
if (craftCount < 0)
|
||||
craftCount = 0;
|
||||
if (craftsPossible < 0)
|
||||
craftsPossible = 0;
|
||||
|
||||
std::ostringstream out;
|
||||
bool filtered = false;
|
||||
@@ -218,8 +204,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
{
|
||||
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(spellInfo->Effects[i].ItemType))
|
||||
{
|
||||
if (craftCount)
|
||||
out << "|cffffff00(x" << craftCount << ")|r ";
|
||||
if (craftsPossible)
|
||||
out << "|cffffff00(x" << craftsPossible << ")|r ";
|
||||
|
||||
out << chat->FormatItem(proto);
|
||||
|
||||
@@ -246,7 +232,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
if (filtered)
|
||||
continue;
|
||||
|
||||
if (craftableOnly && !craftCount)
|
||||
if (canCraftNow && !craftsPossible)
|
||||
continue;
|
||||
|
||||
out << materials.str();
|
||||
@@ -275,10 +261,9 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
continue;
|
||||
|
||||
if (itr->first == 0)
|
||||
{
|
||||
LOG_ERROR("playerbots", "?! {}", itr->first);
|
||||
}
|
||||
spells.push_back(std::pair<uint32, std::string>(itr->first, out.str()));
|
||||
|
||||
spells.emplace_back(itr->first, out.str());
|
||||
alreadySeenList += spellInfo->SpellName[0];
|
||||
alreadySeenList += ",";
|
||||
}
|
||||
@@ -294,25 +279,28 @@ bool ListSpellsAction::Execute(Event event)
|
||||
|
||||
std::string const filter = event.getParam();
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> spells = GetSpellList(filter);
|
||||
std::vector<SpellListEntry> spells = GetSpellList(filter);
|
||||
|
||||
if (spells.empty())
|
||||
{
|
||||
// CHANGE: Give early feedback when no spells match the filter.
|
||||
botAI->TellMaster("No spells found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
botAI->TellMaster("=== Spells ===");
|
||||
|
||||
std::sort(spells.begin(), spells.end(), CompareSpells);
|
||||
|
||||
uint32 count = 0;
|
||||
for (std::vector<std::pair<uint32, std::string>>::iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
{
|
||||
// CHANGE: Send the full spell list again so client-side addons
|
||||
// (e.g. Multibot / Unbot) can reconstruct the
|
||||
// complete spellbook for configuration. The heavy part that caused
|
||||
// freezes before was the old CompareSpells implementation scanning
|
||||
// the entire SkillLineAbility DBC on every comparison. With the new
|
||||
// cheap comparator above, sending all lines here is safe and keeps
|
||||
// behaviour compatible with existing addons.
|
||||
for (std::vector<SpellListEntry>::const_iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
botAI->TellMasterNoFacing(i->second);
|
||||
|
||||
// if (++count >= 50)
|
||||
// {
|
||||
// std::ostringstream msg;
|
||||
// msg << (spells.size() - 50) << " more...";
|
||||
// botAI->TellMasterNoFacing(msg.str());
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,8 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
virtual std::vector<std::pair<uint32, std::string>> GetSpellList(std::string filter = "");
|
||||
|
||||
private:
|
||||
static std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
|
||||
static std::set<uint32> vendorItems;
|
||||
static void InitSpellCaches();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
|
||||
WorldLocation location = *target->getPosition();
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat())
|
||||
if (group && !urand(0, 1) && bot == botAI->GetGroupLeader() && !bot->IsInCombat())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
|
||||
@@ -946,25 +946,7 @@ bool MovementAction::IsWaitingForLastMove(MovementPriority priority)
|
||||
|
||||
bool MovementAction::IsMovingAllowed()
|
||||
{
|
||||
// do not allow if not vehicle driver
|
||||
if (botAI->IsInVehicle() && !botAI->IsInVehicle(true))
|
||||
return false;
|
||||
|
||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
|
||||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
|
||||
return botAI->CanMove();
|
||||
}
|
||||
|
||||
bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); }
|
||||
@@ -972,12 +954,10 @@ bool MovementAction::Follow(Unit* target, float distance) { return Follow(target
|
||||
void MovementAction::UpdateMovementState()
|
||||
{
|
||||
const bool isCurrentlyRestricted = // see if the bot is currently slowed, rooted, or otherwise unable to move
|
||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL) ||
|
||||
bot->IsRooted() ||
|
||||
bot->isFrozen() ||
|
||||
bot->IsPolymorphed() ||
|
||||
bot->HasRootAura() ||
|
||||
bot->HasStunAura() ||
|
||||
bot->HasConfuseAura() ||
|
||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
|
||||
bot->IsPolymorphed();
|
||||
|
||||
// no update movement flags while movement is current restricted.
|
||||
if (!isCurrentlyRestricted && bot->IsAlive())
|
||||
@@ -1102,7 +1082,7 @@ void MovementAction::UpdateMovementState()
|
||||
// {
|
||||
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
|
||||
// {
|
||||
// if (pTarget != botAI->GetGroupMaster())
|
||||
// if (pTarget != botAI->GetGroupLeader())
|
||||
// return;
|
||||
|
||||
// if (!bot->IsWithinMeleeRange(pTarget))
|
||||
@@ -2663,7 +2643,7 @@ bool DisperseSetAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "master target")); }
|
||||
bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "group leader")); }
|
||||
|
||||
bool MoveToLootAction::Execute(Event event)
|
||||
{
|
||||
|
||||
@@ -18,14 +18,14 @@ bool PetsAction::Execute(Event event)
|
||||
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
|
||||
std::string param = event.getParam();
|
||||
if (param.empty() && !defaultCmd.empty())
|
||||
{
|
||||
param = defaultCmd;
|
||||
}
|
||||
|
||||
if (param.empty())
|
||||
{
|
||||
// If no parameter is provided, show usage instructions and return.
|
||||
botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_usage_error", "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ bool PetsAction::Execute(Event event)
|
||||
// If no pets or guardians are found, notify and return.
|
||||
if (targets.empty())
|
||||
{
|
||||
botAI->TellError("You have no pet or guardian pet.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_no_pet_error", "You have no pet or guardian pet.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -63,42 +65,54 @@ bool PetsAction::Execute(Event event)
|
||||
if (param == "aggressive")
|
||||
{
|
||||
react = REACT_AGGRESSIVE;
|
||||
stanceText = "aggressive";
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_aggressive", "aggressive", {});
|
||||
}
|
||||
else if (param == "defensive")
|
||||
{
|
||||
react = REACT_DEFENSIVE;
|
||||
stanceText = "defensive";
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_defensive", "defensive", {});
|
||||
}
|
||||
else if (param == "passive")
|
||||
{
|
||||
react = REACT_PASSIVE;
|
||||
stanceText = "passive";
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_passive", "passive", {});
|
||||
}
|
||||
// The "stance" command simply reports the current stance of each pet/guardian.
|
||||
else if (param == "stance")
|
||||
{
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
std::string type = target->IsPet() ? "pet" : "guardian";
|
||||
std::string type = target->IsPet() ?
|
||||
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_pet", "pet", {}) :
|
||||
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_guardian", "guardian", {});
|
||||
std::string name = target->GetName();
|
||||
std::string stance;
|
||||
switch (target->GetReactState())
|
||||
{
|
||||
case REACT_AGGRESSIVE:
|
||||
stance = "aggressive";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_aggressive", "aggressive", {});
|
||||
break;
|
||||
case REACT_DEFENSIVE:
|
||||
stance = "defensive";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_defensive", "defensive", {});
|
||||
break;
|
||||
case REACT_PASSIVE:
|
||||
stance = "passive";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_passive", "passive", {});
|
||||
break;
|
||||
default:
|
||||
stance = "unknown";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_unknown", "unknown", {});
|
||||
break;
|
||||
}
|
||||
botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + ".");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_report", "Current stance of %type \"%name\": %stance.",
|
||||
{{"type", type}, {"name", name}, {"stance", stance}});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -113,25 +127,38 @@ bool PetsAction::Execute(Event event)
|
||||
{
|
||||
ObjectGuid masterTargetGuid = master->GetTarget();
|
||||
if (!masterTargetGuid.IsEmpty())
|
||||
{
|
||||
targetUnit = botAI->GetUnit(masterTargetGuid);
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid target is selected, show an error and return.
|
||||
if (!targetUnit)
|
||||
{
|
||||
botAI->TellError("No valid target selected by master.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_no_target_error", "No valid target selected by master.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (!targetUnit->IsAlive())
|
||||
{
|
||||
botAI->TellError("Target is not alive.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_target_dead_error", "Target is not alive.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (!bot->IsValidAttackTarget(targetUnit))
|
||||
{
|
||||
botAI->TellError("Target is not a valid attack target for the bot.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_invalid_target_error", "Target is not a valid attack target for the bot.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) &&
|
||||
(targetUnit->IsPlayer() || targetUnit->IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != targetUnit))
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -182,9 +209,17 @@ bool PetsAction::Execute(Event event)
|
||||
}
|
||||
// Inform the master if the command succeeded or failed.
|
||||
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to attack your target.");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_attack_success", "Pet commanded to attack your target.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
else if (!didAttack)
|
||||
botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {});
|
||||
botAI->TellError(text);
|
||||
}
|
||||
return didAttack;
|
||||
}
|
||||
// The "follow" command makes all pets/guardians follow the bot.
|
||||
@@ -192,7 +227,11 @@ bool PetsAction::Execute(Event event)
|
||||
{
|
||||
botAI->PetFollow();
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to follow.");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_follow_success", "Pet commanded to follow.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// The "stay" command causes all pets/guardians to stop and stay in place.
|
||||
@@ -229,14 +268,20 @@ bool PetsAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to stay.");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stay_success", "Pet commanded to stay.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Unknown command: show usage instructions and return.
|
||||
else
|
||||
{
|
||||
botAI->TellError("Unknown pet command: " + param +
|
||||
". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_unknown_command_error", "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
{{"param", param}});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -251,7 +296,12 @@ bool PetsAction::Execute(Event event)
|
||||
|
||||
// Inform the master of the new stance if debug is enabled.
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet stance set to " + stanceText + ".");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_set_success", "Pet stance set to %stance.",
|
||||
{{"stance", stanceText}});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,10 @@ bool RandomBotUpdateAction::Execute(Event event)
|
||||
if (!sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
return false;
|
||||
|
||||
if (bot->GetGroup() && botAI->GetGroupMaster())
|
||||
if (bot->GetGroup() && botAI->GetGroupLeader())
|
||||
{
|
||||
PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
|
||||
if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
|
||||
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader());
|
||||
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ReadyCheckAction.h"
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "ReadyCheckAction.h"
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
@@ -27,14 +30,17 @@ std::string const formatPercent(std::string const name, uint8 value, float perce
|
||||
class ReadyChecker
|
||||
{
|
||||
public:
|
||||
virtual ~ReadyChecker() = default;
|
||||
virtual bool Check(PlayerbotAI* botAI, AiObjectContext* context) = 0;
|
||||
virtual std::string const getName() = 0;
|
||||
virtual bool PrintAlways() { return true; }
|
||||
|
||||
static std::vector<ReadyChecker*> checkers;
|
||||
static std::vector<std::unique_ptr<ReadyChecker>> checkers;
|
||||
static std::once_flag initFlag;
|
||||
};
|
||||
|
||||
std::vector<ReadyChecker*> ReadyChecker::checkers;
|
||||
std::vector<std::unique_ptr<ReadyChecker>> ReadyChecker::checkers;
|
||||
std::once_flag ReadyChecker::initFlag;
|
||||
|
||||
class HealthChecker : public ReadyChecker
|
||||
{
|
||||
@@ -160,25 +166,30 @@ bool ReadyCheckAction::Execute(Event event)
|
||||
|
||||
bool ReadyCheckAction::ReadyCheck()
|
||||
{
|
||||
if (ReadyChecker::checkers.empty())
|
||||
{
|
||||
ReadyChecker::checkers.push_back(new HealthChecker());
|
||||
ReadyChecker::checkers.push_back(new ManaChecker());
|
||||
ReadyChecker::checkers.push_back(new DistanceChecker());
|
||||
ReadyChecker::checkers.push_back(new HunterChecker());
|
||||
std::call_once(
|
||||
ReadyChecker::initFlag,
|
||||
[]()
|
||||
{
|
||||
ReadyChecker::checkers.reserve(8);
|
||||
|
||||
ReadyChecker::checkers.push_back(new ItemCountChecker("food", "Food"));
|
||||
ReadyChecker::checkers.push_back(new ManaPotionChecker("drink", "Water"));
|
||||
ReadyChecker::checkers.push_back(new ItemCountChecker("healing potion", "Hpot"));
|
||||
ReadyChecker::checkers.push_back(new ManaPotionChecker("mana potion", "Mpot"));
|
||||
}
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<HealthChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<DistanceChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<HunterChecker>());
|
||||
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("food", "Food"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("drink", "Water"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("healing potion", "Hpot"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("mana potion", "Mpot"));
|
||||
});
|
||||
|
||||
bool result = true;
|
||||
for (std::vector<ReadyChecker*>::iterator i = ReadyChecker::checkers.begin(); i != ReadyChecker::checkers.end();
|
||||
++i)
|
||||
for (auto const& checkerPtr : ReadyChecker::checkers)
|
||||
{
|
||||
ReadyChecker* checker = *i;
|
||||
bool ok = checker->Check(botAI, context);
|
||||
if (!checkerPtr)
|
||||
continue;
|
||||
|
||||
bool ok = checkerPtr->Check(botAI, context);
|
||||
result = result && ok;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,15 +168,15 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
||||
if (!bot->GetGroup())
|
||||
return true;
|
||||
|
||||
Player* groupMaster = botAI->GetGroupMaster();
|
||||
if (!groupMaster || groupMaster == bot)
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
if (!groupLeader || groupLeader == bot)
|
||||
return true;
|
||||
|
||||
if (!botAI->HasActivePlayerMaster())
|
||||
return true;
|
||||
|
||||
if (botAI->HasActivePlayerMaster() &&
|
||||
groupMaster->GetMapId() == bot->GetMapId() &&
|
||||
groupLeader->GetMapId() == bot->GetMapId() &&
|
||||
bot->GetMap() &&
|
||||
(bot->GetMap()->IsRaid() || bot->GetMap()->IsDungeon()))
|
||||
{
|
||||
@@ -184,7 +184,7 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
||||
}
|
||||
|
||||
return sServerFacade->IsDistanceGreaterThan(
|
||||
AI_VALUE2(float, "distance", "master target"),
|
||||
AI_VALUE2(float, "distance", "group leader"),
|
||||
sPlayerbotAIConfig->sightDistance);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,4 +16,4 @@ bool ResetInstancesAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResetInstancesAction::isUseful() { return botAI->GetGroupMaster() == bot; };
|
||||
bool ResetInstancesAction::isUseful() { return botAI->GetGroupLeader() == bot; };
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
|
||||
bool ReviveFromCorpseAction::Execute(Event event)
|
||||
{
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Corpse* corpse = bot->GetCorpse();
|
||||
|
||||
// follow master when master revives
|
||||
// follow group Leader when group Leader revives
|
||||
WorldPacket& p = event.getPacket();
|
||||
if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && master && !corpse && bot->IsAlive())
|
||||
if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && groupLeader && !corpse && bot->IsAlive())
|
||||
{
|
||||
if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
sPlayerbotAIConfig->farDistance))
|
||||
{
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
@@ -43,10 +43,10 @@ bool ReviveFromCorpseAction::Execute(Event event)
|
||||
// time(nullptr))
|
||||
// return false;
|
||||
|
||||
if (master)
|
||||
if (groupLeader)
|
||||
{
|
||||
if (!GET_PLAYERBOT_AI(master) && master->isDead() && master->GetCorpse() &&
|
||||
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
if (!GET_PLAYERBOT_AI(groupLeader) && groupLeader->isDead() && groupLeader->GetCorpse() &&
|
||||
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
sPlayerbotAIConfig->farDistance))
|
||||
return false;
|
||||
}
|
||||
@@ -79,15 +79,15 @@ bool FindCorpseAction::Execute(Event event)
|
||||
if (bot->InBattleground())
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Corpse* corpse = bot->GetCorpse();
|
||||
if (!corpse)
|
||||
return false;
|
||||
|
||||
// if (master)
|
||||
// if (groupLeader)
|
||||
// {
|
||||
// if (!GET_PLAYERBOT_AI(master) &&
|
||||
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
// if (!GET_PLAYERBOT_AI(groupLeader) &&
|
||||
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
// sPlayerbotAIConfig->farDistance)) return false;
|
||||
// }
|
||||
|
||||
@@ -110,20 +110,20 @@ bool FindCorpseAction::Execute(Event event)
|
||||
WorldPosition botPos(bot);
|
||||
WorldPosition corpsePos(corpse);
|
||||
WorldPosition moveToPos = corpsePos;
|
||||
WorldPosition masterPos(master);
|
||||
WorldPosition leaderPos(groupLeader);
|
||||
|
||||
float reclaimDist = CORPSE_RECLAIM_RADIUS - 5.0f;
|
||||
float corpseDist = botPos.distance(corpsePos);
|
||||
int64 deadTime = time(nullptr) - corpse->GetGhostTime();
|
||||
|
||||
bool moveToMaster = master && master != bot && masterPos.fDist(corpsePos) < reclaimDist;
|
||||
bool moveToLeader = groupLeader && groupLeader != bot && leaderPos.fDist(corpsePos) < reclaimDist;
|
||||
|
||||
// Should we ressurect? If so, return false.
|
||||
if (corpseDist < reclaimDist)
|
||||
{
|
||||
if (moveToMaster) // We are near master.
|
||||
if (moveToLeader) // We are near group leader.
|
||||
{
|
||||
if (botPos.fDist(masterPos) < sPlayerbotAIConfig->spellDistance)
|
||||
if (botPos.fDist(leaderPos) < sPlayerbotAIConfig->spellDistance)
|
||||
return false;
|
||||
}
|
||||
else if (deadTime > 8 * MINUTE) // We have walked too long already.
|
||||
@@ -140,8 +140,8 @@ bool FindCorpseAction::Execute(Event event)
|
||||
// If we are getting close move to a save ressurrection spot instead of just the corpse.
|
||||
if (corpseDist < sPlayerbotAIConfig->reactDistance)
|
||||
{
|
||||
if (moveToMaster)
|
||||
moveToPos = masterPos;
|
||||
if (moveToLeader)
|
||||
moveToPos = leaderPos;
|
||||
else
|
||||
{
|
||||
FleeManager manager(bot, reclaimDist, 0.0, urand(0, 1), moveToPos);
|
||||
@@ -215,12 +215,12 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
|
||||
if (!startZone && ClosestGrave)
|
||||
return ClosestGrave;
|
||||
|
||||
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupMaster() && botAI->GetGroupMaster() != bot)
|
||||
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupLeader() && botAI->GetGroupLeader() != bot)
|
||||
{
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
if (master && master != bot)
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
if (groupLeader && groupLeader != bot)
|
||||
{
|
||||
ClosestGrave = sGraveyard->GetClosestGraveyard(master, bot->GetTeamId());
|
||||
ClosestGrave = sGraveyard->GetClosestGraveyard(groupLeader, bot->GetTeamId());
|
||||
|
||||
if (ClosestGrave)
|
||||
return ClosestGrave;
|
||||
|
||||
@@ -35,8 +35,8 @@ bool RewardAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
Unit* mtar = AI_VALUE(Unit*, "master target");
|
||||
if (mtar && Reward(itemId, mtar))
|
||||
Unit* groupLeaderUnit = AI_VALUE(Unit*, "group leader");
|
||||
if (groupLeaderUnit && Reward(itemId, groupLeaderUnit))
|
||||
return true;
|
||||
|
||||
botAI->TellError("Cannot talk to quest giver");
|
||||
|
||||
@@ -68,17 +68,15 @@ bool RpgAction::SetNextRpgAction()
|
||||
|
||||
triggerNode->setTrigger(trigger);
|
||||
|
||||
NextAction** nextActions = triggerNode->getHandlers();
|
||||
std::vector<NextAction> nextActions = triggerNode->getHandlers();
|
||||
|
||||
Trigger* trigger = triggerNode->getTrigger();
|
||||
|
||||
bool isChecked = false;
|
||||
|
||||
for (int32 i = 0; i < NextAction::size(nextActions); i++)
|
||||
for (NextAction nextAction : nextActions)
|
||||
{
|
||||
NextAction* nextAction = nextActions[i];
|
||||
|
||||
if (nextAction->getRelevance() > 5.0f)
|
||||
if (nextAction.getRelevance() > 5.0f)
|
||||
continue;
|
||||
|
||||
if (!isChecked && !trigger->IsActive())
|
||||
@@ -86,14 +84,13 @@ bool RpgAction::SetNextRpgAction()
|
||||
|
||||
isChecked = true;
|
||||
|
||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction->getName());
|
||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName());
|
||||
if (!dynamic_cast<RpgEnabled*>(action) || !action->isPossible() || !action->isUseful())
|
||||
continue;
|
||||
|
||||
actions.push_back(action);
|
||||
relevances.push_back((nextAction->getRelevance() - 1) * 500);
|
||||
relevances.push_back((nextAction.getRelevance() - 1) * 500);
|
||||
}
|
||||
NextAction::destroy(nextActions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ void RpgHelper::setFacing(GuidPosition guidPosition)
|
||||
|
||||
void RpgHelper::setDelay(bool waitForGroup)
|
||||
{
|
||||
if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupMaster() == bot && bot->GetGroup()))
|
||||
if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupLeader() == bot && bot->GetGroup()))
|
||||
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay);
|
||||
else
|
||||
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay / 5);
|
||||
|
||||
@@ -22,8 +22,8 @@ bool SecurityCheckAction::Execute(Event event)
|
||||
ItemQualities threshold = group->GetLootThreshold();
|
||||
if (method == MASTER_LOOT || method == FREE_FOR_ALL || threshold > ITEM_QUALITY_UNCOMMON)
|
||||
{
|
||||
if ((botAI->GetGroupMaster()->GetSession()->GetSecurity() == SEC_PLAYER) &&
|
||||
(!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupMaster()->GetGuildId()))
|
||||
if ((botAI->GetGroupLeader()->GetSession()->GetSecurity() == SEC_PLAYER) &&
|
||||
(!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupLeader()->GetGuildId()))
|
||||
{
|
||||
botAI->TellError("I will play with this loot type only if I'm in your guild :/");
|
||||
botAI->ChangeStrategy("+passive,+stay", BOT_STATE_NON_COMBAT);
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include "PositionValue.h"
|
||||
#include "ByteBuffer.h"
|
||||
|
||||
std::set<uint32> const FISHING_SPELLS = {7620, 7731, 7732, 18248, 33095, 51294};
|
||||
|
||||
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
|
||||
bool important)
|
||||
{
|
||||
@@ -57,6 +59,16 @@ bool SeeSpellAction::Execute(Event event)
|
||||
// if (!botAI->HasStrategy("RTSC", botAI->GetState()))
|
||||
// return false;
|
||||
|
||||
if (FISHING_SPELLS.find(spellId) != FISHING_SPELLS.end())
|
||||
{
|
||||
if (AI_VALUE(bool, "can fish") && sPlayerbotAIConfig->enableFishingWithMaster)
|
||||
{
|
||||
botAI->ChangeStrategy("+master fishing", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spellId != RTSC_MOVE_SPELL)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ bool OutOfReactRangeAction::Execute(Event event)
|
||||
|
||||
bool OutOfReactRangeAction::isUseful()
|
||||
{
|
||||
bool canFollow = Follow(AI_VALUE(Unit*, "master target"));
|
||||
bool canFollow = Follow(AI_VALUE(Unit*, "group leader"));
|
||||
if (!canFollow)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg)
|
||||
void TrainerAction::Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg)
|
||||
{
|
||||
if (sPlayerbotAIConfig->autoTrainSpells != "free" && !botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
@@ -23,7 +23,7 @@ void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostrings
|
||||
bot->ModifyMoney(-int32(cost));
|
||||
}
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell->spell);
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell.SpellId);
|
||||
if (!spellInfo)
|
||||
return;
|
||||
|
||||
@@ -41,10 +41,8 @@ void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostrings
|
||||
}
|
||||
}
|
||||
|
||||
if (!learned && !bot->HasSpell(tSpell->spell))
|
||||
{
|
||||
bot->learnSpell(tSpell->spell);
|
||||
}
|
||||
if (!learned && !bot->HasSpell(tSpell.SpellId))
|
||||
bot->learnSpell(tSpell.SpellId);
|
||||
|
||||
msg << " - learned";
|
||||
}
|
||||
@@ -53,37 +51,35 @@ void TrainerAction::Iterate(Creature* creature, TrainerSpellAction action, Spell
|
||||
{
|
||||
TellHeader(creature);
|
||||
|
||||
TrainerSpellData const* trainer_spells = creature->GetTrainerSpells();
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
|
||||
|
||||
if (!trainer)
|
||||
return;
|
||||
|
||||
float fDiscountMod = bot->GetReputationPriceDiscount(creature);
|
||||
uint32 totalCost = 0;
|
||||
|
||||
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
|
||||
itr != trainer_spells->spellList.end(); ++itr)
|
||||
for (auto& spell : trainer->GetSpells())
|
||||
{
|
||||
TrainerSpell const* tSpell = &itr->second;
|
||||
if (!tSpell)
|
||||
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
|
||||
continue;
|
||||
|
||||
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
|
||||
if (state != TRAINER_SPELL_GREEN)
|
||||
if (!spells.empty() && spells.find(spell.SpellId) == spells.end())
|
||||
continue;
|
||||
|
||||
uint32 spellId = tSpell->spell;
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId);
|
||||
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (!spells.empty() && spells.find(tSpell->spell) == spells.end())
|
||||
continue;
|
||||
|
||||
uint32 cost = uint32(floor(tSpell->spellCost * fDiscountMod));
|
||||
uint32 cost = uint32(floor(spell.MoneyCost * fDiscountMod));
|
||||
totalCost += cost;
|
||||
|
||||
std::ostringstream out;
|
||||
out << chat->FormatSpell(spellInfo) << chat->formatMoney(cost);
|
||||
|
||||
if (action)
|
||||
(this->*action)(cost, tSpell, out);
|
||||
(this->*action)(cost, spell, out);
|
||||
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
@@ -112,15 +108,14 @@ bool TrainerAction::Execute(Event event)
|
||||
if (!creature || !creature->IsTrainer())
|
||||
return false;
|
||||
|
||||
if (!creature->IsValidTrainerForPlayer(bot))
|
||||
{
|
||||
botAI->TellError("This trainer cannot teach me");
|
||||
return false;
|
||||
}
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
|
||||
|
||||
// check present spell in trainer spell list
|
||||
TrainerSpellData const* cSpells = creature->GetTrainerSpells();
|
||||
if (!cSpells)
|
||||
if (!trainer || !trainer->IsTrainerValidForPlayer(bot))
|
||||
return false;
|
||||
|
||||
std::vector<Trainer::Spell> trainer_spells = trainer->GetSpells();
|
||||
|
||||
if (trainer_spells.empty())
|
||||
{
|
||||
botAI->TellError("No spells can be learned from this trainer");
|
||||
return false;
|
||||
@@ -133,7 +128,7 @@ bool TrainerAction::Execute(Event event)
|
||||
|
||||
if (text.find("learn") != std::string::npos || sRandomPlayerbotMgr->IsRandomBot(bot) ||
|
||||
(sPlayerbotAIConfig->autoTrainSpells != "no" &&
|
||||
(creature->GetCreatureTemplate()->trainer_type != TRAINER_TYPE_TRADESKILLS ||
|
||||
(trainer->GetTrainerType() != Trainer::Type::Tradeskill ||
|
||||
!botAI->HasActivePlayerMaster()))) // Todo rewrite to only exclude start primary profession skills and make
|
||||
// config dependent.
|
||||
Iterate(creature, &TrainerAction::Learn, spells);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "Action.h"
|
||||
#include "ChatHelper.h"
|
||||
#include "Trainer.h"
|
||||
|
||||
class Creature;
|
||||
class PlayerbotAI;
|
||||
@@ -22,9 +23,9 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
typedef void (TrainerAction::*TrainerSpellAction)(uint32, TrainerSpell const*, std::ostringstream& msg);
|
||||
typedef void (TrainerAction::*TrainerSpellAction)(uint32, const Trainer::Spell, std::ostringstream& msg);
|
||||
void Iterate(Creature* creature, TrainerSpellAction action, SpellIds& spells);
|
||||
void Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg);
|
||||
void Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg);
|
||||
void TellHeader(Creature* creature);
|
||||
void TellFooter(uint32 totalCost);
|
||||
};
|
||||
|
||||
@@ -64,7 +64,7 @@ bool MoveToDarkPortalAction::Execute(Event event)
|
||||
{
|
||||
if (bot->GetGroup())
|
||||
if (bot->GetGroup()->GetLeaderGUID() != bot->GetGUID() &&
|
||||
!GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupMaster()))
|
||||
!GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupLeader()))
|
||||
return false;
|
||||
|
||||
if (bot->GetLevel() > 57)
|
||||
|
||||
@@ -52,7 +52,7 @@ bool UseMeetingStoneAction::Execute(Event event)
|
||||
if (!goInfo || goInfo->entry != 179944)
|
||||
return false;
|
||||
|
||||
return Teleport(master, bot);
|
||||
return Teleport(master, bot, false);
|
||||
}
|
||||
|
||||
bool SummonAction::Execute(Event event)
|
||||
@@ -70,16 +70,16 @@ bool SummonAction::Execute(Event event)
|
||||
{
|
||||
// botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({});
|
||||
AI_VALUE(std::list<FleeInfo>&, "recently flee info").clear();
|
||||
return Teleport(master, bot);
|
||||
return Teleport(master, bot, true);
|
||||
}
|
||||
|
||||
if (SummonUsingGos(master, bot) || SummonUsingNpcs(master, bot))
|
||||
if (SummonUsingGos(master, bot, true) || SummonUsingNpcs(master, bot, true))
|
||||
{
|
||||
botAI->TellMasterNoFacing("Hello!");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SummonUsingGos(bot, master) || SummonUsingNpcs(bot, master))
|
||||
if (SummonUsingGos(bot, master, true) || SummonUsingNpcs(bot, master, true))
|
||||
{
|
||||
botAI->TellMasterNoFacing("Welcome!");
|
||||
return true;
|
||||
@@ -88,7 +88,7 @@ bool SummonAction::Execute(Event event)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SummonAction::SummonUsingGos(Player* summoner, Player* player)
|
||||
bool SummonAction::SummonUsingGos(Player* summoner, Player* player, bool preserveAuras)
|
||||
{
|
||||
std::list<GameObject*> targets;
|
||||
AnyGameObjectInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig->sightDistance);
|
||||
@@ -98,14 +98,14 @@ bool SummonAction::SummonUsingGos(Player* summoner, Player* player)
|
||||
for (GameObject* go : targets)
|
||||
{
|
||||
if (go->isSpawned() && go->GetGoType() == GAMEOBJECT_TYPE_MEETINGSTONE)
|
||||
return Teleport(summoner, player);
|
||||
return Teleport(summoner, player, preserveAuras);
|
||||
}
|
||||
|
||||
botAI->TellError(summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
|
||||
bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->summonAtInnkeepersEnabled)
|
||||
return false;
|
||||
@@ -139,7 +139,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
|
||||
Spell spell(player, spellInfo, TRIGGERED_NONE);
|
||||
spell.SendSpellCooldown();
|
||||
|
||||
return Teleport(summoner, player);
|
||||
return Teleport(summoner, player, preserveAuras);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SummonAction::Teleport(Player* summoner, Player* player)
|
||||
bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras)
|
||||
{
|
||||
// Player* master = GetMaster();
|
||||
if (!summoner)
|
||||
@@ -208,7 +208,11 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
|
||||
|
||||
player->GetMotionMaster()->Clear();
|
||||
AI_VALUE(LastMovement&, "last movement").clear();
|
||||
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||
|
||||
if (!preserveAuras)
|
||||
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED |
|
||||
AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||
|
||||
player->TeleportTo(mapId, x, y, z, 0);
|
||||
|
||||
if (botAI->HasStrategy("stay", botAI->GetState()))
|
||||
|
||||
@@ -19,9 +19,9 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
|
||||
protected:
|
||||
bool Teleport(Player* summoner, Player* player);
|
||||
bool SummonUsingGos(Player* summoner, Player* player);
|
||||
bool SummonUsingNpcs(Player* summoner, Player* player);
|
||||
bool Teleport(Player* summoner, Player* player, bool preserveAuras);
|
||||
bool SummonUsingGos(Player* summoner, Player* player, bool preserveAuras);
|
||||
bool SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras);
|
||||
};
|
||||
|
||||
class UseMeetingStoneAction : public SummonAction
|
||||
|
||||
@@ -12,25 +12,10 @@ class BloodDKStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||
public:
|
||||
BloodDKStrategyActionNodeFactory()
|
||||
{
|
||||
// creators["melee"] = &melee;
|
||||
// creators["blood strike"] = &blood_strike;
|
||||
creators["rune strike"] = &rune_strike;
|
||||
creators["heart strike"] = &heart_strike;
|
||||
creators["death strike"] = &death_strike;
|
||||
// creators["death grip"] = &death_grip;
|
||||
// creators["plague strike"] = &plague_strike;
|
||||
// creators["pestilence"] = &pestilence;
|
||||
creators["icy touch"] = &icy_touch;
|
||||
// creators["obliterate"] = &obliterate;
|
||||
// creators["blood boil"] = &blood_boil;
|
||||
// creators["mark of_blood"] = &mark_of_blood;
|
||||
// creators["blood presence"] = &blood_presence;
|
||||
// creators["rune tap"] = &rune_tap;
|
||||
// creators["vampiric blood"] = &vampiric_blood;
|
||||
// creators["death pact"] = &death_pact;
|
||||
// creators["death rune_mastery"] = &death_rune_mastery;
|
||||
// creators["hysteria"] = &hysteria;
|
||||
// creators["dancing weapon"] = &dancing_weapon;
|
||||
creators["dark command"] = &dark_command;
|
||||
creators["taunt spell"] = &dark_command;
|
||||
}
|
||||
@@ -38,39 +23,61 @@ public:
|
||||
private:
|
||||
static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("rune strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"rune strike",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("icy touch",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"icy touch",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* heart_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("heart strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"heart strike",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("death strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"death strike",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* dark_command([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("dark command",
|
||||
/*P*/ NextAction::array(0, new NextAction("frost presence"), NULL),
|
||||
/*A*/ NextAction::array(0, new NextAction("death grip"), NULL),
|
||||
/*C*/ NULL);
|
||||
return new ActionNode(
|
||||
"dark command",
|
||||
{
|
||||
NextAction("frost presence")
|
||||
},
|
||||
/*A*/ {
|
||||
NextAction("death grip")
|
||||
},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,33 +86,80 @@ BloodDKStrategy::BloodDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
|
||||
actionNodeFactories.Add(new BloodDKStrategyActionNodeFactory());
|
||||
}
|
||||
|
||||
NextAction** BloodDKStrategy::getDefaultActions()
|
||||
std::vector<NextAction> BloodDKStrategy::getDefaultActions()
|
||||
{
|
||||
return NextAction::array(
|
||||
0, new NextAction("rune strike", ACTION_DEFAULT + 0.8f), new NextAction("icy touch", ACTION_DEFAULT + 0.7f),
|
||||
new NextAction("heart strike", ACTION_DEFAULT + 0.6f), new NextAction("blood strike", ACTION_DEFAULT + 0.5f),
|
||||
new NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
|
||||
new NextAction("death coil", ACTION_DEFAULT + 0.3f), new NextAction("plague strike", ACTION_DEFAULT + 0.2f),
|
||||
new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL);
|
||||
return {
|
||||
NextAction("rune strike", ACTION_DEFAULT + 0.8f),
|
||||
NextAction("icy touch", ACTION_DEFAULT + 0.7f),
|
||||
NextAction("heart strike", ACTION_DEFAULT + 0.6f),
|
||||
NextAction("blood strike", ACTION_DEFAULT + 0.5f),
|
||||
NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
|
||||
NextAction("death coil", ACTION_DEFAULT + 0.3f),
|
||||
NextAction("plague strike", ACTION_DEFAULT + 0.2f),
|
||||
NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
|
||||
NextAction("melee", ACTION_DEFAULT)
|
||||
};
|
||||
}
|
||||
|
||||
void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
GenericDKStrategy::InitTriggers(triggers);
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"rune strike", NextAction::array(0, new NextAction("rune strike", ACTION_NORMAL + 3), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("blood tap", NextAction::array(0, new NextAction("blood tap", ACTION_HIGH + 5), nullptr)));
|
||||
new TriggerNode(
|
||||
"rune strike",
|
||||
{
|
||||
NextAction("rune strike", ACTION_NORMAL + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("lose aggro", NextAction::array(0, new NextAction("dark command", ACTION_HIGH + 3), nullptr)));
|
||||
new TriggerNode(
|
||||
"blood tap",
|
||||
{
|
||||
NextAction("blood tap", ACTION_HIGH + 5)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("low health", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 4),
|
||||
new NextAction("death strike", ACTION_HIGH + 3), nullptr)));
|
||||
new TriggerNode(
|
||||
"lose aggro",
|
||||
{
|
||||
NextAction("dark command", ACTION_HIGH + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("critical health", NextAction::array(0, new NextAction("vampiric blood", ACTION_HIGH + 5), nullptr)));
|
||||
new TriggerNode(
|
||||
"low health",
|
||||
{
|
||||
NextAction("army of the dead", ACTION_HIGH + 4),
|
||||
NextAction("death strike", ACTION_HIGH + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
|
||||
new TriggerNode(
|
||||
"critical health",
|
||||
{
|
||||
NextAction("vampiric blood", ACTION_HIGH + 5)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"icy touch",
|
||||
{
|
||||
NextAction("icy touch", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"plague strike",
|
||||
{
|
||||
NextAction("plague strike", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
std::string const getName() override { return "blood"; }
|
||||
NextAction** getDefaultActions() override;
|
||||
std::vector<NextAction> getDefaultActions() override;
|
||||
uint32 GetType() const override { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; }
|
||||
};
|
||||
|
||||
|
||||
@@ -11,39 +11,40 @@
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellMgr.h"
|
||||
|
||||
NextAction** CastDeathchillAction::getPrerequisites()
|
||||
std::vector<NextAction> CastDeathchillAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
return NextAction::merge({ NextAction("frost presence") },
|
||||
CastSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
NextAction** CastUnholyMeleeSpellAction::getPrerequisites()
|
||||
std::vector<NextAction> CastUnholyMeleeSpellAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("unholy presence"), nullptr),
|
||||
return NextAction::merge({ NextAction("unholy presence") },
|
||||
CastMeleeSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
NextAction** CastFrostMeleeSpellAction::getPrerequisites()
|
||||
std::vector<NextAction> CastFrostMeleeSpellAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("frost presence"), nullptr),
|
||||
return NextAction::merge({ NextAction("frost presence") },
|
||||
CastMeleeSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
NextAction** CastBloodMeleeSpellAction::getPrerequisites()
|
||||
std::vector<NextAction> CastBloodMeleeSpellAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
return NextAction::merge({ NextAction("blood presence") },
|
||||
CastMeleeSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
bool CastRaiseDeadAction::Execute(Event event)
|
||||
{
|
||||
bool result = CastBuffSpellAction::Execute(event);
|
||||
const bool result = CastBuffSpellAction::Execute(event);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
||||
// SpellInfo const *spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
|
||||
const uint32_t spellId = AI_VALUE2(uint32_t, "spell id", spell);
|
||||
|
||||
bot->AddSpellCooldown(spellId, 0, 3 * 60 * 1000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class CastDeathchillAction : public CastBuffSpellAction
|
||||
public:
|
||||
CastDeathchillAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "deathchill") {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
};
|
||||
|
||||
class CastDarkCommandAction : public CastSpellAction
|
||||
@@ -52,7 +52,7 @@ class CastUnholyMeleeSpellAction : public CastMeleeSpellAction
|
||||
public:
|
||||
CastUnholyMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
};
|
||||
|
||||
// Frost presence
|
||||
@@ -61,7 +61,7 @@ class CastFrostMeleeSpellAction : public CastMeleeSpellAction
|
||||
public:
|
||||
CastFrostMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
};
|
||||
|
||||
// Blood presence
|
||||
@@ -70,7 +70,7 @@ class CastBloodMeleeSpellAction : public CastMeleeSpellAction
|
||||
public:
|
||||
CastBloodMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
};
|
||||
|
||||
class CastRuneStrikeAction : public CastMeleeSpellAction
|
||||
@@ -79,10 +79,6 @@ public:
|
||||
CastRuneStrikeAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "rune strike") {}
|
||||
};
|
||||
|
||||
// debuff
|
||||
// BEGIN_DEBUFF_ACTION(CastPestilenceAction, "pestilence")
|
||||
// END_SPELL_ACTION()
|
||||
|
||||
class CastPestilenceAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
@@ -90,20 +86,12 @@ public:
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::None; }
|
||||
};
|
||||
|
||||
// debuff
|
||||
// BEGIN_DEBUFF_ACTION(CastHowlingBlastAction, "howling blast")
|
||||
// END_SPELL_ACTION()
|
||||
|
||||
class CastHowlingBlastAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastHowlingBlastAction(PlayerbotAI* ai) : CastSpellAction(ai, "howling blast") {}
|
||||
};
|
||||
|
||||
// debuff it
|
||||
// BEGIN_DEBUFF_ACTION(CastIcyTouchAction, "icy touch")
|
||||
// END_SPELL_ACTION()
|
||||
|
||||
class CastIcyTouchAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
@@ -126,8 +114,6 @@ class CastPlagueStrikeAction : public CastSpellAction
|
||||
public:
|
||||
CastPlagueStrikeAction(PlayerbotAI* ai) : CastSpellAction(ai, "plague strike") {}
|
||||
};
|
||||
// BEGIN_DEBUFF_ACTION(CastPlagueStrikeAction, "plague strike")
|
||||
// END_SPELL_ACTION()
|
||||
|
||||
class CastPlagueStrikeOnAttackerAction : public CastDebuffSpellOnMeleeAttackerAction
|
||||
{
|
||||
|
||||
@@ -16,66 +16,68 @@ public:
|
||||
creators["obliterate"] = &obliterate;
|
||||
creators["howling blast"] = &howling_blast;
|
||||
creators["frost strike"] = &frost_strike;
|
||||
// creators["chains of ice"] = &chains_of_ice;
|
||||
creators["rune strike"] = &rune_strike;
|
||||
// creators["icy clutch"] = &icy_clutch;
|
||||
// creators["horn of winter"] = &horn_of_winter;
|
||||
// creators["killing machine"] = &killing_machine;
|
||||
// creators["frost presence"] = &frost_presence;
|
||||
// creators["deathchill"] = &deathchill;
|
||||
// creators["icebound fortitude"] = &icebound_fortitude;
|
||||
// creators["mind freeze"] = &mind_freeze;
|
||||
// creators["hungering cold"] = &hungering_cold;
|
||||
creators["unbreakable armor"] = &unbreakable_armor;
|
||||
// creators["improved icy talons"] = &improved_icy_talons;
|
||||
}
|
||||
|
||||
private:
|
||||
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("icy touch",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"icy touch",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* obliterate([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("obliterate",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"obliterate",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("rune strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"rune strike",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ { NextAction("melee") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* frost_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("frost strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"frost strike",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* howling_blast([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("howling blast",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"howling blast",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* unbreakable_armor([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("unbreakable armor",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood tap"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"unbreakable armor",
|
||||
/*P*/ { NextAction("blood tap") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,41 +86,84 @@ FrostDKStrategy::FrostDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
|
||||
actionNodeFactories.Add(new FrostDKStrategyActionNodeFactory());
|
||||
}
|
||||
|
||||
NextAction** FrostDKStrategy::getDefaultActions()
|
||||
std::vector<NextAction> FrostDKStrategy::getDefaultActions()
|
||||
{
|
||||
return NextAction::array(
|
||||
0, new NextAction("obliterate", ACTION_DEFAULT + 0.7f),
|
||||
new NextAction("frost strike", ACTION_DEFAULT + 0.4f),
|
||||
new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
|
||||
new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL);
|
||||
return {
|
||||
NextAction("obliterate", ACTION_DEFAULT + 0.7f),
|
||||
NextAction("frost strike", ACTION_DEFAULT + 0.4f),
|
||||
NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
|
||||
NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
|
||||
NextAction("melee", ACTION_DEFAULT)
|
||||
};
|
||||
}
|
||||
|
||||
void FrostDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
GenericDKStrategy::InitTriggers(triggers);
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"unbreakable armor", NextAction::array(0, new NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"freezing fog", NextAction::array(0, new NextAction("howling blast", ACTION_DEFAULT + 0.5f), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_DEFAULT + 0.2f), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"army of the dead", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 6), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"unbreakable armor",
|
||||
{
|
||||
NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
|
||||
// triggers.push_back(new TriggerNode("empower rune weapon", NextAction::array(0, new NextAction("empower rune
|
||||
// weapon", ACTION_NORMAL + 4), nullptr)));
|
||||
new TriggerNode(
|
||||
"freezing fog",
|
||||
{
|
||||
NextAction("howling blast", ACTION_DEFAULT + 0.5f)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"high blood rune",
|
||||
{
|
||||
NextAction("blood strike", ACTION_DEFAULT + 0.2f)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"army of the dead",
|
||||
{
|
||||
NextAction("army of the dead", ACTION_HIGH + 6)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"icy touch",
|
||||
{
|
||||
NextAction("icy touch", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"plague strike",
|
||||
{
|
||||
NextAction("plague strike", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
void FrostDKAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(
|
||||
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("howling blast", ACTION_HIGH + 4), nullptr)));
|
||||
new TriggerNode(
|
||||
"medium aoe",
|
||||
{
|
||||
NextAction("howling blast", ACTION_HIGH + 4)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
std::string const getName() override { return "frost"; }
|
||||
NextAction** getDefaultActions() override;
|
||||
std::vector<NextAction> getDefaultActions() override;
|
||||
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; }
|
||||
};
|
||||
|
||||
|
||||
@@ -20,17 +20,17 @@ private:
|
||||
static ActionNode* bone_shield([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("bone shield",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* horn_of_winter([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("horn of winter",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,19 +44,18 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
||||
NonCombatStrategy::InitTriggers(triggers);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 1), nullptr)));
|
||||
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 1) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("horn of winter", NextAction::array(0, new NextAction("horn of winter", 21.0f), nullptr)));
|
||||
new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr)));
|
||||
new TriggerNode("bone shield", { NextAction("bone shield", 21.0f) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), NULL)));
|
||||
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), NULL)));
|
||||
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
|
||||
}
|
||||
|
||||
void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
// triggers.push_back(new TriggerNode("improved icy talons", NextAction::array(0, new NextAction("improved icy
|
||||
// talons", 19.0f), nullptr)));
|
||||
|
||||
}
|
||||
|
||||
@@ -54,105 +54,105 @@ private:
|
||||
static ActionNode* death_coil([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("death coil",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* death_grip([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("death grip",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("icy touch"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("icy touch") },
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* plague_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("plague strike",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("icy touch",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* heart_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("heart strike",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* pestilence([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("pestilence",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* horn_of_winter([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("horn of winter",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* bone_shield([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("bone shield",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* killing_machine([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("killing machine",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("improved icy talons"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("improved icy talons") },
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("corpse explosion",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* death_and_decay([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("death and decay",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* anti_magic_zone([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("anti magic zone",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("anti magic shell"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("anti magic shell") },
|
||||
/*C*/ {});
|
||||
}
|
||||
|
||||
static ActionNode* icebound_fortitude([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("icebound fortitude",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -165,36 +165,29 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
MeleeCombatStrategy::InitTriggers(triggers);
|
||||
|
||||
// triggers.push_back(new TriggerNode("high aoe", NextAction::array(0, new NextAction("anti magic shell",
|
||||
// ACTION_NORMAL + 3), nullptr))); triggers.push_back(new TriggerNode("death coil", NextAction::array(0, new
|
||||
// NextAction("death coil", ACTION_NORMAL + 3), nullptr))); triggers.push_back(new TriggerNode("critical aoe heal",
|
||||
// NextAction::array(0, new NextAction("anti magic zone", ACTION_EMERGENCY + 1), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 5), nullptr)));
|
||||
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 5) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
|
||||
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
|
||||
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr)));
|
||||
new TriggerNode("mind freeze", { NextAction("mind freeze", ACTION_HIGH + 1) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("mind freeze on enemy healer",
|
||||
NextAction::array(0, new NextAction("mind freeze on enemy healer", ACTION_HIGH + 1), nullptr)));
|
||||
{ NextAction("mind freeze on enemy healer", ACTION_HIGH + 1) }));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"horn of winter", NextAction::array(0, new NextAction("horn of winter", ACTION_NORMAL + 1), nullptr)));
|
||||
"horn of winter", { NextAction("horn of winter", ACTION_NORMAL + 1) }));
|
||||
triggers.push_back(new TriggerNode("critical health",
|
||||
NextAction::array(0, new NextAction("death pact", ACTION_HIGH + 5), nullptr)));
|
||||
{ NextAction("death pact", ACTION_HIGH + 5) }));
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("low health", NextAction::array(0, new NextAction("icebound fortitude", ACTION_HIGH + 5),
|
||||
new NextAction("rune tap", ACTION_HIGH + 4), nullptr)));
|
||||
new TriggerNode("low health", { NextAction("icebound fortitude", ACTION_HIGH + 5),
|
||||
NextAction("rune tap", ACTION_HIGH + 4) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("death and decay", ACTION_HIGH + 9),
|
||||
new NextAction("pestilence", ACTION_NORMAL + 4),
|
||||
new NextAction("blood boil", ACTION_NORMAL + 3), nullptr)));
|
||||
// triggers.push_back(new TriggerNode("light aoe", NextAction::array(0,
|
||||
// new NextAction("pestilence", ACTION_NORMAL + 4),
|
||||
// nullptr)));
|
||||
new TriggerNode("medium aoe", { NextAction("death and decay", ACTION_HIGH + 9),
|
||||
NextAction("pestilence", ACTION_NORMAL + 4),
|
||||
NextAction("blood boil", ACTION_NORMAL + 3) }));
|
||||
triggers.push_back(
|
||||
new TriggerNode("pestilence glyph", NextAction::array(0, new NextAction("pestilence", ACTION_HIGH + 9), NULL)));
|
||||
new TriggerNode("pestilence glyph", { NextAction("pestilence", ACTION_HIGH + 9) }));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#/*
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -11,21 +11,8 @@ class UnholyDKStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||
public:
|
||||
UnholyDKStrategyActionNodeFactory()
|
||||
{
|
||||
// Unholy
|
||||
// creators["bone shield"] = &bone_shield;
|
||||
// creators["plague strike"] = &plague_strike;
|
||||
// creators["death grip"] = &death_grip;
|
||||
// creators["death coil"] = &death_coil;
|
||||
creators["death strike"] = &death_strike;
|
||||
// creators["unholy blight"] = &unholy_blight;
|
||||
creators["scourge strike"] = &scourge_strike;
|
||||
// creators["death and decay"] = &death_and_decay;
|
||||
// creators["unholy pressence"] = &unholy_pressence;
|
||||
// creators["raise dead"] = &raise_dead;
|
||||
// creators["army of the dead"] = &army of the dead;
|
||||
// creators["summon gargoyle"] = &army of the dead;
|
||||
// creators["anti magic shell"] = &anti_magic_shell;
|
||||
// creators["anti magic zone"] = &anti_magic_zone;
|
||||
creators["ghoul frenzy"] = &ghoul_frenzy;
|
||||
creators["corpse explosion"] = &corpse_explosion;
|
||||
creators["icy touch"] = &icy_touch;
|
||||
@@ -34,39 +21,49 @@ public:
|
||||
private:
|
||||
static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("death strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"death strike",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* ghoul_frenzy([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("ghoul frenzy",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"ghoul frenzy",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("corpse explosion",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"corpse explosion",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* scourge_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("scourge strike",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"scourge strike",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("icy touch",
|
||||
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"icy touch",
|
||||
/*P*/ { NextAction("blood presence") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,69 +72,121 @@ UnholyDKStrategy::UnholyDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI
|
||||
actionNodeFactories.Add(new UnholyDKStrategyActionNodeFactory());
|
||||
}
|
||||
|
||||
NextAction** UnholyDKStrategy::getDefaultActions()
|
||||
std::vector<NextAction> UnholyDKStrategy::getDefaultActions()
|
||||
{
|
||||
return NextAction::array(
|
||||
0, new NextAction("death and decay", ACTION_HIGH + 5),
|
||||
new NextAction("summon gargoyle", ACTION_DEFAULT + 0.4f),
|
||||
// new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
|
||||
new NextAction("horn of winter", ACTION_DEFAULT + 0.2f),
|
||||
new NextAction("death coil", ACTION_DEFAULT + 0.1f),
|
||||
new NextAction("melee", ACTION_DEFAULT), nullptr);
|
||||
return {
|
||||
NextAction("death and decay", ACTION_HIGH + 5),
|
||||
NextAction("summon gargoyle", ACTION_DEFAULT + 0.4f),
|
||||
NextAction("horn of winter", ACTION_DEFAULT + 0.2f),
|
||||
NextAction("death coil", ACTION_DEFAULT + 0.1f),
|
||||
NextAction("melee", ACTION_DEFAULT)
|
||||
};
|
||||
}
|
||||
|
||||
void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
GenericDKStrategy::InitTriggers(triggers);
|
||||
triggers.push_back(new TriggerNode(
|
||||
"death and decay cooldown", NextAction::array(0,
|
||||
new NextAction("ghoul frenzy", ACTION_DEFAULT + 0.9f),
|
||||
new NextAction("scourge strike", ACTION_DEFAULT + 0.8f),
|
||||
new NextAction("icy touch", ACTION_DEFAULT + 0.7f),
|
||||
new NextAction("blood strike", ACTION_DEFAULT + 0.6f),
|
||||
new NextAction("plague strike", ACTION_DEFAULT + 0.5f),
|
||||
nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode("dd cd and no desolation",
|
||||
NextAction::array(0, new NextAction("blood strike", ACTION_DEFAULT + 0.75f), nullptr)));
|
||||
|
||||
// triggers.push_back(
|
||||
// new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
|
||||
// triggers.push_back(new TriggerNode(
|
||||
// "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 1), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"high frost rune", NextAction::array(0,
|
||||
new NextAction("icy touch", ACTION_NORMAL + 3), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_NORMAL + 2), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"high unholy rune", NextAction::array(0,
|
||||
new NextAction("plague strike", ACTION_NORMAL + 1), nullptr)));
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("dd cd and plague strike 3s", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 1), nullptr)));
|
||||
new TriggerNode(
|
||||
"death and decay cooldown",
|
||||
{
|
||||
NextAction("ghoul frenzy", ACTION_DEFAULT + 0.9f),
|
||||
NextAction("scourge strike", ACTION_DEFAULT + 0.8f),
|
||||
NextAction("icy touch", ACTION_DEFAULT + 0.7f),
|
||||
NextAction("blood strike", ACTION_DEFAULT + 0.6f),
|
||||
NextAction("plague strike", ACTION_DEFAULT + 0.5f),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("dd cd and icy touch 3s", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
|
||||
|
||||
new TriggerNode(
|
||||
"dd cd and no desolation",
|
||||
{
|
||||
NextAction("blood strike", ACTION_DEFAULT + 0.75f)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("no rune", NextAction::array(0, new NextAction("empower rune weapon", ACTION_HIGH + 1), nullptr)));
|
||||
|
||||
// triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction(, ACTION_NORMAL + 2), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"army of the dead", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 6), nullptr)));
|
||||
new TriggerNode(
|
||||
"high frost rune",
|
||||
{
|
||||
NextAction("icy touch", ACTION_NORMAL + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", ACTION_HIGH + 3), nullptr)));
|
||||
new TriggerNode(
|
||||
"high blood rune",
|
||||
{
|
||||
NextAction("blood strike", ACTION_NORMAL + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"high unholy rune",
|
||||
{
|
||||
NextAction("plague strike", ACTION_NORMAL + 1)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("dd cd and plague strike 3s",
|
||||
{
|
||||
NextAction("plague strike", ACTION_HIGH + 1)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("dd cd and icy touch 3s",
|
||||
{
|
||||
NextAction("icy touch", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("no rune",
|
||||
{
|
||||
NextAction("empower rune weapon", ACTION_HIGH + 1)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"army of the dead",
|
||||
{
|
||||
NextAction("army of the dead", ACTION_HIGH + 6)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("bone shield",
|
||||
{
|
||||
NextAction("bone shield", ACTION_HIGH + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void UnholyDKAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(new TriggerNode(
|
||||
"loot available", NextAction::array(0, new NextAction("corpse explosion", ACTION_NORMAL + 1), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"medium aoe", NextAction::array(0, new NextAction("death and decay", ACTION_NORMAL + 3),
|
||||
new NextAction("corpse explosion", ACTION_NORMAL + 3), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"loot available",
|
||||
{
|
||||
NextAction("corpse explosion", ACTION_NORMAL + 1)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"medium aoe",
|
||||
{
|
||||
NextAction("death and decay", ACTION_NORMAL + 3),
|
||||
NextAction("corpse explosion", ACTION_NORMAL + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
std::string const getName() override { return "unholy"; }
|
||||
NextAction** getDefaultActions() override;
|
||||
std::vector<NextAction> getDefaultActions() override;
|
||||
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; }
|
||||
};
|
||||
|
||||
|
||||
@@ -30,107 +30,132 @@ public:
|
||||
private:
|
||||
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("melee",
|
||||
/*P*/ NextAction::array(0, new NextAction("feral charge - bear"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"melee",
|
||||
/*P*/ { NextAction("feral charge - bear") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* feral_charge_bear([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("feral charge - bear",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("reach melee"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"feral charge - bear",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("reach melee") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* swipe_bear([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("swipe (bear)",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"swipe (bear)",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("faerie fire (feral)",
|
||||
/*P*/ NextAction::array(0, new NextAction("feral charge - bear"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"faerie fire (feral)",
|
||||
/*P*/ { NextAction("feral charge - bear") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* bear_form([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("bear form",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"bear form",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* dire_bear_form([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("dire bear form",
|
||||
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
|
||||
/*A*/ NextAction::array(0, new NextAction("bear form"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"dire bear form",
|
||||
/*P*/ { NextAction("caster form") },
|
||||
/*A*/ { NextAction("bear form") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* mangle_bear([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("mangle (bear)",
|
||||
/*P*/ nullptr,
|
||||
// /*A*/ NextAction::array(0, new NextAction("lacerate"), nullptr),
|
||||
nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"mangle (bear)",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* maul([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("maul",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"maul",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("melee") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* bash([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("bash",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"bash",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("melee") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* swipe([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("swipe",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"swipe",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("melee") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* lacerate([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("lacerate",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("maul"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"lacerate",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("maul") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* growl([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("growl",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"growl",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* demoralizing_roar([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("demoralizing roar",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"demoralizing roar",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,38 +164,93 @@ BearTankDruidStrategy::BearTankDruidStrategy(PlayerbotAI* botAI) : FeralDruidStr
|
||||
actionNodeFactories.Add(new BearTankDruidStrategyActionNodeFactory());
|
||||
}
|
||||
|
||||
NextAction** BearTankDruidStrategy::getDefaultActions()
|
||||
std::vector<NextAction> BearTankDruidStrategy::getDefaultActions()
|
||||
{
|
||||
return NextAction::array(
|
||||
0, new NextAction("mangle (bear)", ACTION_DEFAULT + 0.5f),
|
||||
new NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.4f), new NextAction("lacerate", ACTION_DEFAULT + 0.3f),
|
||||
new NextAction("maul", ACTION_DEFAULT + 0.2f), new NextAction("enrage", ACTION_DEFAULT + 0.1f),
|
||||
new NextAction("melee", ACTION_DEFAULT), nullptr);
|
||||
return {
|
||||
NextAction("mangle (bear)", ACTION_DEFAULT + 0.5f),
|
||||
NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.4f),
|
||||
NextAction("lacerate", ACTION_DEFAULT + 0.3f),
|
||||
NextAction("maul", ACTION_DEFAULT + 0.2f),
|
||||
NextAction("enrage", ACTION_DEFAULT + 0.1f),
|
||||
NextAction("melee", ACTION_DEFAULT)
|
||||
};
|
||||
}
|
||||
|
||||
void BearTankDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
FeralDruidStrategy::InitTriggers(triggers);
|
||||
triggers.push_back(new TriggerNode(
|
||||
"enemy out of melee", NextAction::array(0, new NextAction("feral charge - bear", ACTION_NORMAL + 8), nullptr)));
|
||||
// triggers.push_back(new TriggerNode("thorns", NextAction::array(0, new NextAction("thorns", ACTION_HIGH + 9),
|
||||
// nullptr)));
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("bear form", NextAction::array(0, new NextAction("dire bear form", ACTION_HIGH + 8), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"low health", NextAction::array(0, new NextAction("frenzied regeneration", ACTION_HIGH + 7), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"faerie fire (feral)", NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_HIGH + 7), nullptr)));
|
||||
new TriggerNode(
|
||||
"enemy out of melee",
|
||||
{
|
||||
NextAction("feral charge - bear", ACTION_NORMAL + 8)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("lose aggro", NextAction::array(0, new NextAction("growl", ACTION_HIGH + 8), nullptr)));
|
||||
new TriggerNode(
|
||||
"bear form",
|
||||
{
|
||||
NextAction("dire bear form", ACTION_HIGH + 8)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("demoralizing roar", ACTION_HIGH + 6),
|
||||
new NextAction("swipe (bear)", ACTION_HIGH + 6), nullptr)));
|
||||
new TriggerNode(
|
||||
"low health",
|
||||
{
|
||||
NextAction("frenzied regeneration", ACTION_HIGH + 7)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("light aoe", NextAction::array(0, new NextAction("swipe (bear)", ACTION_HIGH + 5), nullptr)));
|
||||
new TriggerNode(
|
||||
"faerie fire (feral)",
|
||||
{
|
||||
NextAction("faerie fire (feral)", ACTION_HIGH + 7)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("bash", NextAction::array(0, new NextAction("bash", ACTION_INTERRUPT + 2), nullptr)));
|
||||
new TriggerNode(
|
||||
"lose aggro",
|
||||
{
|
||||
NextAction("growl", ACTION_HIGH + 8)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("bash on enemy healer",
|
||||
NextAction::array(0, new NextAction("bash on enemy healer", ACTION_INTERRUPT + 1), nullptr)));
|
||||
new TriggerNode(
|
||||
"medium aoe",
|
||||
{
|
||||
NextAction("demoralizing roar", ACTION_HIGH + 6),
|
||||
NextAction("swipe (bear)", ACTION_HIGH + 6)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"light aoe",
|
||||
{
|
||||
NextAction("swipe (bear)", ACTION_HIGH + 5)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"bash",
|
||||
{
|
||||
NextAction("bash", ACTION_INTERRUPT + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"bash on enemy healer",
|
||||
{
|
||||
NextAction("bash on enemy healer", ACTION_INTERRUPT + 1)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
std::string const getName() override { return "bear"; }
|
||||
NextAction** getDefaultActions() override;
|
||||
std::vector<NextAction> getDefaultActions() override;
|
||||
uint32 GetType() const override { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; }
|
||||
};
|
||||
|
||||
|
||||
@@ -28,82 +28,102 @@ public:
|
||||
private:
|
||||
static ActionNode* faerie_fire([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("faerie fire",
|
||||
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"faerie fire",
|
||||
/*P*/ { NextAction("moonkin form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* hibernate([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("hibernate",
|
||||
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
|
||||
/*A*/ NextAction::array(0, new NextAction("entangling roots"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"hibernate",
|
||||
/*P*/ { NextAction("moonkin form") },
|
||||
/*A*/ { NextAction("entangling roots") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* entangling_roots([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("entangling roots",
|
||||
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"entangling roots",
|
||||
/*P*/ { NextAction("moonkin form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* entangling_roots_on_cc([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("entangling roots on cc",
|
||||
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"entangling roots on cc",
|
||||
/*P*/ { NextAction("moonkin form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* wrath([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("wrath",
|
||||
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"wrath",
|
||||
/*P*/ { NextAction("moonkin form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* starfall([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("starfall",
|
||||
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"starfall",
|
||||
/*P*/ { NextAction("moonkin form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* insect_swarm([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("insect swarm",
|
||||
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"insect swarm",
|
||||
/*P*/ { NextAction("moonkin form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* moonfire([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("moonfire",
|
||||
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"moonfire",
|
||||
/*P*/ { NextAction("moonkin form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* starfire([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("starfire",
|
||||
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"starfire",
|
||||
/*P*/ { NextAction("moonkin form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* moonkin_form([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("moonkin form",
|
||||
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"moonkin form",
|
||||
/*P*/ { NextAction("caster form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,55 +133,122 @@ CasterDruidStrategy::CasterDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrat
|
||||
actionNodeFactories.Add(new ShapeshiftDruidStrategyActionNodeFactory());
|
||||
}
|
||||
|
||||
NextAction** CasterDruidStrategy::getDefaultActions()
|
||||
std::vector<NextAction> CasterDruidStrategy::getDefaultActions()
|
||||
{
|
||||
return NextAction::array(0,
|
||||
new NextAction("starfall", ACTION_HIGH + 1.0f),
|
||||
new NextAction("force of nature", ACTION_DEFAULT + 1.0f),
|
||||
new NextAction("wrath", ACTION_DEFAULT + 0.1f),
|
||||
// new NextAction("starfire", ACTION_NORMAL),
|
||||
nullptr);
|
||||
return {
|
||||
NextAction("starfall", ACTION_HIGH + 1.0f),
|
||||
NextAction("force of nature", ACTION_DEFAULT + 1.0f),
|
||||
NextAction("wrath", ACTION_DEFAULT + 0.1f),
|
||||
};
|
||||
}
|
||||
|
||||
void CasterDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
GenericDruidStrategy::InitTriggers(triggers);
|
||||
|
||||
// triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell",
|
||||
// ACTION_MOVE), nullptr)));
|
||||
triggers.push_back(new TriggerNode("eclipse (lunar) cooldown",
|
||||
NextAction::array(0, new NextAction("starfire", ACTION_DEFAULT + 0.2f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("eclipse (solar) cooldown",
|
||||
NextAction::array(0, new NextAction("wrath", ACTION_DEFAULT + 0.2f), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"insect swarm", NextAction::array(0, new NextAction("insect swarm", ACTION_NORMAL + 5), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("moonfire", NextAction::array(0, new NextAction("moonfire", ACTION_NORMAL + 4), nullptr)));
|
||||
new TriggerNode(
|
||||
"eclipse (lunar) cooldown",
|
||||
{
|
||||
NextAction("starfire", ACTION_DEFAULT + 0.2f)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("eclipse (solar)", NextAction::array(0, new NextAction("wrath", ACTION_NORMAL + 6), nullptr)));
|
||||
triggers.push_back(new TriggerNode("eclipse (lunar)",
|
||||
NextAction::array(0, new NextAction("starfire", ACTION_NORMAL + 6), nullptr)));
|
||||
new TriggerNode(
|
||||
"eclipse (solar) cooldown",
|
||||
{
|
||||
NextAction("wrath", ACTION_DEFAULT + 0.2f)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("medium mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 9), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode("enemy too close for spell",
|
||||
NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr)));
|
||||
new TriggerNode(
|
||||
"insect swarm",
|
||||
{
|
||||
NextAction("insect swarm", ACTION_NORMAL + 5)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"moonfire",
|
||||
{
|
||||
NextAction("moonfire", ACTION_NORMAL + 4)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"eclipse (solar)",
|
||||
{
|
||||
NextAction("wrath", ACTION_NORMAL + 6)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"eclipse (lunar)",
|
||||
{
|
||||
NextAction("starfire", ACTION_NORMAL + 6)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"medium mana",
|
||||
{
|
||||
NextAction("innervate", ACTION_HIGH + 9)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"enemy too close for spell",
|
||||
{
|
||||
NextAction("flee", ACTION_MOVE + 9)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void CasterDruidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(
|
||||
new TriggerNode("hurricane channel check", NextAction::array(0, new NextAction("cancel channel", ACTION_HIGH + 2), nullptr)));
|
||||
new TriggerNode(
|
||||
"hurricane channel check",
|
||||
{
|
||||
NextAction("cancel channel", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("hurricane", ACTION_HIGH + 1), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"light aoe", NextAction::array(0, new NextAction("insect swarm on attacker", ACTION_NORMAL + 3),
|
||||
new NextAction("moonfire on attacker", ACTION_NORMAL + 3), NULL)));
|
||||
new TriggerNode(
|
||||
"medium aoe",
|
||||
{
|
||||
NextAction("hurricane", ACTION_HIGH + 1)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"light aoe",
|
||||
{
|
||||
NextAction("insect swarm on attacker", ACTION_NORMAL + 3),
|
||||
NextAction("moonfire on attacker", ACTION_NORMAL + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void CasterDruidDebuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(
|
||||
new TriggerNode("faerie fire", NextAction::array(0, new NextAction("faerie fire", ACTION_HIGH), nullptr)));
|
||||
new TriggerNode(
|
||||
"faerie fire",
|
||||
{
|
||||
NextAction("faerie fire", ACTION_HIGH)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
public:
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
std::string const getName() override { return "caster"; }
|
||||
NextAction** getDefaultActions() override;
|
||||
std::vector<NextAction> getDefaultActions() override;
|
||||
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; }
|
||||
};
|
||||
|
||||
|
||||
@@ -28,90 +28,112 @@ public:
|
||||
private:
|
||||
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("faerie fire (feral)",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"faerie fire (feral)",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("melee",
|
||||
/*P*/ NextAction::array(0, new NextAction("feral charge - cat"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"melee",
|
||||
/*P*/ { NextAction("feral charge - cat") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("feral charge - cat",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("reach melee"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"feral charge - cat",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("reach melee") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("cat form",
|
||||
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"cat form",
|
||||
/*P*/ { NextAction("caster form") },
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("claw",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"claw",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("melee") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("mangle (cat)",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"mangle (cat)",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("rake",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"rake",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("ferocious bite",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("rip"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"ferocious bite",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("rip") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("rip",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ nullptr,
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"rip",
|
||||
/*P*/ {},
|
||||
/*A*/ {},
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("pounce",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("ravage"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"pounce",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("ravage") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode("ravage",
|
||||
/*P*/ nullptr,
|
||||
/*A*/ NextAction::array(0, new NextAction("shred"), nullptr),
|
||||
/*C*/ nullptr);
|
||||
return new ActionNode(
|
||||
"ravage",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("shred") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -120,9 +142,11 @@ CatDpsDruidStrategy::CatDpsDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrateg
|
||||
actionNodeFactories.Add(new CatDpsDruidStrategyActionNodeFactory());
|
||||
}
|
||||
|
||||
NextAction** CatDpsDruidStrategy::getDefaultActions()
|
||||
std::vector<NextAction> CatDpsDruidStrategy::getDefaultActions()
|
||||
{
|
||||
return NextAction::array(0, new NextAction("tiger's fury", ACTION_DEFAULT + 0.1f), nullptr);
|
||||
return {
|
||||
NextAction("tiger's fury", ACTION_DEFAULT + 0.1f)
|
||||
};
|
||||
}
|
||||
|
||||
void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
@@ -130,50 +154,161 @@ void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
FeralDruidStrategy::InitTriggers(triggers);
|
||||
|
||||
// Default priority
|
||||
triggers.push_back(new TriggerNode("almost full energy available",
|
||||
NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("combo points not full",
|
||||
NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("almost full energy available",
|
||||
NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("combo points not full and high energy",
|
||||
NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("almost full energy available",
|
||||
NextAction::array(0, new NextAction("claw", ACTION_DEFAULT + 0.2f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("combo points not full and high energy",
|
||||
NextAction::array(0, new NextAction("claw", ACTION_DEFAULT + 0.2f), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("faerie fire (feral)",
|
||||
NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f), nullptr)));
|
||||
new TriggerNode(
|
||||
"almost full energy available",
|
||||
{
|
||||
NextAction("shred", ACTION_DEFAULT + 0.4f)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"combo points not full",
|
||||
{
|
||||
NextAction("shred", ACTION_DEFAULT + 0.4f)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"almost full energy available",
|
||||
{
|
||||
NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"combo points not full and high energy",
|
||||
{
|
||||
NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"almost full energy available",
|
||||
{
|
||||
NextAction("claw", ACTION_DEFAULT + 0.2f)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"combo points not full and high energy",
|
||||
{
|
||||
NextAction("claw", ACTION_DEFAULT + 0.2f)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"faerie fire (feral)",
|
||||
{
|
||||
NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Main spell
|
||||
triggers.push_back(
|
||||
new TriggerNode("cat form", NextAction::array(0, new NextAction("cat form", ACTION_HIGH + 8), nullptr)));
|
||||
new TriggerNode(
|
||||
"cat form", {
|
||||
NextAction("cat form", ACTION_HIGH + 8)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("savage roar", NextAction::array(0, new NextAction("savage roar", ACTION_HIGH + 7), nullptr)));
|
||||
triggers.push_back(new TriggerNode("combo points available",
|
||||
NextAction::array(0, new NextAction("rip", ACTION_HIGH + 6), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"ferocious bite time", NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 5), nullptr)));
|
||||
new TriggerNode(
|
||||
"savage roar", {
|
||||
NextAction("savage roar", ACTION_HIGH + 7)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("target with combo points almost dead",
|
||||
NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 4), nullptr)));
|
||||
triggers.push_back(new TriggerNode("mangle (cat)",
|
||||
NextAction::array(0, new NextAction("mangle (cat)", ACTION_HIGH + 3), nullptr)));
|
||||
triggers.push_back(new TriggerNode("rake", NextAction::array(0, new NextAction("rake", ACTION_HIGH + 2), nullptr)));
|
||||
new TriggerNode(
|
||||
"combo points available",
|
||||
{
|
||||
NextAction("rip", ACTION_HIGH + 6)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("medium threat", NextAction::array(0, new NextAction("cower", ACTION_HIGH + 1), nullptr)));
|
||||
new TriggerNode(
|
||||
"ferocious bite time",
|
||||
{
|
||||
NextAction("ferocious bite", ACTION_HIGH + 5)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"target with combo points almost dead",
|
||||
{
|
||||
NextAction("ferocious bite", ACTION_HIGH + 4)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"mangle (cat)",
|
||||
{
|
||||
NextAction("mangle (cat)", ACTION_HIGH + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"rake",
|
||||
{
|
||||
NextAction("rake", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"medium threat",
|
||||
{
|
||||
NextAction("cower", ACTION_HIGH + 1)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// AOE
|
||||
triggers.push_back(
|
||||
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("swipe (cat)", ACTION_HIGH + 3), nullptr)));
|
||||
triggers.push_back(new TriggerNode(
|
||||
"light aoe", NextAction::array(0, new NextAction("rake on attacker", ACTION_HIGH + 2), nullptr)));
|
||||
// Reach target
|
||||
triggers.push_back(new TriggerNode(
|
||||
"enemy out of melee", NextAction::array(0, new NextAction("feral charge - cat", ACTION_HIGH + 9), nullptr)));
|
||||
new TriggerNode(
|
||||
"medium aoe",
|
||||
{
|
||||
NextAction("swipe (cat)", ACTION_HIGH + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode("enemy out of melee", NextAction::array(0, new NextAction("dash", ACTION_HIGH + 8), nullptr)));
|
||||
new TriggerNode(
|
||||
"light aoe",
|
||||
{
|
||||
NextAction("rake on attacker", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
// Reach target
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"enemy out of melee",
|
||||
{
|
||||
NextAction("feral charge - cat", ACTION_HIGH + 9)
|
||||
}
|
||||
)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"enemy out of melee",
|
||||
{
|
||||
NextAction("dash", ACTION_HIGH + 8)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void CatAoeDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {}
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
public:
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
std::string const getName() override { return "cat"; }
|
||||
NextAction** getDefaultActions() override;
|
||||
std::vector<NextAction> getDefaultActions() override;
|
||||
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; }
|
||||
};
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
#include "AoeValues.h"
|
||||
#include "TargetValue.h"
|
||||
|
||||
NextAction** CastAbolishPoisonAction::getAlternatives()
|
||||
std::vector<NextAction> CastAbolishPoisonAction::getAlternatives()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("cure poison"), nullptr),
|
||||
return NextAction::merge({ NextAction("cure poison") },
|
||||
CastSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
NextAction** CastAbolishPoisonOnPartyAction::getAlternatives()
|
||||
std::vector<NextAction> CastAbolishPoisonOnPartyAction::getAlternatives()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("cure poison on party"), nullptr),
|
||||
return NextAction::merge({ NextAction("cure poison on party") },
|
||||
CastSpellAction::getPrerequisites());
|
||||
}
|
||||
|
||||
@@ -60,15 +60,15 @@ bool CastStarfallAction::isUseful()
|
||||
return true;
|
||||
}
|
||||
|
||||
NextAction** CastReviveAction::getPrerequisites()
|
||||
std::vector<NextAction> CastReviveAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("caster form"), nullptr),
|
||||
return NextAction::merge({ NextAction("caster form") },
|
||||
ResurrectPartyMemberAction::getPrerequisites());
|
||||
}
|
||||
|
||||
NextAction** CastRebirthAction::getPrerequisites()
|
||||
std::vector<NextAction> CastRebirthAction::getPrerequisites()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("caster form"), nullptr),
|
||||
return NextAction::merge({ NextAction("caster form") },
|
||||
ResurrectPartyMemberAction::getPrerequisites());
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ class CastReviveAction : public ResurrectPartyMemberAction
|
||||
public:
|
||||
CastReviveAction(PlayerbotAI* botAI) : ResurrectPartyMemberAction(botAI, "revive") {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
};
|
||||
|
||||
class CastRebirthAction : public ResurrectPartyMemberAction
|
||||
@@ -82,7 +82,7 @@ class CastRebirthAction : public ResurrectPartyMemberAction
|
||||
public:
|
||||
CastRebirthAction(PlayerbotAI* botAI) : ResurrectPartyMemberAction(botAI, "rebirth") {}
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::vector<NextAction> getPrerequisites() override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
@@ -223,7 +223,7 @@ class CastAbolishPoisonAction : public CastCureSpellAction
|
||||
{
|
||||
public:
|
||||
CastAbolishPoisonAction(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "abolish poison") {}
|
||||
NextAction** getAlternatives() override;
|
||||
std::vector<NextAction> getAlternatives() override;
|
||||
};
|
||||
|
||||
class CastAbolishPoisonOnPartyAction : public CurePartyMemberAction
|
||||
@@ -233,7 +233,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
NextAction** getAlternatives() override;
|
||||
std::vector<NextAction> getAlternatives() override;
|
||||
};
|
||||
|
||||
class CastBarkskinAction : public CastBuffSpellAction
|
||||
|
||||
@@ -17,9 +17,9 @@ bool CastBearFormAction::isUseful()
|
||||
return CastBuffSpellAction::isUseful() && !botAI->HasAura("dire bear form", GetTarget());
|
||||
}
|
||||
|
||||
NextAction** CastDireBearFormAction::getAlternatives()
|
||||
std::vector<NextAction> CastDireBearFormAction::getAlternatives()
|
||||
{
|
||||
return NextAction::merge(NextAction::array(0, new NextAction("bear form"), nullptr),
|
||||
return NextAction::merge({ NextAction("bear form") },
|
||||
CastSpellAction::getAlternatives());
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user