Compare commits

..

2 Commits

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

View File

@@ -25,7 +25,6 @@
# CHEATS # CHEATS
# SPELLS # SPELLS
# FLIGHTPATH # FLIGHTPATH
# PROFESSIONS
# RANDOMBOT-SPECIFIC SETTINGS # RANDOMBOT-SPECIFIC SETTINGS
# GENERAL # GENERAL
# LEVELS # LEVELS
@@ -45,7 +44,7 @@
# HUNTER # HUNTER
# ROGUE # ROGUE
# PRIEST # PRIEST
# DEATH KNIGHT # DEATHKNIGHT
# SHAMAN # SHAMAN
# MAGE # MAGE
# WARLOCK # WARLOCK
@@ -56,7 +55,7 @@
# HUNTER # HUNTER
# ROGUE # ROGUE
# PRIEST # PRIEST
# DEATH KNIGHT # DEATHKNIGHT
# SHAMAN # SHAMAN
# MAGE # MAGE
# WARLOCK # WARLOCK
@@ -580,24 +579,6 @@ 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 # # RANDOMBOT-SPECIFIC SETTINGS #
@@ -649,7 +630,7 @@ AiPlayerbot.RandomBotHordeRatio = 50
AiPlayerbot.DisableDeathKnightLogin = 0 AiPlayerbot.DisableDeathKnightLogin = 0
# Enable simulated expansion limitation for talents and glyphs # 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 # and 7 rows plus the middle talent of the 8th row for bots from level 61 until level 71
# Default: 0 (disabled) # Default: 0 (disabled)
AiPlayerbot.LimitTalentsExpansion = 0 AiPlayerbot.LimitTalentsExpansion = 0
@@ -1204,7 +1185,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951" AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
# PvP Restricted Areas (bots don't pvp) # PvP Restricted Areas (bots don't pvp)
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973" AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
# Improve reaction speeds in battlegrounds and arenas (may cause lag) # Improve reaction speeds in battlegrounds and arenas (may cause lag)
AiPlayerbot.FastReactInBG = 1 AiPlayerbot.FastReactInBG = 1
@@ -1474,7 +1455,7 @@ AiPlayerbot.PremadeSpecLink.5.5.80 = 50332031003--005323241223112003102311351
#################################################################################################### ####################################################################################################
#################################################################################################### ####################################################################################################
# DEATH KNIGHT # DEATHKNIGHT
# #
# #
@@ -1797,7 +1778,7 @@ AiPlayerbot.RandomClassSpecIndex.5.2 = 2
#################################################################################################### ####################################################################################################
#################################################################################################### ####################################################################################################
# DEATH KNIGHT # DEATHKNIGHT
# #
# #

View File

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

View File

@@ -1,15 +0,0 @@
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 nai 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);

View File

@@ -140,37 +140,37 @@ BotRoles AiFactory::GetPlayerRoles(Player* player)
switch (player->getClass()) switch (player->getClass())
{ {
case CLASS_PRIEST: case CLASS_PRIEST:
if (tab == PRIEST_TAB_SHADOW) if (tab == 2)
role = BOT_ROLE_DPS; role = BOT_ROLE_DPS;
else else
role = BOT_ROLE_HEALER; role = BOT_ROLE_HEALER;
break; break;
case CLASS_SHAMAN: case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_RESTORATION) if (tab == 2)
role = BOT_ROLE_HEALER; role = BOT_ROLE_HEALER;
else else
role = BOT_ROLE_DPS; role = BOT_ROLE_DPS;
break; break;
case CLASS_WARRIOR: case CLASS_WARRIOR:
if (tab == WARRIOR_TAB_PROTECTION) if (tab == 2)
role = BOT_ROLE_TANK; role = BOT_ROLE_TANK;
else else
role = BOT_ROLE_DPS; role = BOT_ROLE_DPS;
break; break;
case CLASS_PALADIN: case CLASS_PALADIN:
if (tab == PALADIN_TAB_HOLY) if (tab == 0)
role = BOT_ROLE_HEALER; role = BOT_ROLE_HEALER;
else if (tab == PALADIN_TAB_PROTECTION) else if (tab == 1)
role = BOT_ROLE_TANK; role = BOT_ROLE_TANK;
else if (tab == PALADIN_TAB_RETRIBUTION) else if (tab == 2)
role = BOT_ROLE_DPS; role = BOT_ROLE_DPS;
break; break;
case CLASS_DRUID: case CLASS_DRUID:
if (tab == DRUID_TAB_BALANCE) if (tab == 0)
role = BOT_ROLE_DPS; role = BOT_ROLE_DPS;
else if (tab == DRUID_TAB_FERAL) else if (tab == 1)
role = (BotRoles)(BOT_ROLE_TANK | BOT_ROLE_DPS); role = (BotRoles)(BOT_ROLE_TANK | BOT_ROLE_DPS);
else if (tab == DRUID_TAB_RESTORATION) else if (tab == 2)
role = BOT_ROLE_HEALER; role = BOT_ROLE_HEALER;
break; break;
default: default:
@@ -188,83 +188,84 @@ std::string AiFactory::GetPlayerSpecName(Player* player)
switch (player->getClass()) switch (player->getClass())
{ {
case CLASS_PRIEST: case CLASS_PRIEST:
if (tab == PRIEST_TAB_SHADOW) if (tab == 2)
specName = "shadow"; specName = "shadow";
else if (tab == PRIEST_TAB_HOLY) else if (tab == 1)
specName = "holy"; specName = "holy";
else else
specName = "disc"; specName = "disc";
;
break; break;
case CLASS_SHAMAN: case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_RESTORATION) if (tab == 2)
specName = "resto"; specName = "resto";
else if (tab == SHAMAN_TAB_ENHANCEMENT) else if (tab == 1)
specName = "enhance"; specName = "enhance";
else else
specName = "elem"; specName = "elem";
break; break;
case CLASS_WARRIOR: case CLASS_WARRIOR:
if (tab == WARRIOR_TAB_PROTECTION) if (tab == 2)
specName = "prot"; specName = "prot";
else if (tab == WARRIOR_TAB_FURY) else if (tab == 1)
specName = "fury"; specName = "fury";
else else
specName = "arms"; specName = "arms";
break; break;
case CLASS_PALADIN: case CLASS_PALADIN:
if (tab == PALADIN_TAB_HOLY) if (tab == 0)
specName = "holy"; specName = "holy";
else if (tab == PALADIN_TAB_PROTECTION) else if (tab == 1)
specName = "prot"; specName = "prot";
else if (tab == PALADIN_TAB_RETRIBUTION) else if (tab == 2)
specName = "retrib"; specName = "retrib";
break; break;
case CLASS_DRUID: case CLASS_DRUID:
if (tab == DRUID_TAB_BALANCE) if (tab == 0)
specName = "balance"; specName = "balance";
else if (tab == DRUID_TAB_FERAL) else if (tab == 1)
specName = "feraldps"; specName = "feraldps";
else if (tab == DRUID_TAB_RESTORATION) else if (tab == 2)
specName = "resto"; specName = "resto";
break; break;
case CLASS_ROGUE: case CLASS_ROGUE:
if (tab == ROGUE_TAB_ASSASSINATION) if (tab == 0)
specName = "assas"; specName = "assas";
else if (tab == ROGUE_TAB_COMBAT) else if (tab == 1)
specName = "combat"; specName = "combat";
else if (tab == ROGUE_TAB_SUBTLETY) else if (tab == 2)
specName = "subtle"; specName = "subtle";
break; break;
case CLASS_HUNTER: case CLASS_HUNTER:
if (tab == HUNTER_TAB_BEAST_MASTERY) if (tab == 0)
specName = "beast"; specName = "beast";
else if (tab == HUNTER_TAB_MARKSMANSHIP) else if (tab == 1)
specName = "marks"; specName = "marks";
else if (tab == HUNTER_TAB_SURVIVAL) else if (tab == 2)
specName = "surv"; specName = "surv";
break; break;
case CLASS_DEATH_KNIGHT: case CLASS_DEATH_KNIGHT:
if (tab == DEATH_KNIGHT_TAB_BLOOD) if (tab == 0)
specName = "blooddps"; specName = "blooddps";
else if (tab == DEATH_KNIGHT_TAB_FROST) else if (tab == 1)
specName = "frostdps"; specName = "frostdps";
else if (tab == DEATH_KNIGHT_TAB_UNHOLY) else if (tab == 2)
specName = "unholydps"; specName = "unholydps";
break; break;
case CLASS_MAGE: case CLASS_MAGE:
if (tab == MAGE_TAB_ARCANE) if (tab == 0)
specName = "arcane"; specName = "arcane";
else if (tab == MAGE_TAB_FIRE) else if (tab == 1)
specName = "fire"; specName = "fire";
else if (tab == MAGE_TAB_FROST) else if (tab == 2)
specName = "frost"; specName = "frost";
break; break;
case CLASS_WARLOCK: case CLASS_WARLOCK:
if (tab == WARLOCK_TAB_AFFLICTION) if (tab == 0)
specName = "afflic"; specName = "afflic";
else if (tab == WARLOCK_TAB_DEMONOLOGY) else if (tab == 1)
specName = "demo"; specName = "demo";
else if (tab == WARLOCK_TAB_DESTRUCTION) else if (tab == 2)
specName = "destro"; specName = "destro";
break; break;
default: default:
@@ -279,124 +280,147 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
uint8 tab = GetPlayerSpecTab(player); uint8 tab = GetPlayerSpecTab(player);
if (!player->InBattleground()) if (!player->InBattleground())
{
engine->addStrategiesNoInit("racials", "chat", "default", "cast time", "potions", "duel", "boost", nullptr); engine->addStrategiesNoInit("racials", "chat", "default", "cast time", "potions", "duel", "boost", nullptr);
}
if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster()) if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster())
{
engine->addStrategy("avoid aoe", false); engine->addStrategy("avoid aoe", false);
}
engine->addStrategy("formation", false); engine->addStrategy("formation", false);
switch (player->getClass()) switch (player->getClass())
{ {
case CLASS_PRIEST: case CLASS_PRIEST:
if (tab == PRIEST_TAB_SHADOW) if (tab == 2)
{
engine->addStrategiesNoInit("dps", "shadow debuff", "shadow aoe", nullptr); engine->addStrategiesNoInit("dps", "shadow debuff", "shadow aoe", nullptr);
}
else if (tab == PRIEST_TAB_DISCIPLINE) else if (tab == PRIEST_TAB_DISCIPLINE)
{
engine->addStrategiesNoInit("heal", nullptr); engine->addStrategiesNoInit("heal", nullptr);
}
else else
{
engine->addStrategiesNoInit("holy heal", nullptr); engine->addStrategiesNoInit("holy heal", nullptr);
}
engine->addStrategiesNoInit("dps assist", "cure", nullptr); engine->addStrategiesNoInit("dps assist", "cure", nullptr);
break; break;
case CLASS_MAGE: case CLASS_MAGE:
if (tab == MAGE_TAB_ARCANE) if (tab == 0) // Arcane
engine->addStrategiesNoInit("arcane", nullptr); engine->addStrategiesNoInit("arcane", nullptr);
else if (tab == MAGE_TAB_FIRE) else if (tab == 1) // Fire
{ {
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/) if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
{
engine->addStrategiesNoInit("frostfire", nullptr); engine->addStrategiesNoInit("frostfire", nullptr);
}
else else
{
engine->addStrategiesNoInit("fire", nullptr); engine->addStrategiesNoInit("fire", nullptr);
}
} }
else else // Frost
engine->addStrategiesNoInit("frost", nullptr); engine->addStrategiesNoInit("frost", nullptr);
engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr); engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
break; break;
case CLASS_WARRIOR: case CLASS_WARRIOR:
if (tab == WARRIOR_TAB_PROTECTION) if (tab == 2)
engine->addStrategiesNoInit("tank", "tank assist", "aoe", nullptr); engine->addStrategiesNoInit("tank", "tank assist", "aoe", nullptr);
else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(1680)) // Whirlwind else if (tab == 0 || !player->HasSpell(1680)) // Whirlwind
engine->addStrategiesNoInit("arms", "aoe", "dps assist", nullptr); engine->addStrategiesNoInit("arms", "aoe", "dps assist", /*"behind",*/ nullptr);
else else
engine->addStrategiesNoInit("fury", "aoe", "dps assist", nullptr); engine->addStrategiesNoInit("fury", "aoe", "dps assist", /*"behind",*/ nullptr);
break; break;
case CLASS_SHAMAN: case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_ELEMENTAL) if (tab == 0) // Elemental
engine->addStrategiesNoInit("ele", "stoneskin", "wrath", "mana spring", "wrath of air", nullptr); engine->addStrategiesNoInit("ele", "stoneskin", "wrath", "mana spring", "wrath of air", nullptr);
else if (tab == SHAMAN_TAB_RESTORATION) else if (tab == 2) // Restoration
engine->addStrategiesNoInit("resto", "stoneskin", "flametongue", "mana spring", "wrath of air", nullptr); engine->addStrategiesNoInit("resto", "stoneskin", "flametongue", "mana spring", "wrath of air", nullptr);
else else // Enhancement
engine->addStrategiesNoInit("enh", "strength of earth", "magma", "healing stream", "windfury", nullptr); engine->addStrategiesNoInit("enh", "strength of earth", "magma", "healing stream", "windfury", nullptr);
engine->addStrategiesNoInit("dps assist", "cure", "aoe", nullptr); engine->addStrategiesNoInit("dps assist", "cure", "aoe", nullptr);
break; break;
case CLASS_PALADIN: case CLASS_PALADIN:
if (tab == PALADIN_TAB_PROTECTION) if (tab == 1)
engine->addStrategiesNoInit("tank", "tank assist", "bthreat", "barmor", "cure", nullptr); engine->addStrategiesNoInit("tank", "tank assist", "bthreat", "barmor", "cure", nullptr);
else if (tab == PALADIN_TAB_HOLY) else if (tab == 0)
engine->addStrategiesNoInit("heal", "dps assist", "cure", "bcast", nullptr); engine->addStrategiesNoInit("heal", "dps assist", "cure", "bcast", nullptr);
else else
engine->addStrategiesNoInit("dps", "dps assist", "cure", "baoe", nullptr); engine->addStrategiesNoInit("dps", "dps assist", "cure", "baoe", nullptr);
break; break;
case CLASS_DRUID: case CLASS_DRUID:
if (tab == DRUID_TAB_BALANCE) if (tab == 0)
{ {
engine->addStrategiesNoInit("caster", "cure", "caster aoe", "dps assist", nullptr); engine->addStrategiesNoInit("caster", "cure", "caster aoe", "dps assist", nullptr);
engine->addStrategy("caster debuff", false); engine->addStrategy("caster debuff", false);
} }
else if (tab == DRUID_TAB_RESTORATION) else if (tab == 2)
engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr); engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr);
else 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); engine->addStrategiesNoInit("cat", "dps assist", nullptr);
}
else else
{
engine->addStrategiesNoInit("bear", "tank assist", nullptr); engine->addStrategiesNoInit("bear", "tank assist", nullptr);
}
} }
break; break;
case CLASS_HUNTER: case CLASS_HUNTER:
if (tab == HUNTER_TAB_BEAST_MASTERY) if (tab == 0) // Beast Mastery
engine->addStrategiesNoInit("bm", nullptr); engine->addStrategiesNoInit("bm", nullptr);
else if (tab == HUNTER_TAB_MARKSMANSHIP) else if (tab == 1) // Marksmanship
engine->addStrategiesNoInit("mm", nullptr); engine->addStrategiesNoInit("mm", nullptr);
else else if (tab == 2) // Survival
engine->addStrategiesNoInit("surv", nullptr); engine->addStrategiesNoInit("surv", nullptr);
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr); engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
break; break;
case CLASS_ROGUE: case CLASS_ROGUE:
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)
{
engine->addStrategiesNoInit("melee", "dps assist", "aoe", nullptr); engine->addStrategiesNoInit("melee", "dps assist", "aoe", nullptr);
}
else else
{
engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr); engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr);
}
break; break;
case CLASS_WARLOCK: case CLASS_WARLOCK:
if (tab == WARLOCK_TAB_AFFLICTION) if (tab == 0) // Affliction
engine->addStrategiesNoInit("affli", "curse of agony", nullptr); engine->addStrategiesNoInit("affli", "curse of agony", nullptr);
else if (tab == WARLOCK_TAB_DEMONOLOGY) else if (tab == 1) // Demonology
engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr); engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr);
else else if (tab == 2) // Destruction
engine->addStrategiesNoInit("destro", "curse of elements", nullptr); engine->addStrategiesNoInit("destro", "curse of elements", nullptr);
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr); engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
break; break;
case CLASS_DEATH_KNIGHT: case CLASS_DEATH_KNIGHT:
if (tab == DEATH_KNIGHT_TAB_BLOOD) if (tab == 0)
engine->addStrategiesNoInit("blood", "tank assist", nullptr); engine->addStrategiesNoInit("blood", "tank assist", nullptr);
else if (tab == DEATH_KNIGHT_TAB_FROST) else if (tab == 1)
engine->addStrategiesNoInit("frost", "frost aoe", "dps assist", nullptr); engine->addStrategiesNoInit("frost", "frost aoe", "dps assist", nullptr);
else else
engine->addStrategiesNoInit("unholy", "unholy aoe", "dps assist", nullptr); engine->addStrategiesNoInit("unholy", "unholy aoe", "dps assist", nullptr);
break; break;
} }
if (PlayerbotAI::IsTank(player, true)) if (PlayerbotAI::IsTank(player, true))
{
engine->addStrategy("tank face", false); engine->addStrategy("tank face", false);
}
if (PlayerbotAI::IsMelee(player, true) && PlayerbotAI::IsDps(player, true)) if (PlayerbotAI::IsMelee(player, true) && PlayerbotAI::IsDps(player, true))
{
engine->addStrategy("behind", false); engine->addStrategy("behind", false);
}
if (PlayerbotAI::IsHeal(player, true)) if (PlayerbotAI::IsHeal(player, true))
{ {
if (sPlayerbotAIConfig->autoSaveMana) if (sPlayerbotAIConfig->autoSaveMana)
@@ -404,7 +428,6 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (!sPlayerbotAIConfig->IsRestrictedHealerDPSMap(player->GetMapId())) if (!sPlayerbotAIConfig->IsRestrictedHealerDPSMap(player->GetMapId()))
engine->addStrategy("healer dps", false); engine->addStrategy("healer dps", false);
} }
if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player)) if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player))
{ {
if (!player->GetGroup()) if (!player->GetGroup())
@@ -413,13 +436,15 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
engine->addStrategy("boost", false); engine->addStrategy("boost", false);
engine->addStrategy("dps assist", false); engine->addStrategy("dps assist", false);
engine->removeStrategy("threat", false); engine->removeStrategy("threat", false);
// engine-
switch (player->getClass()) switch (player->getClass())
{ {
case CLASS_PRIEST: case CLASS_PRIEST:
{ {
if (tab != PRIEST_TAB_SHADOW) if (tab != PRIEST_TAB_SHADOW)
{
engine->addStrategiesNoInit("holy dps", "shadow debuff", "shadow aoe", nullptr); engine->addStrategiesNoInit("holy dps", "shadow debuff", "shadow aoe", nullptr);
}
break; break;
} }
case CLASS_DRUID: case CLASS_DRUID:
@@ -434,13 +459,17 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
case CLASS_SHAMAN: case CLASS_SHAMAN:
{ {
if (tab == SHAMAN_TAB_RESTORATION) if (tab == SHAMAN_TAB_RESTORATION)
{
engine->addStrategiesNoInit("caster", "caster aoe", "bmana", nullptr); engine->addStrategiesNoInit("caster", "caster aoe", "bmana", nullptr);
}
break; break;
} }
case CLASS_PALADIN: case CLASS_PALADIN:
{ {
if (tab == PALADIN_TAB_HOLY) if (tab == PALADIN_TAB_HOLY)
{
engine->addStrategiesNoInit("dps", "dps assist", "baoe", nullptr); engine->addStrategiesNoInit("dps", "dps assist", "baoe", nullptr);
}
break; break;
} }
default: default:
@@ -449,9 +478,13 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
} }
} }
if (sRandomPlayerbotMgr->IsRandomBot(player)) if (sRandomPlayerbotMgr->IsRandomBot(player))
{
engine->ChangeStrategy(sPlayerbotAIConfig->randomBotCombatStrategies); engine->ChangeStrategy(sPlayerbotAIConfig->randomBotCombatStrategies);
}
else else
{
engine->ChangeStrategy(sPlayerbotAIConfig->combatStrategies); engine->ChangeStrategy(sPlayerbotAIConfig->combatStrategies);
}
// Battleground switch // Battleground switch
if (player->InBattleground() && player->GetBattleground()) if (player->InBattleground() && player->GetBattleground())
@@ -478,15 +511,23 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (player->InArena()) if (player->InArena())
{ {
engine->addStrategy("arena", false); 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("custom::say", false);
engine->removeStrategy("flee", false); engine->removeStrategy("flee", false);
engine->removeStrategy("threat", false); engine->removeStrategy("threat", false);
engine->addStrategy("boost", 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);
} }
} }
@@ -508,15 +549,19 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr); nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
break; break;
case CLASS_PALADIN: case CLASS_PALADIN:
if (tab == PALADIN_TAB_PROTECTION) if (tab == 1)
{ {
nonCombatEngine->addStrategiesNoInit("bthreat", "tank assist", "barmor", nullptr); nonCombatEngine->addStrategiesNoInit("bthreat", "tank assist", "barmor", nullptr);
if (player->GetLevel() >= 20) if (player->GetLevel() >= 20)
{
nonCombatEngine->addStrategy("bhealth", false); nonCombatEngine->addStrategy("bhealth", false);
}
else else
{
nonCombatEngine->addStrategy("bdps", false); nonCombatEngine->addStrategy("bdps", false);
}
} }
else if (tab == PALADIN_TAB_HOLY) else if (tab == 0)
nonCombatEngine->addStrategiesNoInit("dps assist", "bmana", "bcast", nullptr); nonCombatEngine->addStrategiesNoInit("dps assist", "bmana", "bcast", nullptr);
else else
nonCombatEngine->addStrategiesNoInit("dps assist", "bdps", "baoe", nullptr); nonCombatEngine->addStrategiesNoInit("dps assist", "bdps", "baoe", nullptr);
@@ -527,7 +572,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategiesNoInit("bdps", "dps assist", "pet", nullptr); nonCombatEngine->addStrategiesNoInit("bdps", "dps assist", "pet", nullptr);
break; break;
case CLASS_SHAMAN: case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_ELEMENTAL || tab == SHAMAN_TAB_RESTORATION) if (tab == 0 || tab == 2)
nonCombatEngine->addStrategy("bmana", false); nonCombatEngine->addStrategy("bmana", false);
else else
nonCombatEngine->addStrategy("bdps", false); nonCombatEngine->addStrategy("bdps", false);
@@ -543,34 +588,43 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr); nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
break; break;
case CLASS_DRUID: case CLASS_DRUID:
if (tab == DRUID_TAB_FERAL) if (tab == 1)
{ {
if (player->GetLevel() >= 20 && !player->HasAura(16931) /*thick hide*/) if (player->GetLevel() >= 20 && !player->HasAura(16931) /*thick hide*/)
{
nonCombatEngine->addStrategy("dps assist", false); nonCombatEngine->addStrategy("dps assist", false);
}
else else
{
nonCombatEngine->addStrategy("tank assist", false); nonCombatEngine->addStrategy("tank assist", false);
}
} }
else else
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr); nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
break; break;
case CLASS_WARRIOR: case CLASS_WARRIOR:
if (tab == WARRIOR_TAB_PROTECTION) if (tab == 2)
nonCombatEngine->addStrategy("tank assist", false); nonCombatEngine->addStrategy("tank assist", false);
else else
nonCombatEngine->addStrategy("dps assist", false); nonCombatEngine->addStrategy("dps assist", false);
break; break;
case CLASS_WARLOCK: case CLASS_WARLOCK:
if (tab == WARLOCK_TAB_AFFLICTION) if (tab == WARLOCK_TAB_AFFLICTION)
{
nonCombatEngine->addStrategiesNoInit("felhunter", "spellstone", nullptr); nonCombatEngine->addStrategiesNoInit("felhunter", "spellstone", nullptr);
}
else if (tab == WARLOCK_TAB_DEMONOLOGY) else if (tab == WARLOCK_TAB_DEMONOLOGY)
{
nonCombatEngine->addStrategiesNoInit("felguard", "spellstone", nullptr); nonCombatEngine->addStrategiesNoInit("felguard", "spellstone", nullptr);
}
else if (tab == WARLOCK_TAB_DESTRUCTION) else if (tab == WARLOCK_TAB_DESTRUCTION)
{
nonCombatEngine->addStrategiesNoInit("imp", "firestone", nullptr); nonCombatEngine->addStrategiesNoInit("imp", "firestone", nullptr);
}
nonCombatEngine->addStrategiesNoInit("dps assist", "ss self", nullptr); nonCombatEngine->addStrategiesNoInit("dps assist", "ss self", nullptr);
break; break;
case CLASS_DEATH_KNIGHT: case CLASS_DEATH_KNIGHT:
if (tab == DEATH_KNIGHT_TAB_BLOOD) if (tab == 0)
nonCombatEngine->addStrategy("tank assist", false); nonCombatEngine->addStrategy("tank assist", false);
else else
nonCombatEngine->addStrategy("dps assist", false); nonCombatEngine->addStrategy("dps assist", false);
@@ -587,7 +641,9 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
} }
if (sPlayerbotAIConfig->autoSaveMana && PlayerbotAI::IsHeal(player, true)) if (sPlayerbotAIConfig->autoSaveMana && PlayerbotAI::IsHeal(player, true))
{
nonCombatEngine->addStrategy("save mana", false); nonCombatEngine->addStrategy("save mana", false);
}
if ((sRandomPlayerbotMgr->IsRandomBot(player)) && !player->InBattleground()) if ((sRandomPlayerbotMgr->IsRandomBot(player)) && !player->InBattleground())
{ {
@@ -613,14 +669,18 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategy("grind", false); nonCombatEngine->addStrategy("grind", false);
if (sPlayerbotAIConfig->enableNewRpgStrategy) if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
nonCombatEngine->addStrategy("new rpg", false); nonCombatEngine->addStrategy("new rpg", false);
}
else if (sPlayerbotAIConfig->autoDoQuests) else if (sPlayerbotAIConfig->autoDoQuests)
{ {
// nonCombatEngine->addStrategy("travel"); // nonCombatEngine->addStrategy("travel");
nonCombatEngine->addStrategy("rpg", false); nonCombatEngine->addStrategy("rpg", false);
} }
else else
{
nonCombatEngine->addStrategy("move random", false); nonCombatEngine->addStrategy("move random", false);
}
if (sPlayerbotAIConfig->randomBotJoinBG) if (sPlayerbotAIConfig->randomBotJoinBG)
nonCombatEngine->addStrategy("bg", false); nonCombatEngine->addStrategy("bg", false);
@@ -669,8 +729,11 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
} }
} }
else else
{
nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig->nonCombatStrategies); nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig->nonCombatStrategies);
}
// nonCombatEngine->addStrategy("battleground");
// nonCombatEngine->addStrategy("warsong");
// Battleground switch // Battleground switch
if (player->InBattleground() && player->GetBattleground()) if (player->InBattleground() && player->GetBattleground())
{ {
@@ -727,7 +790,9 @@ void AiFactory::AddDefaultDeadStrategies(Player* player, PlayerbotAI* const faca
deadEngine->addStrategiesNoInit("dead", "stay", "chat", "default", "follow", nullptr); deadEngine->addStrategiesNoInit("dead", "stay", "chat", "default", "follow", nullptr);
if (sRandomPlayerbotMgr->IsRandomBot(player) && !player->GetGroup()) if (sRandomPlayerbotMgr->IsRandomBot(player) && !player->GetGroup())
{
deadEngine->removeStrategy("follow", false); deadEngine->removeStrategy("follow", false);
}
} }
Engine* AiFactory::createDeadEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext) Engine* AiFactory::createDeadEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext)

View File

@@ -43,7 +43,6 @@
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "PlayerbotDbStore.h" #include "PlayerbotDbStore.h"
#include "PlayerbotMgr.h" #include "PlayerbotMgr.h"
#include "PlayerbotGuildMgr.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PointMovementGenerator.h" #include "PointMovementGenerator.h"
#include "PositionValue.h" #include "PositionValue.h"
@@ -243,8 +242,8 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
nextAICheckDelay = 0; nextAICheckDelay = 0;
// Early return if bot is in invalid state // Early return if bot is in invalid state
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() || if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() ||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld()) bot->IsDuringRemoveFromWorld())
return; return;
// Handle cheat options (set bot health and power if cheats are enabled) // Handle cheat options (set bot health and power if cheats are enabled)
@@ -366,7 +365,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
} }
// Update the bot's group status (moved to helper function) // Update the bot's group status (moved to helper function)
UpdateAIGroupMaster(); UpdateAIGroupAndMaster();
// Update internal AI // Update internal AI
UpdateAIInternal(elapsed, minimal); UpdateAIInternal(elapsed, minimal);
@@ -374,7 +373,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
} }
// Helper function for UpdateAI to check group membership and handle removal if necessary // Helper function for UpdateAI to check group membership and handle removal if necessary
void PlayerbotAI::UpdateAIGroupMaster() void PlayerbotAI::UpdateAIGroupAndMaster()
{ {
if (!bot) if (!bot)
return; return;
@@ -421,7 +420,7 @@ void PlayerbotAI::UpdateAIGroupMaster()
{ {
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
if (botAI->GetMaster() == botAI->GetGroupLeader()) if (botAI->GetMaster() == botAI->GetGroupMaster())
botAI->TellMaster("Hello, I follow you!"); botAI->TellMaster("Hello, I follow you!");
else else
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!"); botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
@@ -432,12 +431,14 @@ void PlayerbotAI::UpdateAIGroupMaster()
botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT);
} }
} }
else if (!newMaster && !bot->InBattleground())
LeaveOrDisbandGroup();
} }
} }
void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal) void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal)
{ {
if (!bot || bot->IsBeingTeleported() || !bot->IsInWorld()) if (bot->IsBeingTeleported() || !bot->IsInWorld())
return; return;
std::string const mapString = WorldPosition(bot).isOverworld() ? std::to_string(bot->GetMapId()) : "I"; std::string const mapString = WorldPosition(bot).isOverworld() ? std::to_string(bot->GetMapId()) : "I";
@@ -516,37 +517,23 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
void PlayerbotAI::HandleCommands() void PlayerbotAI::HandleCommands()
{ {
ExternalEventHelper helper(aiObjectContext); ExternalEventHelper helper(aiObjectContext);
for (auto it = chatCommands.begin(); it != chatCommands.end();) for (auto it = chatCommands.begin(); it != chatCommands.end();)
{ {
time_t& checkTime = it->GetTime(); time_t& checkTime = it->GetTime();
if (checkTime && time(nullptr) < checkTime) if (checkTime && time(0) < checkTime)
{ {
++it; ++it;
continue; continue;
} }
Player* owner = it->GetOwner();
if (!owner)
{
it = chatCommands.erase(it);
continue;
}
const std::string& command = it->GetCommand(); const std::string& command = it->GetCommand();
if (command.empty()) Player* owner = it->GetOwner();
{
it = chatCommands.erase(it);
continue;
}
if (!helper.ParseChatCommand(command, owner) && it->GetType() == CHAT_MSG_WHISPER) if (!helper.ParseChatCommand(command, owner) && it->GetType() == CHAT_MSG_WHISPER)
{ {
// ostringstream out; out << "Unknown command " << command; // ostringstream out; out << "Unknown command " << command;
// TellPlayer(out); // TellPlayer(out);
// helper.ParseChatCommand("help"); // helper.ParseChatCommand("help");
} }
it = chatCommands.erase(it); it = chatCommands.erase(it);
} }
} }
@@ -554,9 +541,6 @@ void PlayerbotAI::HandleCommands()
std::map<std::string, ChatMsg> chatMap; std::map<std::string, ChatMsg> chatMap;
void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang) void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang)
{ {
if (!bot)
return;
std::string filtered = text; std::string filtered = text;
if (!IsAllowedCommand(filtered) && !GetSecurity()->CheckLevelFor(PlayerbotSecurityLevel::PLAYERBOT_SECURITY_INVITE, if (!IsAllowedCommand(filtered) && !GetSecurity()->CheckLevelFor(PlayerbotSecurityLevel::PLAYERBOT_SECURITY_INVITE,
@@ -728,82 +712,45 @@ void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fr
void PlayerbotAI::HandleTeleportAck() void PlayerbotAI::HandleTeleportAck()
{ {
if (!bot || !bot->GetSession())
return;
// only for bots
if (IsRealPlayer()) if (IsRealPlayer())
return; return;
/* bot->GetMotionMaster()->Clear(true);
* FAR TELEPORT (worldport / map change) bot->StopMoving();
* Player may NOT be in world or grid here.
* Handle this FIRST.
*/
if (bot->IsBeingTeleportedFar())
{
bot->GetSession()->HandleMoveWorldportAck();
// after worldport ACK the player should be in a valid map
if (!bot->GetMap())
{
LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString());
return;
}
// 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;
}
/*
* NEAR TELEPORT (same map / instance)
* Player MUST be in world (and in grid).
*/
if (bot->IsBeingTeleportedNear()) if (bot->IsBeingTeleportedNear())
{ {
// Temporary fix for instance can not enter
if (!bot->IsInWorld()) 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->GetMap()->AddPlayerToMap(bot);
bot->StopMoving();
} }
while (bot->IsInWorld() && bot->IsBeingTeleportedNear())
// simulate near teleport latency {
SetNextCheckDelay(urand(1000, 2000)); Player* plMover = bot->m_mover->ToPlayer();
return; 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);
};
} }
if (bot->IsBeingTeleportedFar())
{
while (bot->IsBeingTeleportedFar())
{
bot->GetSession()->HandleMoveWorldportAck();
}
// SetNextCheckDelay(urand(2000, 5000));
if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true);
if (sPlayerbotAIConfig->restrictHealerDPS)
EvaluateHealerDpsStrategy();
Reset(true);
}
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
} }
void PlayerbotAI::Reset(bool full) void PlayerbotAI::Reset(bool full)
@@ -965,6 +912,7 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro
fromPlayer->SendDirectMessage(&data); fromPlayer->SendDirectMessage(&data);
return; return;
} }
if (!IsAllowedCommand(filtered) && if (!IsAllowedCommand(filtered) &&
(!GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER, fromPlayer))) (!GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER, fromPlayer)))
return; return;
@@ -1042,10 +990,10 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
{ {
if (packet.empty()) if (packet.empty())
return; return;
if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld()) if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
{
return; return;
}
switch (packet.GetOpcode()) switch (packet.GetOpcode())
{ {
case SMSG_SPELL_FAILURE: case SMSG_SPELL_FAILURE:
@@ -1213,26 +1161,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
return; return;
} }
case SMSG_FORCE_MOVE_ROOT: // CMSG_FORCE_MOVE_ROOT_ACK case SMSG_MOVE_KNOCK_BACK: // handle knockbacks
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); WorldPacket p(packet);
p.rpos(0); p.rpos(0);
@@ -1320,12 +1249,7 @@ void PlayerbotAI::SpellInterrupted(uint32 spellid)
Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type); Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type);
if (!spell) if (!spell)
continue; continue;
if (spell->GetSpellInfo()->Id == spellid)
SpellInfo const* spellInfo = spell->GetSpellInfo();
if (!spellInfo)
continue;
if (spellInfo->Id == spellid)
bot->InterruptSpell((CurrentSpellTypes)type); bot->InterruptSpell((CurrentSpellTypes)type);
} }
// LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get(); // LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get();
@@ -1409,6 +1333,10 @@ void PlayerbotAI::DoNextAction(bool min)
bool isBotAlive = bot->IsAlive(); bool isBotAlive = bot->IsAlive();
if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive) if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive)
{ {
bot->StopMoving();
bot->GetMotionMaster()->Clear();
bot->GetMotionMaster()->MoveIdle();
// Death Count to prevent skeleton piles // Death Count to prevent skeleton piles
// Player* master = GetMaster(); // warning here - whipowill // Player* master = GetMaster(); // warning here - whipowill
if (!HasActivePlayerMaster() && !bot->InBattleground()) if (!HasActivePlayerMaster() && !bot->InBattleground())
@@ -1426,11 +1354,9 @@ void PlayerbotAI::DoNextAction(bool min)
return; return;
} }
// Change engine if just ressed (no movement update when rooted) // Change engine if just ressed
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive && !bot->IsRooted()) if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
{ {
bot->SendMovementFlagUpdate();
ChangeEngine(BOT_STATE_NON_COMBAT); ChangeEngine(BOT_STATE_NON_COMBAT);
return; return;
} }
@@ -1460,6 +1386,9 @@ void PlayerbotAI::DoNextAction(bool min)
else if (bot->isAFK()) else if (bot->isAFK())
bot->ToggleAFK(); bot->ToggleAFK();
Group* group = bot->GetGroup();
PlayerbotAI* masterBotAI = nullptr;
if (master && master->IsInWorld()) if (master && master->IsInWorld())
{ {
float distance = sServerFacade->GetDistance2d(bot, master); float distance = sServerFacade->GetDistance2d(bot, master);
@@ -1532,7 +1461,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
strategyName = "onyxia"; // Onyxia's Lair strategyName = "onyxia"; // Onyxia's Lair
break; break;
case 409: case 409:
strategyName = "moltencore"; // Molten Core strategyName = "mc"; // Molten Core
break; break;
case 469: case 469:
strategyName = "bwl"; // Blackwing Lair strategyName = "bwl"; // Blackwing Lair
@@ -1543,9 +1472,11 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 532: case 532:
strategyName = "karazhan"; // Karazhan strategyName = "karazhan"; // Karazhan
break; break;
case 533:
strategyName = "naxx"; // Naxxramas
break;
case 544: case 544:
strategyName = "magtheridon"; // Magtheridon's Lair strategyName = "magtheridon"; // Magtheridon's Lair
break;
case 565: case 565:
strategyName = "gruulslair"; // Gruul's Lair strategyName = "gruulslair"; // Gruul's Lair
break; break;
@@ -1767,7 +1698,6 @@ bool PlayerbotAI::IsRanged(Player* player, bool bySpec)
} }
break; break;
} }
return true; return true;
} }
@@ -1861,9 +1791,10 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
bool PlayerbotAI::HasAggro(Unit* unit) bool PlayerbotAI::HasAggro(Unit* unit)
{ {
if (!IsValidUnit(unit)) if (!unit)
{
return false; return false;
}
bool isMT = IsMainTank(bot); bool isMT = IsMainTank(bot);
Unit* victim = unit->GetVictim(); Unit* victim = unit->GetVictim();
if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && IsTank(victim->ToPlayer())))) if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && IsTank(victim->ToPlayer()))))
@@ -2071,7 +2002,7 @@ bool PlayerbotAI::IsTank(Player* player, bool bySpec)
switch (player->getClass()) switch (player->getClass())
{ {
case CLASS_DEATH_KNIGHT: case CLASS_DEATH_KNIGHT:
if (tab == DEATH_KNIGHT_TAB_BLOOD) if (tab == DEATHKNIGHT_TAB_BLOOD)
{ {
return true; return true;
} }
@@ -2179,7 +2110,7 @@ bool PlayerbotAI::IsDps(Player* player, bool bySpec)
} }
break; break;
case CLASS_DEATH_KNIGHT: case CLASS_DEATH_KNIGHT:
if (tab != DEATH_KNIGHT_TAB_BLOOD) if (tab != DEATHKNIGHT_TAB_BLOOD)
{ {
return true; return true;
} }
@@ -2317,7 +2248,7 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player)
bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); } bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); }
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers) bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
{ {
Group* group = player->GetGroup(); Group* group = player->GetGroup();
if (!group) if (!group)
@@ -2334,9 +2265,6 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDead
continue; continue;
} }
if (ignoreDeadPlayers && !member->IsAlive())
continue;
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
{ {
if (index == counter) if (index == counter)
@@ -2356,9 +2284,6 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDead
continue; continue;
} }
if (ignoreDeadPlayers && !member->IsAlive())
continue;
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
{ {
if (index == counter) if (index == counter)
@@ -2839,12 +2764,7 @@ bool PlayerbotAI::TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel
bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel) bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel)
{ {
if (!master) if (!master || !TellMasterNoFacing(text, securityLevel))
{
if (sPlayerbotAIConfig->randomBotSayWithoutMaster)
return TellMasterNoFacing(text, securityLevel);
}
if (!TellMasterNoFacing(text, securityLevel))
return false; return false;
if (!bot->isMoving() && !bot->IsInCombat() && bot->GetMapId() == master->GetMapId() && if (!bot->isMoving() && !bot->IsInCombat() && bot->GetMapId() == master->GetMapId() &&
@@ -2861,9 +2781,6 @@ bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel secu
bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit) bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
{ {
if (!unit || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld())
return false;
if (!aurEff) if (!aurEff)
return false; return false;
@@ -2871,8 +2788,6 @@ bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
return true; return true;
SpellInfo const* spellInfo = aurEff->GetSpellInfo(); SpellInfo const* spellInfo = aurEff->GetSpellInfo();
if (!spellInfo)
return false;
uint32 stacks = aurEff->GetBase()->GetStackAmount(); uint32 stacks = aurEff->GetBase()->GetStackAmount();
if (stacks >= spellInfo->StackAmount) if (stacks >= spellInfo->StackAmount)
@@ -2888,7 +2803,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 PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, bool checkIsOwner, int maxAuraAmount,
bool checkDuration) bool checkDuration)
{ {
if (!IsValidUnit(unit)) if (!unit)
return false; return false;
std::wstring wnamepart; std::wstring wnamepart;
@@ -2984,7 +2899,7 @@ bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit)
Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack) Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack)
{ {
if (!IsValidUnit(unit)) if (!unit)
return nullptr; return nullptr;
std::wstring wnamepart; std::wstring wnamepart;
@@ -3002,9 +2917,6 @@ Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner
for (AuraEffect const* aurEff : auras) for (AuraEffect const* aurEff : auras)
{ {
SpellInfo const* spellInfo = aurEff->GetSpellInfo(); SpellInfo const* spellInfo = aurEff->GetSpellInfo();
if (!spellInfo)
continue;
std::string const& auraName = spellInfo->SpellName[0]; std::string const& auraName = spellInfo->SpellName[0];
// Directly skip if name mismatch (both length and content) // Directly skip if name mismatch (both length and content)
@@ -3085,9 +2997,6 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
if (!target) if (!target)
target = bot; target = bot;
if (!IsValidUnit(target))
return false;
if (Pet* pet = bot->GetPet()) if (Pet* pet = bot->GetPet())
if (pet->HasSpell(spellid)) if (pet->HasSpell(spellid))
return true; return true;
@@ -3349,9 +3258,6 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool c
bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarget) 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); bool result = CastSpell(aiObjectContext->GetValue<uint32>("spell id", name)->Get(), target, itemTarget);
if (result) if (result)
{ {
@@ -3364,19 +3270,15 @@ bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarg
bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget) bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
{ {
if (!spellId) if (!spellId)
{
return false; return false;
}
if (!target) if (!target)
target = bot; target = bot;
if (!IsValidUnit(target))
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return false;
Pet* pet = bot->GetPet(); Pet* pet = bot->GetPet();
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (pet && pet->HasSpell(spellId)) if (pet && pet->HasSpell(spellId))
{ {
// List of spell IDs for which we do NOT want to toggle auto-cast or send message // List of spell IDs for which we do NOT want to toggle auto-cast or send message
@@ -3779,9 +3681,6 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
if (!spellId) if (!spellId)
return false; return false;
if (!IsValidUnit(target))
return false;
Vehicle* vehicle = bot->GetVehicle(); Vehicle* vehicle = bot->GetVehicle();
if (!vehicle) if (!vehicle)
return false; return false;
@@ -3792,12 +3691,12 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
return false; return false;
Unit* vehicleBase = vehicle->GetBase(); Unit* vehicleBase = vehicle->GetBase();
Unit* spellTarget = target;
Unit* spellTarget = target;
if (!spellTarget) if (!spellTarget)
spellTarget = vehicleBase; spellTarget = vehicleBase;
if (!IsValidUnit(spellTarget)) if (!spellTarget)
return false; return false;
if (vehicleBase->HasSpellCooldown(spellId)) if (vehicleBase->HasSpellCooldown(spellId))
@@ -3864,9 +3763,6 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
if (!spellId) if (!spellId)
return false; return false;
if (!IsValidUnit(target))
return false;
Vehicle* vehicle = bot->GetVehicle(); Vehicle* vehicle = bot->GetVehicle();
if (!vehicle) if (!vehicle)
return false; return false;
@@ -3877,12 +3773,12 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
return false; return false;
Unit* vehicleBase = vehicle->GetBase(); Unit* vehicleBase = vehicle->GetBase();
Unit* spellTarget = target;
Unit* spellTarget = target;
if (!spellTarget) if (!spellTarget)
spellTarget = vehicleBase; spellTarget = vehicleBase;
if (!IsValidUnit(spellTarget)) if (!spellTarget)
return false; return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
@@ -4035,13 +3931,9 @@ bool PlayerbotAI::IsInVehicle(bool canControl, bool canCast, bool canAttack, boo
void PlayerbotAI::WaitForSpellCast(Spell* spell) void PlayerbotAI::WaitForSpellCast(Spell* spell)
{ {
if (!spell)
return;
SpellInfo const* spellInfo = spell->GetSpellInfo(); SpellInfo const* spellInfo = spell->GetSpellInfo();
uint32 castTime = spell->GetCastTime(); uint32 castTime = spell->GetCastTime();
if (spellInfo->IsChanneled())
if (spellInfo && spellInfo->IsChanneled())
{ {
int32 duration = spellInfo->GetDuration(); int32 duration = spellInfo->GetDuration();
bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration); bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration);
@@ -4089,9 +3981,6 @@ void PlayerbotAI::RemoveAura(std::string const name)
bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const spell) bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const spell)
{ {
if (!IsValidUnit(target))
return false;
uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", spell)->Get(); uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", spell)->Get();
if (!spellid || !target->IsNonMeleeSpellCast(true)) if (!spellid || !target->IsNonMeleeSpellCast(true))
return false; return false;
@@ -4120,25 +4009,17 @@ bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const sp
bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType) bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
{ {
if (!IsValidUnit(target) || !target->IsAlive()) if (!target->IsInWorld())
{
return false; return false;
}
if (!IsValidPlayer(bot))
return false;
bool isFriend = bot->IsFriendlyTo(target); bool isFriend = bot->IsFriendlyTo(target);
Unit::VisibleAuraMap const* visibleAuras = target->GetVisibleAuras(); Unit::VisibleAuraMap const* visibleAuras = target->GetVisibleAuras();
if (!visibleAuras)
return false;
for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr) for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr)
{ {
if (!itr->second)
continue;
Aura* aura = itr->second->GetBase(); Aura* aura = itr->second->GetBase();
if (!aura || aura->IsPassive() || aura->IsRemoved())
if (aura->IsPassive())
continue; continue;
if (sPlayerbotAIConfig->dispelAuraDuration && aura->GetDuration() && if (sPlayerbotAIConfig->dispelAuraDuration && aura->GetDuration() &&
@@ -4146,8 +4027,6 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
continue; continue;
SpellInfo const* spellInfo = aura->GetSpellInfo(); SpellInfo const* spellInfo = aura->GetSpellInfo();
if (!spellInfo)
continue;
bool isPositiveSpell = spellInfo->IsPositive(); bool isPositiveSpell = spellInfo->IsPositive();
if (isPositiveSpell && isFriend) if (isPositiveSpell && isFriend)
@@ -4159,7 +4038,6 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
if (canDispel(spellInfo, dispelType)) if (canDispel(spellInfo, dispelType))
return true; return true;
} }
return false; return false;
} }
@@ -4214,7 +4092,7 @@ Player* PlayerbotAI::FindNewMaster()
if (!group) if (!group)
return nullptr; return nullptr;
Player* groupLeader = GetGroupLeader(); Player* groupLeader = GetGroupMaster();
PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(groupLeader); PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(groupLeader);
if (!leaderBotAI || leaderBotAI->IsRealPlayer()) if (!leaderBotAI || leaderBotAI->IsRealPlayer())
return groupLeader; return groupLeader;
@@ -4223,7 +4101,8 @@ Player* PlayerbotAI::FindNewMaster()
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{ {
Player* member = gref->GetSource(); Player* member = gref->GetSource();
if (!member || member == bot || !member->IsInWorld() || !member->IsInSameRaidWith(bot)) if (!member || member == bot || !member->IsInWorld() ||
!member->IsInSameRaidWith(bot))
continue; continue;
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
@@ -4264,7 +4143,7 @@ bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(m
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); } bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
Player* PlayerbotAI::GetGroupLeader() Player* PlayerbotAI::GetGroupMaster()
{ {
if (!bot->InBattleground()) if (!bot->InBattleground())
if (Group* group = bot->GetGroup()) if (Group* group = bot->GetGroup())
@@ -4458,11 +4337,6 @@ inline bool ZoneHasRealPlayers(Player* bot)
bool PlayerbotAI::AllowActive(ActivityType activityType) 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 // when botActiveAlone is 100% and smartScale disabled
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale) if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
{ {
@@ -4553,8 +4427,10 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{ {
Player* member = gref->GetSource(); Player* member = gref->GetSource();
if (!member || !member->IsInWorld() || member->GetMapId() != bot->GetMapId()) if ((!member || !member->IsInWorld()) && member->GetMapId() != bot->GetMapId())
{
continue; continue;
}
if (member == bot) if (member == bot)
{ {
@@ -4605,23 +4481,23 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
// HasFriend // HasFriend
if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend) if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend)
{ {
// shouldnt be needed analyse in future if (!bot || !bot->IsInWorld() || !bot->GetGUID())
if (!bot->GetGUID())
return false; return false;
for (auto& player : sRandomPlayerbotMgr->GetPlayers()) for (auto& player : sRandomPlayerbotMgr->GetPlayers())
{ {
if (!player || !player->GetSession() || !player->IsInWorld() || player->IsDuringRemoveFromWorld() || if (!player || !player->IsInWorld())
player->GetSession()->isLogingOut())
continue; continue;
PlayerbotAI* playerAI = GET_PLAYERBOT_AI(player); Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
if (!playerAI || !playerAI->IsRealPlayer()) if (!connectedPlayer)
continue; continue;
// if a real player has the bot as a friend
PlayerSocial* social = player->GetSocial(); PlayerSocial* social = player->GetSocial();
if (social && social->HasFriend(bot->GetGUID())) if (!social)
continue;
if (social->HasFriend(bot->GetGUID()))
return true; return true;
} }
} }
@@ -4635,7 +4511,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
} }
} }
// Bots don't need react to PathGenerator activities // Bots don't need to move using PathGenerator.
if (activityType == DETAILED_MOVE_ACTIVITY) if (activityType == DETAILED_MOVE_ACTIVITY)
{ {
return false; return false;
@@ -4671,25 +4547,15 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
{ {
const int activityIndex = static_cast<int>(activityType); if (!allowActiveCheckTimer[activityType])
allowActiveCheckTimer[activityType] = time(nullptr);
// Unknown/out-of-range avoid blocking, added logging for further analysing should not happen in the first place. if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityType] + 5))
if (activityIndex <= 0 || activityIndex >= MAX_ACTIVITY_TYPE) return allowActive[activityType];
{
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; return allowed;
} }
@@ -5473,13 +5339,15 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
if (!item_template) if (!item_template)
return nullptr; return nullptr;
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = { static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE, ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE}; SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
};
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = { static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE}; HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
};
Item* stone = nullptr; Item* stone = nullptr;
ItemTemplate const* pProto = weapon->GetTemplate(); ItemTemplate const* pProto = weapon->GetTemplate();
@@ -5515,6 +5383,7 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
Item* PlayerbotAI::FindOilFor(Item* weapon) const Item* PlayerbotAI::FindOilFor(Item* weapon) const
{ {
if (!weapon) if (!weapon)
return nullptr; return nullptr;
@@ -5523,12 +5392,12 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
return nullptr; return nullptr;
static const std::vector<uint32_t> uPrioritizedWizardOilIds = { static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_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_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
static const std::vector<uint32_t> uPrioritizedManaOilIds = { static const std::vector<uint32_t> uPrioritizedManaOilIds = {
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL, BRILLIANT_WIZARD_OIL, BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL}; BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
Item* oil = nullptr; Item* oil = nullptr;
int botClass = bot->getClass(); int botClass = bot->getClass();
@@ -5544,22 +5413,22 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
prioritizedOils = &uPrioritizedWizardOilIds; prioritizedOils = &uPrioritizedWizardOilIds;
break; break;
case CLASS_DRUID: case CLASS_DRUID:
if (specTab == 0) // Balance if (specTab == 0) // Balance
prioritizedOils = &uPrioritizedWizardOilIds; prioritizedOils = &uPrioritizedWizardOilIds;
else if (specTab == 1) // Feral else if (specTab == 1) // Feral
prioritizedOils = nullptr; prioritizedOils = nullptr;
else // Restoration (specTab == 2) or any other/unspecified spec else // Restoration (specTab == 2) or any other/unspecified spec
prioritizedOils = &uPrioritizedManaOilIds; prioritizedOils = &uPrioritizedManaOilIds;
break; break;
case CLASS_HUNTER: case CLASS_HUNTER:
prioritizedOils = &uPrioritizedManaOilIds; prioritizedOils = &uPrioritizedManaOilIds;
break; break;
case CLASS_PALADIN: case CLASS_PALADIN:
if (specTab == 1) // Protection if (specTab == 1) // Protection
prioritizedOils = &uPrioritizedWizardOilIds; prioritizedOils = &uPrioritizedWizardOilIds;
else if (specTab == 2) // Retribution else if (specTab == 2) // Retribution
prioritizedOils = nullptr; prioritizedOils = nullptr;
else // Holy (specTab == 0) or any other/unspecified spec else // Holy (specTab == 0) or any other/unspecified spec
prioritizedOils = &uPrioritizedManaOilIds; prioritizedOils = &uPrioritizedManaOilIds;
break; break;
default: default:
@@ -5790,7 +5659,7 @@ void PlayerbotAI::ImbueItem(Item* item) { ImbueItem(item, TARGET_FLAG_NONE, Obje
// item on unit // item on unit
void PlayerbotAI::ImbueItem(Item* item, Unit* target) void PlayerbotAI::ImbueItem(Item* item, Unit* target)
{ {
if (!IsValidUnit(target)) if (!target)
return; return;
ImbueItem(item, TARGET_FLAG_UNIT, target->GetGUID()); ImbueItem(item, TARGET_FLAG_UNIT, target->GetGUID());
@@ -5932,38 +5801,30 @@ int32 PlayerbotAI::GetNearGroupMemberCount(float dis)
bool PlayerbotAI::CanMove() bool PlayerbotAI::CanMove()
{ {
// Most common checks: confused, stunned, fleeing, jumping, charging. All these // do not allow if not vehicle driver
// states are set when handling certain aura effects. We don't check against if (IsInVehicle() && !IsInVehicle(true))
// UNIT_STATE_ROOT here, because this state is used by vehicles.
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false; return false;
// Death state (w/o spirit release) and Spirit of Redemption aura (priest) if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
if ((bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || bot->HasSpiritOfRedemptionAura()) bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false; return false;
// Common CC effects, ordered by frequency: rooted > charmed > frozen > polymorphed. return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
// 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())) bool PlayerbotAI::IsRealGuild(uint32 guildId)
{
Guild* guild = sGuildMgr->GetGuildById(guildId);
if (!guild)
{
return false; return false;
if (bot->isFrozen() || bot->IsPolymorphed()) }
uint32 leaderAccount = sCharacterCache->GetCharacterAccountIdByGuid(guild->GetLeaderGUID());
if (!leaderAccount)
return false; return false;
// Check for the MM controlled slot types: feared, confused, fleeing, etc. return !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount));
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() bool PlayerbotAI::IsInRealGuild()
@@ -5971,7 +5832,7 @@ bool PlayerbotAI::IsInRealGuild()
if (!bot->GetGuildId()) if (!bot->GetGuildId())
return false; return false;
return sPlayerbotGuildMgr->IsRealGuild(bot->GetGuildId()); return IsRealGuild(bot->GetGuildId());
} }
void PlayerbotAI::QueueChatResponse(const ChatQueuedReply chatReply) { chatReplies.push_back(std::move(chatReply)); } void PlayerbotAI::QueueChatResponse(const ChatQueuedReply chatReply) { chatReplies.push_back(std::move(chatReply)); }

View File

@@ -276,7 +276,7 @@ enum BotRoles : uint8
enum HUNTER_TABS enum HUNTER_TABS
{ {
HUNTER_TAB_BEAST_MASTERY, HUNTER_TAB_BEASTMASTERY,
HUNTER_TAB_MARKSMANSHIP, HUNTER_TAB_MARKSMANSHIP,
HUNTER_TAB_SURVIVAL, HUNTER_TAB_SURVIVAL,
}; };
@@ -295,11 +295,11 @@ enum PRIEST_TABS
PRIEST_TAB_SHADOW, PRIEST_TAB_SHADOW,
}; };
enum DEATH_KNIGHT_TABS enum DEATHKNIGHT_TABS
{ {
DEATH_KNIGHT_TAB_BLOOD, DEATHKNIGHT_TAB_BLOOD,
DEATH_KNIGHT_TAB_FROST, DEATHKNIGHT_TAB_FROST,
DEATH_KNIGHT_TAB_UNHOLY, DEATHKNIGHT_TAB_UNHOLY,
}; };
enum DRUID_TABS enum DRUID_TABS
@@ -428,7 +428,7 @@ public:
static bool IsMainTank(Player* player); static bool IsMainTank(Player* player);
static uint32 GetGroupTankNum(Player* player); static uint32 GetGroupTankNum(Player* player);
static bool IsAssistTank(Player* player); static bool IsAssistTank(Player* player);
static bool IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers = false); static bool IsAssistTankOfIndex(Player* player, int index);
static bool IsHealAssistantOfIndex(Player* player, int index); static bool IsHealAssistantOfIndex(Player* player, int index);
static bool IsRangedDpsAssistantOfIndex(Player* player, int index); static bool IsRangedDpsAssistantOfIndex(Player* player, int index);
bool HasAggro(Unit* unit); bool HasAggro(Unit* unit);
@@ -540,7 +540,7 @@ public:
// Get the group leader or the master of the bot. // Get the group leader or the master of the bot.
// Checks if the bot is summoned as alt of a player // Checks if the bot is summoned as alt of a player
bool IsAlt(); bool IsAlt();
Player* GetGroupLeader(); Player* GetGroupMaster();
// Returns a semi-random (cycling) number that is fixed for each bot. // Returns a semi-random (cycling) number that is fixed for each bot.
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1); uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1);
GrouperType GetGrouperType(); GrouperType GetGrouperType();
@@ -579,6 +579,7 @@ public:
void ResetJumpDestination() { jumpDestination = Position(); } void ResetJumpDestination() { jumpDestination = Position(); }
bool CanMove(); bool CanMove();
static bool IsRealGuild(uint32 guildId);
bool IsInRealGuild(); bool IsInRealGuild();
static std::vector<std::string> dispel_whitelist; static std::vector<std::string> dispel_whitelist;
bool EqualLowercaseName(std::string s1, std::string s2); bool EqualLowercaseName(std::string s1, std::string s2);
@@ -611,20 +612,12 @@ private:
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore, static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
bool mixed = false); bool mixed = false);
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
void UpdateAIGroupMaster(); void UpdateAIGroupAndMaster();
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const; Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
void HandleCommands(); void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL); void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
bool _isBotInitializing = false; 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: protected:
Player* bot; Player* bot;
Player* master; Player* master;

View File

@@ -10,7 +10,6 @@
#include "PlayerbotDungeonSuggestionMgr.h" #include "PlayerbotDungeonSuggestionMgr.h"
#include "PlayerbotFactory.h" #include "PlayerbotFactory.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PlayerbotGuildMgr.h"
#include "RandomItemMgr.h" #include "RandomItemMgr.h"
#include "RandomPlayerbotFactory.h" #include "RandomPlayerbotFactory.h"
#include "RandomPlayerbotMgr.h" #include "RandomPlayerbotMgr.h"
@@ -166,7 +165,7 @@ bool PlayerbotAIConfig::Initialize()
pvpProhibitedZoneIds); pvpProhibitedZoneIds);
LoadList<std::vector<uint32>>( LoadList<std::vector<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds",
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"), "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
pvpProhibitedAreaIds); pvpProhibitedAreaIds);
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true); fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
LoadList<std::vector<uint32>>( LoadList<std::vector<uint32>>(
@@ -223,11 +222,6 @@ bool PlayerbotAIConfig::Initialize()
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true); 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 //////////////////////////// CHAT
enableBroadcasts = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableBroadcasts", true); enableBroadcasts = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableBroadcasts", true);
randomBotTalk = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotTalk", false); randomBotTalk = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotTalk", false);
@@ -667,7 +661,6 @@ bool PlayerbotAIConfig::Initialize()
sRandomPlayerbotMgr->Init(); sRandomPlayerbotMgr->Init();
} }
sPlayerbotGuildMgr->Init();
sRandomItemMgr->Init(); sRandomItemMgr->Init();
sRandomItemMgr->InitAfterAhBot(); sRandomItemMgr->InitAfterAhBot();
sPlayerbotTextMgr->LoadBotTexts(); sPlayerbotTextMgr->LoadBotTexts();

View File

@@ -145,10 +145,6 @@ public:
// Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff. Default: 30 // Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff. Default: 30
int32 rpWarningCooldown; int32 rpWarningCooldown;
// Professions
bool enableFishingWithMaster;
float fishingDistanceFromMaster, fishingDistance, endFishingWithMaster;
// chat // chat
bool randomBotTalk; bool randomBotTalk;
bool randomBotEmote; bool randomBotEmote;
@@ -273,6 +269,7 @@ public:
bool deleteRandomBotAccounts; bool deleteRandomBotAccounts;
uint32 randomBotGuildCount, randomBotGuildSizeMax; uint32 randomBotGuildCount, randomBotGuildSizeMax;
bool deleteRandomBotGuilds; bool deleteRandomBotGuilds;
std::vector<uint32> randomBotGuilds;
std::vector<uint32> pvpProhibitedZoneIds; std::vector<uint32> pvpProhibitedZoneIds;
std::vector<uint32> pvpProhibitedAreaIds; std::vector<uint32> pvpProhibitedAreaIds;
bool fastReactInBG; bool fastReactInBG;

View File

@@ -1,322 +0,0 @@
#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();
}

View File

@@ -1,52 +0,0 @@
#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

View File

@@ -9,10 +9,9 @@
#include <cstring> #include <cstring>
#include <istream> #include <istream>
#include <string> #include <string>
#include <unordered_set>
#include <openssl/sha.h> #include <openssl/sha.h>
#include <unordered_set>
#include <iomanip> #include <iomanip>
#include <algorithm>
#include "ChannelMgr.h" #include "ChannelMgr.h"
#include "CharacterCache.h" #include "CharacterCache.h"
@@ -32,13 +31,15 @@
#include "PlayerbotSecurity.h" #include "PlayerbotSecurity.h"
#include "PlayerbotWorldThreadProcessor.h" #include "PlayerbotWorldThreadProcessor.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PlayerbotGuildMgr.h"
#include "RandomPlayerbotMgr.h" #include "RandomPlayerbotMgr.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "WorldSession.h" #include "WorldSession.h"
#include "ChannelMgr.h"
#include "BroadcastHelper.h" #include "BroadcastHelper.h"
#include "PlayerbotDbStore.h"
#include "WorldSessionMgr.h" #include "WorldSessionMgr.h"
#include "DatabaseEnv.h" #include "DatabaseEnv.h" // Added for gender choice
#include <algorithm> // Added for gender choice
class BotInitGuard class BotInitGuard
{ {
@@ -67,7 +68,6 @@ private:
}; };
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized; std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
std::unordered_set<ObjectGuid> PlayerbotHolder::botLoading;
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {} PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
class PlayerbotLoginQueryHolder : public LoginQueryHolder class PlayerbotLoginQueryHolder : public LoginQueryHolder
@@ -76,12 +76,13 @@ private:
uint32 masterAccountId; uint32 masterAccountId;
PlayerbotHolder* playerbotHolder; PlayerbotHolder* playerbotHolder;
public: public:
PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid) PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount) : LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
{ {
} }
uint32 GetMasterAccountId() const { return masterAccountId; } uint32 GetMasterAccountId() const { return masterAccountId; }
PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; }
}; };
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId) void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
@@ -142,7 +143,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
return; return;
} }
std::shared_ptr<PlayerbotLoginQueryHolder> holder = std::shared_ptr<PlayerbotLoginQueryHolder> holder =
std::make_shared<PlayerbotLoginQueryHolder>(masterAccountId, accountId, playerGuid); std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
if (!holder->Initialize()) if (!holder->Initialize())
{ {
return; return;
@@ -152,27 +153,8 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
// Always login in with world session to avoid race condition // Always login in with world session to avoid race condition
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder)) sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
.AfterComplete( .AfterComplete([this](SQLQueryHolderBase const& holder)
[](SQLQueryHolderBase const& queryHolder) { HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
{
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) bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
@@ -187,9 +169,8 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
uint32 botAccountId = holder.GetAccountId(); 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 // 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) // allows channels to work as intended)
WorldSession* botSession = WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0), time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
@@ -200,27 +181,26 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId); LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
botSession->LogoutPlayer(true); botSession->LogoutPlayer(true);
delete botSession; delete botSession;
PlayerbotHolder::botLoading.erase(holder.GetGuid()); botLoading.erase(holder.GetGuid());
return; return;
} }
uint32 masterAccountId = holder.GetMasterAccountId(); uint32 masterAccount = holder.GetMasterAccountId();
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr; WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr;
// Check if masterSession->GetPlayer() is valid // Check if masterSession->GetPlayer() is valid
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr; Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
if (masterSession && !masterPlayer) if (masterSession && !masterPlayer)
{ {
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
masterAccountId);
} }
sRandomPlayerbotMgr->OnPlayerLogin(bot); sRandomPlayerbotMgr->OnPlayerLogin(bot);
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), masterAccountId);
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), this);
sPlayerbotWorldProcessor->QueueOperation(std::move(op)); sPlayerbotWorldProcessor->QueueOperation(std::move(op));
PlayerbotHolder::botLoading.erase(holder.GetGuid()); botLoading.erase(holder.GetGuid());
} }
void PlayerbotHolder::UpdateSessions() void PlayerbotHolder::UpdateSessions()
@@ -1194,7 +1174,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
if (ObjectAccessor::FindConnectedPlayer(guid)) if (ObjectAccessor::FindConnectedPlayer(guid))
continue; continue;
uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid); uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid);
if (guildId && sPlayerbotGuildMgr->IsRealGuild(guildId)) if (guildId && PlayerbotAI::IsRealGuild(guildId))
continue; continue;
AddPlayerBot(guid, master->GetSession()->GetAccountId()); AddPlayerBot(guid, master->GetSession()->GetAccountId());
messages.push_back("Add class " + std::string(charname)); messages.push_back("Add class " + std::string(charname));

View File

@@ -60,7 +60,7 @@ protected:
virtual void OnBotLoginInternal(Player* const bot) = 0; virtual void OnBotLoginInternal(Player* const bot) = 0;
PlayerBotMap playerBots; PlayerBotMap playerBots;
static std::unordered_set<ObjectGuid> botLoading; std::unordered_set<ObjectGuid> botLoading;
}; };
class PlayerbotMgr : public PlayerbotHolder class PlayerbotMgr : public PlayerbotHolder

View File

@@ -9,7 +9,6 @@
#include "Group.h" #include "Group.h"
#include "GroupMgr.h" #include "GroupMgr.h"
#include "GuildMgr.h" #include "GuildMgr.h"
#include "Playerbots.h"
#include "ObjectAccessor.h" #include "ObjectAccessor.h"
#include "PlayerbotOperation.h" #include "PlayerbotOperation.h"
#include "Player.h" #include "Player.h"
@@ -17,8 +16,6 @@
#include "PlayerbotMgr.h" #include "PlayerbotMgr.h"
#include "PlayerbotDbStore.h" #include "PlayerbotDbStore.h"
#include "RandomPlayerbotMgr.h" #include "RandomPlayerbotMgr.h"
#include "WorldSession.h"
#include "WorldSessionMgr.h"
// Group invite operation // Group invite operation
class GroupInviteOperation : public PlayerbotOperation class GroupInviteOperation : public PlayerbotOperation
@@ -471,31 +468,18 @@ private:
class OnBotLoginOperation : public PlayerbotOperation class OnBotLoginOperation : public PlayerbotOperation
{ {
public: public:
OnBotLoginOperation(ObjectGuid botGuid, uint32 masterAccountId) OnBotLoginOperation(ObjectGuid botGuid, PlayerbotHolder* holder)
: m_botGuid(botGuid), m_masterAccountId(masterAccountId) : m_botGuid(botGuid), m_holder(holder)
{ {
} }
bool Execute() override bool Execute() override
{ {
// find and verify bot still exists
Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid); Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid);
if (!bot) if (!bot || !m_holder)
return false; return false;
PlayerbotHolder* holder = sRandomPlayerbotMgr; m_holder->OnBotLogin(bot);
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; return true;
} }
@@ -503,11 +487,14 @@ public:
uint32 GetPriority() const override { return 100; } uint32 GetPriority() const override { return 100; }
std::string GetName() const override { return "OnBotLogin"; } 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: private:
ObjectGuid m_botGuid; ObjectGuid m_botGuid;
uint32 m_masterAccountId = 0; PlayerbotHolder* m_holder;
}; };
#endif #endif

View File

@@ -17,28 +17,14 @@ PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot)
PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup) 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) if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
return PLAYERBOT_SECURITY_ALLOW_ALL; return PLAYERBOT_SECURITY_ALLOW_ALL;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI) if (!botAI)
{ {
if (reason)
*reason = PLAYERBOT_DENY_NONE;
return PLAYERBOT_SECURITY_DENY_ALL; return PLAYERBOT_SECURITY_DENY_ALL;
} }
if (botAI->IsOpposing(from)) if (botAI->IsOpposing(from))
{ {
if (reason) if (reason)
@@ -49,7 +35,6 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
if (sPlayerbotAIConfig->IsInRandomAccountList(account)) if (sPlayerbotAIConfig->IsInRandomAccountList(account))
{ {
// (duplicate check in case of faction change)
if (botAI->IsOpposing(from)) if (botAI->IsOpposing(from))
{ {
if (reason) if (reason)
@@ -58,17 +43,27 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_DENY_ALL; return PLAYERBOT_SECURITY_DENY_ALL;
} }
Group* fromGroup = from->GetGroup(); // if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
Group* botGroup = bot->GetGroup(); // {
// if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
// {
// if (reason)
// *reason = PLAYERBOT_DENY_LFG;
if (fromGroup && botGroup && fromGroup == botGroup && !ignoreGroup) // return PLAYERBOT_SECURITY_TALK;
// }
// }
Group* group = from->GetGroup();
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() == from)
{ {
if (botAI->GetMaster() == from) return PLAYERBOT_SECURITY_ALLOW_ALL;
return PLAYERBOT_SECURITY_ALLOW_ALL; }
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() != from)
{
if (reason) if (reason)
*reason = PLAYERBOT_DENY_NOT_YOURS; *reason = PLAYERBOT_DENY_NOT_YOURS;
return PLAYERBOT_SECURITY_TALK; return PLAYERBOT_SECURITY_TALK;
} }
@@ -80,34 +75,27 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_TALK; return PLAYERBOT_SECURITY_TALK;
} }
if (sPlayerbotAIConfig->groupInvitationPermission <= 1) if (sPlayerbotAIConfig->groupInvitationPermission <= 1 && (int32)bot->GetLevel() - (int8)from->GetLevel() > 5)
{ {
int32 levelDiff = int32(bot->GetLevel()) - int32(from->GetLevel()); if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
if (levelDiff > 5)
{ {
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId()) if (reason)
{ *reason = PLAYERBOT_DENY_LOW_LEVEL;
if (reason)
*reason = PLAYERBOT_DENY_LOW_LEVEL;
return PLAYERBOT_SECURITY_TALK; return PLAYERBOT_SECURITY_TALK;
}
} }
} }
int32 botGS = static_cast<int32>(botAI->GetEquipGearScore(bot)); int32 botGS = (int32)botAI->GetEquipGearScore(bot/*, false, false*/);
int32 fromGS = static_cast<int32>(botAI->GetEquipGearScore(from)); int32 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/);
if (sPlayerbotAIConfig->gearscorecheck)
if (sPlayerbotAIConfig->gearscorecheck && botGS && bot->GetLevel() > 15 && botGS > fromGS)
{ {
uint32 diffPct = uint32(100 * (botGS - fromGS) / botGS); if (botGS && bot->GetLevel() > 15 && botGS > fromGS &&
uint32 reqPct = uint32(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel()); static_cast<float>(100 * (botGS - fromGS) / botGS) >=
static_cast<float>(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel()))
if (diffPct >= reqPct)
{ {
if (reason) if (reason)
*reason = PLAYERBOT_DENY_GEARSCORE; *reason = PLAYERBOT_DENY_GEARSCORE;
return PLAYERBOT_SECURITY_TALK; return PLAYERBOT_SECURITY_TALK;
} }
} }
@@ -123,17 +111,35 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
} }
} }
// If the bot is not in the group, we offer an invite /*if (bot->isDead())
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) if (reason)
*reason = PLAYERBOT_DENY_INVITE; *reason = PLAYERBOT_DENY_INVITE;
return PLAYERBOT_SECURITY_INVITE; return PLAYERBOT_SECURITY_INVITE;
} }
if (!ignoreGroup && botGroup->IsFull()) if (!ignoreGroup && group->IsFull())
{ {
if (reason) if (reason)
*reason = PLAYERBOT_DENY_FULL_GROUP; *reason = PLAYERBOT_DENY_FULL_GROUP;
@@ -141,22 +147,27 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_TALK; return PLAYERBOT_SECURITY_TALK;
} }
if (!ignoreGroup && botGroup->GetLeaderGUID() != bot->GetGUID()) if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID())
{ {
if (reason) if (reason)
*reason = PLAYERBOT_DENY_NOT_LEADER; *reason = PLAYERBOT_DENY_NOT_LEADER;
return PLAYERBOT_SECURITY_TALK; 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) if (reason)
*reason = PLAYERBOT_DENY_IS_LEADER; *reason = PLAYERBOT_DENY_INVITE;
return PLAYERBOT_SECURITY_INVITE; return PLAYERBOT_SECURITY_INVITE;
} }
// Non-random bots: only their master has full access
if (botAI->GetMaster() == from) if (botAI->GetMaster() == from)
return PLAYERBOT_SECURITY_ALLOW_ALL; return PLAYERBOT_SECURITY_ALLOW_ALL;
@@ -168,13 +179,8 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup) 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; DenyReason reason = PLAYERBOT_DENY_NONE;
PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup); PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup);
if (realLevel >= level || from == bot) if (realLevel >= level || from == bot)
return true; return true;
@@ -183,17 +189,11 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
return false; return false;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI) Player* master = botAI->GetMaster();
if (master && botAI && botAI->IsOpposing(master) && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
return false; 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; std::ostringstream out;
switch (realLevel) switch (realLevel)
{ {
case PLAYERBOT_SECURITY_DENY_ALL: case PLAYERBOT_SECURITY_DENY_ALL:
@@ -206,20 +206,19 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
out << "I'll do it later"; out << "I'll do it later";
break; break;
case PLAYERBOT_DENY_LOW_LEVEL: case PLAYERBOT_DENY_LOW_LEVEL:
out << "You are too low level: |cffff0000" << uint32(from->GetLevel()) << "|cffffffff/|cff00ff00" out << "You are too low level: |cffff0000" << (uint32)from->GetLevel() << "|cffffffff/|cff00ff00"
<< uint32(bot->GetLevel()); << (uint32)bot->GetLevel();
break; break;
case PLAYERBOT_DENY_GEARSCORE: case PLAYERBOT_DENY_GEARSCORE:
{ {
int botGS = int(botAI->GetEquipGearScore(bot)); int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/);
int fromGS = int(botAI->GetEquipGearScore(from)); int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/);
int diff = (100 * (botGS - fromGS) / botGS); int diff = (100 * (botGS - fromGS) / botGS);
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel(); int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS
<< " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%"; << " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%";
break;
} }
break;
case PLAYERBOT_DENY_NOT_YOURS: case PLAYERBOT_DENY_NOT_YOURS:
out << "I have a master already"; out << "I have a master already";
break; break;
@@ -238,10 +237,13 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
case PLAYERBOT_DENY_FAR: case PLAYERBOT_DENY_FAR:
{ {
out << "You must be closer to invite me to your group. I am in "; out << "You must be closer to invite me to your group. I am in ";
if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId())) if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId()))
{
out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)"; out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)";
break; }
} }
break;
case PLAYERBOT_DENY_FULL_GROUP: case PLAYERBOT_DENY_FULL_GROUP:
out << "I am in a full group. Will do it later"; out << "I am in a full group. Will do it later";
break; break;
@@ -249,10 +251,15 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
out << "I am currently leading a group. I can invite you if you want."; out << "I am currently leading a group. I can invite you if you want.";
break; break;
case PLAYERBOT_DENY_NOT_LEADER: case PLAYERBOT_DENY_NOT_LEADER:
if (Player* leader = botAI->GetGroupLeader()) if (botAI->GetGroupMaster())
out << "I am in a group with " << leader->GetName() << ". You can ask him for invite."; {
out << "I am in a group with " << botAI->GetGroupMaster()->GetName()
<< ". You can ask him for invite.";
}
else else
{
out << "I am in a group with someone else. You can ask him for invite."; out << "I am in a group with someone else. You can ask him for invite.";
}
break; break;
case PLAYERBOT_DENY_BG: case PLAYERBOT_DENY_BG:
out << "I am in a queue for BG. Will do it later"; out << "I am in a queue for BG. Will do it later";
@@ -276,14 +283,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
std::string const text = out.str(); std::string const text = out.str();
ObjectGuid guid = from->GetGUID(); ObjectGuid guid = from->GetGUID();
time_t lastSaid = whispers[guid][text]; time_t lastSaid = whispers[guid][text];
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000) if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
{ {
whispers[guid][text] = time(nullptr); 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; return false;

View File

@@ -25,8 +25,6 @@
#include "Metric.h" #include "Metric.h"
#include "PlayerScript.h" #include "PlayerScript.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "PlayerbotGuildMgr.h"
#include "PlayerbotSpellCache.h"
#include "PlayerbotWorldThreadProcessor.h" #include "PlayerbotWorldThreadProcessor.h"
#include "RandomPlayerbotMgr.h" #include "RandomPlayerbotMgr.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
@@ -84,12 +82,12 @@ public:
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", { PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", {
PLAYERHOOK_ON_LOGIN, PLAYERHOOK_ON_LOGIN,
PLAYERHOOK_ON_AFTER_UPDATE, PLAYERHOOK_ON_AFTER_UPDATE,
PLAYERHOOK_ON_CHAT,
PLAYERHOOK_ON_CHAT_WITH_CHANNEL,
PLAYERHOOK_ON_CHAT_WITH_GROUP,
PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS, PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS,
PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE, PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE,
PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT, PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT,
PLAYERHOOK_CAN_PLAYER_USE_GROUP_CHAT,
PLAYERHOOK_CAN_PLAYER_USE_GUILD_CHAT,
PLAYERHOOK_CAN_PLAYER_USE_CHANNEL_CHAT,
PLAYERHOOK_ON_GIVE_EXP, PLAYERHOOK_ON_GIVE_EXP,
PLAYERHOOK_ON_BEFORE_TELEPORT PLAYERHOOK_ON_BEFORE_TELEPORT
}) {} }) {}
@@ -125,49 +123,24 @@ 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
{ {
/* for now commmented out until proven its actually required // Only apply to bots to prevent affecting real players
* havent seen any proof CleanVisibilityReferences() is needed if (!player || !player->GetSession()->IsBot())
// If the player is not safe to touch, do nothing
if (!player)
return true; return true;
// If same map or not in world do nothing // If changing maps, proactively clean visibility references to prevent
if (!player->IsInWorld() || player->GetMapId() == mapid) // stale pointers in other players' visibility maps during the teleport.
return true; // 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 real player do nothing return true; // Allow teleport to continue
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 void OnPlayerAfterUpdate(Player* player, uint32 diff) override
@@ -191,17 +164,14 @@ public:
{ {
botAI->HandleCommand(type, msg, player); botAI->HandleCommand(type, msg, player);
// hotfix; otherwise the server will crash when whispering logout return false;
// https://github.com/mod-playerbots/mod-playerbots/pull/1838
// TODO: find the root cause and solve it. (does not happen in party chat)
if (msg == "logout")
return false;
} }
} }
return true; return true;
} }
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
{ {
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{ {
@@ -213,10 +183,9 @@ public:
} }
} }
} }
return true;
} }
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Guild* guild) override void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg) override
{ {
if (type == CHAT_MSG_GUILD) if (type == CHAT_MSG_GUILD)
{ {
@@ -235,10 +204,9 @@ public:
} }
} }
} }
return true;
} }
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
{ {
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player)) if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
{ {
@@ -249,7 +217,6 @@ public:
} }
sRandomPlayerbotMgr->HandleCommand(type, msg, player); sRandomPlayerbotMgr->HandleCommand(type, msg, player);
return true;
} }
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
@@ -364,9 +331,6 @@ public:
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " "); LOG_INFO("server.loading", " ");
sPlayerbotSpellCache->Initialize();
LOG_INFO("server.loading", "Playerbots World Thread Processor initialized"); LOG_INFO("server.loading", "Playerbots World Thread Processor initialized");
} }
@@ -503,8 +467,6 @@ public:
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); } void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
}; };
void AddPlayerbotsSecureLoginScripts();
void AddPlayerbotsScripts() void AddPlayerbotsScripts()
{ {
new PlayerbotsDatabaseScript(); new PlayerbotsDatabaseScript();
@@ -514,7 +476,6 @@ void AddPlayerbotsScripts()
new PlayerbotsWorldScript(); new PlayerbotsWorldScript();
new PlayerbotsScript(); new PlayerbotsScript();
new PlayerBotsBGScript(); new PlayerBotsBGScript();
AddPlayerbotsSecureLoginScripts();
AddSC_playerbots_commandscript(); AddSC_playerbots_commandscript();
PlayerBotsGuildValidationScript();
} }

View File

@@ -1,82 +0,0 @@
#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();
}

View File

@@ -2834,20 +2834,22 @@ inline bool ContainsInternal(ItemTemplate const* proto, uint32 skillId)
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates(); CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr) for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
{ {
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(itr->first); if (itr->second.trainer_type != TRAINER_TYPE_TRADESKILLS)
if (!trainer)
continue; continue;
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill) uint32 trainerId = itr->second.Entry;
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
if (!trainer_spells)
continue; continue;
for (auto& spell : trainer->GetSpells()) for (TrainerSpellMap::const_iterator iter = trainer_spells->spellList.begin();
iter != trainer_spells->spellList.end(); ++iter)
{ {
if (spell.ReqSkillLine != skillId) TrainerSpell const* tSpell = &iter->second;
if (!tSpell || tSpell->reqSkill != skillId)
continue; continue;
if (IsCraftedBy(proto, spell.SpellId)) if (IsCraftedBy(proto, tSpell->spell))
return true; return true;
} }
} }

View File

@@ -11,7 +11,6 @@
#include "GuildMgr.h" #include "GuildMgr.h"
#include "PlayerbotFactory.h" #include "PlayerbotFactory.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PlayerbotGuildMgr.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "SocialMgr.h" #include "SocialMgr.h"
@@ -755,6 +754,187 @@ void RandomPlayerbotFactory::CreateRandomBots()
sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars); 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 const RandomPlayerbotFactory::CreateRandomGuildName()
{ {
std::string guildName = ""; std::string guildName = "";

View File

@@ -51,6 +51,7 @@ public:
Player* CreateRandomBot(WorldSession* session, uint8 cls, std::unordered_map<NameRaceAndGender, std::vector<std::string>>& names); Player* CreateRandomBot(WorldSession* session, uint8 cls, std::unordered_map<NameRaceAndGender, std::vector<std::string>>& names);
static void CreateRandomBots(); static void CreateRandomBots();
static void CreateRandomGuilds();
static void CreateRandomArenaTeams(ArenaType slot, uint32 count); static void CreateRandomArenaTeams(ArenaType slot, uint32 count);
static std::string const CreateRandomGuildName(); static std::string const CreateRandomGuildName();
static uint32 CalculateTotalAccountCount(); static uint32 CalculateTotalAccountCount();

View File

@@ -26,6 +26,7 @@
#include "DatabaseEnv.h" #include "DatabaseEnv.h"
#include "Define.h" #include "Define.h"
#include "FleeManager.h" #include "FleeManager.h"
#include "GameTime.h"
#include "GridNotifiers.h" #include "GridNotifiers.h"
#include "GridNotifiersImpl.h" #include "GridNotifiersImpl.h"
#include "GuildMgr.h" #include "GuildMgr.h"
@@ -669,9 +670,9 @@ void RandomPlayerbotMgr::AssignAccountTypes()
uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts; uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts;
uint32 assigned = 0; uint32 assigned = 0;
for (size_t idx = allRandomBotAccounts.size(); idx-- > 0 && assigned < toAssign;) for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--)
{ {
uint32 accountId = allRandomBotAccounts[idx]; uint32 accountId = allRandomBotAccounts[i];
if (currentAssignments[accountId] == 0) // Unassigned if (currentAssignments[accountId] == 0) // Unassigned
{ {
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId); PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId);
@@ -1424,7 +1425,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
LOG_DEBUG("playerbots", "Bot #{}: log out", bot); LOG_DEBUG("playerbots", "Bot #{}: log out", bot);
SetEventValue(bot, "add", 0, 0); SetEventValue(bot, "add", 0, 0);
currentBots.remove(bot); currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
if (player) if (player)
LogoutPlayerBot(botGUID); LogoutPlayerBot(botGUID);
@@ -1479,10 +1480,10 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
if (!sRandomPlayerbotMgr->IsRandomBot(player)) if (!sRandomPlayerbotMgr->IsRandomBot(player))
update = false; update = false;
if (player->GetGroup() && botAI->GetGroupLeader()) if (player->GetGroup() && botAI->GetGroupMaster())
{ {
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader()); PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer()) if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
{ {
update = false; update = false;
} }
@@ -1654,10 +1655,6 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
if (bot->IsBeingTeleported() || !bot->IsInWorld()) if (bot->IsBeingTeleported() || !bot->IsInWorld())
return; return;
// no teleport / movement update when rooted.
if (bot->IsRooted())
return;
// ignore when in queue for battle grounds. // ignore when in queue for battle grounds.
if (bot->InBattlegroundQueue()) if (bot->InBattlegroundQueue())
return; return;
@@ -2715,73 +2712,69 @@ std::vector<uint32> RandomPlayerbotMgr::GetBgBots(uint32 bracket)
return std::move(BgBots); return std::move(BgBots);
} }
CachedEvent* RandomPlayerbotMgr::FindEvent(uint32 bot, std::string const& event) uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event)
{ {
BotEventCache& cache = eventCache[bot]; // load all events at once on first event load
if (eventCache[bot].empty())
// Load once
if (!cache.loaded)
{ {
cache.events.clear();
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT); PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT);
stmt->SetData(0, 0); stmt->SetData(0, 0);
stmt->SetData(1, bot); stmt->SetData(1, bot);
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt)) if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{ {
do do
{ {
Field* fields = result->Fetch(); Field* fields = result->Fetch();
std::string const eventName = fields[0].Get<std::string>();
CachedEvent e; CachedEvent e;
e.value = fields[1].Get<uint32>(); e.value = fields[1].Get<uint32>();
e.lastChangeTime = fields[2].Get<uint32>(); e.lastChangeTime = fields[2].Get<uint32>();
e.validIn = fields[3].Get<uint32>(); e.validIn = fields[3].Get<uint32>();
e.data = fields[4].Get<std::string>(); 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()); } while (result->NextRow());
} }
cache.loaded = true;
} }
auto it = cache.events.find(event); CachedEvent& e = eventCache[bot][event];
if (it == cache.events.end()) /*if (e.IsEmpty())
return nullptr;
CachedEvent& e = it->second;
// remove expired events
if (e.validIn && (NowSeconds() - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
{ {
cache.events.erase(it); QueryResult results = PlayerbotsDatabase.Query("SELECT `value`, `time`, validIn, `data` FROM
return nullptr; 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>();
}
}
*/
if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
e.value = 0;
return e.value;
}
std::string const RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const event)
{
std::string data = "";
if (GetEventValue(bot, event))
{
CachedEvent e = eventCache[bot][event];
data = e.data;
} }
return &e; return data;
} }
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const& event) uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
{ std::string const data)
if (CachedEvent* e = FindEvent(bot, event))
return e->value;
return 0;
}
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(); PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction();
@@ -2797,55 +2790,43 @@ uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const& event, u
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS); stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS);
stmt->SetData(0, 0); stmt->SetData(0, 0);
stmt->SetData(1, bot); stmt->SetData(1, bot);
stmt->SetData(2, NowSeconds()); stmt->SetData(2, static_cast<uint32>(GameTime::GetGameTime().count()));
stmt->SetData(3, validIn); stmt->SetData(3, validIn);
stmt->SetData(4, event.c_str()); stmt->SetData(4, event.c_str());
stmt->SetData(5, value); stmt->SetData(5, value);
if (data != "")
if (!data.empty()) {
stmt->SetData(6, data.c_str()); stmt->SetData(6, data.c_str());
}
else else
stmt->SetData(6); // NULL {
stmt->SetData(6);
}
trans->Append(stmt); trans->Append(stmt);
} }
PlayerbotsDatabase.CommitTransaction(trans); PlayerbotsDatabase.CommitTransaction(trans);
// Update in-memory cache CachedEvent e(value, (uint32)time(nullptr), validIn, data);
BotEventCache& cache = eventCache[bot]; eventCache[bot][event] = std::move(e);
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; 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); return GetValue(bot->GetGUID().GetCounter(), type);
} }
std::string RandomPlayerbotMgr::GetData(uint32 bot, std::string const& type) { return GetEventData(bot, type); } std::string const 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); 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); SetValue(bot->GetGUID().GetCounter(), type, value, data);
} }
@@ -3134,7 +3115,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot) void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot)
{ {
SetEventValue(bot, "add", 0, 0); SetEventValue(bot, "add", 0, 0);
currentBots.remove(bot); currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
} }
Player* RandomPlayerbotMgr::GetRandomPlayer() Player* RandomPlayerbotMgr::GetRandomPlayer()
@@ -3516,8 +3497,7 @@ void RandomPlayerbotMgr::Remove(Player* bot)
stmt->SetData(1, owner.GetCounter()); stmt->SetData(1, owner.GetCounter());
PlayerbotsDatabase.Execute(stmt); PlayerbotsDatabase.Execute(stmt);
uint32 botId = owner.GetCounter(); eventCache[owner.GetCounter()].clear();
eventCache.erase(botId);
LogoutPlayerBot(owner); LogoutPlayerBot(owner);
} }
@@ -3534,7 +3514,7 @@ CreatureData const* RandomPlayerbotMgr::GetCreatureDataByEntry(uint32 entry)
return nullptr; return nullptr;
} }
ObjectGuid RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId) ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
{ {
ObjectGuid battleMasterGUID = ObjectGuid::Empty; ObjectGuid battleMasterGUID = ObjectGuid::Empty;

View File

@@ -9,7 +9,6 @@
#include "NewRpgInfo.h" #include "NewRpgInfo.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "PlayerbotMgr.h" #include "PlayerbotMgr.h"
#include "GameTime.h"
struct BattlegroundInfo struct BattlegroundInfo
{ {
@@ -46,20 +45,25 @@ class ChatHandler;
class PerformanceMonitorOperation; class PerformanceMonitorOperation;
class WorldLocation; class WorldLocation;
struct CachedEvent class CachedEvent
{ {
uint32 value = 0; public:
uint32 lastChangeTime = 0; CachedEvent() : value(0), lastChangeTime(0), validIn(0), data("") {}
uint32 validIn = 0; 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;
std::string data; 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 // https://gist.github.com/bradley219/5373998
@@ -135,13 +139,13 @@ public:
void Revive(Player* player); void Revive(Player* player);
void ChangeStrategy(Player* player); void ChangeStrategy(Player* player);
void ChangeStrategyOnce(Player* player); void ChangeStrategyOnce(Player* player);
uint32 GetValue(Player* bot, std::string const& type); uint32 GetValue(Player* bot, std::string const type);
uint32 GetValue(uint32 bot, std::string const& type); uint32 GetValue(uint32 bot, std::string const type);
std::string GetData(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(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 SetValue(Player* bot, std::string const type, uint32 value, std::string const data = "");
void Remove(Player* bot); void Remove(Player* bot);
ObjectGuid GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId); ObjectGuid const GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
CreatureData const* GetCreatureDataByEntry(uint32 entry); CreatureData const* GetCreatureDataByEntry(uint32 entry);
void LoadBattleMastersCache(); void LoadBattleMastersCache();
std::map<uint32, std::map<uint32, BattlegroundInfo>> BattlegroundData; std::map<uint32, std::map<uint32, BattlegroundInfo>> BattlegroundData;
@@ -199,11 +203,10 @@ private:
bool _isBotInitializing = true; bool _isBotInitializing = true;
bool _isBotLogging = true; bool _isBotLogging = true;
NewRpgStatistic rpgStasticTotal; NewRpgStatistic rpgStasticTotal;
CachedEvent* FindEvent(uint32 bot, std::string const& event); uint32 GetEventValue(uint32 bot, std::string const event);
uint32 GetEventValue(uint32 bot, std::string const& event); std::string const GetEventData(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,
uint32 SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn, std::string const data = "");
std::string const& data = "");
void GetBots(); void GetBots();
std::vector<uint32> GetBgBots(uint32 bracket); std::vector<uint32> GetBgBots(uint32 bracket);
time_t BgCheckTimer; time_t BgCheckTimer;
@@ -225,7 +228,7 @@ private:
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache; // std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel; std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache; std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache;
std::unordered_map<uint32, BotEventCache> eventCache; std::map<uint32, std::map<std::string, CachedEvent>> eventCache;
std::list<uint32> currentBots; std::list<uint32> currentBots;
uint32 bgBotsCount; uint32 bgBotsCount;
uint32 playersLevel; uint32 playersLevel;
@@ -235,7 +238,6 @@ private:
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2) std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
//void ScaleBotActivity(); // Deprecated function //void ScaleBotActivity(); // Deprecated function
static inline uint32 NowSeconds() { return static_cast<uint32>(GameTime::GetGameTime().count()); }
}; };
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance() #define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()

View File

@@ -41,17 +41,13 @@ bool ServerFacade::IsDistanceLessOrEqualThan(float dist1, float dist2) { return
void ServerFacade::SetFacingTo(Player* bot, WorldObject* wo, bool force) void ServerFacade::SetFacingTo(Player* bot, WorldObject* wo, bool force)
{ {
if (!bot)
return;
float angle = bot->GetAngle(wo); float angle = bot->GetAngle(wo);
// if (!force && bot->isMoving()) // if (!force && bot->isMoving())
// bot->SetFacingTo(bot->GetAngle(wo)); // bot->SetFacingTo(bot->GetAngle(wo));
// else // else
// { // {
bot->SetOrientation(angle); bot->SetOrientation(angle);
if (!bot->IsRooted()) bot->SendMovementFlagUpdate();
bot->SendMovementFlagUpdate();
// } // }
} }

View File

@@ -218,11 +218,6 @@ bool WorldPosition::isUnderWater()
: false; : false;
}; };
bool WorldPosition::IsValid()
{
return !(GetMapId() == MAPID_INVALID && GetPositionX() == 0 && GetPositionY() == 0 && GetPositionZ() == 0);
}
WorldPosition WorldPosition::relPoint(WorldPosition* center) WorldPosition WorldPosition::relPoint(WorldPosition* center)
{ {
return WorldPosition(GetMapId(), GetPositionX() - center->GetPositionX(), GetPositionY() - center->GetPositionY(), return WorldPosition(GetMapId(), GetPositionX() - center->GetPositionX(), GetPositionY() - center->GetPositionY(),
@@ -3409,14 +3404,13 @@ void TravelMgr::LoadQuestTravelTable()
{ {
Strategy* strat = con->GetStrategy(stratName); Strategy* strat = con->GetStrategy(stratName);
const std::vector<NextAction> defaultActions = strat->getDefaultActions(); if (strat->getDefaultActions())
for (uint32 i = 0; i < NextAction::size(strat->getDefaultActions()); i++)
if (defaultActions.size() > 0)
{
for (NextAction nextAction : defaultActions)
{ {
NextAction* nextAction = strat->getDefaultActions()[i];
std::ostringstream aout; std::ostringstream aout;
aout << nextAction.getRelevance() << "," << nextAction.getName() aout << nextAction->getRelevance() << "," << nextAction->getName()
<< ",,S:" << stratName; << ",,S:" << stratName;
if (actions.find(aout.str().c_str()) != actions.end()) if (actions.find(aout.str().c_str()) != actions.end())
@@ -3428,24 +3422,27 @@ void TravelMgr::LoadQuestTravelTable()
actions.insert_or_assign(aout.str().c_str(), classSpecLevel); actions.insert_or_assign(aout.str().c_str(), classSpecLevel);
} }
}
std::vector<TriggerNode*> triggers; std::vector<TriggerNode*> triggers;
strat->InitTriggers(triggers); strat->InitTriggers(triggers);
for (auto& triggerNode : triggers)
for (TriggerNode*& triggerNode : triggers)
{ {
// out << " TN:" << triggerNode->getName();
if (Trigger* trigger = con->GetTrigger(triggerNode->getName())) if (Trigger* trigger = con->GetTrigger(triggerNode->getName()))
{ {
triggerNode->setTrigger(trigger); triggerNode->setTrigger(trigger);
std::vector<NextAction> nextActions = triggerNode->getHandlers(); NextAction** nextActions = triggerNode->getHandlers();
// for (uint32_t i = 0; i < nextActions.size(); ++i) for (uint32 i = 0; i < NextAction::size(nextActions); i++)
for (NextAction nextAction : nextActions)
{ {
NextAction* nextAction = nextActions[i];
// out << " A:" << nextAction->getName() << "(" <<
// nextAction->getRelevance() << ")";
std::ostringstream aout; std::ostringstream aout;
aout << nextAction.getRelevance() << "," << nextAction.getName() aout << nextAction->getRelevance() << "," << nextAction->getName()
<< "," << triggerNode->getName() << "," << stratName; << "," << triggerNode->getName() << "," << stratName;
if (actions.find(aout.str().c_str()) != actions.end()) if (actions.find(aout.str().c_str()) != actions.end())

View File

@@ -141,7 +141,6 @@ public:
bool isOverworld(); bool isOverworld();
bool isInWater(); bool isInWater();
bool isUnderWater(); bool isUnderWater();
bool IsValid();
WorldPosition relPoint(WorldPosition* center); WorldPosition relPoint(WorldPosition* center);
WorldPosition offset(WorldPosition* center); WorldPosition offset(WorldPosition* center);

View File

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

View File

@@ -1,34 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PLAYERBOTSPELLCACHE_H
#define _PLAYERBOT_PLAYERBOTSPELLCACHE_H
#include "Playerbots.h"
class PlayerbotSpellCache
{
public:
static PlayerbotSpellCache* Instance()
{
static PlayerbotSpellCache instance;
return &instance;
}
void Initialize(); // call once on startup
SkillLineAbilityEntry const* GetSkillLine(uint32 spellId) const;
bool IsItemBuyable(uint32 itemId) const;
private:
PlayerbotSpellCache() = default;
std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
std::set<uint32> vendorItems;
};
#define sPlayerbotSpellCache PlayerbotSpellCache::Instance()
#endif

View File

@@ -30,7 +30,6 @@
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "PlayerbotDbStore.h" #include "PlayerbotDbStore.h"
#include "PlayerbotGuildMgr.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "QuestDef.h" #include "QuestDef.h"
#include "RandomItemMgr.h" #include "RandomItemMgr.h"
@@ -1103,6 +1102,8 @@ void PlayerbotFactory::ResetQuests()
} }
} }
void PlayerbotFactory::InitSpells() { InitAvailableSpells(); }
void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_template /*true*/, bool reset /*false*/) void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_template /*true*/, bool reset /*false*/)
{ {
uint32 specTab; uint32 specTab;
@@ -2524,35 +2525,66 @@ void PlayerbotFactory::InitAvailableSpells()
for (CreatureTemplateContainer::const_iterator i = creatureTemplateContainer->begin(); for (CreatureTemplateContainer::const_iterator i = creatureTemplateContainer->begin();
i != creatureTemplateContainer->end(); ++i) i != creatureTemplateContainer->end(); ++i)
{ {
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(i->first); CreatureTemplate const& co = i->second;
if (co.trainer_type != TRAINER_TYPE_TRADESKILLS && co.trainer_type != TRAINER_TYPE_CLASS)
if (!trainer)
continue; continue;
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill && if (co.trainer_type == TRAINER_TYPE_CLASS && co.trainer_class != bot->getClass())
trainer->GetTrainerType() != Trainer::Type::Class)
continue; continue;
if (trainer->GetTrainerType() == Trainer::Type::Class && uint32 trainerId = co.Entry;
!trainer->IsTrainerValidForPlayer(bot)) trainerIdCache[bot->getClass()].push_back(trainerId);
continue;
trainerIdCache[bot->getClass()].push_back(i->first);
} }
} }
for (uint32 trainerId : trainerIdCache[bot->getClass()]) for (uint32 trainerId : trainerIdCache[bot->getClass()])
{ {
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(trainerId); TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
if (!trainer_spells)
trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
for (auto& spell : trainer->GetSpells()) if (!trainer_spells)
continue;
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
itr != trainer_spells->spellList.end(); ++itr)
{ {
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId))) TrainerSpell const* tSpell = &itr->second;
if (!tSpell)
continue; continue;
if (spell.IsCastable()) if (tSpell->learnedSpell[0] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[0]))
bot->CastSpell(bot, spell.SpellId, true); 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);
else else
bot->learnSpell(spell.SpellId, false); bot->learnSpell(tSpell->learnedSpell[0], false);
} }
} }
} }
@@ -3933,33 +3965,45 @@ void PlayerbotFactory::InitInventoryEquip()
void PlayerbotFactory::InitGuild() void PlayerbotFactory::InitGuild()
{ {
if (bot->GetGuildId()) 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())
{ {
if (!bot->HasItemCount(5976, 1) && bot->GetLevel() > 9) LOG_ERROR("playerbots", "No random guilds available");
StoreItem(5976, 1);
return; return;
} }
std::string guildName = sPlayerbotGuildMgr->AssignToGuild(bot); int index = urand(0, guilds.size() - 1);
if (guildName.empty()) uint32 guildId = guilds[index];
return; Guild* guild = sGuildMgr->GetGuildById(guildId);
Guild* guild = sGuildMgr->GetGuildByName(guildName);
if (!guild) if (!guild)
{ {
if (!sPlayerbotGuildMgr->CreateGuild(bot, guildName)) LOG_ERROR("playerbots", "Invalid guild {}", guildId);
LOG_ERROR("playerbots","Failed to create guild {} for bot {}", guildName, bot->GetName());
return; return;
} }
else
{ if (guild->GetMemberSize() < urand(10, sPlayerbotAIConfig->randomBotGuildSizeMax))
if (guild->AddMember(bot->GetGUID(),urand(GR_OFFICER, GR_INITIATE))) 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 // add guild tabard
if (bot->GetGuildId() && bot->GetLevel() > 9 && urand(0, 4) && !bot->HasItemCount(5976, 1)) if (bot->GetGuildId() && bot->GetLevel() > 9 && urand(0, 4) && !bot->HasItemCount(5976, 1))
StoreItem(5976, 1); StoreItem(5976, 1);
// bot->SaveToDB(false, false);
} }
void PlayerbotFactory::InitImmersive() void PlayerbotFactory::InitImmersive()
@@ -4055,7 +4099,6 @@ void PlayerbotFactory::InitImmersive()
void PlayerbotFactory::InitArenaTeam() void PlayerbotFactory::InitArenaTeam()
{ {
if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId())) if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId()))
return; return;
@@ -4142,34 +4185,10 @@ void PlayerbotFactory::InitArenaTeam()
if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need? if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need?
{ {
// Add bot to arena team
arenateam->AddMember(bot->GetGUID()); arenateam->AddMember(bot->GetGUID());
arenateam->SaveToDB();
// Only synchronize ratings once the team is full (avoid redundant work)
// The captain was added with incorrect ratings when the team was created,
// so we fix everyone's ratings once the roster is complete
if (arenateam->GetMembersSize() >= (uint32)arenateam->GetType())
{
uint32 teamRating = arenateam->GetRating();
// Use SetRatingForAll to align all members with team rating
arenateam->SetRatingForAll(teamRating);
// For bot-only teams, keep MMR synchronized with team rating
// This ensures matchmaking reflects the artificial team strength (1000-2000 range)
// instead of being influenced by the global CONFIG_ARENA_START_MATCHMAKER_RATING
for (auto& member : arenateam->GetMembers())
{
// Set MMR to match personal rating (which already matches team rating)
member.MatchMakerRating = member.PersonalRating;
member.MaxMMR = std::max(member.MaxMMR, member.PersonalRating);
}
// Force save all member data to database
arenateam->SaveToDB(true);
}
} }
} }
arenateams.erase(arenateams.begin() + index); arenateams.erase(arenateams.begin() + index);
} }

View File

@@ -95,6 +95,7 @@ private:
void InitTradeSkills(); void InitTradeSkills();
void UpdateTradeSkills(); void UpdateTradeSkills();
void SetRandomSkill(uint16 id); void SetRandomSkill(uint16 id);
void InitSpells();
void ClearSpells(); void ClearSpells();
void ClearSkills(); void ClearSkills();
void InitTalents(uint32 specNo); void InitTalents(uint32 specNo);

View File

@@ -37,7 +37,7 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
tab = AiFactory::GetPlayerSpecTab(player); tab = AiFactory::GetPlayerSpecTab(player);
collector_ = std::make_unique<StatsCollector>(type_, cls); collector_ = std::make_unique<StatsCollector>(type_, cls);
if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY) if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
hitOverflowType_ = CollectorType::SPELL; hitOverflowType_ = CollectorType::SPELL;
else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT) else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)
hitOverflowType_ = CollectorType::SPELL; hitOverflowType_ = CollectorType::SPELL;
@@ -193,7 +193,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f; stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 0.01f; stats_weights_[STATS_TYPE_RANGED_DPS] += 0.01f;
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEAST_MASTERY || tab == HUNTER_TAB_SURVIVAL)) if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTERY || tab == HUNTER_TAB_SURVIVAL))
{ {
stats_weights_[STATS_TYPE_AGILITY] += 2.5f; stats_weights_[STATS_TYPE_AGILITY] += 2.5f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; 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_EXPERTISE] += 2.1f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
} }
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY) else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY) // fury
{ {
stats_weights_[STATS_TYPE_AGILITY] += 1.8f; stats_weights_[STATS_TYPE_AGILITY] += 1.8f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.6f; 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_EXPERTISE] += 2.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
} }
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) // arm
{ {
stats_weights_[STATS_TYPE_AGILITY] += 1.6f; stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.3f; 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_EXPERTISE] += 1.4f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
} }
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST) else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) // frost dk
{ {
stats_weights_[STATS_TYPE_AGILITY] += 1.7f; stats_weights_[STATS_TYPE_AGILITY] += 1.7f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.8f; 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_EXPERTISE] += 2.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
} }
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY) else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
{ {
stats_weights_[STATS_TYPE_AGILITY] += 0.9f; stats_weights_[STATS_TYPE_AGILITY] += 0.9f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f; 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_EXPERTISE] += 1.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
} }
else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) // retribution
{ {
stats_weights_[STATS_TYPE_AGILITY] += 1.6f; stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f; 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_EXPERTISE] += 2.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 9.0f; stats_weights_[STATS_TYPE_MELEE_DPS] += 9.0f;
} }
else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)) else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)) // enhancement
{ {
stats_weights_[STATS_TYPE_AGILITY] += 1.4f; stats_weights_[STATS_TYPE_AGILITY] += 1.4f;
stats_weights_[STATS_TYPE_STRENGTH] += 1.1f; stats_weights_[STATS_TYPE_STRENGTH] += 1.1f;
@@ -325,10 +325,9 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f; stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f;
} }
else if (cls == CLASS_WARLOCK || else if (cls == CLASS_WARLOCK || (cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
(cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) || (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || (cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE))
{ {
stats_weights_[STATS_TYPE_INTELLECT] += 0.3f; stats_weights_[STATS_TYPE_INTELLECT] += 0.3f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.6f; stats_weights_[STATS_TYPE_SPIRIT] += 0.6f;
@@ -356,8 +355,8 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_CRIT] += 0.8f; stats_weights_[STATS_TYPE_CRIT] += 0.8f;
stats_weights_[STATS_TYPE_HASTE] += 1.0f; stats_weights_[STATS_TYPE_HASTE] += 1.0f;
} }
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) // heal
{ {
stats_weights_[STATS_TYPE_INTELLECT] += 0.9f; stats_weights_[STATS_TYPE_INTELLECT] += 0.9f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.15f; stats_weights_[STATS_TYPE_SPIRIT] += 0.15f;
@@ -366,7 +365,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_CRIT] += 0.6f; stats_weights_[STATS_TYPE_CRIT] += 0.6f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f; stats_weights_[STATS_TYPE_HASTE] += 0.8f;
} }
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
(cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION)) (cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION))
{ {
stats_weights_[STATS_TYPE_INTELLECT] += 0.8f; stats_weights_[STATS_TYPE_INTELLECT] += 0.8f;
@@ -397,7 +396,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f; stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f; stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f;
} }
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD) else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD)
{ {
stats_weights_[STATS_TYPE_AGILITY] += 2.0f; stats_weights_[STATS_TYPE_AGILITY] += 2.0f;
stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; stats_weights_[STATS_TYPE_STRENGTH] += 1.0f;
@@ -540,7 +539,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
// spec without double hand // spec without double hand
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield // 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()) || if (((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST) || (cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() && (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() &&
player_->CanDualWield()) || player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
@@ -557,7 +556,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) || (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_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) || (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) ||
(cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) ||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield())) (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield()))
{ {
weight_ *= 0.1; weight_ *= 0.1;

View File

@@ -8,6 +8,90 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "Timer.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()); } Value<Unit*>* Action::GetTargetValue() { return context->GetValue<Unit*>(GetTargetName()); }
Unit* Action::GetTarget() { return GetTargetValue()->Get(); } Unit* Action::GetTarget() { return GetTargetValue()->Get(); }
@@ -17,4 +101,4 @@ ActionBasket::ActionBasket(ActionNode* action, float relevance, bool skipPrerequ
{ {
} }
bool ActionBasket::isExpired(uint32_t msecs) { return getMSTime() - created >= msecs; } bool ActionBasket::isExpired(uint32 msecs) { return getMSTime() - created >= msecs; }

View File

@@ -3,7 +3,8 @@
* and/or modify it under version 3 of the License, or (at your option), any later version. * and/or modify it under version 3 of the License, or (at your option), any later version.
*/ */
#pragma once #ifndef _PLAYERBOT_ACTION_H
#define _PLAYERBOT_ACTION_H
#include "AiObject.h" #include "AiObject.h"
#include "Common.h" #include "Common.h"
@@ -23,26 +24,15 @@ public:
std::string const getName() { return name; } std::string const getName() { return name; }
float getRelevance() { return relevance; } float getRelevance() { return relevance; }
static std::vector<NextAction> merge(std::vector<NextAction> const& what, std::vector<NextAction> const& with) static uint32 size(NextAction** actions);
{ static NextAction** clone(NextAction** actions);
std::vector<NextAction> result = {}; static NextAction** merge(NextAction** what, NextAction** with);
static NextAction** array(uint32 nil, ...);
for (NextAction const& action : what) static void destroy(NextAction** actions);
{
result.push_back(action);
}
for (NextAction const& action : with)
{
result.push_back(action);
}
return result;
};
private: private:
float relevance; float relevance;
std::string name; std::string const name;
}; };
class Action : public AiNamedObject class Action : public AiNamedObject
@@ -62,9 +52,9 @@ public:
virtual bool Execute([[maybe_unused]] Event event) { return true; } virtual bool Execute([[maybe_unused]] Event event) { return true; }
virtual bool isPossible() { return true; } virtual bool isPossible() { return true; }
virtual bool isUseful() { return true; } virtual bool isUseful() { return true; }
virtual std::vector<NextAction> getPrerequisites() { return {}; } virtual NextAction** getPrerequisites() { return nullptr; }
virtual std::vector<NextAction> getAlternatives() { return {}; } virtual NextAction** getAlternatives() { return nullptr; }
virtual std::vector<NextAction> getContinuers() { return {}; } virtual NextAction** getContinuers() { return nullptr; }
virtual ActionThreatType getThreatType() { return ActionThreatType::None; } virtual ActionThreatType getThreatType() { return ActionThreatType::None; }
void Update() {} void Update() {}
void Reset() {} void Reset() {}
@@ -83,44 +73,39 @@ protected:
class ActionNode class ActionNode
{ {
public: public:
ActionNode( ActionNode(std::string const name, NextAction** prerequisites = nullptr, NextAction** alternatives = nullptr,
std::string name, NextAction** continuers = nullptr)
std::vector<NextAction> prerequisites = {}, : name(name), action(nullptr), continuers(continuers), alternatives(alternatives), prerequisites(prerequisites)
std::vector<NextAction> alternatives = {}, {
std::vector<NextAction> continuers = {} } // reorder arguments - whipowill
) :
name(std::move(name)),
action(nullptr),
continuers(continuers),
alternatives(alternatives),
prerequisites(prerequisites)
{}
virtual ~ActionNode() = default; virtual ~ActionNode()
{
NextAction::destroy(prerequisites);
NextAction::destroy(alternatives);
NextAction::destroy(continuers);
}
Action* getAction() { return action; } Action* getAction() { return action; }
void setAction(Action* action) { this->action = action; } void setAction(Action* action) { this->action = action; }
const std::string getName() { return name; } std::string const getName() { return name; }
std::vector<NextAction> getContinuers() NextAction** getContinuers() { return NextAction::merge(NextAction::clone(continuers), action->getContinuers()); }
NextAction** getAlternatives()
{ {
return NextAction::merge(this->continuers, action->getContinuers()); return NextAction::merge(NextAction::clone(alternatives), action->getAlternatives());
} }
std::vector<NextAction> getAlternatives() NextAction** getPrerequisites()
{ {
return NextAction::merge(this->alternatives, action->getAlternatives()); return NextAction::merge(NextAction::clone(prerequisites), action->getPrerequisites());
}
std::vector<NextAction> getPrerequisites()
{
return NextAction::merge(this->prerequisites, action->getPrerequisites());
} }
private: private:
const std::string name; std::string const name;
Action* action; Action* action;
std::vector<NextAction> continuers; NextAction** continuers;
std::vector<NextAction> alternatives; NextAction** alternatives;
std::vector<NextAction> prerequisites; NextAction** prerequisites;
}; };
class ActionBasket class ActionBasket
@@ -136,12 +121,14 @@ public:
bool isSkipPrerequisites() { return skipPrerequisites; } bool isSkipPrerequisites() { return skipPrerequisites; }
void AmendRelevance(float k) { relevance *= k; } void AmendRelevance(float k) { relevance *= k; }
void setRelevance(float relevance) { this->relevance = relevance; } void setRelevance(float relevance) { this->relevance = relevance; }
bool isExpired(uint32_t msecs); bool isExpired(uint32 msecs);
private: private:
ActionNode* action; ActionNode* action;
float relevance; float relevance;
bool skipPrerequisites; bool skipPrerequisites;
Event event; Event event;
uint32_t created; uint32 created;
}; };
#endif

View File

@@ -42,6 +42,9 @@ protected:
// TRIGGERS // TRIGGERS
// //
#define NEXT_TRIGGERS(name, relevance) \
virtual NextAction* getNextAction() { return new NextAction(name, relevance); }
#define BEGIN_TRIGGER(clazz, super) \ #define BEGIN_TRIGGER(clazz, super) \
class clazz : public super \ class clazz : public super \
{ \ { \
@@ -75,6 +78,14 @@ protected:
clazz(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, spell) {} \ 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) \ #define DEBUFF_TRIGGER(clazz, spell) \
class clazz : public DebuffTrigger \ class clazz : public DebuffTrigger \
{ \ { \
@@ -285,6 +296,14 @@ protected:
clazz(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, spell) {} \ 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) \ #define HEAL_PARTY_ACTION(clazz, spell, estAmount, manaEfficiency) \
class clazz : public HealPartyMemberAction \ class clazz : public HealPartyMemberAction \
{ \ { \
@@ -385,6 +404,14 @@ protected:
clazz(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, spell, range) {} \ 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) \ #define ENEMY_HEALER_ACTION(clazz, spell) \
class clazz : public CastSpellOnEnemyHealerAction \ class clazz : public CastSpellOnEnemyHealerAction \
{ \ { \
@@ -413,6 +440,10 @@ protected:
clazz(PlayerbotAI* botAI) : CastProtectSpellAction(botAI, spell) {} \ clazz(PlayerbotAI* botAI) : CastProtectSpellAction(botAI, spell) {} \
} }
#define END_RANGED_SPELL_ACTION() \
} \
;
#define BEGIN_SPELL_ACTION(clazz, name) \ #define BEGIN_SPELL_ACTION(clazz, name) \
class clazz : public CastSpellAction \ class clazz : public CastSpellAction \
{ \ { \
@@ -441,4 +472,42 @@ protected:
public: \ public: \
clazz(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, name) {} 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 #endif

View File

@@ -43,6 +43,8 @@
#include "raids/magtheridon/RaidMagtheridonTriggerContext.h" #include "raids/magtheridon/RaidMagtheridonTriggerContext.h"
#include "raids/gruulslair/RaidGruulsLairActionContext.h" #include "raids/gruulslair/RaidGruulsLairActionContext.h"
#include "raids/gruulslair/RaidGruulsLairTriggerContext.h" #include "raids/gruulslair/RaidGruulsLairTriggerContext.h"
#include "raids/naxxramas/RaidNaxxActionContext.h"
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
#include "raids/eyeofeternity/RaidEoEActionContext.h" #include "raids/eyeofeternity/RaidEoEActionContext.h"
#include "raids/eyeofeternity/RaidEoETriggerContext.h" #include "raids/eyeofeternity/RaidEoETriggerContext.h"
#include "raids/vaultofarchavon/RaidVoAActionContext.h" #include "raids/vaultofarchavon/RaidVoAActionContext.h"
@@ -115,6 +117,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
actionContexts.Add(new RaidKarazhanActionContext()); actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidMagtheridonActionContext()); actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidGruulsLairActionContext()); actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext()); actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext()); actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext()); actionContexts.Add(new RaidVoAActionContext());
@@ -149,6 +152,7 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
triggerContexts.Add(new RaidKarazhanTriggerContext()); triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext()); triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext()); triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext()); triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext()); triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidVoATriggerContext()); triggerContexts.Add(new RaidVoATriggerContext());

View File

@@ -6,40 +6,36 @@
#include "CustomStrategy.h" #include "CustomStrategy.h"
#include <regex> #include <regex>
#include <stdexcept>
#include "Playerbots.h" #include "Playerbots.h"
std::map<std::string, std::string> CustomStrategy::actionLinesCache; 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, '!'); std::vector<std::string> tokens = split(action, '!');
if (tokens.size() == 2 && !tokens[0].empty())
if (tokens[0].empty()) return new NextAction(tokens[0], atof(tokens[1].c_str()));
throw std::invalid_argument("Invalid action"); else if (tokens.size() == 1 && !tokens[0].empty())
return new NextAction(tokens[0], ACTION_NORMAL);
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()); LOG_ERROR("playerbots", "Invalid action {}", action.c_str());
return nullptr;
throw std::invalid_argument("Invalid action");
} }
std::vector<NextAction> toNextActionArray(const std::string actions) NextAction** toNextActionArray(std::string const actions)
{ {
const std::vector<std::string> tokens = split(actions, ','); std::vector<std::string> tokens = split(actions, ',');
std::vector<NextAction> res = {}; NextAction** res = new NextAction*[tokens.size() + 1];
for (const std::string token : tokens) uint32 index = 0;
for (std::vector<std::string>::iterator i = tokens.begin(); i != tokens.end(); ++i)
{ {
res.push_back(toNextAction(token)); if (NextAction* na = toNextAction(*i))
res[index++] = na;
} }
res[index++] = nullptr;
return res; return res;
} }

View File

@@ -258,45 +258,48 @@ ActionNode* Engine::CreateActionNode(std::string const name)
return node; return node;
return new ActionNode(name, return new ActionNode(name,
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
bool Engine::MultiplyAndPush( bool Engine::MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event,
std::vector<NextAction> actions, char const* pushType)
float forceRelevance,
bool skipPrerequisites,
Event event,
char const* pushType
)
{ {
bool pushed = false; bool pushed = false;
if (actions)
for (NextAction nextAction : actions)
{ {
ActionNode* action = this->CreateActionNode(nextAction.getName()); for (uint32 j = 0; actions[j]; j++)
this->InitializeAction(action);
float k = nextAction.getRelevance();
if (forceRelevance > 0.0f)
{ {
k = forceRelevance; 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;
} }
if (k > 0) delete[] actions;
{
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; return pushed;
@@ -527,10 +530,10 @@ std::vector<std::string> Engine::GetStrategies()
void Engine::PushAgain(ActionNode* actionNode, float relevance, Event event) void Engine::PushAgain(ActionNode* actionNode, float relevance, Event event)
{ {
std::vector<NextAction> nextAction = { NextAction(actionNode->getName(), relevance) }; NextAction** nextAction = new NextAction*[2];
nextAction[0] = new NextAction(actionNode->getName(), relevance);
nextAction[1] = nullptr;
MultiplyAndPush(nextAction, relevance, true, event, "again"); MultiplyAndPush(nextAction, relevance, true, event, "again");
delete actionNode; delete actionNode;
} }
@@ -560,13 +563,6 @@ bool Engine::ListenAndExecute(Action* action, Event event)
{ {
bool actionExecuted = false; bool actionExecuted = false;
if (action == nullptr)
{
LOG_ERROR("playerbots", "Action is nullptr");
return actionExecuted;
}
if (actionExecutionListeners.Before(action, event)) if (actionExecutionListeners.Before(action, event))
{ {
actionExecuted = actionExecutionListeners.AllowExecution(action, event) ? action->Execute(event) : true; actionExecuted = actionExecutionListeners.AllowExecution(action, event) ? action->Execute(event) : true;

View File

@@ -90,7 +90,7 @@ public:
bool testMode; bool testMode;
private: private:
bool MultiplyAndPush(std::vector<NextAction> actions, float forceRelevance, bool skipPrerequisites, Event event, bool MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event,
const char* pushType); const char* pushType);
void Reset(); void Reset();
void ProcessTriggers(bool minimal); void ProcessTriggers(bool minimal);

View File

@@ -28,112 +28,90 @@ public:
private: private:
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("melee",
"melee", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* healthstone([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* healthstone([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("healthstone",
"healthstone", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("healing potion"), nullptr),
/*A*/ { NextAction("healing potion") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* follow_master_random([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* follow_master_random([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("be near",
"be near", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("follow"), nullptr),
/*A*/ { NextAction("follow") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* attack_anything([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* attack_anything([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("attack anything",
"attack anything", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* move_random([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* move_random([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("move random",
"move random", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("stay line"), nullptr),
/*A*/ { NextAction("stay line") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* move_to_loot([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* move_to_loot([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("move to loot",
"move to loot", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* food([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* food([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("food",
"food", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* drink([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* drink([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("drink",
"drink", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* mana_potion([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* mana_potion([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("mana potion",
"mana potion", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* healing_potion([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* healing_potion([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("healing potion",
"healing potion", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("food"), nullptr),
/*A*/ { NextAction("food") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* flee([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* flee([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("flee",
"flee", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
}; };

View File

@@ -60,7 +60,7 @@ public:
Strategy(PlayerbotAI* botAI); Strategy(PlayerbotAI* botAI);
virtual ~Strategy() {} virtual ~Strategy() {}
virtual std::vector<NextAction> getDefaultActions() { return {}; } virtual NextAction** getDefaultActions() { return nullptr; }
virtual void InitTriggers([[maybe_unused]] std::vector<TriggerNode*>& triggers) {} virtual void InitTriggers([[maybe_unused]] std::vector<TriggerNode*>& triggers) {}
virtual void InitMultipliers([[maybe_unused]] std::vector<Multiplier*>& multipliers) {} virtual void InitMultipliers([[maybe_unused]] std::vector<Multiplier*>& multipliers) {}
virtual std::string const getName() = 0; virtual std::string const getName() = 0;

View File

@@ -120,8 +120,6 @@ public:
creators["formation"] = &StrategyContext::combat_formation; creators["formation"] = &StrategyContext::combat_formation;
creators["move from group"] = &StrategyContext::move_from_group; creators["move from group"] = &StrategyContext::move_from_group;
creators["worldbuff"] = &StrategyContext::world_buff; creators["worldbuff"] = &StrategyContext::world_buff;
creators["use bobber"] = &StrategyContext::bobber_strategy;
creators["master fishing"] = &StrategyContext::master_fishing;
} }
private: private:
@@ -190,8 +188,6 @@ private:
static Strategy* combat_formation(PlayerbotAI* botAI) { return new CombatFormationStrategy(botAI); } static Strategy* combat_formation(PlayerbotAI* botAI) { return new CombatFormationStrategy(botAI); }
static Strategy* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupStrategy(botAI); } static Strategy* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupStrategy(botAI); }
static Strategy* world_buff(PlayerbotAI* botAI) { return new WorldBuffStrategy(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> class MovementStrategyContext : public NamedObjectContext<Strategy>

View File

@@ -3,7 +3,8 @@
* and/or modify it under version 3 of the License, or (at your option), any later version. * and/or modify it under version 3 of the License, or (at your option), any later version.
*/ */
#pragma once #ifndef _PLAYERBOT_TRIGGER_H
#define _PLAYERBOT_TRIGGER_H
#include "Action.h" #include "Action.h"
#include "Common.h" #include "Common.h"
@@ -14,11 +15,7 @@ class Unit;
class Trigger : public AiNamedObject class Trigger : public AiNamedObject
{ {
public: public:
Trigger( Trigger(PlayerbotAI* botAI, std::string const name = "trigger", int32 checkInterval = 1);
PlayerbotAI* botAI,
const std::string name = "trigger",
int32_t checkInterval = 1
);
virtual ~Trigger() {} virtual ~Trigger() {}
@@ -26,7 +23,7 @@ public:
virtual void ExternalEvent([[maybe_unused]] std::string const param, [[maybe_unused]] Player* owner = nullptr) {} 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 void ExternalEvent([[maybe_unused]] WorldPacket& packet, [[maybe_unused]] Player* owner = nullptr) {}
virtual bool IsActive() { return false; } virtual bool IsActive() { return false; }
virtual std::vector<NextAction> getHandlers() { return {}; } virtual NextAction** getHandlers() { return nullptr; }
void Update() {} void Update() {}
virtual void Reset() {} virtual void Reset() {}
virtual Unit* GetTarget(); virtual Unit* GetTarget();
@@ -36,49 +33,32 @@ public:
bool needCheck(uint32 now); bool needCheck(uint32 now);
protected: protected:
int32_t checkInterval; int32 checkInterval;
uint32_t lastCheckTime; uint32 lastCheckTime;
}; };
class TriggerNode class TriggerNode
{ {
public: public:
TriggerNode( TriggerNode(std::string const name, NextAction** handlers = nullptr)
const std::string& name, : trigger(nullptr), handlers(handlers), name(name)
std::vector<NextAction> handlers = {} {
) : } // reorder args - whipowill
trigger(nullptr),
handlers(std::move(handlers)), virtual ~TriggerNode() { NextAction::destroy(handlers); }
name(name)
{}
Trigger* getTrigger() { return trigger; } Trigger* getTrigger() { return trigger; }
void setTrigger(Trigger* trigger) { this->trigger = trigger; } void setTrigger(Trigger* trigger) { this->trigger = trigger; }
const std::string getName() { return name; } std::string const getName() { return name; }
std::vector<NextAction> getHandlers() NextAction** getHandlers() { return NextAction::merge(NextAction::clone(handlers), trigger->getHandlers()); }
{
std::vector<NextAction> result = this->handlers;
if (trigger != nullptr) float getFirstRelevance() { return handlers[0] ? handlers[0]->getRelevance() : -1; }
{
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: private:
Trigger* trigger; Trigger* trigger;
std::vector<NextAction> handlers; NextAction** handlers;
const std::string name; std::string const name;
}; };
#endif

View File

@@ -59,7 +59,7 @@ bool AcceptInvitationAction::Execute(Event event)
if (sPlayerbotAIConfig->summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig->sightDistance) if (sPlayerbotAIConfig->summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig->sightDistance)
{ {
Teleport(inviter, bot, true); Teleport(inviter, bot);
} }
return true; return true;
} }

View File

@@ -23,5 +23,7 @@ bool AcceptResurrectAction::Execute(Event event)
packet << uint8(1); // accept packet << uint8(1); // accept
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
return true; return true;
} }

View File

@@ -46,6 +46,7 @@
#include "OutfitAction.h" #include "OutfitAction.h"
#include "PositionAction.h" #include "PositionAction.h"
#include "DropQuestAction.h" #include "DropQuestAction.h"
#include "RaidNaxxActions.h"
#include "RandomBotUpdateAction.h" #include "RandomBotUpdateAction.h"
#include "ReachTargetActions.h" #include "ReachTargetActions.h"
#include "ReleaseSpiritAction.h" #include "ReleaseSpiritAction.h"
@@ -63,7 +64,6 @@
#include "WorldBuffAction.h" #include "WorldBuffAction.h"
#include "XpGainAction.h" #include "XpGainAction.h"
#include "NewRpgAction.h" #include "NewRpgAction.h"
#include "FishingAction.h"
#include "CancelChannelAction.h" #include "CancelChannelAction.h"
class PlayerbotAI; class PlayerbotAI;
@@ -121,7 +121,7 @@ public:
creators["shoot"] = &ActionContext::shoot; creators["shoot"] = &ActionContext::shoot;
creators["follow"] = &ActionContext::follow; creators["follow"] = &ActionContext::follow;
creators["move from group"] = &ActionContext::move_from_group; creators["move from group"] = &ActionContext::move_from_group;
creators["flee to group leader"] = &ActionContext::flee_to_group_leader; creators["flee to master"] = &ActionContext::flee_to_master;
creators["runaway"] = &ActionContext::runaway; creators["runaway"] = &ActionContext::runaway;
creators["stay"] = &ActionContext::stay; creators["stay"] = &ActionContext::stay;
creators["sit"] = &ActionContext::sit; creators["sit"] = &ActionContext::sit;
@@ -191,11 +191,6 @@ public:
creators["buy tabard"] = &ActionContext::buy_tabard; creators["buy tabard"] = &ActionContext::buy_tabard;
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby; creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
creators["clean quest log"] = &ActionContext::clean_quest_log; 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["roll"] = &ActionContext::roll_action;
creators["cancel channel"] = &ActionContext::cancel_channel; creators["cancel channel"] = &ActionContext::cancel_channel;
@@ -323,7 +318,7 @@ private:
static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); } static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); }
static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); } static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); }
static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); } static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); }
static Action* flee_to_group_leader(PlayerbotAI* botAI) { return new FleeToGroupLeaderAction(botAI); } static Action* flee_to_master(PlayerbotAI* botAI) { return new FleeToMasterAction(botAI); }
static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); } static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); }
static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); } static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); }
static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); } static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); }
@@ -385,11 +380,6 @@ private:
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); } static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(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* 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); } static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
// BG Tactics // BG Tactics

View File

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

View File

@@ -224,36 +224,42 @@ bool BuyAction::Execute(Event event)
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto) bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
{ {
if (!tItems || !proto) uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1);
if (!tItems)
return false; return false;
uint32 itemId = proto->ItemId; uint32 itemId = proto->ItemId;
uint32 oldCount = bot->GetItemCount(itemId, false); for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++)
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)
{ {
std::ostringstream out; uint32 botMoney = bot->GetMoney();
out << "Buying " << ChatHelper::FormatItem(proto); if (botAI->HasCheat(BotCheatMask::gold))
botAI->TellMaster(out.str()); {
return true; bot->SetMoney(10000000);
} }
return false; 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;
}
} }
return false; return false;

View File

@@ -185,6 +185,7 @@ public:
creators["guild remove"] = &ChatActionContext::guild_remove; creators["guild remove"] = &ChatActionContext::guild_remove;
creators["guild leave"] = &ChatActionContext::guild_leave; creators["guild leave"] = &ChatActionContext::guild_leave;
creators["rtsc"] = &ChatActionContext::rtsc; creators["rtsc"] = &ChatActionContext::rtsc;
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut; creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps; creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps;
creators["join"] = &ChatActionContext::join; creators["join"] = &ChatActionContext::join;
@@ -297,6 +298,7 @@ private:
static Action* guild_remove(PlayerbotAI* botAI) { return new GuildRemoveAction(botAI); } static Action* guild_remove(PlayerbotAI* botAI) { return new GuildRemoveAction(botAI); }
static Action* guild_leave(PlayerbotAI* botAI) { return new GuildLeaveAction(botAI); } static Action* guild_leave(PlayerbotAI* botAI) { return new GuildLeaveAction(botAI); }
static Action* rtsc(PlayerbotAI* botAI) { return new RTSCAction(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* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); } static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); }
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); } static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }

View File

@@ -241,6 +241,20 @@ bool MaxDpsChatShortcutAction::Execute(Event event)
return true; 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) bool BwlChatShortcutAction::Execute(Event event)
{ {
Player* master = GetMaster(); Player* master = GetMaster();

View File

@@ -85,6 +85,13 @@ public:
bool Execute(Event event) override; 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 class BwlChatShortcutAction : public Action
{ {
public: public:

View File

@@ -78,17 +78,20 @@ float ChooseRpgTargetAction::getMaxRelevance(GuidPosition guidP)
if (!trigger->IsActive()) if (!trigger->IsActive())
continue; continue;
std::vector<NextAction> nextActions = triggerNode->getHandlers(); NextAction** nextActions = triggerNode->getHandlers();
bool isRpg = false; bool isRpg = false;
for (NextAction nextAction : nextActions) for (int32 i = 0; i < NextAction::size(nextActions); i++)
{ {
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName()); NextAction* nextAction = nextActions[i];
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction->getName());
if (dynamic_cast<RpgEnabled*>(action)) if (dynamic_cast<RpgEnabled*>(action))
isRpg = true; isRpg = true;
} }
NextAction::destroy(nextActions);
if (isRpg) if (isRpg)
{ {
@@ -308,7 +311,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target)
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos) bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
{ {
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
Player* groupLeader = botAI->GetGroupLeader(); Player* gmaster = botAI->GetGroupMaster();
Player* realMaster = botAI->GetMaster(); Player* realMaster = botAI->GetMaster();
AiObjectContext* context = botAI->GetAiObjectContext(); AiObjectContext* context = botAI->GetAiObjectContext();
@@ -324,30 +327,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
return false; return false;
} }
if (!groupLeader || bot == groupLeader) if (!gmaster || bot == gmaster)
return true; return true;
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
return true; return true;
if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2) if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2)
return false; return false;
Formation* formation = AI_VALUE(Formation*, "formation"); Formation* formation = AI_VALUE(Formation*, "formation");
float distance = groupLeader->GetDistance2d(pos.getX(), pos.getY()); float distance = gmaster->GetDistance2d(pos.getX(), pos.getY());
if (!botAI->HasActivePlayerMaster() && distance < 50.0f) if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
{ {
Player* player = groupLeader; Player* player = gmaster;
if (groupLeader && !groupLeader->isMoving() || if (gmaster && !gmaster->isMoving() ||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance) PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
return true; return true;
} }
if ((inDungeon || !groupLeader->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == groupLeader && distance > 5.0f) if ((inDungeon || !gmaster->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == gmaster && distance > 5.0f)
return false; return false;
if (!groupLeader->isMoving() && distance < 25.0f) if (!gmaster->isMoving() && distance < 25.0f)
return true; return true;
if (distance < formation->GetMaxDistance()) if (distance < formation->GetMaxDistance())

View File

@@ -10,7 +10,6 @@
#include "LootObjectStack.h" #include "LootObjectStack.h"
#include "NewRpgStrategy.h" #include "NewRpgStrategy.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RtiTargetValue.h"
#include "PossibleRpgTargetsValue.h" #include "PossibleRpgTargetsValue.h"
#include "PvpTriggers.h" #include "PvpTriggers.h"
#include "ServerFacade.h" #include "ServerFacade.h"
@@ -88,7 +87,9 @@ bool DropTargetAction::Execute(Event event)
{ {
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot 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 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->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
}
} }
bot->AttackStop(); bot->AttackStop();
@@ -141,23 +142,6 @@ bool AttackRtiTargetAction::Execute(Event event)
{ {
Unit* rtiTarget = AI_VALUE(Unit*, "rti target"); 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()) if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
{ {
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()}); botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
@@ -169,7 +153,9 @@ bool AttackRtiTargetAction::Execute(Event event)
} }
} }
else else
{
botAI->TellError("I dont see my rti attack target"); botAI->TellError("I dont see my rti attack target");
}
return false; return false;
} }

View File

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

View File

@@ -344,27 +344,6 @@ bool EquipUpgradesAction::Execute(Event event)
return false; 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; CollectItemsVisitor visitor;
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);

View File

@@ -1,493 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#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;
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_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

View File

@@ -70,7 +70,7 @@ bool FollowAction::isUseful()
if (!target.empty()) if (!target.empty())
fTarget = AI_VALUE(Unit*, target); fTarget = AI_VALUE(Unit*, target);
else else
fTarget = AI_VALUE(Unit*, "group leader"); fTarget = AI_VALUE(Unit*, "master target");
if (fTarget) if (fTarget)
{ {
@@ -97,8 +97,6 @@ bool FollowAction::isUseful()
distance = bot->GetDistance(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()); 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()); return sServerFacade->IsDistanceGreaterThan(distance, formation->GetMaxDistance());
} }
@@ -116,9 +114,9 @@ bool FollowAction::CanDeadFollow(Unit* target)
return true; return true;
} }
bool FleeToGroupLeaderAction::Execute(Event event) bool FleeToMasterAction::Execute(Event event)
{ {
Unit* fTarget = AI_VALUE(Unit*, "group leader"); Unit* fTarget = AI_VALUE(Unit*, "master target");
bool canFollow = Follow(fTarget); bool canFollow = Follow(fTarget);
if (!canFollow) if (!canFollow)
{ {
@@ -148,22 +146,22 @@ bool FleeToGroupLeaderAction::Execute(Event event)
return true; return true;
} }
bool FleeToGroupLeaderAction::isUseful() bool FleeToMasterAction::isUseful()
{ {
if (!botAI->GetGroupLeader()) if (!botAI->GetGroupMaster())
return false; return false;
if (botAI->GetGroupLeader() == bot) if (botAI->GetGroupMaster() == bot)
return false; return false;
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (target && botAI->GetGroupLeader()->GetTarget() == target->GetGUID()) if (target && botAI->GetGroupMaster()->GetTarget() == target->GetGUID())
return false; return false;
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
return false; return false;
Unit* fTarget = AI_VALUE(Unit*, "group leader"); Unit* fTarget = AI_VALUE(Unit*, "master target");
if (!CanDeadFollow(fTarget)) if (!CanDeadFollow(fTarget))
return false; return false;

View File

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

View File

@@ -265,6 +265,11 @@ CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "s
} }
} }
NextAction** CastSpellAction::getPrerequisites()
{
return nullptr;
}
Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue() Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue()
{ {
return context->GetValue<Unit*>("attacker without aura", spell); return context->GetValue<Unit*>("attacker without aura", spell);

View File

@@ -27,11 +27,7 @@ public:
bool isUseful() override; bool isUseful() override;
ActionThreatType getThreatType() override { return ActionThreatType::Single; } ActionThreatType getThreatType() override { return ActionThreatType::Single; }
std::vector<NextAction> getPrerequisites() override NextAction** getPrerequisites() override;
{
return {};
}
std::string const getSpell() { return spell; } std::string const getSpell() { return spell; }
protected: protected:
@@ -197,12 +193,10 @@ public:
ResurrectPartyMemberAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {} ResurrectPartyMemberAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
std::string const GetTargetName() override { return "party member to resurrect"; } std::string const GetTargetName() override { return "party member to resurrect"; }
std::vector<NextAction> getPrerequisites() override NextAction** getPrerequisites() override
{ {
return NextAction::merge( return NextAction::merge(NextAction::array(0, new NextAction("reach party member to resurrect"), NULL),
{ NextAction("reach party member to resurrect") }, Action::getPrerequisites());
Action::getPrerequisites()
);
} }
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -192,23 +192,30 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
{ {
return false; return false;
} }
bool generatePath = !bot->IsFlying() && !bot->isSwimming(); bool generatePath = !bot->IsFlying() && !bot->isSwimming();
bool disableMoveSplinePath = bool disableMoveSplinePath = sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
sPlayerbotAIConfig->disableMoveSplinePath >= 2 || (sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
if (Vehicle* vehicle = bot->GetVehicle()) if (Vehicle* vehicle = bot->GetVehicle())
{ {
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot); VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
Unit* vehicleBase = vehicle->GetBase(); Unit* vehicleBase = vehicle->GetBase();
generatePath = !vehicleBase || !vehicleBase->CanFly(); generatePath = vehicleBase->CanFly();
if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway
return false; return false;
float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot
if (distance > 0.01f) if (distance > 0.01f)
{ {
DoMovePoint(vehicleBase, x, y, z, generatePath, backwards); MotionMaster& mm = *vehicleBase->GetMotionMaster(); // need to move vehicle, not bot
mm.Clear();
if (!backwards)
{
mm.MovePoint(0, x, y, z, generatePath);
}
else
{
mm.MovePointBackwards(0, x, y, z, generatePath);
}
float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN); float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN);
float delay = 1000.0f * (distance / speed); float delay = 1000.0f * (distance / speed);
if (lessDelay) if (lessDelay)
@@ -234,7 +241,16 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop(); // bot->CastStop();
// botAI->InterruptSpell(); // botAI->InterruptSpell();
// } // }
DoMovePoint(bot, x, y, z, generatePath, backwards); MotionMaster& mm = *bot->GetMotionMaster();
mm.Clear();
if (!backwards)
{
mm.MovePoint(0, x, y, z, generatePath);
}
else
{
mm.MovePointBackwards(0, x, y, z, generatePath);
}
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
{ {
@@ -252,7 +268,9 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
Movement::PointsArray path = Movement::PointsArray path =
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only); SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only);
if (modifiedZ == INVALID_HEIGHT) if (modifiedZ == INVALID_HEIGHT)
{
return false; return false;
}
float distance = bot->GetExactDist(x, y, modifiedZ); float distance = bot->GetExactDist(x, y, modifiedZ);
if (distance > 0.01f) if (distance > 0.01f)
{ {
@@ -264,8 +282,17 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop(); // bot->CastStop();
// botAI->InterruptSpell(); // botAI->InterruptSpell();
// } // }
MotionMaster& mm = *bot->GetMotionMaster();
G3D::Vector3 endP = path.back(); G3D::Vector3 endP = path.back();
DoMovePoint(bot, x, y, z, generatePath, backwards); mm.Clear();
if (!backwards)
{
mm.MovePoint(0, x, y, z, generatePath);
}
else
{
mm.MovePointBackwards(0, x, y, z, generatePath);
}
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
{ {
@@ -482,8 +509,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// { // {
// AI_VALUE(LastMovement&, "last area trigger").lastAreaTrigger = entry; // AI_VALUE(LastMovement&, "last area trigger").lastAreaTrigger = entry;
// } // }
// else // else {
// {
// LOG_DEBUG("playerbots", "!entry"); // LOG_DEBUG("playerbots", "!entry");
// return bot->TeleportTo(movePosition.getMapId(), movePosition.getX(), movePosition.getY(), // return bot->TeleportTo(movePosition.getMapId(), movePosition.getX(), movePosition.getY(),
// movePosition.getZ(), movePosition.getO(), 0); // movePosition.getZ(), movePosition.getO(), 0);
@@ -533,7 +559,9 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1); // bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1);
// if (botAI->HasCheat(BotCheatMask::gold)) // if (botAI->HasCheat(BotCheatMask::gold))
// {
// bot->SetMoney(botMoney); // bot->SetMoney(botMoney);
// }
// LOG_DEBUG("playerbots", "goTaxi"); // LOG_DEBUG("playerbots", "goTaxi");
// return goTaxi; // return goTaxi;
// } // }
@@ -946,100 +974,78 @@ bool MovementAction::IsWaitingForLastMove(MovementPriority priority)
bool MovementAction::IsMovingAllowed() bool MovementAction::IsMovingAllowed()
{ {
return botAI->CanMove(); // 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;
} }
bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); } bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); }
void MovementAction::UpdateMovementState() void MovementAction::UpdateMovementState()
{ {
const bool isCurrentlyRestricted = // see if the bot is currently slowed, rooted, or otherwise unable to move int8 botInLiquidState = bot->GetLiquidData().Status;
bot->HasUnitState(UNIT_STATE_LOST_CONTROL) ||
bot->IsRooted() ||
bot->isFrozen() ||
bot->IsPolymorphed();
// no update movement flags while movement is current restricted. if (botInLiquidState == LIQUID_MAP_IN_WATER || botInLiquidState == LIQUID_MAP_UNDER_WATER)
if (!isCurrentlyRestricted && bot->IsAlive())
{ {
// state flags bot->SetSwim(true);
const auto master = botAI ? botAI->GetMaster() : nullptr; // real player or not }
const bool masterIsFlying = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) : true; else
const bool masterIsSwimming = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) : true; {
const auto liquidState = bot->GetLiquidData().Status; // default LIQUID_MAP_NO_WATER bot->SetSwim(false);
const float gZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
const bool wantsToFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
const bool isWaterArea = liquidState != LIQUID_MAP_NO_WATER;
const bool isUnderWater = liquidState == LIQUID_MAP_UNDER_WATER;
const bool isInWater = liquidState == LIQUID_MAP_IN_WATER;
const bool isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
const bool isSwimming = bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
const bool wantsToWaterWalk = bot->HasWaterWalkAura();
const bool wantsToSwim = isInWater || isUnderWater;
const bool onGroundZ = (bot->GetPositionZ() < gZ + 1.f) && !isWaterArea;
bool movementFlagsUpdated = false;
// handle water state
if (isWaterArea && !isFlying)
{
// water walking
if (wantsToWaterWalk && !isWaterWalking && !masterIsSwimming)
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
bot->AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
movementFlagsUpdated = true;
}
// swimming
else if (wantsToSwim && !isSwimming && masterIsSwimming)
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
bot->AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
movementFlagsUpdated = true;
}
}
else if (isSwimming || isWaterWalking)
{
// reset water flags
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
movementFlagsUpdated = true;
}
// handle flying state
if (wantsToFly && !isFlying && masterIsFlying)
{
bot->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
bot->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
movementFlagsUpdated = true;
}
else if ((!wantsToFly || onGroundZ) && isFlying)
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
movementFlagsUpdated = true;
}
// detect if movement restrictions have been lifted, CC just ended.
if (wasMovementRestricted)
movementFlagsUpdated = true; // refresh movement state to ensure animations play correctly
if (movementFlagsUpdated)
bot->SendMovementFlagUpdate();
} }
// Save current state for the next check bool onGround = bot->GetPositionZ() <
bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()) + 1.0f;
// Keep bot->SendMovementFlagUpdate() withing the if statements to not intefere with bot behavior on
// ground/(shallow) waters
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) &&
bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !onGround)
{
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
bot->SendMovementFlagUpdate();
}
else if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) &&
(!bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || onGround))
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
bot->SendMovementFlagUpdate();
}
// See if the bot is currently slowed, rooted, or otherwise unable to move
bool isCurrentlyRestricted = bot->isFrozen() || bot->IsPolymorphed() || bot->HasRootAura() || bot->HasStunAura() ||
bot->HasConfuseAura() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
// Detect if movement restrictions have been lifted
if (wasMovementRestricted && !isCurrentlyRestricted && bot->IsAlive())
{
// CC just ended - refresh movement state to ensure animations play correctly
bot->SendMovementFlagUpdate();
}
// Save current state for the next check
wasMovementRestricted = isCurrentlyRestricted; wasMovementRestricted = isCurrentlyRestricted;
// Temporary speed increase in group // Temporary speed increase in group
// if (botAI->HasRealPlayerMaster()) // if (botAI->HasRealPlayerMaster()) {
// {
// bot->SetSpeedRate(MOVE_RUN, 1.1f); // bot->SetSpeedRate(MOVE_RUN, 1.1f);
// } // } else {
// else
// {
// bot->SetSpeedRate(MOVE_RUN, 1.0f); // bot->SetSpeedRate(MOVE_RUN, 1.0f);
// } // }
// check if target is not reachable (from Vmangos) // check if target is not reachable (from Vmangos)
@@ -1048,7 +1054,7 @@ void MovementAction::UpdateMovementState()
// { // {
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot)) // if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
// { // {
// if (!bot->IsWithinMeleeRange(pTarget) && pTarget->IsCreature()) // if (!bot->IsWithinMeleeRange(pTarget) && pTarget->GetTypeId() == TYPEID_UNIT)
// { // {
// float angle = bot->GetAngle(pTarget); // float angle = bot->GetAngle(pTarget);
// float distance = 5.0f; // float distance = 5.0f;
@@ -1082,7 +1088,7 @@ void MovementAction::UpdateMovementState()
// { // {
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot)) // if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
// { // {
// if (pTarget != botAI->GetGroupLeader()) // if (pTarget != botAI->GetGroupMaster())
// return; // return;
// if (!bot->IsWithinMeleeRange(pTarget)) // if (!bot->IsWithinMeleeRange(pTarget))
@@ -1676,8 +1682,7 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
// float MovementAction::SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range, bool // float MovementAction::SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range, bool
// normal_only, float step) // normal_only, float step)
// { // {
// if (!generatePath) // if (!generatePath) {
// {
// return z; // return z;
// } // }
// float min_length = 100000.0f; // float min_length = 100000.0f;
@@ -1688,12 +1693,10 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta); // modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
// PathGenerator gen(bot); // PathGenerator gen(bot);
// gen.CalculatePath(x, y, modified_z); // gen.CalculatePath(x, y, modified_z);
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) // if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) {
// {
// min_length = gen.getPathLength(); // min_length = gen.getPathLength();
// current_z = modified_z; // current_z = modified_z;
// if (abs(current_z - z) < 0.5f) // if (abs(current_z - z) < 0.5f) {
// {
// return current_z; // return current_z;
// } // }
// } // }
@@ -1702,34 +1705,30 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta); // modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
// PathGenerator gen(bot); // PathGenerator gen(bot);
// gen.CalculatePath(x, y, modified_z); // gen.CalculatePath(x, y, modified_z);
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) // if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) {
// {
// min_length = gen.getPathLength(); // min_length = gen.getPathLength();
// current_z = modified_z; // current_z = modified_z;
// if (abs(current_z - z) < 0.5f) // if (abs(current_z - z) < 0.5f) {
// return current_z; // return current_z;
// }
// } // }
// } // }
// for (delta = range / 2 + step; delta <= range; delta += 2) { // for (delta = range / 2 + step; delta <= range; delta += 2) {
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta); // modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
// PathGenerator gen(bot); // PathGenerator gen(bot);
// gen.CalculatePath(x, y, modified_z); // gen.CalculatePath(x, y, modified_z);
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) // if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) {
// {
// min_length = gen.getPathLength(); // min_length = gen.getPathLength();
// current_z = modified_z; // current_z = modified_z;
// if (abs(current_z - z) < 0.5f) // if (abs(current_z - z) < 0.5f) {
// {
// return current_z; // return current_z;
// } // }
// } // }
// } // }
// if (current_z == INVALID_HEIGHT && normal_only) // if (current_z == INVALID_HEIGHT && normal_only) {
// {
// return INVALID_HEIGHT; // return INVALID_HEIGHT;
// } // }
// if (current_z == INVALID_HEIGHT && !normal_only) // if (current_z == INVALID_HEIGHT && !normal_only) {
// {
// return z; // return z;
// } // }
// return current_z; // return current_z;
@@ -1804,46 +1803,6 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
return result; return result;
} }
void MovementAction::DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards)
{
if (!unit)
return;
MotionMaster* mm = unit->GetMotionMaster();
if (!mm)
return;
// enable water walking
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING))
{
float gZ = unit->GetMapWaterOrGroundLevel(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ());
unit->UpdatePosition(unit->GetPositionX(), unit->GetPositionY(), gZ, false);
// z = gZ; no overwrite Z axe otherwise you cant steer the bots into swimming when water walking.
}
mm->Clear();
if (backwards)
{
mm->MovePointBackwards(
/*id*/ 0,
/*coords*/ x, y, z,
/*generatePath*/ generatePath,
/*forceDestination*/ false);
return;
}
else
{
mm->MovePoint(
/*id*/ 0,
/*coords*/ x, y, z,
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
/*speed*/ 0.f,
/*orientation*/ 0.f,
/*generatePath*/ generatePath, // true => terrain path (2d mmap); false => straight spline (3d vmap)
/*forceDestination*/ false);
}
}
bool FleeAction::Execute(Event event) bool FleeAction::Execute(Event event)
{ {
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true); return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
@@ -2104,8 +2063,8 @@ Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
if (currentTarget) if (currentTarget)
{ {
// Normally, move to left or right is the best position // Normally, move to left or right is the best position
bool isTanking = (!currentTarget->isFrozen() bool isTanking =
&& !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot); (!currentTarget->isFrozen() && !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
float angle = bot->GetAngle(currentTarget); float angle = bot->GetAngle(currentTarget);
float angleLeft = angle + (float)M_PI / 2; float angleLeft = angle + (float)M_PI / 2;
float angleRight = angle - (float)M_PI / 2; float angleRight = angle - (float)M_PI / 2;
@@ -2521,7 +2480,9 @@ bool RearFlankAction::isUseful()
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (!target) if (!target)
{
return false; return false;
}
// Need to double the front angle check to account for mirrored angle. // Need to double the front angle check to account for mirrored angle.
bool inFront = target->HasInArc(2.f * minAngle, bot); bool inFront = target->HasInArc(2.f * minAngle, bot);
@@ -2536,7 +2497,9 @@ bool RearFlankAction::Execute(Event event)
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (!target) if (!target)
{
return false; return false;
}
float angle = frand(minAngle, maxAngle); float angle = frand(minAngle, maxAngle);
float baseDistance = bot->GetMeleeRange(target) * 0.5f; float baseDistance = bot->GetMeleeRange(target) * 0.5f;
@@ -2643,7 +2606,7 @@ bool DisperseSetAction::Execute(Event event)
return true; return true;
} }
bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "group leader")); } bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "master target")); }
bool MoveToLootAction::Execute(Event event) bool MoveToLootAction::Execute(Event event)
{ {
@@ -2830,7 +2793,7 @@ bool MoveAwayFromCreatureAction::Execute(Event event)
// Find all creatures with the specified Id // Find all creatures with the specified Id
std::vector<Unit*> creatures; std::vector<Unit*> creatures;
for (auto const& guid : targets) for (const auto& guid : targets)
{ {
Unit* unit = botAI->GetUnit(guid); Unit* unit = botAI->GetUnit(guid);
if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId) if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId)

View File

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

View File

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

View File

@@ -3,11 +3,8 @@
* and/or modify it under version 3 of the License, or (at your option), any later version. * and/or modify it under version 3 of the License, or (at your option), any later version.
*/ */
#include <memory>
#include <mutex>
#include <vector>
#include "ReadyCheckAction.h" #include "ReadyCheckAction.h"
#include "Event.h" #include "Event.h"
#include "Playerbots.h" #include "Playerbots.h"
@@ -30,17 +27,14 @@ std::string const formatPercent(std::string const name, uint8 value, float perce
class ReadyChecker class ReadyChecker
{ {
public: public:
virtual ~ReadyChecker() = default;
virtual bool Check(PlayerbotAI* botAI, AiObjectContext* context) = 0; virtual bool Check(PlayerbotAI* botAI, AiObjectContext* context) = 0;
virtual std::string const getName() = 0; virtual std::string const getName() = 0;
virtual bool PrintAlways() { return true; } virtual bool PrintAlways() { return true; }
static std::vector<std::unique_ptr<ReadyChecker>> checkers; static std::vector<ReadyChecker*> checkers;
static std::once_flag initFlag;
}; };
std::vector<std::unique_ptr<ReadyChecker>> ReadyChecker::checkers; std::vector<ReadyChecker*> ReadyChecker::checkers;
std::once_flag ReadyChecker::initFlag;
class HealthChecker : public ReadyChecker class HealthChecker : public ReadyChecker
{ {
@@ -166,30 +160,25 @@ bool ReadyCheckAction::Execute(Event event)
bool ReadyCheckAction::ReadyCheck() bool ReadyCheckAction::ReadyCheck()
{ {
std::call_once( if (ReadyChecker::checkers.empty())
ReadyChecker::initFlag, {
[]() ReadyChecker::checkers.push_back(new HealthChecker());
{ ReadyChecker::checkers.push_back(new ManaChecker());
ReadyChecker::checkers.reserve(8); ReadyChecker::checkers.push_back(new DistanceChecker());
ReadyChecker::checkers.push_back(new HunterChecker());
ReadyChecker::checkers.emplace_back(std::make_unique<HealthChecker>()); ReadyChecker::checkers.push_back(new ItemCountChecker("food", "Food"));
ReadyChecker::checkers.emplace_back(std::make_unique<ManaChecker>()); ReadyChecker::checkers.push_back(new ManaPotionChecker("drink", "Water"));
ReadyChecker::checkers.emplace_back(std::make_unique<DistanceChecker>()); ReadyChecker::checkers.push_back(new ItemCountChecker("healing potion", "Hpot"));
ReadyChecker::checkers.emplace_back(std::make_unique<HunterChecker>()); ReadyChecker::checkers.push_back(new ManaPotionChecker("mana potion", "Mpot"));
}
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; bool result = true;
for (auto const& checkerPtr : ReadyChecker::checkers) for (std::vector<ReadyChecker*>::iterator i = ReadyChecker::checkers.begin(); i != ReadyChecker::checkers.end();
++i)
{ {
if (!checkerPtr) ReadyChecker* checker = *i;
continue; bool ok = checker->Check(botAI, context);
bool ok = checkerPtr->Check(botAI, context);
result = result && ok; result = result && ok;
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -68,15 +68,17 @@ bool RpgAction::SetNextRpgAction()
triggerNode->setTrigger(trigger); triggerNode->setTrigger(trigger);
std::vector<NextAction> nextActions = triggerNode->getHandlers(); NextAction** nextActions = triggerNode->getHandlers();
Trigger* trigger = triggerNode->getTrigger(); Trigger* trigger = triggerNode->getTrigger();
bool isChecked = false; bool isChecked = false;
for (NextAction nextAction : nextActions) for (int32 i = 0; i < NextAction::size(nextActions); i++)
{ {
if (nextAction.getRelevance() > 5.0f) NextAction* nextAction = nextActions[i];
if (nextAction->getRelevance() > 5.0f)
continue; continue;
if (!isChecked && !trigger->IsActive()) if (!isChecked && !trigger->IsActive())
@@ -84,13 +86,14 @@ bool RpgAction::SetNextRpgAction()
isChecked = true; 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()) if (!dynamic_cast<RpgEnabled*>(action) || !action->isPossible() || !action->isUseful())
continue; continue;
actions.push_back(action); actions.push_back(action);
relevances.push_back((nextAction.getRelevance() - 1) * 500); relevances.push_back((nextAction->getRelevance() - 1) * 500);
} }
NextAction::destroy(nextActions);
} }
} }

View File

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

View File

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

View File

@@ -14,8 +14,6 @@
#include "PositionValue.h" #include "PositionValue.h"
#include "ByteBuffer.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, Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
bool important) bool important)
{ {
@@ -59,16 +57,6 @@ bool SeeSpellAction::Execute(Event event)
// if (!botAI->HasStrategy("RTSC", botAI->GetState())) // if (!botAI->HasStrategy("RTSC", botAI->GetState()))
// return false; // 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) if (spellId != RTSC_MOVE_SPELL)
return false; return false;

View File

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

View File

@@ -10,7 +10,7 @@
#include "PlayerbotFactory.h" #include "PlayerbotFactory.h"
#include "Playerbots.h" #include "Playerbots.h"
void TrainerAction::Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg) void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg)
{ {
if (sPlayerbotAIConfig->autoTrainSpells != "free" && !botAI->HasCheat(BotCheatMask::gold)) if (sPlayerbotAIConfig->autoTrainSpells != "free" && !botAI->HasCheat(BotCheatMask::gold))
{ {
@@ -23,7 +23,7 @@ void TrainerAction::Learn(uint32 cost, const Trainer::Spell tSpell, std::ostring
bot->ModifyMoney(-int32(cost)); bot->ModifyMoney(-int32(cost));
} }
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell.SpellId); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell->spell);
if (!spellInfo) if (!spellInfo)
return; return;
@@ -41,8 +41,10 @@ void TrainerAction::Learn(uint32 cost, const Trainer::Spell tSpell, std::ostring
} }
} }
if (!learned && !bot->HasSpell(tSpell.SpellId)) if (!learned && !bot->HasSpell(tSpell->spell))
bot->learnSpell(tSpell.SpellId); {
bot->learnSpell(tSpell->spell);
}
msg << " - learned"; msg << " - learned";
} }
@@ -51,35 +53,37 @@ void TrainerAction::Iterate(Creature* creature, TrainerSpellAction action, Spell
{ {
TellHeader(creature); TellHeader(creature);
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry()); TrainerSpellData const* trainer_spells = creature->GetTrainerSpells();
if (!trainer)
return;
float fDiscountMod = bot->GetReputationPriceDiscount(creature); float fDiscountMod = bot->GetReputationPriceDiscount(creature);
uint32 totalCost = 0; uint32 totalCost = 0;
for (auto& spell : trainer->GetSpells()) for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
itr != trainer_spells->spellList.end(); ++itr)
{ {
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId))) TrainerSpell const* tSpell = &itr->second;
if (!tSpell)
continue; continue;
if (!spells.empty() && spells.find(spell.SpellId) == spells.end()) TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
if (state != TRAINER_SPELL_GREEN)
continue; continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId); uint32 spellId = tSpell->spell;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo) if (!spellInfo)
continue; continue;
uint32 cost = uint32(floor(spell.MoneyCost * fDiscountMod)); if (!spells.empty() && spells.find(tSpell->spell) == spells.end())
continue;
uint32 cost = uint32(floor(tSpell->spellCost * fDiscountMod));
totalCost += cost; totalCost += cost;
std::ostringstream out; std::ostringstream out;
out << chat->FormatSpell(spellInfo) << chat->formatMoney(cost); out << chat->FormatSpell(spellInfo) << chat->formatMoney(cost);
if (action) if (action)
(this->*action)(cost, spell, out); (this->*action)(cost, tSpell, out);
botAI->TellMaster(out); botAI->TellMaster(out);
} }
@@ -108,14 +112,15 @@ bool TrainerAction::Execute(Event event)
if (!creature || !creature->IsTrainer()) if (!creature || !creature->IsTrainer())
return false; return false;
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry()); if (!creature->IsValidTrainerForPlayer(bot))
{
if (!trainer || !trainer->IsTrainerValidForPlayer(bot)) botAI->TellError("This trainer cannot teach me");
return false; return false;
}
std::vector<Trainer::Spell> trainer_spells = trainer->GetSpells(); // check present spell in trainer spell list
TrainerSpellData const* cSpells = creature->GetTrainerSpells();
if (trainer_spells.empty()) if (!cSpells)
{ {
botAI->TellError("No spells can be learned from this trainer"); botAI->TellError("No spells can be learned from this trainer");
return false; return false;
@@ -128,7 +133,7 @@ bool TrainerAction::Execute(Event event)
if (text.find("learn") != std::string::npos || sRandomPlayerbotMgr->IsRandomBot(bot) || if (text.find("learn") != std::string::npos || sRandomPlayerbotMgr->IsRandomBot(bot) ||
(sPlayerbotAIConfig->autoTrainSpells != "no" && (sPlayerbotAIConfig->autoTrainSpells != "no" &&
(trainer->GetTrainerType() != Trainer::Type::Tradeskill || (creature->GetCreatureTemplate()->trainer_type != TRAINER_TYPE_TRADESKILLS ||
!botAI->HasActivePlayerMaster()))) // Todo rewrite to only exclude start primary profession skills and make !botAI->HasActivePlayerMaster()))) // Todo rewrite to only exclude start primary profession skills and make
// config dependent. // config dependent.
Iterate(creature, &TrainerAction::Learn, spells); Iterate(creature, &TrainerAction::Learn, spells);

View File

@@ -8,7 +8,6 @@
#include "Action.h" #include "Action.h"
#include "ChatHelper.h" #include "ChatHelper.h"
#include "Trainer.h"
class Creature; class Creature;
class PlayerbotAI; class PlayerbotAI;
@@ -23,9 +22,9 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
private: private:
typedef void (TrainerAction::*TrainerSpellAction)(uint32, const Trainer::Spell, std::ostringstream& msg); typedef void (TrainerAction::*TrainerSpellAction)(uint32, TrainerSpell const*, std::ostringstream& msg);
void Iterate(Creature* creature, TrainerSpellAction action, SpellIds& spells); void Iterate(Creature* creature, TrainerSpellAction action, SpellIds& spells);
void Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg); void Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg);
void TellHeader(Creature* creature); void TellHeader(Creature* creature);
void TellFooter(uint32 totalCost); void TellFooter(uint32 totalCost);
}; };

View File

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

View File

@@ -52,7 +52,7 @@ bool UseMeetingStoneAction::Execute(Event event)
if (!goInfo || goInfo->entry != 179944) if (!goInfo || goInfo->entry != 179944)
return false; return false;
return Teleport(master, bot, false); return Teleport(master, bot);
} }
bool SummonAction::Execute(Event event) bool SummonAction::Execute(Event event)
@@ -70,16 +70,16 @@ bool SummonAction::Execute(Event event)
{ {
// botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({}); // botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({});
AI_VALUE(std::list<FleeInfo>&, "recently flee info").clear(); AI_VALUE(std::list<FleeInfo>&, "recently flee info").clear();
return Teleport(master, bot, true); return Teleport(master, bot);
} }
if (SummonUsingGos(master, bot, true) || SummonUsingNpcs(master, bot, true)) if (SummonUsingGos(master, bot) || SummonUsingNpcs(master, bot))
{ {
botAI->TellMasterNoFacing("Hello!"); botAI->TellMasterNoFacing("Hello!");
return true; return true;
} }
if (SummonUsingGos(bot, master, true) || SummonUsingNpcs(bot, master, true)) if (SummonUsingGos(bot, master) || SummonUsingNpcs(bot, master))
{ {
botAI->TellMasterNoFacing("Welcome!"); botAI->TellMasterNoFacing("Welcome!");
return true; return true;
@@ -88,7 +88,7 @@ bool SummonAction::Execute(Event event)
return false; return false;
} }
bool SummonAction::SummonUsingGos(Player* summoner, Player* player, bool preserveAuras) bool SummonAction::SummonUsingGos(Player* summoner, Player* player)
{ {
std::list<GameObject*> targets; std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig->sightDistance); AnyGameObjectInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig->sightDistance);
@@ -98,14 +98,14 @@ bool SummonAction::SummonUsingGos(Player* summoner, Player* player, bool preserv
for (GameObject* go : targets) for (GameObject* go : targets)
{ {
if (go->isSpawned() && go->GetGoType() == GAMEOBJECT_TYPE_MEETINGSTONE) if (go->isSpawned() && go->GetGoType() == GAMEOBJECT_TYPE_MEETINGSTONE)
return Teleport(summoner, player, preserveAuras); return Teleport(summoner, player);
} }
botAI->TellError(summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you"); botAI->TellError(summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you");
return false; return false;
} }
bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras) bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
{ {
if (!sPlayerbotAIConfig->summonAtInnkeepersEnabled) if (!sPlayerbotAIConfig->summonAtInnkeepersEnabled)
return false; return false;
@@ -139,7 +139,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preser
Spell spell(player, spellInfo, TRIGGERED_NONE); Spell spell(player, spellInfo, TRIGGERED_NONE);
spell.SendSpellCooldown(); spell.SendSpellCooldown();
return Teleport(summoner, player, preserveAuras); return Teleport(summoner, player);
} }
} }
@@ -147,7 +147,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preser
return false; return false;
} }
bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras) bool SummonAction::Teleport(Player* summoner, Player* player)
{ {
// Player* master = GetMaster(); // Player* master = GetMaster();
if (!summoner) if (!summoner)
@@ -208,11 +208,7 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras
player->GetMotionMaster()->Clear(); player->GetMotionMaster()->Clear();
AI_VALUE(LastMovement&, "last movement").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); player->TeleportTo(mapId, x, y, z, 0);
if (botAI->HasStrategy("stay", botAI->GetState())) if (botAI->HasStrategy("stay", botAI->GetState()))

View File

@@ -19,9 +19,9 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
protected: protected:
bool Teleport(Player* summoner, Player* player, bool preserveAuras); bool Teleport(Player* summoner, Player* player);
bool SummonUsingGos(Player* summoner, Player* player, bool preserveAuras); bool SummonUsingGos(Player* summoner, Player* player);
bool SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras); bool SummonUsingNpcs(Player* summoner, Player* player);
}; };
class UseMeetingStoneAction : public SummonAction class UseMeetingStoneAction : public SummonAction

View File

@@ -12,10 +12,25 @@ class BloodDKStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
public: public:
BloodDKStrategyActionNodeFactory() BloodDKStrategyActionNodeFactory()
{ {
// creators["melee"] = &melee;
// creators["blood strike"] = &blood_strike;
creators["rune strike"] = &rune_strike; creators["rune strike"] = &rune_strike;
creators["heart strike"] = &heart_strike; creators["heart strike"] = &heart_strike;
creators["death strike"] = &death_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["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["dark command"] = &dark_command;
creators["taunt spell"] = &dark_command; creators["taunt spell"] = &dark_command;
} }
@@ -23,61 +38,39 @@ public:
private: private:
static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("rune strike",
"rune strike", /*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
{ /*A*/ nullptr,
NextAction("frost presence") /*C*/ nullptr);
},
/*A*/ {},
/*C*/ {}
);
} }
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("icy touch",
"icy touch", /*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
{ /*A*/ nullptr,
NextAction("frost presence") /*C*/ nullptr);
},
/*A*/ {},
/*C*/ {}
);
} }
static ActionNode* heart_strike([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* heart_strike([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("heart strike",
"heart strike", /*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
{ /*A*/ nullptr,
NextAction("frost presence") /*C*/ nullptr);
},
/*A*/ {},
/*C*/ {}
);
} }
static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("death strike",
"death strike", /*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
{ /*A*/ nullptr,
NextAction("frost presence") /*C*/ nullptr);
},
/*A*/ {},
/*C*/ {}
);
} }
static ActionNode* dark_command([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* dark_command([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("dark command",
"dark command", /*P*/ NextAction::array(0, new NextAction("frost presence"), NULL),
{ /*A*/ NextAction::array(0, new NextAction("death grip"), NULL),
NextAction("frost presence") /*C*/ NULL);
},
/*A*/ {
NextAction("death grip")
},
/*C*/ {}
);
} }
}; };
@@ -86,80 +79,33 @@ BloodDKStrategy::BloodDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
actionNodeFactories.Add(new BloodDKStrategyActionNodeFactory()); actionNodeFactories.Add(new BloodDKStrategyActionNodeFactory());
} }
std::vector<NextAction> BloodDKStrategy::getDefaultActions() NextAction** BloodDKStrategy::getDefaultActions()
{ {
return { return NextAction::array(
NextAction("rune strike", ACTION_DEFAULT + 0.8f), 0, new NextAction("rune strike", ACTION_DEFAULT + 0.8f), new NextAction("icy touch", ACTION_DEFAULT + 0.7f),
NextAction("icy touch", ACTION_DEFAULT + 0.7f), new NextAction("heart strike", ACTION_DEFAULT + 0.6f), new NextAction("blood strike", ACTION_DEFAULT + 0.5f),
NextAction("heart strike", ACTION_DEFAULT + 0.6f), new NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
NextAction("blood strike", ACTION_DEFAULT + 0.5f), new NextAction("death coil", ACTION_DEFAULT + 0.3f), new NextAction("plague strike", ACTION_DEFAULT + 0.2f),
NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f), new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL);
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) void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericDKStrategy::InitTriggers(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( triggers.push_back(
new TriggerNode( new TriggerNode("blood tap", NextAction::array(0, new NextAction("blood tap", ACTION_HIGH + 5), nullptr)));
"rune strike",
{
NextAction("rune strike", ACTION_NORMAL + 3)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("lose aggro", NextAction::array(0, new NextAction("dark command", ACTION_HIGH + 3), nullptr)));
"blood tap",
{
NextAction("blood tap", ACTION_HIGH + 5)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("low health", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 4),
"lose aggro", new NextAction("death strike", ACTION_HIGH + 3), nullptr)));
{
NextAction("dark command", ACTION_HIGH + 3)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("critical health", NextAction::array(0, new NextAction("vampiric blood", ACTION_HIGH + 5), nullptr)));
"low health",
{
NextAction("army of the dead", ACTION_HIGH + 4),
NextAction("death strike", ACTION_HIGH + 3)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
"critical health", triggers.push_back(new TriggerNode(
{ "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
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)
}
)
);
} }

View File

@@ -17,7 +17,7 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "blood"; } std::string const getName() override { return "blood"; }
std::vector<NextAction> getDefaultActions() override; NextAction** getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; } uint32 GetType() const override { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; }
}; };

View File

@@ -11,40 +11,39 @@
#include "SpellInfo.h" #include "SpellInfo.h"
#include "SpellMgr.h" #include "SpellMgr.h"
std::vector<NextAction> CastDeathchillAction::getPrerequisites() NextAction** CastDeathchillAction::getPrerequisites()
{ {
return NextAction::merge({ NextAction("frost presence") }, return NextAction::merge(NextAction::array(0, new NextAction("frost presence"), nullptr),
CastSpellAction::getPrerequisites()); CastSpellAction::getPrerequisites());
} }
std::vector<NextAction> CastUnholyMeleeSpellAction::getPrerequisites() NextAction** CastUnholyMeleeSpellAction::getPrerequisites()
{ {
return NextAction::merge({ NextAction("unholy presence") }, return NextAction::merge(NextAction::array(0, new NextAction("unholy presence"), nullptr),
CastMeleeSpellAction::getPrerequisites()); CastMeleeSpellAction::getPrerequisites());
} }
std::vector<NextAction> CastFrostMeleeSpellAction::getPrerequisites() NextAction** CastFrostMeleeSpellAction::getPrerequisites()
{ {
return NextAction::merge({ NextAction("frost presence") }, return NextAction::merge(NextAction::array(0, new NextAction("frost presence"), nullptr),
CastMeleeSpellAction::getPrerequisites()); CastMeleeSpellAction::getPrerequisites());
} }
std::vector<NextAction> CastBloodMeleeSpellAction::getPrerequisites() NextAction** CastBloodMeleeSpellAction::getPrerequisites()
{ {
return NextAction::merge({ NextAction("blood presence") }, return NextAction::merge(NextAction::array(0, new NextAction("blood presence"), nullptr),
CastMeleeSpellAction::getPrerequisites()); CastMeleeSpellAction::getPrerequisites());
} }
bool CastRaiseDeadAction::Execute(Event event) bool CastRaiseDeadAction::Execute(Event event)
{ {
const bool result = CastBuffSpellAction::Execute(event); bool result = CastBuffSpellAction::Execute(event);
if (!result) if (!result)
{
return false; return false;
}
const uint32_t spellId = AI_VALUE2(uint32_t, "spell id", spell); uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
// SpellInfo const *spellInfo = sSpellMgr->GetSpellInfo(spellId);
bot->AddSpellCooldown(spellId, 0, 3 * 60 * 1000); bot->AddSpellCooldown(spellId, 0, 3 * 60 * 1000);
return true; return true;
} }

View File

@@ -34,7 +34,7 @@ class CastDeathchillAction : public CastBuffSpellAction
public: public:
CastDeathchillAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "deathchill") {} CastDeathchillAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "deathchill") {}
std::vector<NextAction> getPrerequisites() override; NextAction** getPrerequisites() override;
}; };
class CastDarkCommandAction : public CastSpellAction class CastDarkCommandAction : public CastSpellAction
@@ -52,7 +52,7 @@ class CastUnholyMeleeSpellAction : public CastMeleeSpellAction
public: public:
CastUnholyMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {} CastUnholyMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
std::vector<NextAction> getPrerequisites() override; NextAction** getPrerequisites() override;
}; };
// Frost presence // Frost presence
@@ -61,7 +61,7 @@ class CastFrostMeleeSpellAction : public CastMeleeSpellAction
public: public:
CastFrostMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {} CastFrostMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
std::vector<NextAction> getPrerequisites() override; NextAction** getPrerequisites() override;
}; };
// Blood presence // Blood presence
@@ -70,7 +70,7 @@ class CastBloodMeleeSpellAction : public CastMeleeSpellAction
public: public:
CastBloodMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {} CastBloodMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
std::vector<NextAction> getPrerequisites() override; NextAction** getPrerequisites() override;
}; };
class CastRuneStrikeAction : public CastMeleeSpellAction class CastRuneStrikeAction : public CastMeleeSpellAction
@@ -79,6 +79,10 @@ public:
CastRuneStrikeAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "rune strike") {} CastRuneStrikeAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "rune strike") {}
}; };
// debuff
// BEGIN_DEBUFF_ACTION(CastPestilenceAction, "pestilence")
// END_SPELL_ACTION()
class CastPestilenceAction : public CastSpellAction class CastPestilenceAction : public CastSpellAction
{ {
public: public:
@@ -86,12 +90,20 @@ public:
ActionThreatType getThreatType() override { return ActionThreatType::None; } ActionThreatType getThreatType() override { return ActionThreatType::None; }
}; };
// debuff
// BEGIN_DEBUFF_ACTION(CastHowlingBlastAction, "howling blast")
// END_SPELL_ACTION()
class CastHowlingBlastAction : public CastSpellAction class CastHowlingBlastAction : public CastSpellAction
{ {
public: public:
CastHowlingBlastAction(PlayerbotAI* ai) : CastSpellAction(ai, "howling blast") {} CastHowlingBlastAction(PlayerbotAI* ai) : CastSpellAction(ai, "howling blast") {}
}; };
// debuff it
// BEGIN_DEBUFF_ACTION(CastIcyTouchAction, "icy touch")
// END_SPELL_ACTION()
class CastIcyTouchAction : public CastSpellAction class CastIcyTouchAction : public CastSpellAction
{ {
public: public:
@@ -114,6 +126,8 @@ class CastPlagueStrikeAction : public CastSpellAction
public: public:
CastPlagueStrikeAction(PlayerbotAI* ai) : CastSpellAction(ai, "plague strike") {} CastPlagueStrikeAction(PlayerbotAI* ai) : CastSpellAction(ai, "plague strike") {}
}; };
// BEGIN_DEBUFF_ACTION(CastPlagueStrikeAction, "plague strike")
// END_SPELL_ACTION()
class CastPlagueStrikeOnAttackerAction : public CastDebuffSpellOnMeleeAttackerAction class CastPlagueStrikeOnAttackerAction : public CastDebuffSpellOnMeleeAttackerAction
{ {

View File

@@ -16,68 +16,66 @@ public:
creators["obliterate"] = &obliterate; creators["obliterate"] = &obliterate;
creators["howling blast"] = &howling_blast; creators["howling blast"] = &howling_blast;
creators["frost strike"] = &frost_strike; creators["frost strike"] = &frost_strike;
// creators["chains of ice"] = &chains_of_ice;
creators["rune strike"] = &rune_strike; 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["unbreakable armor"] = &unbreakable_armor;
// creators["improved icy talons"] = &improved_icy_talons;
} }
private: private:
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("icy touch",
"icy touch", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* obliterate([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* obliterate([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("obliterate",
"obliterate", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("rune strike",
"rune strike", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*A*/ { NextAction("melee") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* frost_strike([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* frost_strike([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("frost strike",
"frost strike", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* howling_blast([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* howling_blast([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("howling blast",
"howling blast", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* unbreakable_armor([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* unbreakable_armor([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("unbreakable armor",
"unbreakable armor", /*P*/ NextAction::array(0, new NextAction("blood tap"), nullptr),
/*P*/ { NextAction("blood tap") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
}; };
@@ -86,84 +84,41 @@ FrostDKStrategy::FrostDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
actionNodeFactories.Add(new FrostDKStrategyActionNodeFactory()); actionNodeFactories.Add(new FrostDKStrategyActionNodeFactory());
} }
std::vector<NextAction> FrostDKStrategy::getDefaultActions() NextAction** FrostDKStrategy::getDefaultActions()
{ {
return { return NextAction::array(
NextAction("obliterate", ACTION_DEFAULT + 0.7f), 0, new NextAction("obliterate", ACTION_DEFAULT + 0.7f),
NextAction("frost strike", ACTION_DEFAULT + 0.4f), new NextAction("frost strike", ACTION_DEFAULT + 0.4f),
NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f), new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL);
NextAction("melee", ACTION_DEFAULT)
};
} }
void FrostDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void FrostDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericDKStrategy::InitTriggers(triggers); GenericDKStrategy::InitTriggers(triggers);
triggers.push_back( triggers.push_back(new TriggerNode(
new TriggerNode( "unbreakable armor", NextAction::array(0, new NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f), nullptr)));
"unbreakable armor",
{ triggers.push_back(new TriggerNode(
NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f) "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( triggers.push_back(
new TriggerNode( new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
"freezing fog", triggers.push_back(new TriggerNode(
{ "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
NextAction("howling blast", ACTION_DEFAULT + 0.5f) // triggers.push_back(new TriggerNode("empower rune weapon", NextAction::array(0, new NextAction("empower rune
} // weapon", ACTION_NORMAL + 4), nullptr)));
)
);
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) void FrostDKAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("medium aoe", NextAction::array(0, new NextAction("howling blast", ACTION_HIGH + 4), nullptr)));
"medium aoe",
{
NextAction("howling blast", ACTION_HIGH + 4)
}
)
);
} }

View File

@@ -17,7 +17,7 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "frost"; } std::string const getName() override { return "frost"; }
std::vector<NextAction> getDefaultActions() override; NextAction** getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; } uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; }
}; };

View File

@@ -20,17 +20,17 @@ private:
static ActionNode* bone_shield([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* bone_shield([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("bone shield", return new ActionNode("bone shield",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* horn_of_winter([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* horn_of_winter([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("horn of winter", return new ActionNode("horn of winter",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
}; };
@@ -44,18 +44,19 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
NonCombatStrategy::InitTriggers(triggers); NonCombatStrategy::InitTriggers(triggers);
triggers.push_back( triggers.push_back(
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 1) })); new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 1), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) })); new TriggerNode("horn of winter", NextAction::array(0, new NextAction("horn of winter", 21.0f), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("bone shield", { NextAction("bone shield", 21.0f) })); new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) })); new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), NULL)));
triggers.push_back( triggers.push_back(
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) })); new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), NULL)));
} }
void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) 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)));
} }

View File

@@ -54,105 +54,105 @@ private:
static ActionNode* death_coil([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* death_coil([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("death coil", return new ActionNode("death coil",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* death_grip([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* death_grip([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("death grip", return new ActionNode("death grip",
/*P*/ {}, /*P*/ nullptr,
/*A*/ { NextAction("icy touch") }, /*A*/ NextAction::array(0, new NextAction("icy touch"), nullptr),
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* plague_strike([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* plague_strike([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("plague strike", return new ActionNode("plague strike",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("icy touch", return new ActionNode("icy touch",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* heart_strike([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* heart_strike([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("heart strike", return new ActionNode("heart strike",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* pestilence([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* pestilence([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("pestilence", return new ActionNode("pestilence",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* horn_of_winter([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* horn_of_winter([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("horn of winter", return new ActionNode("horn of winter",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* bone_shield([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* bone_shield([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("bone shield", return new ActionNode("bone shield",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* killing_machine([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* killing_machine([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("killing machine", return new ActionNode("killing machine",
/*P*/ {}, /*P*/ nullptr,
/*A*/ { NextAction("improved icy talons") }, /*A*/ NextAction::array(0, new NextAction("improved icy talons"), nullptr),
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("corpse explosion", return new ActionNode("corpse explosion",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* death_and_decay([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* death_and_decay([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("death and decay", return new ActionNode("death and decay",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* anti_magic_zone([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* anti_magic_zone([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("anti magic zone", return new ActionNode("anti magic zone",
/*P*/ {}, /*P*/ nullptr,
/*A*/ { NextAction("anti magic shell") }, /*A*/ NextAction::array(0, new NextAction("anti magic shell"), nullptr),
/*C*/ {}); /*C*/ nullptr);
} }
static ActionNode* icebound_fortitude([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* icebound_fortitude([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode("icebound fortitude", return new ActionNode("icebound fortitude",
/*P*/ {}, /*P*/ nullptr,
/*A*/ {}, /*A*/ nullptr,
/*C*/ {}); /*C*/ nullptr);
} }
}; };
@@ -165,29 +165,36 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
MeleeCombatStrategy::InitTriggers(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( triggers.push_back(
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 5) })); new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 5), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) })); new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) })); new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("mind freeze", { NextAction("mind freeze", ACTION_HIGH + 1) })); new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("mind freeze on enemy healer", new TriggerNode("mind freeze on enemy healer",
{ NextAction("mind freeze on enemy healer", ACTION_HIGH + 1) })); NextAction::array(0, new NextAction("mind freeze on enemy healer", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"horn of winter", { NextAction("horn of winter", ACTION_NORMAL + 1) })); "horn of winter", NextAction::array(0, new NextAction("horn of winter", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(new TriggerNode("critical health", triggers.push_back(new TriggerNode("critical health",
{ NextAction("death pact", ACTION_HIGH + 5) })); NextAction::array(0, new NextAction("death pact", ACTION_HIGH + 5), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("low health", { NextAction("icebound fortitude", ACTION_HIGH + 5), new TriggerNode("low health", NextAction::array(0, new NextAction("icebound fortitude", ACTION_HIGH + 5),
NextAction("rune tap", ACTION_HIGH + 4) })); new NextAction("rune tap", ACTION_HIGH + 4), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("medium aoe", { NextAction("death and decay", ACTION_HIGH + 9), new TriggerNode("medium aoe", NextAction::array(0, new NextAction("death and decay", ACTION_HIGH + 9),
NextAction("pestilence", ACTION_NORMAL + 4), new NextAction("pestilence", ACTION_NORMAL + 4),
NextAction("blood boil", ACTION_NORMAL + 3) })); 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)));
triggers.push_back( triggers.push_back(
new TriggerNode("pestilence glyph", { NextAction("pestilence", ACTION_HIGH + 9) })); new TriggerNode("pestilence glyph", NextAction::array(0, new NextAction("pestilence", ACTION_HIGH + 9), NULL)));
} }

View File

@@ -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. * 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,8 +11,21 @@ class UnholyDKStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
public: public:
UnholyDKStrategyActionNodeFactory() 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["death strike"] = &death_strike;
// creators["unholy blight"] = &unholy_blight;
creators["scourge strike"] = &scourge_strike; 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["ghoul frenzy"] = &ghoul_frenzy;
creators["corpse explosion"] = &corpse_explosion; creators["corpse explosion"] = &corpse_explosion;
creators["icy touch"] = &icy_touch; creators["icy touch"] = &icy_touch;
@@ -21,49 +34,39 @@ public:
private: private:
static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("death strike",
"death strike", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* ghoul_frenzy([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* ghoul_frenzy([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("ghoul frenzy",
"ghoul frenzy", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("corpse explosion",
"corpse explosion", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* scourge_strike([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* scourge_strike([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("scourge strike",
"scourge strike", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("icy touch",
"icy touch", /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*P*/ { NextAction("blood presence") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
}; };
@@ -72,121 +75,69 @@ UnholyDKStrategy::UnholyDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI
actionNodeFactories.Add(new UnholyDKStrategyActionNodeFactory()); actionNodeFactories.Add(new UnholyDKStrategyActionNodeFactory());
} }
std::vector<NextAction> UnholyDKStrategy::getDefaultActions() NextAction** UnholyDKStrategy::getDefaultActions()
{ {
return { return NextAction::array(
NextAction("death and decay", ACTION_HIGH + 5), 0, new NextAction("death and decay", ACTION_HIGH + 5),
NextAction("summon gargoyle", ACTION_DEFAULT + 0.4f), new NextAction("summon gargoyle", ACTION_DEFAULT + 0.4f),
NextAction("horn of winter", ACTION_DEFAULT + 0.2f), // new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
NextAction("death coil", ACTION_DEFAULT + 0.1f), new NextAction("horn of winter", ACTION_DEFAULT + 0.2f),
NextAction("melee", ACTION_DEFAULT) new NextAction("death coil", ACTION_DEFAULT + 0.1f),
}; new NextAction("melee", ACTION_DEFAULT), nullptr);
} }
void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericDKStrategy::InitTriggers(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( triggers.push_back(
new TriggerNode( new TriggerNode("dd cd and plague strike 3s", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 1), nullptr)));
"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( triggers.push_back(
new TriggerNode( new TriggerNode("dd cd and icy touch 3s", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
"dd cd and no desolation",
{
NextAction("blood strike", ACTION_DEFAULT + 0.75f)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("no rune", NextAction::array(0, new NextAction("empower rune weapon", ACTION_HIGH + 1), nullptr)));
"high frost rune",
{ // triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction(, ACTION_NORMAL + 2), nullptr)));
NextAction("icy touch", ACTION_NORMAL + 3) 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( triggers.push_back(
new TriggerNode( new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", ACTION_HIGH + 3), nullptr)));
"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) void UnholyDKAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(new TriggerNode(
new TriggerNode( "loot available", NextAction::array(0, new NextAction("corpse explosion", ACTION_NORMAL + 1), nullptr)));
"loot available", triggers.push_back(new TriggerNode(
{ "medium aoe", NextAction::array(0, new NextAction("death and decay", ACTION_NORMAL + 3),
NextAction("corpse explosion", ACTION_NORMAL + 1) new NextAction("corpse explosion", ACTION_NORMAL + 3), nullptr)));
}
)
);
triggers.push_back(
new TriggerNode(
"medium aoe",
{
NextAction("death and decay", ACTION_NORMAL + 3),
NextAction("corpse explosion", ACTION_NORMAL + 3)
}
)
);
} }

View File

@@ -17,7 +17,7 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "unholy"; } std::string const getName() override { return "unholy"; }
std::vector<NextAction> getDefaultActions() override; NextAction** getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; } uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; }
}; };

View File

@@ -30,132 +30,107 @@ public:
private: private:
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("melee",
"melee", /*P*/ NextAction::array(0, new NextAction("feral charge - bear"), nullptr),
/*P*/ { NextAction("feral charge - bear") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* feral_charge_bear([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* feral_charge_bear([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("feral charge - bear",
"feral charge - bear", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("reach melee"), nullptr),
/*A*/ { NextAction("reach melee") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* swipe_bear([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* swipe_bear([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("swipe (bear)",
"swipe (bear)", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("faerie fire (feral)",
"faerie fire (feral)", /*P*/ NextAction::array(0, new NextAction("feral charge - bear"), nullptr),
/*P*/ { NextAction("feral charge - bear") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* bear_form([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* bear_form([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("bear form",
"bear form", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* dire_bear_form([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* dire_bear_form([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("dire bear form",
"dire bear form", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*P*/ { NextAction("caster form") }, /*A*/ NextAction::array(0, new NextAction("bear form"), nullptr),
/*A*/ { NextAction("bear form") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* mangle_bear([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* mangle_bear([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("mangle (bear)",
"mangle (bear)", /*P*/ nullptr,
/*P*/ {}, // /*A*/ NextAction::array(0, new NextAction("lacerate"), nullptr),
/*A*/ {}, nullptr,
/*C*/ {} /*C*/ nullptr);
);
} }
static ActionNode* maul([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* maul([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("maul",
"maul", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*A*/ { NextAction("melee") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* bash([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* bash([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("bash",
"bash", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*A*/ { NextAction("melee") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* swipe([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* swipe([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("swipe",
"swipe", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*A*/ { NextAction("melee") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* lacerate([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* lacerate([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("lacerate",
"lacerate", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("maul"), nullptr),
/*A*/ { NextAction("maul") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* growl([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* growl([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("growl",
"growl", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* demoralizing_roar([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* demoralizing_roar([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("demoralizing roar",
"demoralizing roar", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
}; };
@@ -164,93 +139,38 @@ BearTankDruidStrategy::BearTankDruidStrategy(PlayerbotAI* botAI) : FeralDruidStr
actionNodeFactories.Add(new BearTankDruidStrategyActionNodeFactory()); actionNodeFactories.Add(new BearTankDruidStrategyActionNodeFactory());
} }
std::vector<NextAction> BearTankDruidStrategy::getDefaultActions() NextAction** BearTankDruidStrategy::getDefaultActions()
{ {
return { return NextAction::array(
NextAction("mangle (bear)", ACTION_DEFAULT + 0.5f), 0, new NextAction("mangle (bear)", ACTION_DEFAULT + 0.5f),
NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.4f), new NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.4f), new NextAction("lacerate", ACTION_DEFAULT + 0.3f),
NextAction("lacerate", ACTION_DEFAULT + 0.3f), new NextAction("maul", ACTION_DEFAULT + 0.2f), new NextAction("enrage", ACTION_DEFAULT + 0.1f),
NextAction("maul", ACTION_DEFAULT + 0.2f), new NextAction("melee", ACTION_DEFAULT), nullptr);
NextAction("enrage", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT)
};
} }
void BearTankDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void BearTankDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
FeralDruidStrategy::InitTriggers(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( triggers.push_back(
new TriggerNode( new TriggerNode("bear form", NextAction::array(0, new NextAction("dire bear form", ACTION_HIGH + 8), nullptr)));
"enemy out of melee", triggers.push_back(new TriggerNode(
{ "low health", NextAction::array(0, new NextAction("frenzied regeneration", ACTION_HIGH + 7), nullptr)));
NextAction("feral charge - bear", ACTION_NORMAL + 8) triggers.push_back(new TriggerNode(
} "faerie fire (feral)", NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_HIGH + 7), nullptr)));
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("lose aggro", NextAction::array(0, new NextAction("growl", ACTION_HIGH + 8), nullptr)));
"bear form",
{
NextAction("dire bear form", ACTION_HIGH + 8)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("medium aoe", NextAction::array(0, new NextAction("demoralizing roar", ACTION_HIGH + 6),
"low health", new NextAction("swipe (bear)", ACTION_HIGH + 6), nullptr)));
{
NextAction("frenzied regeneration", ACTION_HIGH + 7)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("light aoe", NextAction::array(0, new NextAction("swipe (bear)", ACTION_HIGH + 5), nullptr)));
"faerie fire (feral)",
{
NextAction("faerie fire (feral)", ACTION_HIGH + 7)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("bash", NextAction::array(0, new NextAction("bash", ACTION_INTERRUPT + 2), nullptr)));
"lose aggro",
{
NextAction("growl", ACTION_HIGH + 8)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("bash on enemy healer",
"medium aoe", NextAction::array(0, new NextAction("bash on enemy healer", ACTION_INTERRUPT + 1), nullptr)));
{
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)
}
)
);
} }

View File

@@ -17,7 +17,7 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "bear"; } std::string const getName() override { return "bear"; }
std::vector<NextAction> getDefaultActions() override; NextAction** getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; } uint32 GetType() const override { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; }
}; };

View File

@@ -28,102 +28,82 @@ public:
private: private:
static ActionNode* faerie_fire([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* faerie_fire([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("faerie fire",
"faerie fire", /*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*P*/ { NextAction("moonkin form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* hibernate([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* hibernate([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("hibernate",
"hibernate", /*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*P*/ { NextAction("moonkin form") }, /*A*/ NextAction::array(0, new NextAction("entangling roots"), nullptr),
/*A*/ { NextAction("entangling roots") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* entangling_roots([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* entangling_roots([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("entangling roots",
"entangling roots", /*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*P*/ { NextAction("moonkin form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* entangling_roots_on_cc([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* entangling_roots_on_cc([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("entangling roots on cc",
"entangling roots on cc", /*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*P*/ { NextAction("moonkin form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* wrath([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* wrath([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("wrath",
"wrath", /*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*P*/ { NextAction("moonkin form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* starfall([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* starfall([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("starfall",
"starfall", /*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*P*/ { NextAction("moonkin form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* insect_swarm([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* insect_swarm([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("insect swarm",
"insect swarm", /*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*P*/ { NextAction("moonkin form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* moonfire([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* moonfire([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("moonfire",
"moonfire", /*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*P*/ { NextAction("moonkin form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* starfire([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* starfire([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("starfire",
"starfire", /*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*P*/ { NextAction("moonkin form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* moonkin_form([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* moonkin_form([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("moonkin form",
"moonkin form", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*P*/ { NextAction("caster form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
}; };
@@ -133,122 +113,55 @@ CasterDruidStrategy::CasterDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrat
actionNodeFactories.Add(new ShapeshiftDruidStrategyActionNodeFactory()); actionNodeFactories.Add(new ShapeshiftDruidStrategyActionNodeFactory());
} }
std::vector<NextAction> CasterDruidStrategy::getDefaultActions() NextAction** CasterDruidStrategy::getDefaultActions()
{ {
return { return NextAction::array(0,
NextAction("starfall", ACTION_HIGH + 1.0f), new NextAction("starfall", ACTION_HIGH + 1.0f),
NextAction("force of nature", ACTION_DEFAULT + 1.0f), new NextAction("force of nature", ACTION_DEFAULT + 1.0f),
NextAction("wrath", ACTION_DEFAULT + 0.1f), new NextAction("wrath", ACTION_DEFAULT + 0.1f),
}; // new NextAction("starfire", ACTION_NORMAL),
nullptr);
} }
void CasterDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void CasterDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericDruidStrategy::InitTriggers(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( triggers.push_back(
new TriggerNode( new TriggerNode("moonfire", NextAction::array(0, new NextAction("moonfire", ACTION_NORMAL + 4), nullptr)));
"eclipse (lunar) cooldown",
{
NextAction("starfire", ACTION_DEFAULT + 0.2f)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("eclipse (solar)", NextAction::array(0, new NextAction("wrath", ACTION_NORMAL + 6), nullptr)));
"eclipse (solar) cooldown", triggers.push_back(new TriggerNode("eclipse (lunar)",
{ NextAction::array(0, new NextAction("starfire", ACTION_NORMAL + 6), nullptr)));
NextAction("wrath", ACTION_DEFAULT + 0.2f)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("medium mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 9), nullptr)));
"insect swarm",
{ triggers.push_back(new TriggerNode("enemy too close for spell",
NextAction("insect swarm", ACTION_NORMAL + 5) NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr)));
}
)
);
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) void CasterDruidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("hurricane channel check", NextAction::array(0, new NextAction("cancel channel", ACTION_HIGH + 2), nullptr)));
"hurricane channel check",
{
NextAction("cancel channel", ACTION_HIGH + 2)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("medium aoe", NextAction::array(0, new NextAction("hurricane", ACTION_HIGH + 1), nullptr)));
"medium aoe", triggers.push_back(new TriggerNode(
{ "light aoe", NextAction::array(0, new NextAction("insect swarm on attacker", ACTION_NORMAL + 3),
NextAction("hurricane", ACTION_HIGH + 1) new NextAction("moonfire on attacker", ACTION_NORMAL + 3), NULL)));
}
)
);
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) void CasterDruidDebuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("faerie fire", NextAction::array(0, new NextAction("faerie fire", ACTION_HIGH), nullptr)));
"faerie fire",
{
NextAction("faerie fire", ACTION_HIGH)
}
)
);
} }

View File

@@ -18,7 +18,7 @@ public:
public: public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "caster"; } std::string const getName() override { return "caster"; }
std::vector<NextAction> getDefaultActions() override; NextAction** getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; } uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; }
}; };

View File

@@ -28,112 +28,90 @@ public:
private: private:
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("faerie fire (feral)",
"faerie fire (feral)", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("melee",
"melee", /*P*/ NextAction::array(0, new NextAction("feral charge - cat"), nullptr),
/*P*/ { NextAction("feral charge - cat") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("feral charge - cat",
"feral charge - cat", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("reach melee"), nullptr),
/*A*/ { NextAction("reach melee") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("cat form",
"cat form", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*P*/ { NextAction("caster form") }, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("claw",
"claw", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*A*/ { NextAction("melee") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("mangle (cat)",
"mangle (cat)", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("rake",
"rake", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("ferocious bite",
"ferocious bite", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("rip"), nullptr),
/*A*/ { NextAction("rip") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("rip",
"rip", /*P*/ nullptr,
/*P*/ {}, /*A*/ nullptr,
/*A*/ {}, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("pounce",
"pounce", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("ravage"), nullptr),
/*A*/ { NextAction("ravage") }, /*C*/ nullptr);
/*C*/ {}
);
} }
static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode( return new ActionNode("ravage",
"ravage", /*P*/ nullptr,
/*P*/ {}, /*A*/ NextAction::array(0, new NextAction("shred"), nullptr),
/*A*/ { NextAction("shred") }, /*C*/ nullptr);
/*C*/ {}
);
} }
}; };
@@ -142,11 +120,9 @@ CatDpsDruidStrategy::CatDpsDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrateg
actionNodeFactories.Add(new CatDpsDruidStrategyActionNodeFactory()); actionNodeFactories.Add(new CatDpsDruidStrategyActionNodeFactory());
} }
std::vector<NextAction> CatDpsDruidStrategy::getDefaultActions() NextAction** CatDpsDruidStrategy::getDefaultActions()
{ {
return { return NextAction::array(0, new NextAction("tiger's fury", ACTION_DEFAULT + 0.1f), nullptr);
NextAction("tiger's fury", ACTION_DEFAULT + 0.1f)
};
} }
void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -154,161 +130,50 @@ void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
FeralDruidStrategy::InitTriggers(triggers); FeralDruidStrategy::InitTriggers(triggers);
// Default priority // 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( triggers.push_back(
new TriggerNode( new TriggerNode("faerie fire (feral)",
"almost full energy available", NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f), nullptr)));
{
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 // Main spell
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("cat form", NextAction::array(0, new NextAction("cat form", ACTION_HIGH + 8), nullptr)));
"cat form", {
NextAction("cat form", ACTION_HIGH + 8)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("savage roar", NextAction::array(0, new NextAction("savage roar", ACTION_HIGH + 7), nullptr)));
"savage roar", { triggers.push_back(new TriggerNode("combo points available",
NextAction("savage roar", ACTION_HIGH + 7) 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)));
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("target with combo points almost dead",
"combo points available", NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 4), nullptr)));
{ triggers.push_back(new TriggerNode("mangle (cat)",
NextAction("rip", ACTION_HIGH + 6) 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)));
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("medium threat", NextAction::array(0, new NextAction("cower", ACTION_HIGH + 1), nullptr)));
"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 // AOE
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("medium aoe", NextAction::array(0, new NextAction("swipe (cat)", ACTION_HIGH + 3), nullptr)));
"medium aoe", triggers.push_back(new TriggerNode(
{ "light aoe", NextAction::array(0, new NextAction("rake on attacker", ACTION_HIGH + 2), nullptr)));
NextAction("swipe (cat)", ACTION_HIGH + 3)
}
)
);
triggers.push_back(
new TriggerNode(
"light aoe",
{
NextAction("rake on attacker", ACTION_HIGH + 2)
}
)
);
// Reach target // Reach target
triggers.push_back(new TriggerNode(
"enemy out of melee", NextAction::array(0, new NextAction("feral charge - cat", ACTION_HIGH + 9), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode("enemy out of melee", NextAction::array(0, new NextAction("dash", ACTION_HIGH + 8), nullptr)));
"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) {} void CatAoeDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {}

View File

@@ -18,7 +18,7 @@ public:
public: public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "cat"; } std::string const getName() override { return "cat"; }
std::vector<NextAction> getDefaultActions() override; NextAction** getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; } uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; }
}; };

View File

@@ -11,15 +11,15 @@
#include "AoeValues.h" #include "AoeValues.h"
#include "TargetValue.h" #include "TargetValue.h"
std::vector<NextAction> CastAbolishPoisonAction::getAlternatives() NextAction** CastAbolishPoisonAction::getAlternatives()
{ {
return NextAction::merge({ NextAction("cure poison") }, return NextAction::merge(NextAction::array(0, new NextAction("cure poison"), nullptr),
CastSpellAction::getPrerequisites()); CastSpellAction::getPrerequisites());
} }
std::vector<NextAction> CastAbolishPoisonOnPartyAction::getAlternatives() NextAction** CastAbolishPoisonOnPartyAction::getAlternatives()
{ {
return NextAction::merge({ NextAction("cure poison on party") }, return NextAction::merge(NextAction::array(0, new NextAction("cure poison on party"), nullptr),
CastSpellAction::getPrerequisites()); CastSpellAction::getPrerequisites());
} }
@@ -60,15 +60,15 @@ bool CastStarfallAction::isUseful()
return true; return true;
} }
std::vector<NextAction> CastReviveAction::getPrerequisites() NextAction** CastReviveAction::getPrerequisites()
{ {
return NextAction::merge({ NextAction("caster form") }, return NextAction::merge(NextAction::array(0, new NextAction("caster form"), nullptr),
ResurrectPartyMemberAction::getPrerequisites()); ResurrectPartyMemberAction::getPrerequisites());
} }
std::vector<NextAction> CastRebirthAction::getPrerequisites() NextAction** CastRebirthAction::getPrerequisites()
{ {
return NextAction::merge({ NextAction("caster form") }, return NextAction::merge(NextAction::array(0, new NextAction("caster form"), nullptr),
ResurrectPartyMemberAction::getPrerequisites()); ResurrectPartyMemberAction::getPrerequisites());
} }

View File

@@ -74,7 +74,7 @@ class CastReviveAction : public ResurrectPartyMemberAction
public: public:
CastReviveAction(PlayerbotAI* botAI) : ResurrectPartyMemberAction(botAI, "revive") {} CastReviveAction(PlayerbotAI* botAI) : ResurrectPartyMemberAction(botAI, "revive") {}
std::vector<NextAction> getPrerequisites() override; NextAction** getPrerequisites() override;
}; };
class CastRebirthAction : public ResurrectPartyMemberAction class CastRebirthAction : public ResurrectPartyMemberAction
@@ -82,7 +82,7 @@ class CastRebirthAction : public ResurrectPartyMemberAction
public: public:
CastRebirthAction(PlayerbotAI* botAI) : ResurrectPartyMemberAction(botAI, "rebirth") {} CastRebirthAction(PlayerbotAI* botAI) : ResurrectPartyMemberAction(botAI, "rebirth") {}
std::vector<NextAction> getPrerequisites() override; NextAction** getPrerequisites() override;
bool isUseful() override; bool isUseful() override;
}; };
@@ -223,7 +223,7 @@ class CastAbolishPoisonAction : public CastCureSpellAction
{ {
public: public:
CastAbolishPoisonAction(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "abolish poison") {} CastAbolishPoisonAction(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "abolish poison") {}
std::vector<NextAction> getAlternatives() override; NextAction** getAlternatives() override;
}; };
class CastAbolishPoisonOnPartyAction : public CurePartyMemberAction class CastAbolishPoisonOnPartyAction : public CurePartyMemberAction
@@ -233,7 +233,7 @@ public:
{ {
} }
std::vector<NextAction> getAlternatives() override; NextAction** getAlternatives() override;
}; };
class CastBarkskinAction : public CastBuffSpellAction class CastBarkskinAction : public CastBuffSpellAction

View File

@@ -17,9 +17,9 @@ bool CastBearFormAction::isUseful()
return CastBuffSpellAction::isUseful() && !botAI->HasAura("dire bear form", GetTarget()); return CastBuffSpellAction::isUseful() && !botAI->HasAura("dire bear form", GetTarget());
} }
std::vector<NextAction> CastDireBearFormAction::getAlternatives() NextAction** CastDireBearFormAction::getAlternatives()
{ {
return NextAction::merge({ NextAction("bear form") }, return NextAction::merge(NextAction::array(0, new NextAction("bear form"), nullptr),
CastSpellAction::getAlternatives()); CastSpellAction::getAlternatives());
} }

Some files were not shown because too many files have changed in this diff Show More