mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 09:07:19 +00:00
Compare commits
33 Commits
05057ae9b5
...
hermensbas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c9451b73f | ||
|
|
88016789ba | ||
|
|
6be860c967 | ||
|
|
9971622093 | ||
|
|
895df9b197 | ||
|
|
467b63b840 | ||
|
|
66f5f597bb | ||
|
|
cafbd4681e | ||
|
|
f5c84ee7ff | ||
|
|
b6f882886d | ||
|
|
c1222da8b0 | ||
|
|
00cb177c86 | ||
|
|
5f697e806e | ||
|
|
934e73ae20 | ||
|
|
f4b4d8967f | ||
|
|
910b8a9c53 | ||
|
|
bb569b4d39 | ||
|
|
dde16674c3 | ||
|
|
e5b2791053 | ||
|
|
353c29dfc4 | ||
|
|
52c3e96641 | ||
|
|
38e2d8584b | ||
|
|
d5dbc4ddd7 | ||
|
|
2424f73bc4 | ||
|
|
cf743a186a | ||
|
|
10213d8381 | ||
|
|
d97870facd | ||
|
|
0c1700c117 | ||
|
|
0b1b0eaecc | ||
|
|
8e03371147 | ||
|
|
27311b734d | ||
|
|
bb5ed37cd3 | ||
|
|
e88c1b779b |
5
.github/workflows/codestyle_cpp.yml
vendored
5
.github/workflows/codestyle_cpp.yml
vendored
@@ -5,11 +5,16 @@ on:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
paths:
|
||||
- src/**
|
||||
- "!README.md"
|
||||
- "!docs/**"
|
||||
|
||||
concurrency:
|
||||
group: "codestyle-cppcheck-${{ github.event.pull_request.number }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -34,7 +34,7 @@ We also have a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can
|
||||
|
||||
Supported platforms are Ubuntu, Windows, and macOS. Other Linux distributions may work, but may not receive support.
|
||||
|
||||
**All `mod-playerbots` installations require a custom branch of AzerothCore: [mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot).** This branch allows the playerbots module to build and function. Updates from the upstream are implemneted regularly to this branch. Instructions for installing this required branch and this module are provided below.
|
||||
**All `mod-playerbots` installations require a custom branch of AzerothCore: [mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot).** This branch allows the `mod-playerbots` module to build and function. Updates from the upstream are implemented regularly to this branch. Instructions for installing this required branch and this module are provided below.
|
||||
|
||||
### Cloning the Repositories
|
||||
|
||||
@@ -50,7 +50,7 @@ For more information, refer to the [AzerothCore Installation Guide](https://www.
|
||||
|
||||
### Docker Installation
|
||||
|
||||
Docker installations are considered experimental (unofficial with limited support), and previous Docker experience is recommended. To install the `mod-playerbots` on Docker, first clone the required branch of AzerothCore and this module:
|
||||
Docker installations are considered experimental (unofficial with limited support), and previous Docker experience is recommended. To install `mod-playerbots` on Docker, first clone the required branch of AzerothCore and this module:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
||||
@@ -103,7 +103,7 @@ Please click on the "⭐" button to stay up to date and help us gain more visibi
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
`mod-playerbots` is is based off [ZhengPeiRu21/mod-playerbots](https://github.com/ZhengPeiRu21/mod-playerbots) and [celguar/mangosbot-bots](https://github.com/celguar/mangosbot-bots). We extend our gratitude to [@ZhengPeiRu21](https://github.com/ZhengPeiRu21) and [@celguar](https://github.com/celguar) for the continued efforts in maintaining the module.
|
||||
`mod-playerbots` is based on [ZhengPeiRu21/mod-playerbots](https://github.com/ZhengPeiRu21/mod-playerbots) and [celguar/mangosbot-bots](https://github.com/celguar/mangosbot-bots). We extend our gratitude to [@ZhengPeiRu21](https://github.com/ZhengPeiRu21) and [@celguar](https://github.com/celguar) for their continued efforts in maintaining the module.
|
||||
|
||||
Also, a thank you to the many contributors who've helped build this project:
|
||||
|
||||
|
||||
@@ -630,7 +630,7 @@ AiPlayerbot.RandomBotHordeRatio = 50
|
||||
AiPlayerbot.DisableDeathKnightLogin = 0
|
||||
|
||||
# Enable simulated expansion limitation for talents and glyphs
|
||||
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
|
||||
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
|
||||
# and 7 rows plus the middle talent of the 8th row for bots from level 61 until level 71
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.LimitTalentsExpansion = 0
|
||||
@@ -1185,7 +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"
|
||||
|
||||
# PvP Restricted Areas (bots don't pvp)
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"
|
||||
|
||||
# Improve reaction speeds in battlegrounds and arenas (may cause lag)
|
||||
AiPlayerbot.FastReactInBG = 1
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||
'pet_usage_error',
|
||||
'pet_no_pet_error',
|
||||
'pet_stance_report',
|
||||
'pet_no_target_error',
|
||||
'pet_target_dead_error',
|
||||
'pet_invalid_target_error',
|
||||
'pet_pvp_prohibited_error',
|
||||
'pet_attack_success',
|
||||
'pet_attack_failed',
|
||||
'pet_follow_success',
|
||||
'pet_stay_success',
|
||||
'pet_unknown_command_error',
|
||||
'pet_stance_set_success',
|
||||
'pet_type_pet',
|
||||
'pet_type_guardian',
|
||||
'pet_stance_aggressive',
|
||||
'pet_stance_defensive',
|
||||
'pet_stance_passive',
|
||||
'pet_stance_unknown'
|
||||
);
|
||||
|
||||
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||
'pet_usage_error',
|
||||
'pet_no_pet_error',
|
||||
'pet_stance_report',
|
||||
'pet_no_target_error',
|
||||
'pet_target_dead_error',
|
||||
'pet_invalid_target_error',
|
||||
'pet_pvp_prohibited_error',
|
||||
'pet_attack_success',
|
||||
'pet_attack_failed',
|
||||
'pet_follow_success',
|
||||
'pet_stay_success',
|
||||
'pet_unknown_command_error',
|
||||
'pet_stance_set_success',
|
||||
'pet_type_pet',
|
||||
'pet_type_guardian',
|
||||
'pet_stance_aggressive',
|
||||
'pet_stance_defensive',
|
||||
'pet_stance_passive',
|
||||
'pet_stance_unknown'
|
||||
);
|
||||
|
||||
INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES
|
||||
(1717, 'pet_usage_error', "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
|
||||
"사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
|
||||
|
||||
(1718, 'pet_no_pet_error', "You have no pet or guardian pet.", 0, 0,
|
||||
"펫이나 수호자 펫이 없습니다.",
|
||||
"Vous n'avez pas de familier ou gardien.",
|
||||
"Du hast kein Tier oder Wächter.",
|
||||
"你没有宠物或守护者宠物。",
|
||||
"你沒有寵物或守護者寵物。",
|
||||
"No tienes mascota o mascota guardián.",
|
||||
"No tienes mascota o mascota guardián.",
|
||||
"У вас нет питомца или защитника."),
|
||||
|
||||
(1719, 'pet_stance_report', "Current stance of %type \"%name\": %stance.", 0, 0,
|
||||
"%type \"%name\"의 현재 태세: %stance.",
|
||||
"Position actuelle du %type \"%name\": %stance.",
|
||||
"Aktuelle Haltung des %type \"%name\": %stance.",
|
||||
"%type \"%name\" 的当前姿态: %stance。",
|
||||
"%type \"%name\" 的當前姿態: %stance。",
|
||||
"Postura actual del %type \"%name\": %stance.",
|
||||
"Postura actual del %type \"%name\": %stance.",
|
||||
"Текущая позиция %type \"%name\": %stance."),
|
||||
|
||||
(1720, 'pet_no_target_error', "No valid target selected by master.", 0, 0,
|
||||
"주인이 유효한 대상을 선택하지 않았습니다.",
|
||||
"Aucune cible valide sélectionnée par le maître.",
|
||||
"Kein gültiges Ziel vom Meister ausgewählt.",
|
||||
"主人未选择有效目标。",
|
||||
"主人未選擇有效目標。",
|
||||
"No hay objetivo válido seleccionado por el maestro.",
|
||||
"No hay objetivo válido seleccionado por el maestro.",
|
||||
"Хозяин не выбрал действительную цель."),
|
||||
|
||||
(1721, 'pet_target_dead_error', "Target is not alive.", 0, 0,
|
||||
"대상이 살아있지 않습니다.",
|
||||
"La cible n'est pas vivante.",
|
||||
"Das Ziel ist nicht am Leben.",
|
||||
"目标未存活。",
|
||||
"目標未存活。",
|
||||
"El objetivo no está vivo.",
|
||||
"El objetivo no está vivo.",
|
||||
"Цель не жива."),
|
||||
|
||||
(1722, 'pet_invalid_target_error', "Target is not a valid attack target for the bot.", 0, 0,
|
||||
"대상이 봇에게 유효한 공격 대상이 아닙니다.",
|
||||
"La cible n'est pas une cible d'attaque valide pour le bot.",
|
||||
"Das Ziel ist kein gültiges Angriffsziel für den Bot.",
|
||||
"目标不是机器人的有效攻击目标。",
|
||||
"目標不是機器人的有效攻擊目標。",
|
||||
"El objetivo no es un objetivo de ataque válido para el bot.",
|
||||
"El objetivo no es un objetivo de ataque válido para el bot.",
|
||||
"Цель не является допустимой целью атаки для бота."),
|
||||
|
||||
(1723, 'pet_pvp_prohibited_error', "I cannot command my pet to attack players in PvP prohibited areas.", 0, 0,
|
||||
"PvP 금지 지역에서는 펫에게 플레이어 공격 명령을 내릴 수 없습니다.",
|
||||
"Je ne peux pas commander à mon familier d'attaquer des joueurs dans les zones où le PvP est interdit.",
|
||||
"Ich kann meinem Tier nicht befehlen, Spieler in PvP-verbotenen Gebieten anzugreifen.",
|
||||
"我不能命令我的宠物在禁止PvP的区域攻击玩家。",
|
||||
"我不能命令我的寵物在禁止PvP的區域攻擊玩家。",
|
||||
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
|
||||
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
|
||||
"Я не могу приказать своему питомцу атаковать игроков в зонах, где PvP запрещено."),
|
||||
|
||||
(1724, 'pet_attack_success', "Pet commanded to attack your target.", 0, 0,
|
||||
"펫이 당신의 대상을 공격하도록 명령했습니다.",
|
||||
"Le familier a reçu l'ordre d'attaquer votre cible.",
|
||||
"Tier wurde befohlen, dein Ziel anzugreifen.",
|
||||
"宠物已命令攻击你的目标。",
|
||||
"寵物已命令攻擊你的目標。",
|
||||
"Mascota ordenada a atacar tu objetivo.",
|
||||
"Mascota ordenada a atacar tu objetivo.",
|
||||
"Питомцу приказано атаковать вашу цель."),
|
||||
|
||||
(1725, 'pet_attack_failed', "Pet did not attack. (Already attacking or unable to attack target)", 0, 0,
|
||||
"펫이 공격하지 않았습니다. (이미 공격 중이거나 대상 공격 불가)",
|
||||
"Le familier n'a pas attaqué. (Attaque déjà en cours ou impossible d'attaquer la cible)",
|
||||
"Tier hat nicht angegriffen. (Greift bereits an oder kann Ziel nicht angreifen)",
|
||||
"宠物未攻击。(已在攻击或无法攻击目标)",
|
||||
"寵物未攻擊。(已在攻擊或無法攻擊目標)",
|
||||
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
|
||||
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
|
||||
"Питомец не атаковал. (Уже атакует или не может атаковать цель)"),
|
||||
|
||||
(1726, 'pet_follow_success', "Pet commanded to follow.", 0, 0,
|
||||
"펫이 따라오도록 명령했습니다.",
|
||||
"Le familier a reçu l'ordre de suivre.",
|
||||
"Tier wurde befohlen zu folgen.",
|
||||
"宠物已命令跟随。",
|
||||
"寵物已命令跟隨。",
|
||||
"Mascota ordenada a seguir.",
|
||||
"Mascota ordenada a seguir.",
|
||||
"Питомцу приказано следовать."),
|
||||
|
||||
(1727, 'pet_stay_success', "Pet commanded to stay.", 0, 0,
|
||||
"펫이 머물도록 명령했습니다.",
|
||||
"Le familier a reçu l'ordre de rester.",
|
||||
"Tier wurde befohlen zu bleiben.",
|
||||
"宠物已命令停留。",
|
||||
"寵物已命令停留。",
|
||||
"Mascota ordenada a quedarse.",
|
||||
"Mascota ordenada a quedarse.",
|
||||
"Питомцу приказано остаться."),
|
||||
|
||||
(1728, 'pet_unknown_command_error', "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
|
||||
"알 수 없는 펫 명령: %param. 사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Commande de familier inconnue: %param. Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Unbekannter Tierbefehl: %param. Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"未知宠物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"未知寵物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
"Неизвестная команда питомца: %param. Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
|
||||
|
||||
(1729, 'pet_stance_set_success', "Pet stance set to %stance.", 0, 0,
|
||||
"펫 태세가 %stance(으)로 설정되었습니다.",
|
||||
"Position du familier définie sur %stance.",
|
||||
"Tierhaltung auf %stance gesetzt.",
|
||||
"宠物姿态设置为 %stance。",
|
||||
"寵物姿態設置為 %stance。",
|
||||
"Postura de mascota establecida en %stance.",
|
||||
"Postura de mascota establecida en %stance.",
|
||||
"Позиция питомца установлена на %stance."),
|
||||
|
||||
(1730, 'pet_type_pet', "pet", 0, 0,
|
||||
"펫",
|
||||
"familier",
|
||||
"Tier",
|
||||
"宠物",
|
||||
"寵物",
|
||||
"mascota",
|
||||
"mascota",
|
||||
"питомец"),
|
||||
|
||||
(1731, 'pet_type_guardian', "guardian", 0, 0,
|
||||
"수호자",
|
||||
"gardien",
|
||||
"Wächter",
|
||||
"守护者",
|
||||
"守護者",
|
||||
"guardián",
|
||||
"guardián",
|
||||
"защитник"),
|
||||
|
||||
(1732, 'pet_stance_aggressive', "aggressive", 0, 0,
|
||||
"공격적",
|
||||
"agressif",
|
||||
"aggressiv",
|
||||
"进攻",
|
||||
"進攻",
|
||||
"agresivo",
|
||||
"agresivo",
|
||||
"агрессивная"),
|
||||
|
||||
(1733, 'pet_stance_defensive', "defensive", 0, 0,
|
||||
"방어적",
|
||||
"défensif",
|
||||
"defensiv",
|
||||
"防御",
|
||||
"防禦",
|
||||
"defensivo",
|
||||
"defensivo",
|
||||
"защитная"),
|
||||
|
||||
(1734, 'pet_stance_passive', "passive", 0, 0,
|
||||
"수동적",
|
||||
"passif",
|
||||
"passiv",
|
||||
"被动",
|
||||
"被動",
|
||||
"pasivo",
|
||||
"pasivo",
|
||||
"пассивная"),
|
||||
|
||||
(1735, 'pet_stance_unknown', "unknown", 0, 0,
|
||||
"알 수 없음",
|
||||
"inconnu",
|
||||
"unbekannt",
|
||||
"未知",
|
||||
"未知",
|
||||
"desconocido",
|
||||
"desconocido",
|
||||
"неизвестная");
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||
('pet_usage_error', 100),
|
||||
('pet_no_pet_error', 100),
|
||||
('pet_stance_report', 100),
|
||||
('pet_no_target_error', 100),
|
||||
('pet_target_dead_error', 100),
|
||||
('pet_invalid_target_error', 100),
|
||||
('pet_pvp_prohibited_error', 100),
|
||||
('pet_attack_success', 100),
|
||||
('pet_attack_failed', 100),
|
||||
('pet_follow_success', 100),
|
||||
('pet_stay_success', 100),
|
||||
('pet_unknown_command_error', 100),
|
||||
('pet_stance_set_success', 100),
|
||||
('pet_type_pet', 100),
|
||||
('pet_type_guardian', 100),
|
||||
('pet_stance_aggressive', 100),
|
||||
('pet_stance_defensive', 100),
|
||||
('pet_stance_passive', 100),
|
||||
('pet_stance_unknown', 100);
|
||||
@@ -242,8 +242,8 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
nextAICheckDelay = 0;
|
||||
|
||||
// Early return if bot is in invalid state
|
||||
if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() ||
|
||||
bot->IsDuringRemoveFromWorld())
|
||||
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
|
||||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
|
||||
return;
|
||||
|
||||
// Handle cheat options (set bot health and power if cheats are enabled)
|
||||
@@ -365,7 +365,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
}
|
||||
|
||||
// Update the bot's group status (moved to helper function)
|
||||
UpdateAIGroupAndMaster();
|
||||
UpdateAIGroupMaster();
|
||||
|
||||
// Update internal AI
|
||||
UpdateAIInternal(elapsed, minimal);
|
||||
@@ -373,7 +373,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
}
|
||||
|
||||
// Helper function for UpdateAI to check group membership and handle removal if necessary
|
||||
void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
void PlayerbotAI::UpdateAIGroupMaster()
|
||||
{
|
||||
if (!bot)
|
||||
return;
|
||||
@@ -420,7 +420,7 @@ void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
{
|
||||
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
|
||||
|
||||
if (botAI->GetMaster() == botAI->GetGroupMaster())
|
||||
if (botAI->GetMaster() == botAI->GetGroupLeader())
|
||||
botAI->TellMaster("Hello, I follow you!");
|
||||
else
|
||||
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
|
||||
@@ -431,8 +431,6 @@ void PlayerbotAI::UpdateAIGroupAndMaster()
|
||||
botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT);
|
||||
}
|
||||
}
|
||||
else if (!newMaster && !bot->InBattleground())
|
||||
LeaveOrDisbandGroup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,39 +713,59 @@ void PlayerbotAI::HandleTeleportAck()
|
||||
if (IsRealPlayer())
|
||||
return;
|
||||
|
||||
// Clearing motion generators and stopping movement which prevents
|
||||
// conflicts between teleport and any active motion (walk, run, swim, flight, etc.)
|
||||
bot->GetMotionMaster()->Clear(true);
|
||||
bot->StopMoving();
|
||||
|
||||
// Near teleport (within map/instance)
|
||||
if (bot->IsBeingTeleportedNear())
|
||||
{
|
||||
// Temporary fix for instance can not enter
|
||||
if (!bot->IsInWorld())
|
||||
{
|
||||
bot->GetMap()->AddPlayerToMap(bot);
|
||||
}
|
||||
while (bot->IsInWorld() && bot->IsBeingTeleportedNear())
|
||||
{
|
||||
Player* plMover = bot->m_mover->ToPlayer();
|
||||
if (!plMover)
|
||||
return;
|
||||
WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 20);
|
||||
p << plMover->GetPackGUID();
|
||||
p << (uint32)0; // supposed to be flags? not used currently
|
||||
p << (uint32)0; // time - not currently used
|
||||
bot->GetSession()->HandleMoveTeleportAck(p);
|
||||
};
|
||||
// Previous versions manually added the bot to the map if it was not in the world.
|
||||
// not needed: HandleMoveTeleportAckOpcode() safely attaches the player to the map
|
||||
// and clears IsBeingTeleportedNear().
|
||||
|
||||
Player* plMover = bot->m_mover->ToPlayer();
|
||||
if (!plMover)
|
||||
return;
|
||||
|
||||
// Send the near teleport ACK packet
|
||||
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
|
||||
p << plMover->GetPackGUID();
|
||||
p << uint32(0);
|
||||
p << uint32(0);
|
||||
bot->GetSession()->HandleMoveTeleportAck(p);
|
||||
|
||||
// Simulate teleport latency and prevent AI from running too early (used cmangos delays)
|
||||
SetNextCheckDelay(urand(1000, 2000));
|
||||
}
|
||||
|
||||
// Far teleport (worldport / different map)
|
||||
if (bot->IsBeingTeleportedFar())
|
||||
{
|
||||
while (bot->IsBeingTeleportedFar())
|
||||
// Handle far teleport ACK:
|
||||
// Moves the bot to the new map, clears IsBeingTeleportedFar(), updates session/map references
|
||||
bot->GetSession()->HandleMoveWorldportAck();
|
||||
|
||||
// Ensure bot now has a valid map. If this fails, there is a core/session bug?
|
||||
if (!bot->GetMap())
|
||||
{
|
||||
bot->GetSession()->HandleMoveWorldportAck();
|
||||
LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString());
|
||||
}
|
||||
// SetNextCheckDelay(urand(2000, 5000));
|
||||
|
||||
// Instance strategies after teleport
|
||||
if (sPlayerbotAIConfig->applyInstanceStrategies)
|
||||
ApplyInstanceStrategies(bot->GetMapId(), true);
|
||||
|
||||
// healer DPS strategies if restrictions are enabled
|
||||
if (sPlayerbotAIConfig->restrictHealerDPS)
|
||||
EvaluateHealerDpsStrategy();
|
||||
|
||||
// Reset AI state to to before teleport conditions
|
||||
Reset(true);
|
||||
|
||||
// Slightly longer delay to simulate far teleport latency (used cmangos delays)
|
||||
SetNextCheckDelay(urand(2000, 5000));
|
||||
}
|
||||
|
||||
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
|
||||
@@ -990,10 +1008,10 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
{
|
||||
if (packet.empty())
|
||||
return;
|
||||
|
||||
if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (packet.GetOpcode())
|
||||
{
|
||||
case SMSG_SPELL_FAILURE:
|
||||
@@ -1161,7 +1179,26 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
|
||||
return;
|
||||
}
|
||||
case SMSG_MOVE_KNOCK_BACK: // handle knockbacks
|
||||
case SMSG_FORCE_MOVE_ROOT: // CMSG_FORCE_MOVE_ROOT_ACK
|
||||
case SMSG_FORCE_MOVE_UNROOT: // CMSG_FORCE_MOVE_UNROOT_ACK
|
||||
{
|
||||
// Quick fix for CMSG_FORCE_MOVE_ROOT_ACK and CMSG_FORCE_MOVE_UNROOT_ACK:
|
||||
// this should resolve issues with MOVEMENTFLAG_ROOT being permanently set
|
||||
// when rooted during lost client control (charm + root effects)
|
||||
// @see https://github.com/azerothcore/azerothcore-wotlk/pull/23147
|
||||
bool forceRoot = (packet.GetOpcode() == SMSG_FORCE_MOVE_ROOT);
|
||||
if (forceRoot)
|
||||
{
|
||||
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_MASK_MOVING_FLY);
|
||||
bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_ROOT);
|
||||
bot->StopMoving();
|
||||
}
|
||||
else
|
||||
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ROOT);
|
||||
|
||||
return;
|
||||
}
|
||||
case SMSG_MOVE_KNOCK_BACK: // CMSG_MOVE_KNOCK_BACK_ACK
|
||||
{
|
||||
WorldPacket p(packet);
|
||||
p.rpos(0);
|
||||
@@ -1333,10 +1370,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
bool isBotAlive = bot->IsAlive();
|
||||
if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive)
|
||||
{
|
||||
bot->StopMoving();
|
||||
bot->GetMotionMaster()->Clear();
|
||||
bot->GetMotionMaster()->MoveIdle();
|
||||
|
||||
// Death Count to prevent skeleton piles
|
||||
// Player* master = GetMaster(); // warning here - whipowill
|
||||
if (!HasActivePlayerMaster() && !bot->InBattleground())
|
||||
@@ -1357,6 +1390,8 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
// Change engine if just ressed
|
||||
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
|
||||
{
|
||||
bot->SendMovementFlagUpdate();
|
||||
|
||||
ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
return;
|
||||
}
|
||||
@@ -1386,9 +1421,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
else if (bot->isAFK())
|
||||
bot->ToggleAFK();
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
PlayerbotAI* masterBotAI = nullptr;
|
||||
|
||||
if (master && master->IsInWorld())
|
||||
{
|
||||
float distance = sServerFacade->GetDistance2d(bot, master);
|
||||
@@ -1461,7 +1493,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
|
||||
strategyName = "onyxia"; // Onyxia's Lair
|
||||
break;
|
||||
case 409:
|
||||
strategyName = "mc"; // Molten Core
|
||||
strategyName = "moltencore"; // Molten Core
|
||||
break;
|
||||
case 469:
|
||||
strategyName = "bwl"; // Blackwing Lair
|
||||
@@ -1477,6 +1509,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
|
||||
break;
|
||||
case 544:
|
||||
strategyName = "magtheridon"; // Magtheridon's Lair
|
||||
break;
|
||||
case 565:
|
||||
strategyName = "gruulslair"; // Gruul's Lair
|
||||
break;
|
||||
@@ -2248,7 +2281,7 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player)
|
||||
|
||||
bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); }
|
||||
|
||||
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers)
|
||||
{
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
@@ -2265,6 +2298,9 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreDeadPlayers && !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2284,6 +2320,9 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreDeadPlayers && !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -4092,7 +4131,7 @@ Player* PlayerbotAI::FindNewMaster()
|
||||
if (!group)
|
||||
return nullptr;
|
||||
|
||||
Player* groupLeader = GetGroupMaster();
|
||||
Player* groupLeader = GetGroupLeader();
|
||||
PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(groupLeader);
|
||||
if (!leaderBotAI || leaderBotAI->IsRealPlayer())
|
||||
return groupLeader;
|
||||
@@ -4101,8 +4140,7 @@ Player* PlayerbotAI::FindNewMaster()
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (!member || member == bot || !member->IsInWorld() ||
|
||||
!member->IsInSameRaidWith(bot))
|
||||
if (!member || member == bot || !member->IsInWorld() || !member->IsInSameRaidWith(bot))
|
||||
continue;
|
||||
|
||||
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
|
||||
@@ -4143,7 +4181,7 @@ bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(m
|
||||
|
||||
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
|
||||
|
||||
Player* PlayerbotAI::GetGroupMaster()
|
||||
Player* PlayerbotAI::GetGroupLeader()
|
||||
{
|
||||
if (!bot->InBattleground())
|
||||
if (Group* group = bot->GetGroup())
|
||||
@@ -4337,6 +4375,11 @@ inline bool ZoneHasRealPlayers(Player* bot)
|
||||
|
||||
bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
{
|
||||
// Early return if bot is in invalid state
|
||||
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
|
||||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
|
||||
return false;
|
||||
|
||||
// when botActiveAlone is 100% and smartScale disabled
|
||||
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
|
||||
{
|
||||
@@ -4427,10 +4470,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if ((!member || !member->IsInWorld()) && member->GetMapId() != bot->GetMapId())
|
||||
{
|
||||
if (!member || !member->IsInWorld() || member->GetMapId() != bot->GetMapId())
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member == bot)
|
||||
{
|
||||
@@ -4481,23 +4522,23 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
// HasFriend
|
||||
if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend)
|
||||
{
|
||||
if (!bot || !bot->IsInWorld() || !bot->GetGUID())
|
||||
// shouldnt be needed analyse in future
|
||||
if (!bot->GetGUID())
|
||||
return false;
|
||||
|
||||
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
|
||||
{
|
||||
if (!player || !player->IsInWorld())
|
||||
if (!player || !player->GetSession() || !player->IsInWorld() || player->IsDuringRemoveFromWorld() ||
|
||||
player->GetSession()->isLogingOut())
|
||||
continue;
|
||||
|
||||
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
|
||||
if (!connectedPlayer)
|
||||
PlayerbotAI* playerAI = GET_PLAYERBOT_AI(player);
|
||||
if (!playerAI || !playerAI->IsRealPlayer())
|
||||
continue;
|
||||
|
||||
// if a real player has the bot as a friend
|
||||
PlayerSocial* social = player->GetSocial();
|
||||
if (!social)
|
||||
continue;
|
||||
|
||||
if (social->HasFriend(bot->GetGUID()))
|
||||
if (social && social->HasFriend(bot->GetGUID()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -4511,7 +4552,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
}
|
||||
}
|
||||
|
||||
// Bots don't need to move using PathGenerator.
|
||||
// Bots don't need react to PathGenerator activities
|
||||
if (activityType == DETAILED_MOVE_ACTIVITY)
|
||||
{
|
||||
return false;
|
||||
@@ -4547,15 +4588,25 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
|
||||
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
|
||||
{
|
||||
if (!allowActiveCheckTimer[activityType])
|
||||
allowActiveCheckTimer[activityType] = time(nullptr);
|
||||
const int activityIndex = static_cast<int>(activityType);
|
||||
|
||||
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityType] + 5))
|
||||
return allowActive[activityType];
|
||||
// Unknown/out-of-range avoid blocking, added logging for further analysing should not happen in the first place.
|
||||
if (activityIndex <= 0 || activityIndex >= MAX_ACTIVITY_TYPE)
|
||||
{
|
||||
LOG_ERROR("playerbots", "AllowActivity received invalid activity type value: {}", activityIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!allowActiveCheckTimer[activityIndex])
|
||||
allowActiveCheckTimer[activityIndex] = time(nullptr);
|
||||
|
||||
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityIndex] + 5))
|
||||
return allowActive[activityIndex];
|
||||
|
||||
const bool allowed = AllowActive(activityType);
|
||||
allowActive[activityIndex] = allowed;
|
||||
allowActiveCheckTimer[activityIndex] = time(nullptr);
|
||||
|
||||
bool allowed = AllowActive(activityType);
|
||||
allowActive[activityType] = allowed;
|
||||
allowActiveCheckTimer[activityType] = time(nullptr);
|
||||
return allowed;
|
||||
}
|
||||
|
||||
@@ -5339,15 +5390,13 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
if (!item_template)
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
|
||||
};
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
|
||||
};
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
||||
|
||||
Item* stone = nullptr;
|
||||
ItemTemplate const* pProto = weapon->GetTemplate();
|
||||
@@ -5383,7 +5432,6 @@ static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
|
||||
Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
{
|
||||
|
||||
if (!weapon)
|
||||
return nullptr;
|
||||
|
||||
@@ -5392,12 +5440,12 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedManaOilIds = {
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL, BRILLIANT_WIZARD_OIL,
|
||||
SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
|
||||
Item* oil = nullptr;
|
||||
int botClass = bot->getClass();
|
||||
@@ -5413,22 +5461,22 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (specTab == 0) // Balance
|
||||
if (specTab == 0) // Balance
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 1) // Feral
|
||||
else if (specTab == 1) // Feral
|
||||
prioritizedOils = nullptr;
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (specTab == 1) // Protection
|
||||
if (specTab == 1) // Protection
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 2) // Retribution
|
||||
else if (specTab == 2) // Retribution
|
||||
prioritizedOils = nullptr;
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -428,7 +428,7 @@ public:
|
||||
static bool IsMainTank(Player* player);
|
||||
static uint32 GetGroupTankNum(Player* player);
|
||||
static bool IsAssistTank(Player* player);
|
||||
static bool IsAssistTankOfIndex(Player* player, int index);
|
||||
static bool IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers = false);
|
||||
static bool IsHealAssistantOfIndex(Player* player, int index);
|
||||
static bool IsRangedDpsAssistantOfIndex(Player* player, int index);
|
||||
bool HasAggro(Unit* unit);
|
||||
@@ -540,7 +540,7 @@ public:
|
||||
// Get the group leader or the master of the bot.
|
||||
// Checks if the bot is summoned as alt of a player
|
||||
bool IsAlt();
|
||||
Player* GetGroupMaster();
|
||||
Player* GetGroupLeader();
|
||||
// Returns a semi-random (cycling) number that is fixed for each bot.
|
||||
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1);
|
||||
GrouperType GetGrouperType();
|
||||
@@ -612,7 +612,7 @@ private:
|
||||
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
|
||||
bool mixed = false);
|
||||
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
|
||||
void UpdateAIGroupAndMaster();
|
||||
void UpdateAIGroupMaster();
|
||||
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
|
||||
void HandleCommands();
|
||||
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
|
||||
|
||||
@@ -165,7 +165,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
pvpProhibitedZoneIds);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds",
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"),
|
||||
pvpProhibitedAreaIds);
|
||||
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
|
||||
LoadList<std::vector<uint32>>(
|
||||
@@ -725,8 +725,8 @@ std::string const PlayerbotAIConfig::GetTimestampStr()
|
||||
// HH hour (2 digits 00-23)
|
||||
// MM minutes (2 digits 00-59)
|
||||
// SS seconds (2 digits 00-59)
|
||||
char buf[20];
|
||||
snprintf(buf, 20, "%04d-%02d-%02d %02d-%02d-%02d", aTm->tm_year + 1900, aTm->tm_mon + 1, aTm->tm_mday, aTm->tm_hour,
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d-%02d-%02d", aTm->tm_year + 1900, aTm->tm_mon + 1, aTm->tm_mday, aTm->tm_hour,
|
||||
aTm->tm_min, aTm->tm_sec);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
#include <cstring>
|
||||
#include <istream>
|
||||
#include <string>
|
||||
#include <openssl/sha.h>
|
||||
#include <unordered_set>
|
||||
#include <openssl/sha.h>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ChannelMgr.h"
|
||||
#include "CharacterCache.h"
|
||||
@@ -27,17 +28,16 @@
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "PlayerbotOperations.h"
|
||||
#include "PlayerbotSecurity.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "WorldSession.h"
|
||||
#include "ChannelMgr.h"
|
||||
#include "BroadcastHelper.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
#include "DatabaseEnv.h" // Added for gender choice
|
||||
#include <algorithm> // Added for gender choice
|
||||
#include "DatabaseEnv.h"
|
||||
|
||||
class BotInitGuard
|
||||
{
|
||||
@@ -66,6 +66,7 @@ private:
|
||||
};
|
||||
|
||||
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
|
||||
std::unordered_set<ObjectGuid> PlayerbotHolder::botLoading;
|
||||
|
||||
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
|
||||
class PlayerbotLoginQueryHolder : public LoginQueryHolder
|
||||
@@ -74,18 +75,16 @@ private:
|
||||
uint32 masterAccountId;
|
||||
PlayerbotHolder* playerbotHolder;
|
||||
public:
|
||||
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
|
||||
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
|
||||
PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid)
|
||||
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount)
|
||||
{
|
||||
}
|
||||
|
||||
uint32 GetMasterAccountId() const { return masterAccountId; }
|
||||
PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; }
|
||||
};
|
||||
|
||||
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
|
||||
{
|
||||
// bot is loading
|
||||
if (botLoading.find(playerGuid) != botLoading.end())
|
||||
return;
|
||||
|
||||
@@ -142,7 +141,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
||||
return;
|
||||
}
|
||||
std::shared_ptr<PlayerbotLoginQueryHolder> holder =
|
||||
std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
|
||||
std::make_shared<PlayerbotLoginQueryHolder>(masterAccountId, accountId, playerGuid);
|
||||
if (!holder->Initialize())
|
||||
{
|
||||
return;
|
||||
@@ -152,8 +151,27 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
||||
|
||||
// Always login in with world session to avoid race condition
|
||||
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
|
||||
.AfterComplete([this](SQLQueryHolderBase const& holder)
|
||||
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
|
||||
.AfterComplete(
|
||||
[](SQLQueryHolderBase const& queryHolder)
|
||||
{
|
||||
PlayerbotLoginQueryHolder const& holder = static_cast<PlayerbotLoginQueryHolder const&>(queryHolder);
|
||||
PlayerbotHolder* mgr = sRandomPlayerbotMgr; // could be null
|
||||
uint32 masterAccountId = holder.GetMasterAccountId();
|
||||
|
||||
if (masterAccountId)
|
||||
{
|
||||
// verify and find current world session of master
|
||||
WorldSession* masterSession = sWorldSessionMgr->FindSession(masterAccountId);
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterPlayer)
|
||||
mgr = GET_PLAYERBOT_MGR(masterPlayer);
|
||||
}
|
||||
|
||||
if (mgr)
|
||||
mgr->HandlePlayerBotLoginCallback(holder);
|
||||
else
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
});
|
||||
}
|
||||
|
||||
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
|
||||
@@ -168,8 +186,9 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
uint32 botAccountId = holder.GetAccountId();
|
||||
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
|
||||
// allows channels to work as intended)
|
||||
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
WorldSession* botSession =
|
||||
new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
|
||||
sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
|
||||
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
|
||||
|
||||
@@ -180,24 +199,27 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
|
||||
botSession->LogoutPlayer(true);
|
||||
delete botSession;
|
||||
botLoading.erase(holder.GetGuid());
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 masterAccount = holder.GetMasterAccountId();
|
||||
WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr;
|
||||
uint32 masterAccountId = holder.GetMasterAccountId();
|
||||
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr;
|
||||
|
||||
// Check if masterSession->GetPlayer() is valid
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterSession && !masterPlayer)
|
||||
{
|
||||
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
|
||||
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}",
|
||||
masterAccountId);
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->OnPlayerLogin(bot);
|
||||
OnBotLogin(bot);
|
||||
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), masterAccountId);
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||
|
||||
botLoading.erase(holder.GetGuid());
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
}
|
||||
|
||||
void PlayerbotHolder::UpdateSessions()
|
||||
@@ -316,11 +338,9 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
|
||||
if (!botAI)
|
||||
return;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
|
||||
{
|
||||
sPlayerbotDbStore->Save(botAI);
|
||||
}
|
||||
// Queue group cleanup operation for world thread
|
||||
auto cleanupOp = std::make_unique<BotLogoutGroupCleanupOperation>(guid);
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(cleanupOp));
|
||||
|
||||
LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str());
|
||||
bot->SaveToDB(false, false);
|
||||
@@ -549,6 +569,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
|
||||
botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK);
|
||||
|
||||
// Queue group operations for world thread
|
||||
if (master && master->GetGroup() && !group)
|
||||
{
|
||||
Group* mgroup = master->GetGroup();
|
||||
@@ -556,24 +577,29 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
{
|
||||
if (!mgroup->isRaidGroup() && !mgroup->isLFGGroup() && !mgroup->isBGGroup() && !mgroup->isBFGroup())
|
||||
{
|
||||
mgroup->ConvertToRaid();
|
||||
// Queue ConvertToRaid operation
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(master->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
if (mgroup->isRaidGroup())
|
||||
{
|
||||
mgroup->AddMember(bot);
|
||||
// Queue AddMember operation
|
||||
auto addOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(addOp));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mgroup->AddMember(bot);
|
||||
// Queue AddMember operation
|
||||
auto addOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(addOp));
|
||||
}
|
||||
}
|
||||
else if (master && !group)
|
||||
{
|
||||
Group* newGroup = new Group();
|
||||
newGroup->Create(master);
|
||||
sGroupMgr->AddGroup(newGroup);
|
||||
newGroup->AddMember(bot);
|
||||
// Queue group creation and AddMember operation
|
||||
auto inviteOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(inviteOp));
|
||||
}
|
||||
// if (master)
|
||||
// {
|
||||
@@ -1602,8 +1628,26 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot)
|
||||
|
||||
void PlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
{
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
WorldSession* session = player->GetSession();
|
||||
if (!session)
|
||||
{
|
||||
LOG_WARN("playerbots", "Unable to register locale priority for player {} because the session is missing", player->GetName());
|
||||
return;
|
||||
}
|
||||
|
||||
// DB locale (source of bot text translation)
|
||||
LocaleConstant const databaseLocale = session->GetSessionDbLocaleIndex();
|
||||
|
||||
// For bot texts (DB-driven), prefer the database locale with a safe fallback.
|
||||
LocaleConstant usedLocale = databaseLocale;
|
||||
if (usedLocale >= MAX_LOCALES)
|
||||
usedLocale = LOCALE_enUS; // fallback
|
||||
|
||||
// set locale priority for bot texts
|
||||
sPlayerbotTextMgr->AddLocalePriority(player->GetSession()->GetSessionDbcLocale());
|
||||
sPlayerbotTextMgr->AddLocalePriority(usedLocale);
|
||||
|
||||
if (sPlayerbotAIConfig->selfBotLevel > 2)
|
||||
HandlePlayerbotCommand("self", player);
|
||||
@@ -1611,7 +1655,7 @@ void PlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
if (!sPlayerbotAIConfig->botAutologin)
|
||||
return;
|
||||
|
||||
uint32 accountId = player->GetSession()->GetAccountId();
|
||||
uint32 accountId = session->GetAccountId();
|
||||
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
|
||||
if (results)
|
||||
{
|
||||
|
||||
@@ -60,7 +60,7 @@ protected:
|
||||
virtual void OnBotLoginInternal(Player* const bot) = 0;
|
||||
|
||||
PlayerBotMap playerBots;
|
||||
std::unordered_set<ObjectGuid> botLoading;
|
||||
static std::unordered_set<ObjectGuid> botLoading;
|
||||
};
|
||||
|
||||
class PlayerbotMgr : public PlayerbotHolder
|
||||
|
||||
93
src/PlayerbotOperation.h
Normal file
93
src/PlayerbotOperation.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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_OPERATION_H
|
||||
#define _PLAYERBOT_OPERATION_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @brief Base class for thread-unsafe operations that must be executed in the world thread
|
||||
*
|
||||
* PlayerbotOperation represents an operation that needs to be deferred from a map thread
|
||||
* to the world thread for safe execution. Examples include group modifications, LFG operations,
|
||||
* guild operations, etc.
|
||||
*
|
||||
* Thread Safety:
|
||||
* - The constructor and data members must be thread-safe (use copies, not pointers)
|
||||
* - Execute() is called in the world thread and can safely perform thread-unsafe operations
|
||||
* - Subclasses must not store raw pointers to (core/world thread) game object (use ObjectGuid instead)
|
||||
*/
|
||||
class PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
virtual ~PlayerbotOperation() = default;
|
||||
|
||||
/**
|
||||
* @brief Execute this operation in the world thread
|
||||
*
|
||||
* This method is called by PlayerbotWorldThreadProcessor::Update() which runs in the world thread.
|
||||
* It's safe to perform any thread-unsafe operation here (Group, LFG, Guild, etc.)
|
||||
*
|
||||
* @return true if operation succeeded, false if it failed
|
||||
*/
|
||||
virtual bool Execute() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the bot GUID this operation is for (optional)
|
||||
*
|
||||
* Used for logging and debugging purposes.
|
||||
*
|
||||
* @return ObjectGuid of the bot, or ObjectGuid::Empty if not applicable
|
||||
*/
|
||||
virtual ObjectGuid GetBotGuid() const { return ObjectGuid::Empty; }
|
||||
|
||||
/**
|
||||
* @brief Get the operation priority (higher = more urgent)
|
||||
*
|
||||
* Priority levels:
|
||||
* - 100: Critical (crash prevention, cleanup operations)
|
||||
* - 50: High (player-facing operations like group invites)
|
||||
* - 10: Normal (background operations)
|
||||
* - 0: Low (statistics, logging)
|
||||
*
|
||||
* @return Priority value (0-100)
|
||||
*/
|
||||
virtual uint32 GetPriority() const { return 10; }
|
||||
|
||||
/**
|
||||
* @brief Get a human-readable name for this operation
|
||||
*
|
||||
* Used for logging and debugging.
|
||||
*
|
||||
* @return Operation name
|
||||
*/
|
||||
virtual std::string GetName() const { return "Unknown Operation"; }
|
||||
|
||||
/**
|
||||
* @brief Check if this operation is still valid
|
||||
*
|
||||
* Called before Execute() to check if the operation should still be executed.
|
||||
* For example, if a bot logged out, group invite operations for that bot can be skipped.
|
||||
*
|
||||
* @return true if operation should be executed, false to skip
|
||||
*/
|
||||
virtual bool IsValid() const { return true; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Comparison operator for priority queue (higher priority first)
|
||||
*/
|
||||
struct PlayerbotOperationComparator
|
||||
{
|
||||
bool operator()(const std::unique_ptr<PlayerbotOperation>& a, const std::unique_ptr<PlayerbotOperation>& b) const
|
||||
{
|
||||
return a->GetPriority() < b->GetPriority(); // Lower priority goes to back of queue
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
513
src/PlayerbotOperations.h
Normal file
513
src/PlayerbotOperations.h
Normal file
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* 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_OPERATIONS_H
|
||||
#define _PLAYERBOT_OPERATIONS_H
|
||||
|
||||
#include "Group.h"
|
||||
#include "GroupMgr.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "PlayerbotOperation.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "WorldSession.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
|
||||
// Group invite operation
|
||||
class GroupInviteOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupInviteOperation(ObjectGuid botGuid, ObjectGuid targetGuid)
|
||||
: m_botGuid(botGuid), m_targetGuid(targetGuid)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||
|
||||
if (!bot || !target)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Bot or target not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if target is already in a group
|
||||
if (target->GetGroup())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Target {} is already in a group", target->GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
|
||||
// Create group if bot doesn't have one
|
||||
if (!group)
|
||||
{
|
||||
group = new Group;
|
||||
if (!group->Create(bot))
|
||||
{
|
||||
delete group;
|
||||
LOG_ERROR("playerbots", "GroupInviteOperation: Failed to create group for bot {}", bot->GetName());
|
||||
return false;
|
||||
}
|
||||
sGroupMgr->AddGroup(group);
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Created new group for bot {}", bot->GetName());
|
||||
}
|
||||
|
||||
// Convert to raid if needed (more than 5 members)
|
||||
if (!group->isRaidGroup() && group->GetMembersCount() >= 5)
|
||||
{
|
||||
group->ConvertToRaid();
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Converted group to raid");
|
||||
}
|
||||
|
||||
// Add member to group
|
||||
if (group->AddMember(target))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupInviteOperation: Successfully added {} to group", target->GetName());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("playerbots", "GroupInviteOperation: Failed to add {} to group", target->GetName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; } // High priority (player-facing)
|
||||
|
||||
std::string GetName() const override { return "GroupInvite"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
// Check if bot still exists and is online
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||
return bot && target;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
ObjectGuid m_targetGuid;
|
||||
};
|
||||
|
||||
// Remove member from group
|
||||
class GroupRemoveMemberOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupRemoveMemberOperation(ObjectGuid botGuid, ObjectGuid targetGuid)
|
||||
: m_botGuid(botGuid), m_targetGuid(targetGuid)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||
|
||||
if (!bot || !target)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Bot is not in a group");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!group->IsMember(target->GetGUID()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Target is not in bot's group");
|
||||
return false;
|
||||
}
|
||||
|
||||
group->RemoveMember(target->GetGUID());
|
||||
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Removed {} from group", target->GetName());
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; }
|
||||
|
||||
std::string GetName() const override { return "GroupRemoveMember"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
return bot != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
ObjectGuid m_targetGuid;
|
||||
};
|
||||
|
||||
// Convert group to raid
|
||||
class GroupConvertToRaidOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupConvertToRaidOperation(ObjectGuid botGuid) : m_botGuid(botGuid) {}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Bot is not in a group");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (group->isRaidGroup())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Group is already a raid");
|
||||
return true; // Success - already in desired state
|
||||
}
|
||||
|
||||
group->ConvertToRaid();
|
||||
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Converted group to raid");
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; }
|
||||
|
||||
std::string GetName() const override { return "GroupConvertToRaid"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
return bot != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
};
|
||||
|
||||
// Set group leader
|
||||
class GroupSetLeaderOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
GroupSetLeaderOperation(ObjectGuid botGuid, ObjectGuid newLeaderGuid)
|
||||
: m_botGuid(botGuid), m_newLeaderGuid(newLeaderGuid)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* newLeader = ObjectAccessor::FindPlayer(m_newLeaderGuid);
|
||||
|
||||
if (!bot || !newLeader)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: Bot is not in a group");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!group->IsMember(newLeader->GetGUID()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: New leader is not in the group");
|
||||
return false;
|
||||
}
|
||||
|
||||
group->ChangeLeader(newLeader->GetGUID());
|
||||
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: Changed leader to {}", newLeader->GetName());
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; }
|
||||
|
||||
std::string GetName() const override { return "GroupSetLeader"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
Player* newLeader = ObjectAccessor::FindPlayer(m_newLeaderGuid);
|
||||
return bot && newLeader;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
ObjectGuid m_newLeaderGuid;
|
||||
};
|
||||
|
||||
// Form arena group
|
||||
class ArenaGroupFormationOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
ArenaGroupFormationOperation(ObjectGuid leaderGuid, std::vector<ObjectGuid> memberGuids,
|
||||
uint32 requiredSize, uint32 arenaTeamId, std::string arenaTeamName)
|
||||
: m_leaderGuid(leaderGuid), m_memberGuids(memberGuids),
|
||||
m_requiredSize(requiredSize), m_arenaTeamId(arenaTeamId), m_arenaTeamName(arenaTeamName)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* leader = ObjectAccessor::FindPlayer(m_leaderGuid);
|
||||
if (!leader)
|
||||
{
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Leader not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: Remove all members from their existing groups
|
||||
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||
{
|
||||
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||
if (!member)
|
||||
continue;
|
||||
|
||||
Group* memberGroup = member->GetGroup();
|
||||
if (memberGroup)
|
||||
{
|
||||
memberGroup->RemoveMember(memberGuid);
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Removed {} from their existing group",
|
||||
member->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Disband leader's existing group
|
||||
Group* leaderGroup = leader->GetGroup();
|
||||
if (leaderGroup)
|
||||
{
|
||||
leaderGroup->Disband(true);
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Disbanded leader's existing group");
|
||||
}
|
||||
|
||||
// Step 3: Create new group with leader
|
||||
Group* newGroup = new Group();
|
||||
if (!newGroup->Create(leader))
|
||||
{
|
||||
delete newGroup;
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Failed to create arena group for leader {}",
|
||||
leader->GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
sGroupMgr->AddGroup(newGroup);
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Created new arena group with leader {}",
|
||||
leader->GetName());
|
||||
|
||||
// Step 4: Add members to the new group
|
||||
uint32 addedMembers = 0;
|
||||
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||
{
|
||||
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||
if (!member)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Member {} not found, skipping",
|
||||
memberGuid.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member->GetLevel() < 70)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Member {} is below level 70, skipping",
|
||||
member->GetName());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newGroup->AddMember(member))
|
||||
{
|
||||
addedMembers++;
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Added {} to arena group",
|
||||
member->GetName());
|
||||
}
|
||||
else
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Failed to add {} to arena group",
|
||||
member->GetName());
|
||||
}
|
||||
|
||||
if (addedMembers == 0)
|
||||
{
|
||||
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: No members were added to the arena group");
|
||||
newGroup->Disband();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: Teleport members to leader and reset AI
|
||||
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||
{
|
||||
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||
if (!member || !newGroup->IsMember(memberGuid))
|
||||
continue;
|
||||
|
||||
PlayerbotAI* memberBotAI = sPlayerbotsMgr->GetPlayerbotAI(member);
|
||||
if (memberBotAI)
|
||||
memberBotAI->Reset();
|
||||
|
||||
member->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||
member->TeleportTo(leader->GetMapId(), leader->GetPositionX(), leader->GetPositionY(),
|
||||
leader->GetPositionZ(), 0);
|
||||
|
||||
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Teleported {} to leader", member->GetName());
|
||||
}
|
||||
|
||||
// Check if we have enough members
|
||||
if (newGroup->GetMembersCount() < m_requiredSize)
|
||||
{
|
||||
LOG_INFO("playerbots", "Team #{} <{}> Group is not ready for match (not enough members: {}/{})",
|
||||
m_arenaTeamId, m_arenaTeamName, newGroup->GetMembersCount(), m_requiredSize);
|
||||
newGroup->Disband();
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("playerbots", "Team #{} <{}> Group is ready for match with {} members",
|
||||
m_arenaTeamId, m_arenaTeamName, newGroup->GetMembersCount());
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_leaderGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 60; } // Very high priority (arena/BG operations)
|
||||
|
||||
std::string GetName() const override { return "ArenaGroupFormation"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* leader = ObjectAccessor::FindPlayer(m_leaderGuid);
|
||||
return leader != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_leaderGuid;
|
||||
std::vector<ObjectGuid> m_memberGuids;
|
||||
uint32 m_requiredSize;
|
||||
uint32 m_arenaTeamId;
|
||||
std::string m_arenaTeamName;
|
||||
};
|
||||
|
||||
// Bot logout group cleanup operation
|
||||
class BotLogoutGroupCleanupOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
BotLogoutGroupCleanupOperation(ObjectGuid botGuid) : m_botGuid(botGuid) {}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
PlayerbotAI* botAI = sPlayerbotsMgr->GetPlayerbotAI(bot);
|
||||
if (!botAI)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
|
||||
sPlayerbotDbStore->Save(botAI);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
uint32 GetPriority() const override { return 70; }
|
||||
std::string GetName() const override { return "BotLogoutGroupCleanup"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||
return bot != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
};
|
||||
|
||||
// Add player bot operation (for logging in bots from map threads)
|
||||
class AddPlayerBotOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
AddPlayerBotOperation(ObjectGuid botGuid, uint32 masterAccountId)
|
||||
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
sRandomPlayerbotMgr->AddPlayerBot(m_botGuid, m_masterAccountId);
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
|
||||
uint32 GetPriority() const override { return 50; } // High priority
|
||||
|
||||
std::string GetName() const override { return "AddPlayerBot"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
return !ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
uint32 m_masterAccountId;
|
||||
};
|
||||
|
||||
class OnBotLoginOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
OnBotLoginOperation(ObjectGuid botGuid, uint32 masterAccountId)
|
||||
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
// find and verify bot still exists
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
PlayerbotHolder* holder = sRandomPlayerbotMgr;
|
||||
if (m_masterAccountId)
|
||||
{
|
||||
WorldSession* masterSession = sWorldSessionMgr->FindSession(m_masterAccountId);
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterPlayer)
|
||||
holder = GET_PLAYERBOT_MGR(masterPlayer);
|
||||
}
|
||||
|
||||
if (!holder)
|
||||
return false;
|
||||
|
||||
holder->OnBotLogin(bot);
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||
uint32 GetPriority() const override { return 100; }
|
||||
std::string GetName() const override { return "OnBotLogin"; }
|
||||
|
||||
bool IsValid() const override { return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr; }
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
uint32 m_masterAccountId = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -17,14 +17,28 @@ PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot)
|
||||
|
||||
PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup)
|
||||
{
|
||||
// Basic pointer validity checks
|
||||
if (!bot || !from || !from->GetSession())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NONE;
|
||||
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
// GMs always have full access
|
||||
if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
if (!botAI)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NONE;
|
||||
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
if (botAI->IsOpposing(from))
|
||||
{
|
||||
if (reason)
|
||||
@@ -35,6 +49,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
|
||||
if (sPlayerbotAIConfig->IsInRandomAccountList(account))
|
||||
{
|
||||
// (duplicate check in case of faction change)
|
||||
if (botAI->IsOpposing(from))
|
||||
{
|
||||
if (reason)
|
||||
@@ -43,27 +58,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
// if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
|
||||
// {
|
||||
// if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
// {
|
||||
// if (reason)
|
||||
// *reason = PLAYERBOT_DENY_LFG;
|
||||
Group* fromGroup = from->GetGroup();
|
||||
Group* botGroup = bot->GetGroup();
|
||||
|
||||
// return PLAYERBOT_SECURITY_TALK;
|
||||
// }
|
||||
// }
|
||||
|
||||
Group* group = from->GetGroup();
|
||||
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() == from)
|
||||
if (fromGroup && botGroup && fromGroup == botGroup && !ignoreGroup)
|
||||
{
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
}
|
||||
if (botAI->GetMaster() == from)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() != from)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NOT_YOURS;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
@@ -75,27 +80,34 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->groupInvitationPermission <= 1 && (int32)bot->GetLevel() - (int8)from->GetLevel() > 5)
|
||||
if (sPlayerbotAIConfig->groupInvitationPermission <= 1)
|
||||
{
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
int32 levelDiff = int32(bot->GetLevel()) - int32(from->GetLevel());
|
||||
if (levelDiff > 5)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_LOW_LEVEL;
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_LOW_LEVEL;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 botGS = (int32)botAI->GetEquipGearScore(bot/*, false, false*/);
|
||||
int32 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/);
|
||||
if (sPlayerbotAIConfig->gearscorecheck)
|
||||
int32 botGS = static_cast<int32>(botAI->GetEquipGearScore(bot));
|
||||
int32 fromGS = static_cast<int32>(botAI->GetEquipGearScore(from));
|
||||
|
||||
if (sPlayerbotAIConfig->gearscorecheck && botGS && bot->GetLevel() > 15 && botGS > fromGS)
|
||||
{
|
||||
if (botGS && bot->GetLevel() > 15 && botGS > fromGS &&
|
||||
static_cast<float>(100 * (botGS - fromGS) / botGS) >=
|
||||
static_cast<float>(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel()))
|
||||
uint32 diffPct = uint32(100 * (botGS - fromGS) / botGS);
|
||||
uint32 reqPct = uint32(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel());
|
||||
|
||||
if (diffPct >= reqPct)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_GEARSCORE;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}
|
||||
@@ -111,35 +123,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
}
|
||||
}
|
||||
|
||||
/*if (bot->isDead())
|
||||
// If the bot is not in the group, we offer an invite
|
||||
botGroup = bot->GetGroup();
|
||||
if (!botGroup)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_DEAD;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}*/
|
||||
|
||||
group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
/*if (bot->GetMapId() != from->GetMapId() || bot->GetDistance(from) > sPlayerbotAIConfig->whisperDistance)
|
||||
{
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_FAR;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_INVITE;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
if (!ignoreGroup && group->IsFull())
|
||||
if (!ignoreGroup && botGroup->IsFull())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_FULL_GROUP;
|
||||
@@ -147,27 +141,22 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID())
|
||||
if (!ignoreGroup && botGroup->GetLeaderGUID() != bot->GetGUID())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NOT_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_IS_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
// The bot is the group leader, you can invite the initiator
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_INVITE;
|
||||
*reason = PLAYERBOT_DENY_IS_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
// Non-random bots: only their master has full access
|
||||
if (botAI->GetMaster() == from)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
@@ -179,8 +168,13 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
|
||||
bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup)
|
||||
{
|
||||
// If something is wrong with the pointers, we silently refuse
|
||||
if (!bot || !from || !from->GetSession())
|
||||
return false;
|
||||
|
||||
DenyReason reason = PLAYERBOT_DENY_NONE;
|
||||
PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup);
|
||||
|
||||
if (realLevel >= level || from == bot)
|
||||
return true;
|
||||
|
||||
@@ -189,11 +183,17 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
return false;
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
Player* master = botAI->GetMaster();
|
||||
if (master && botAI && botAI->IsOpposing(master) && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
||||
if (!botAI)
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (master && botAI->IsOpposing(master))
|
||||
if (WorldSession* session = master->GetSession())
|
||||
if (session->GetSecurity() < SEC_GAMEMASTER)
|
||||
return false;
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
switch (realLevel)
|
||||
{
|
||||
case PLAYERBOT_SECURITY_DENY_ALL:
|
||||
@@ -206,19 +206,20 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
out << "I'll do it later";
|
||||
break;
|
||||
case PLAYERBOT_DENY_LOW_LEVEL:
|
||||
out << "You are too low level: |cffff0000" << (uint32)from->GetLevel() << "|cffffffff/|cff00ff00"
|
||||
<< (uint32)bot->GetLevel();
|
||||
out << "You are too low level: |cffff0000" << uint32(from->GetLevel()) << "|cffffffff/|cff00ff00"
|
||||
<< uint32(bot->GetLevel());
|
||||
break;
|
||||
case PLAYERBOT_DENY_GEARSCORE:
|
||||
{
|
||||
int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/);
|
||||
int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/);
|
||||
int botGS = int(botAI->GetEquipGearScore(bot));
|
||||
int fromGS = int(botAI->GetEquipGearScore(from));
|
||||
int diff = (100 * (botGS - fromGS) / botGS);
|
||||
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
|
||||
|
||||
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS
|
||||
<< " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_NOT_YOURS:
|
||||
out << "I have a master already";
|
||||
break;
|
||||
@@ -237,13 +238,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
case PLAYERBOT_DENY_FAR:
|
||||
{
|
||||
out << "You must be closer to invite me to your group. I am in ";
|
||||
|
||||
if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId()))
|
||||
{
|
||||
out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)";
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_FULL_GROUP:
|
||||
out << "I am in a full group. Will do it later";
|
||||
break;
|
||||
@@ -251,15 +249,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
out << "I am currently leading a group. I can invite you if you want.";
|
||||
break;
|
||||
case PLAYERBOT_DENY_NOT_LEADER:
|
||||
if (botAI->GetGroupMaster())
|
||||
{
|
||||
out << "I am in a group with " << botAI->GetGroupMaster()->GetName()
|
||||
<< ". You can ask him for invite.";
|
||||
}
|
||||
if (Player* leader = botAI->GetGroupLeader())
|
||||
out << "I am in a group with " << leader->GetName() << ". You can ask him for invite.";
|
||||
else
|
||||
{
|
||||
out << "I am in a group with someone else. You can ask him for invite.";
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_BG:
|
||||
out << "I am in a queue for BG. Will do it later";
|
||||
@@ -283,10 +276,14 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
std::string const text = out.str();
|
||||
ObjectGuid guid = from->GetGUID();
|
||||
time_t lastSaid = whispers[guid][text];
|
||||
|
||||
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
|
||||
{
|
||||
whispers[guid][text] = time(nullptr);
|
||||
bot->Whisper(text, LANG_UNIVERSAL, from);
|
||||
|
||||
// Additional protection against crashes during logout
|
||||
if (bot->IsInWorld() && from->IsInWorld())
|
||||
bot->Whisper(text, LANG_UNIVERSAL, from);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -190,26 +190,29 @@ bool PlayerbotTextMgr::GetBotText(std::string name, std::string& text, std::map<
|
||||
|
||||
void PlayerbotTextMgr::AddLocalePriority(uint32 locale)
|
||||
{
|
||||
if (!locale)
|
||||
if (locale >= MAX_LOCALES)
|
||||
{
|
||||
LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds MAX_LOCALES ({})", locale, MAX_LOCALES - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
botTextLocalePriority[locale]++;
|
||||
}
|
||||
|
||||
uint32 PlayerbotTextMgr::GetLocalePriority()
|
||||
{
|
||||
uint32 topLocale = 0;
|
||||
|
||||
// if no real players online, reset top locale
|
||||
if (!sWorldSessionMgr->GetActiveSessionCount())
|
||||
uint32 const activeSessions = sWorldSessionMgr->GetActiveSessionCount();
|
||||
if (!activeSessions)
|
||||
{
|
||||
ResetLocalePriority();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 topLocale = 0;
|
||||
for (uint8 i = 0; i < MAX_LOCALES; ++i)
|
||||
{
|
||||
if (botTextLocalePriority[i] > topLocale)
|
||||
if (botTextLocalePriority[i] > botTextLocalePriority[topLocale])
|
||||
topLocale = i;
|
||||
}
|
||||
|
||||
|
||||
217
src/PlayerbotWorldThreadProcessor.cpp
Normal file
217
src/PlayerbotWorldThreadProcessor.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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 "PlayerbotWorldThreadProcessor.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
PlayerbotWorldThreadProcessor::PlayerbotWorldThreadProcessor()
|
||||
: m_enabled(true), m_maxQueueSize(10000), m_batchSize(100), m_queueWarningThreshold(80),
|
||||
m_timeSinceLastUpdate(0), m_updateInterval(50) // Process at least every 50ms
|
||||
{
|
||||
LOG_INFO("playerbots", "PlayerbotWorldThreadProcessor initialized");
|
||||
}
|
||||
|
||||
PlayerbotWorldThreadProcessor::~PlayerbotWorldThreadProcessor() { ClearQueue(); }
|
||||
|
||||
PlayerbotWorldThreadProcessor* PlayerbotWorldThreadProcessor::instance()
|
||||
{
|
||||
static PlayerbotWorldThreadProcessor instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::Update(uint32 diff)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
// Accumulate time
|
||||
m_timeSinceLastUpdate += diff;
|
||||
|
||||
// Don't process too frequently to reduce overhead
|
||||
if (m_timeSinceLastUpdate < m_updateInterval)
|
||||
return;
|
||||
|
||||
m_timeSinceLastUpdate = 0;
|
||||
|
||||
// Check queue health (warn if getting full)
|
||||
CheckQueueHealth();
|
||||
|
||||
// Process a batch of operations
|
||||
ProcessBatch();
|
||||
}
|
||||
|
||||
bool PlayerbotWorldThreadProcessor::QueueOperation(std::unique_ptr<PlayerbotOperation> operation)
|
||||
{
|
||||
if (!operation)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Attempted to queue null operation");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
|
||||
// Check if queue is full
|
||||
if (m_operationQueue.size() >= m_maxQueueSize)
|
||||
{
|
||||
LOG_ERROR("playerbots",
|
||||
"PlayerbotWorldThreadProcessor queue is full ({} operations). Dropping operation: {}",
|
||||
m_maxQueueSize, operation->GetName());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsSkipped++;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Queue the operation
|
||||
m_operationQueue.push(std::move(operation));
|
||||
|
||||
// Update statistics
|
||||
{
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.currentQueueSize = static_cast<uint32>(m_operationQueue.size());
|
||||
m_stats.maxQueueSize = std::max(m_stats.maxQueueSize, m_stats.currentQueueSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::ProcessBatch()
|
||||
{
|
||||
// Extract a batch of operations from the queue
|
||||
std::vector<std::unique_ptr<PlayerbotOperation>> batch;
|
||||
batch.reserve(m_batchSize);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
|
||||
// Extract up to batchSize operations
|
||||
while (!m_operationQueue.empty() && batch.size() < m_batchSize)
|
||||
{
|
||||
batch.push_back(std::move(m_operationQueue.front()));
|
||||
m_operationQueue.pop();
|
||||
}
|
||||
|
||||
// Update current queue size stat
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.currentQueueSize = static_cast<uint32>(m_operationQueue.size());
|
||||
}
|
||||
|
||||
// Execute operations outside of lock to avoid blocking queue
|
||||
uint32 totalExecutionTime = 0;
|
||||
for (auto& operation : batch)
|
||||
{
|
||||
if (!operation)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
// Check if operation is still valid
|
||||
if (!operation->IsValid())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "Skipping invalid operation: {}", operation->GetName());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Time the execution
|
||||
uint32 startTime = getMSTime();
|
||||
|
||||
// Execute the operation
|
||||
bool success = operation->Execute();
|
||||
|
||||
uint32 executionTime = GetMSTimeDiffToNow(startTime);
|
||||
totalExecutionTime += executionTime;
|
||||
|
||||
// Log slow operations
|
||||
if (executionTime > 100)
|
||||
LOG_WARN("playerbots", "Slow operation: {} took {}ms", operation->GetName(), executionTime);
|
||||
|
||||
// Update statistics
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
if (success)
|
||||
m_stats.totalOperationsProcessed++;
|
||||
else
|
||||
{
|
||||
m_stats.totalOperationsFailed++;
|
||||
LOG_DEBUG("playerbots", "Operation failed: {}", operation->GetName());
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Exception in operation {}: {}", operation->GetName(), e.what());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsFailed++;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_ERROR("playerbots", "Unknown exception in operation {}", operation->GetName());
|
||||
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.totalOperationsFailed++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update average execution time
|
||||
if (!batch.empty())
|
||||
{
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
uint32 avgTime = totalExecutionTime / static_cast<uint32>(batch.size());
|
||||
// Exponential moving average
|
||||
m_stats.averageExecutionTimeMs =
|
||||
(m_stats.averageExecutionTimeMs * 9 + avgTime) / 10; // 90% old, 10% new
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::CheckQueueHealth()
|
||||
{
|
||||
uint32 queueSize = GetQueueSize();
|
||||
uint32 threshold = (m_maxQueueSize * m_queueWarningThreshold) / 100;
|
||||
|
||||
if (queueSize >= threshold)
|
||||
{
|
||||
LOG_WARN("playerbots",
|
||||
"PlayerbotWorldThreadProcessor queue is {}% full ({}/{}). "
|
||||
"Consider increasing update frequency or batch size.",
|
||||
(queueSize * 100) / m_maxQueueSize, queueSize, m_maxQueueSize);
|
||||
}
|
||||
}
|
||||
|
||||
uint32 PlayerbotWorldThreadProcessor::GetQueueSize() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
return static_cast<uint32>(m_operationQueue.size());
|
||||
}
|
||||
|
||||
void PlayerbotWorldThreadProcessor::ClearQueue()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||
|
||||
uint32 cleared = static_cast<uint32>(m_operationQueue.size());
|
||||
if (cleared > 0)
|
||||
LOG_INFO("playerbots", "Clearing {} queued operations", cleared);
|
||||
|
||||
// Clear the queue
|
||||
while (!m_operationQueue.empty())
|
||||
{
|
||||
m_operationQueue.pop();
|
||||
}
|
||||
|
||||
// Reset queue size stat
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
m_stats.currentQueueSize = 0;
|
||||
}
|
||||
|
||||
PlayerbotWorldThreadProcessor::Statistics PlayerbotWorldThreadProcessor::GetStatistics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||
return m_stats; // Return a copy
|
||||
}
|
||||
142
src/PlayerbotWorldThreadProcessor.h
Normal file
142
src/PlayerbotWorldThreadProcessor.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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_WORLD_THREAD_PROCESSOR_H
|
||||
#define _PLAYERBOT_WORLD_THREAD_PROCESSOR_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "PlayerbotOperation.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
/**
|
||||
* @brief Processes thread-unsafe bot operations in the world thread
|
||||
*
|
||||
* The PlayerbotWorldThreadProcessor manages a queue of operations that must be executed
|
||||
* in the world thread rather than map threads. This ensures thread safety for operations
|
||||
* like group modifications, LFG, guilds, battlegrounds, etc.
|
||||
*
|
||||
* Architecture:
|
||||
* - Map threads queue operations via QueueOperation()
|
||||
* - World thread processes operations via Update() (called from WorldScript::OnUpdate)
|
||||
* - Operations are processed in priority order
|
||||
* - Thread-safe queue protected by mutex
|
||||
*
|
||||
* Usage:
|
||||
* auto op = std::make_unique<MyOperation>(botGuid, params);
|
||||
* sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||
*/
|
||||
class PlayerbotWorldThreadProcessor
|
||||
{
|
||||
public:
|
||||
PlayerbotWorldThreadProcessor();
|
||||
~PlayerbotWorldThreadProcessor();
|
||||
|
||||
static PlayerbotWorldThreadProcessor* instance();
|
||||
|
||||
/**
|
||||
* @brief Update and process queued operations (called from world thread)
|
||||
*
|
||||
* This method should be called from WorldScript::OnUpdate hook, which runs in the world thread.
|
||||
* It processes a batch of queued operations.
|
||||
*
|
||||
* @param diff Time since last update in milliseconds
|
||||
*/
|
||||
void Update(uint32 diff);
|
||||
|
||||
/**
|
||||
* @brief Queue an operation for execution in the world thread
|
||||
*
|
||||
* Thread-safe method that can be called from any thread (typically map threads).
|
||||
* The operation will be executed later during Update().
|
||||
*
|
||||
* @param operation Unique pointer to the operation (ownership is transferred)
|
||||
* @return true if operation was queued, false if queue is full
|
||||
*/
|
||||
bool QueueOperation(std::unique_ptr<PlayerbotOperation> operation);
|
||||
|
||||
/**
|
||||
* @brief Get current queue size
|
||||
*
|
||||
* Thread-safe method for monitoring queue size.
|
||||
*
|
||||
* @return Number of operations waiting to be processed
|
||||
*/
|
||||
uint32 GetQueueSize() const;
|
||||
|
||||
/**
|
||||
* @brief Clear all queued operations
|
||||
*
|
||||
* Used during shutdown or emergency situations.
|
||||
*/
|
||||
void ClearQueue();
|
||||
|
||||
/**
|
||||
* @brief Get statistics about operation processing
|
||||
*/
|
||||
struct Statistics
|
||||
{
|
||||
uint64 totalOperationsProcessed = 0;
|
||||
uint64 totalOperationsFailed = 0;
|
||||
uint64 totalOperationsSkipped = 0;
|
||||
uint32 currentQueueSize = 0;
|
||||
uint32 maxQueueSize = 0;
|
||||
uint32 averageExecutionTimeMs = 0;
|
||||
};
|
||||
|
||||
Statistics GetStatistics() const;
|
||||
|
||||
/**
|
||||
* @brief Enable/disable operation processing
|
||||
*
|
||||
* When disabled, operations are still queued but not processed.
|
||||
* Useful for testing or temporary suspension.
|
||||
*
|
||||
* @param enabled true to enable processing, false to disable
|
||||
*/
|
||||
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
||||
|
||||
bool IsEnabled() const { return m_enabled; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Process a single batch of operations
|
||||
*
|
||||
* Extracts operations from queue and executes them.
|
||||
* Called internally by Update().
|
||||
*/
|
||||
void ProcessBatch();
|
||||
|
||||
/**
|
||||
* @brief Check if queue is approaching capacity
|
||||
*
|
||||
* Logs warning if queue is getting full.
|
||||
*/
|
||||
void CheckQueueHealth();
|
||||
|
||||
// Thread-safe queue
|
||||
mutable std::mutex m_queueMutex;
|
||||
std::queue<std::unique_ptr<PlayerbotOperation>> m_operationQueue;
|
||||
|
||||
// Configuration
|
||||
bool m_enabled;
|
||||
uint32 m_maxQueueSize; // Maximum operations in queue
|
||||
uint32 m_batchSize; // Operations to process per Update()
|
||||
uint32 m_queueWarningThreshold; // Warn when queue reaches this percentage
|
||||
|
||||
// Statistics
|
||||
mutable std::mutex m_statsMutex;
|
||||
Statistics m_stats;
|
||||
|
||||
// Timing
|
||||
uint32 m_timeSinceLastUpdate;
|
||||
uint32 m_updateInterval; // Minimum ms between updates
|
||||
};
|
||||
|
||||
#define sPlayerbotWorldProcessor PlayerbotWorldThreadProcessor::instance()
|
||||
|
||||
#endif
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "Metric.h"
|
||||
#include "PlayerScript.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotSpellCache.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "cs_playerbots.h"
|
||||
@@ -81,12 +83,12 @@ public:
|
||||
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", {
|
||||
PLAYERHOOK_ON_LOGIN,
|
||||
PLAYERHOOK_ON_AFTER_UPDATE,
|
||||
PLAYERHOOK_ON_CHAT,
|
||||
PLAYERHOOK_ON_CHAT_WITH_CHANNEL,
|
||||
PLAYERHOOK_ON_CHAT_WITH_GROUP,
|
||||
PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS,
|
||||
PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_GROUP_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_GUILD_CHAT,
|
||||
PLAYERHOOK_CAN_PLAYER_USE_CHANNEL_CHAT,
|
||||
PLAYERHOOK_ON_GIVE_EXP,
|
||||
PLAYERHOOK_ON_BEFORE_TELEPORT
|
||||
}) {}
|
||||
@@ -122,24 +124,49 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeTeleport(Player* player, uint32 mapid, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
|
||||
bool OnPlayerBeforeTeleport(Player* /*player*/, uint32 /*mapid*/, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
|
||||
{
|
||||
// Only apply to bots to prevent affecting real players
|
||||
if (!player || !player->GetSession()->IsBot())
|
||||
/* for now commmented out until proven its actually required
|
||||
* havent seen any proof CleanVisibilityReferences() is needed
|
||||
|
||||
// If the player is not safe to touch, do nothing
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
// If changing maps, proactively clean visibility references to prevent
|
||||
// stale pointers in other players' visibility maps during the teleport.
|
||||
// This fixes a race condition where:
|
||||
// 1. Bot A teleports and its visible objects start getting cleaned up
|
||||
// 2. Bot B is simultaneously updating visibility and tries to access objects in Bot A's old visibility map
|
||||
// 3. Those objects may already be freed, causing a segmentation fault
|
||||
if (player->GetMapId() != mapid && player->IsInWorld())
|
||||
{
|
||||
player->GetObjectVisibilityContainer().CleanVisibilityReferences();
|
||||
}
|
||||
// If same map or not in world do nothing
|
||||
if (!player->IsInWorld() || player->GetMapId() == mapid)
|
||||
return true;
|
||||
|
||||
return true; // Allow teleport to continue
|
||||
// If real player do nothing
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(player);
|
||||
if (!ai || ai->IsRealPlayer())
|
||||
return true;
|
||||
|
||||
// Cross-map bot teleport: defer visibility reference cleanup.
|
||||
// CleanVisibilityReferences() erases this bot's GUID from other objects' visibility containers.
|
||||
// This is intentionally done via the event queue (instead of directly here) because erasing
|
||||
// from other players' visibility maps inside the teleport call stack can hit unsafe re-entrancy
|
||||
// or iterator invalidation while visibility updates are in progress
|
||||
ObjectGuid guid = player->GetGUID();
|
||||
player->m_Events.AddEventAtOffset(
|
||||
[guid, mapid]()
|
||||
{
|
||||
// do nothing, if the player is not safe to touch
|
||||
Player* p = ObjectAccessor::FindPlayer(guid);
|
||||
if (!p || !p->IsInWorld() || p->IsDuringRemoveFromWorld())
|
||||
return;
|
||||
|
||||
// do nothing if we are already on the target map
|
||||
if (p->GetMapId() == mapid)
|
||||
return;
|
||||
|
||||
p->GetObjectVisibilityContainer().CleanVisibilityReferences();
|
||||
},
|
||||
Milliseconds(0));
|
||||
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerAfterUpdate(Player* player, uint32 diff) override
|
||||
@@ -163,14 +190,17 @@ public:
|
||||
{
|
||||
botAI->HandleCommand(type, msg, player);
|
||||
|
||||
return false;
|
||||
// hotfix; otherwise the server will crash when whispering logout
|
||||
// https://github.com/mod-playerbots/mod-playerbots/pull/1838
|
||||
// TODO: find the root cause and solve it. (does not happen in party chat)
|
||||
if (msg == "logout")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
{
|
||||
@@ -182,9 +212,10 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg) override
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Guild* guild) override
|
||||
{
|
||||
if (type == CHAT_MSG_GUILD)
|
||||
{
|
||||
@@ -203,9 +234,10 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
|
||||
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
|
||||
{
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
@@ -216,6 +248,7 @@ public:
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
|
||||
@@ -300,7 +333,8 @@ class PlayerbotsWorldScript : public WorldScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript", {
|
||||
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED
|
||||
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED,
|
||||
WORLDHOOK_ON_UPDATE
|
||||
}) {}
|
||||
|
||||
void OnBeforeWorldInitialized() override
|
||||
@@ -329,6 +363,16 @@ public:
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
|
||||
sPlayerbotSpellCache->Initialize();
|
||||
|
||||
LOG_INFO("server.loading", "Playerbots World Thread Processor initialized");
|
||||
}
|
||||
|
||||
void OnUpdate(uint32 diff) override
|
||||
{
|
||||
sPlayerbotWorldProcessor->Update(diff);
|
||||
sRandomPlayerbotMgr->UpdateAI(diff); // World thread only
|
||||
}
|
||||
};
|
||||
|
||||
@@ -390,8 +434,7 @@ public:
|
||||
|
||||
void OnPlayerbotUpdate(uint32 diff) override
|
||||
{
|
||||
sRandomPlayerbotMgr->UpdateAI(diff);
|
||||
sRandomPlayerbotMgr->UpdateSessions();
|
||||
sRandomPlayerbotMgr->UpdateSessions(); // Per-bot updates only
|
||||
}
|
||||
|
||||
void OnPlayerbotUpdateSessions(Player* player) override
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Define.h"
|
||||
#include "FleeManager.h"
|
||||
#include "GameTime.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "GuildMgr.h"
|
||||
@@ -670,9 +669,9 @@ void RandomPlayerbotMgr::AssignAccountTypes()
|
||||
uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts;
|
||||
uint32 assigned = 0;
|
||||
|
||||
for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--)
|
||||
for (size_t idx = allRandomBotAccounts.size(); idx-- > 0 && assigned < toAssign;)
|
||||
{
|
||||
uint32 accountId = allRandomBotAccounts[i];
|
||||
uint32 accountId = allRandomBotAccounts[idx];
|
||||
if (currentAssignments[accountId] == 0) // Unassigned
|
||||
{
|
||||
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId);
|
||||
@@ -1425,7 +1424,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
|
||||
LOG_DEBUG("playerbots", "Bot #{}: log out", bot);
|
||||
|
||||
SetEventValue(bot, "add", 0, 0);
|
||||
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
|
||||
currentBots.remove(bot);
|
||||
|
||||
if (player)
|
||||
LogoutPlayerBot(botGUID);
|
||||
@@ -1480,10 +1479,10 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
|
||||
if (!sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
update = false;
|
||||
|
||||
if (player->GetGroup() && botAI->GetGroupMaster())
|
||||
if (player->GetGroup() && botAI->GetGroupLeader())
|
||||
{
|
||||
PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
|
||||
if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
|
||||
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader());
|
||||
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())
|
||||
{
|
||||
update = false;
|
||||
}
|
||||
@@ -2594,17 +2593,14 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
|
||||
|
||||
bool RandomPlayerbotMgr::IsRandomBot(Player* bot)
|
||||
{
|
||||
if (bot && GET_PLAYERBOT_AI(bot))
|
||||
{
|
||||
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
|
||||
return false;
|
||||
}
|
||||
if (bot)
|
||||
{
|
||||
return IsRandomBot(bot->GetGUID().GetCounter());
|
||||
}
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
return false;
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
if (!botAI || botAI->IsRealPlayer())
|
||||
return false;
|
||||
|
||||
return IsRandomBot(bot->GetGUID().GetCounter());
|
||||
}
|
||||
|
||||
bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)
|
||||
@@ -2712,69 +2708,73 @@ std::vector<uint32> RandomPlayerbotMgr::GetBgBots(uint32 bracket)
|
||||
return std::move(BgBots);
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event)
|
||||
CachedEvent* RandomPlayerbotMgr::FindEvent(uint32 bot, std::string const& event)
|
||||
{
|
||||
// load all events at once on first event load
|
||||
if (eventCache[bot].empty())
|
||||
BotEventCache& cache = eventCache[bot];
|
||||
|
||||
// Load once
|
||||
if (!cache.loaded)
|
||||
{
|
||||
cache.events.clear();
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT);
|
||||
stmt->SetData(0, 0);
|
||||
stmt->SetData(1, bot);
|
||||
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
std::string const eventName = fields[0].Get<std::string>();
|
||||
|
||||
CachedEvent e;
|
||||
e.value = fields[1].Get<uint32>();
|
||||
e.lastChangeTime = fields[2].Get<uint32>();
|
||||
e.validIn = fields[3].Get<uint32>();
|
||||
e.data = fields[4].Get<std::string>();
|
||||
eventCache[bot][eventName] = std::move(e);
|
||||
|
||||
cache.events.emplace(fields[0].Get<std::string>(), std::move(e));
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
cache.loaded = true;
|
||||
}
|
||||
|
||||
CachedEvent& e = eventCache[bot][event];
|
||||
/*if (e.IsEmpty())
|
||||
auto it = cache.events.find(event);
|
||||
if (it == cache.events.end())
|
||||
return nullptr;
|
||||
|
||||
CachedEvent& e = it->second;
|
||||
|
||||
// remove expired events
|
||||
if (e.validIn && (NowSeconds() - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
|
||||
{
|
||||
QueryResult results = PlayerbotsDatabase.Query("SELECT `value`, `time`, validIn, `data` FROM
|
||||
playerbots_random_bots WHERE owner = 0 AND bot = {} AND event = {}", bot, event.c_str());
|
||||
|
||||
if (results)
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
e.value = fields[0].Get<uint32>();
|
||||
e.lastChangeTime = fields[1].Get<uint32>();
|
||||
e.validIn = fields[2].Get<uint32>();
|
||||
e.data = fields[3].Get<std::string>();
|
||||
}
|
||||
cache.events.erase(it);
|
||||
return nullptr;
|
||||
}
|
||||
*/
|
||||
|
||||
if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
|
||||
e.value = 0;
|
||||
|
||||
return e.value;
|
||||
return &e;
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const event)
|
||||
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const& event)
|
||||
{
|
||||
std::string data = "";
|
||||
if (GetEventValue(bot, event))
|
||||
{
|
||||
CachedEvent e = eventCache[bot][event];
|
||||
data = e.data;
|
||||
}
|
||||
if (CachedEvent* e = FindEvent(bot, event))
|
||||
return e->value;
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
|
||||
std::string const data)
|
||||
std::string RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const& event)
|
||||
{
|
||||
if (CachedEvent* e = FindEvent(bot, event))
|
||||
return e->data;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
|
||||
std::string const& data)
|
||||
{
|
||||
PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction();
|
||||
|
||||
@@ -2790,43 +2790,55 @@ uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, ui
|
||||
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS);
|
||||
stmt->SetData(0, 0);
|
||||
stmt->SetData(1, bot);
|
||||
stmt->SetData(2, static_cast<uint32>(GameTime::GetGameTime().count()));
|
||||
stmt->SetData(2, NowSeconds());
|
||||
stmt->SetData(3, validIn);
|
||||
stmt->SetData(4, event.c_str());
|
||||
stmt->SetData(5, value);
|
||||
if (data != "")
|
||||
{
|
||||
|
||||
if (!data.empty())
|
||||
stmt->SetData(6, data.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt->SetData(6);
|
||||
}
|
||||
stmt->SetData(6); // NULL
|
||||
|
||||
trans->Append(stmt);
|
||||
}
|
||||
|
||||
PlayerbotsDatabase.CommitTransaction(trans);
|
||||
|
||||
CachedEvent e(value, (uint32)time(nullptr), validIn, data);
|
||||
eventCache[bot][event] = std::move(e);
|
||||
// Update in-memory cache
|
||||
BotEventCache& cache = eventCache[bot];
|
||||
cache.loaded = true;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
cache.events.erase(event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CachedEvent& e = cache.events[event]; // create-on-write is OK here
|
||||
e.value = value;
|
||||
e.lastChangeTime = NowSeconds();
|
||||
e.validIn = validIn;
|
||||
e.data = data;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const type) { return GetEventValue(bot, type); }
|
||||
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const& type) { return GetEventValue(bot, type); }
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const type)
|
||||
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const& type)
|
||||
{
|
||||
return GetValue(bot->GetGUID().GetCounter(), type);
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotMgr::GetData(uint32 bot, std::string const type) { return GetEventData(bot, type); }
|
||||
std::string RandomPlayerbotMgr::GetData(uint32 bot, std::string const& type) { return GetEventData(bot, type); }
|
||||
|
||||
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const type, uint32 value, std::string const data)
|
||||
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data)
|
||||
{
|
||||
SetEventValue(bot, type, value, sPlayerbotAIConfig->maxRandomBotInWorldTime, data);
|
||||
}
|
||||
|
||||
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const type, uint32 value, std::string const data)
|
||||
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data)
|
||||
{
|
||||
SetValue(bot->GetGUID().GetCounter(), type, value, data);
|
||||
}
|
||||
@@ -3115,7 +3127,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot)
|
||||
{
|
||||
SetEventValue(bot, "add", 0, 0);
|
||||
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
|
||||
currentBots.remove(bot);
|
||||
}
|
||||
|
||||
Player* RandomPlayerbotMgr::GetRandomPlayer()
|
||||
@@ -3497,7 +3509,8 @@ void RandomPlayerbotMgr::Remove(Player* bot)
|
||||
stmt->SetData(1, owner.GetCounter());
|
||||
PlayerbotsDatabase.Execute(stmt);
|
||||
|
||||
eventCache[owner.GetCounter()].clear();
|
||||
uint32 botId = owner.GetCounter();
|
||||
eventCache.erase(botId);
|
||||
|
||||
LogoutPlayerBot(owner);
|
||||
}
|
||||
@@ -3514,7 +3527,7 @@ CreatureData const* RandomPlayerbotMgr::GetCreatureDataByEntry(uint32 entry)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
|
||||
ObjectGuid RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
|
||||
{
|
||||
ObjectGuid battleMasterGUID = ObjectGuid::Empty;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "NewRpgInfo.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "GameTime.h"
|
||||
|
||||
struct BattlegroundInfo
|
||||
{
|
||||
@@ -45,25 +46,20 @@ class ChatHandler;
|
||||
class PerformanceMonitorOperation;
|
||||
class WorldLocation;
|
||||
|
||||
class CachedEvent
|
||||
struct CachedEvent
|
||||
{
|
||||
public:
|
||||
CachedEvent() : value(0), lastChangeTime(0), validIn(0), data("") {}
|
||||
CachedEvent(const CachedEvent& other)
|
||||
: value(other.value), lastChangeTime(other.lastChangeTime), validIn(other.validIn), data(other.data)
|
||||
{
|
||||
}
|
||||
CachedEvent(uint32 value, uint32 lastChangeTime, uint32 validIn, std::string const data = "")
|
||||
: value(value), lastChangeTime(lastChangeTime), validIn(validIn), data(data)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsEmpty() { return !lastChangeTime; }
|
||||
|
||||
uint32 value;
|
||||
uint32 lastChangeTime;
|
||||
uint32 validIn;
|
||||
uint32 value = 0;
|
||||
uint32 lastChangeTime = 0;
|
||||
uint32 validIn = 0;
|
||||
std::string data;
|
||||
|
||||
bool IsEmpty() const { return !lastChangeTime; }
|
||||
};
|
||||
|
||||
struct BotEventCache
|
||||
{
|
||||
bool loaded = false;
|
||||
std::unordered_map<std::string, CachedEvent> events;
|
||||
};
|
||||
|
||||
// https://gist.github.com/bradley219/5373998
|
||||
@@ -139,13 +135,13 @@ public:
|
||||
void Revive(Player* player);
|
||||
void ChangeStrategy(Player* player);
|
||||
void ChangeStrategyOnce(Player* player);
|
||||
uint32 GetValue(Player* bot, std::string const type);
|
||||
uint32 GetValue(uint32 bot, std::string const type);
|
||||
std::string const GetData(uint32 bot, std::string const type);
|
||||
void SetValue(uint32 bot, std::string const type, uint32 value, std::string const data = "");
|
||||
void SetValue(Player* bot, std::string const type, uint32 value, std::string const data = "");
|
||||
uint32 GetValue(Player* bot, std::string const& type);
|
||||
uint32 GetValue(uint32 bot, std::string const& type);
|
||||
std::string GetData(uint32 bot, std::string const& type);
|
||||
void SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data = "");
|
||||
void SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data = "");
|
||||
void Remove(Player* bot);
|
||||
ObjectGuid const GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
|
||||
ObjectGuid GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
|
||||
CreatureData const* GetCreatureDataByEntry(uint32 entry);
|
||||
void LoadBattleMastersCache();
|
||||
std::map<uint32, std::map<uint32, BattlegroundInfo>> BattlegroundData;
|
||||
@@ -203,10 +199,11 @@ private:
|
||||
bool _isBotInitializing = true;
|
||||
bool _isBotLogging = true;
|
||||
NewRpgStatistic rpgStasticTotal;
|
||||
uint32 GetEventValue(uint32 bot, std::string const event);
|
||||
std::string const GetEventData(uint32 bot, std::string const event);
|
||||
uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
|
||||
std::string const data = "");
|
||||
CachedEvent* FindEvent(uint32 bot, std::string const& event);
|
||||
uint32 GetEventValue(uint32 bot, std::string const& event);
|
||||
std::string GetEventData(uint32 bot, std::string const& event);
|
||||
uint32 SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
|
||||
std::string const& data = "");
|
||||
void GetBots();
|
||||
std::vector<uint32> GetBgBots(uint32 bracket);
|
||||
time_t BgCheckTimer;
|
||||
@@ -228,7 +225,7 @@ private:
|
||||
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
|
||||
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
|
||||
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache;
|
||||
std::map<uint32, std::map<std::string, CachedEvent>> eventCache;
|
||||
std::unordered_map<uint32, BotEventCache> eventCache;
|
||||
std::list<uint32> currentBots;
|
||||
uint32 bgBotsCount;
|
||||
uint32 playersLevel;
|
||||
@@ -238,6 +235,7 @@ private:
|
||||
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
|
||||
|
||||
//void ScaleBotActivity(); // Deprecated function
|
||||
static inline uint32 NowSeconds() { return static_cast<uint32>(GameTime::GetGameTime().count()); }
|
||||
};
|
||||
|
||||
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()
|
||||
|
||||
45
src/database/PlayerbotSpellCache.cpp
Normal file
45
src/database/PlayerbotSpellCache.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "PlayerbotSpellCache.h"
|
||||
|
||||
void PlayerbotSpellCache::Initialize()
|
||||
{
|
||||
LOG_INFO("playerbots",
|
||||
"Playerbots: ListSpellsAction caches initialized");
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
skillSpells[skillLine->Spell] = skillLine;
|
||||
}
|
||||
|
||||
// Fill the vendorItems cache once from the world database.
|
||||
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
|
||||
if (results)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
int32 entry = fields[0].Get<int32>();
|
||||
if (entry <= 0)
|
||||
continue;
|
||||
|
||||
vendorItems.insert(static_cast<uint32>(entry));
|
||||
}
|
||||
while (results->NextRow());
|
||||
}
|
||||
|
||||
LOG_DEBUG("playerbots",
|
||||
"ListSpellsAction: initialized caches (skillSpells={}, vendorItems={}).",
|
||||
skillSpells.size(), vendorItems.size());
|
||||
}
|
||||
|
||||
SkillLineAbilityEntry const* PlayerbotSpellCache::GetSkillLine(uint32 spellId) const
|
||||
{
|
||||
auto itr = skillSpells.find(spellId);
|
||||
if (itr != skillSpells.end())
|
||||
return itr->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PlayerbotSpellCache::IsItemBuyable(uint32 itemId) const
|
||||
{
|
||||
return vendorItems.find(itemId) != vendorItems.end();
|
||||
}
|
||||
34
src/database/PlayerbotSpellCache.h
Normal file
34
src/database/PlayerbotSpellCache.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_PLAYERBOTSPELLCACHE_H
|
||||
#define _PLAYERBOT_PLAYERBOTSPELLCACHE_H
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
class PlayerbotSpellCache
|
||||
{
|
||||
public:
|
||||
static PlayerbotSpellCache* Instance()
|
||||
{
|
||||
static PlayerbotSpellCache instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void Initialize(); // call once on startup
|
||||
|
||||
SkillLineAbilityEntry const* GetSkillLine(uint32 spellId) const;
|
||||
bool IsItemBuyable(uint32 itemId) const;
|
||||
|
||||
private:
|
||||
PlayerbotSpellCache() = default;
|
||||
|
||||
std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
|
||||
std::set<uint32> vendorItems;
|
||||
};
|
||||
|
||||
#define sPlayerbotSpellCache PlayerbotSpellCache::Instance()
|
||||
|
||||
#endif
|
||||
@@ -4099,6 +4099,7 @@ void PlayerbotFactory::InitImmersive()
|
||||
|
||||
void PlayerbotFactory::InitArenaTeam()
|
||||
{
|
||||
|
||||
if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId()))
|
||||
return;
|
||||
|
||||
@@ -4185,10 +4186,34 @@ void PlayerbotFactory::InitArenaTeam()
|
||||
|
||||
if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need?
|
||||
{
|
||||
// Add bot to arena team
|
||||
arenateam->AddMember(bot->GetGUID());
|
||||
arenateam->SaveToDB();
|
||||
|
||||
// Only synchronize ratings once the team is full (avoid redundant work)
|
||||
// The captain was added with incorrect ratings when the team was created,
|
||||
// so we fix everyone's ratings once the roster is complete
|
||||
if (arenateam->GetMembersSize() >= (uint32)arenateam->GetType())
|
||||
{
|
||||
uint32 teamRating = arenateam->GetRating();
|
||||
|
||||
// Use SetRatingForAll to align all members with team rating
|
||||
arenateam->SetRatingForAll(teamRating);
|
||||
|
||||
// For bot-only teams, keep MMR synchronized with team rating
|
||||
// This ensures matchmaking reflects the artificial team strength (1000-2000 range)
|
||||
// instead of being influenced by the global CONFIG_ARENA_START_MATCHMAKER_RATING
|
||||
for (auto& member : arenateam->GetMembers())
|
||||
{
|
||||
// Set MMR to match personal rating (which already matches team rating)
|
||||
member.MatchMakerRating = member.PersonalRating;
|
||||
member.MaxMMR = std::max(member.MaxMMR, member.PersonalRating);
|
||||
}
|
||||
// Force save all member data to database
|
||||
arenateam->SaveToDB(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arenateams.erase(arenateams.begin() + index);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,5 @@ bool AcceptResurrectAction::Execute(Event event)
|
||||
packet << uint8(1); // accept
|
||||
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
|
||||
|
||||
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ public:
|
||||
creators["shoot"] = &ActionContext::shoot;
|
||||
creators["follow"] = &ActionContext::follow;
|
||||
creators["move from group"] = &ActionContext::move_from_group;
|
||||
creators["flee to master"] = &ActionContext::flee_to_master;
|
||||
creators["flee to group leader"] = &ActionContext::flee_to_group_leader;
|
||||
creators["runaway"] = &ActionContext::runaway;
|
||||
creators["stay"] = &ActionContext::stay;
|
||||
creators["sit"] = &ActionContext::sit;
|
||||
@@ -318,7 +318,7 @@ private:
|
||||
static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); }
|
||||
static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); }
|
||||
static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); }
|
||||
static Action* flee_to_master(PlayerbotAI* botAI) { return new FleeToMasterAction(botAI); }
|
||||
static Action* flee_to_group_leader(PlayerbotAI* botAI) { return new FleeToGroupLeaderAction(botAI); }
|
||||
static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); }
|
||||
static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); }
|
||||
static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); }
|
||||
|
||||
@@ -15,20 +15,19 @@
|
||||
#include "SharedDefines.h"
|
||||
#include "Unit.h"
|
||||
|
||||
bool AttackAction::Execute(Event event)
|
||||
bool AttackAction::Execute(Event /*event*/)
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!target->IsInWorld())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Attack(target);
|
||||
}
|
||||
|
||||
bool AttackMyTargetAction::Execute(Event event)
|
||||
bool AttackMyTargetAction::Execute(Event /*event*/)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
@@ -51,7 +50,7 @@ bool AttackMyTargetAction::Execute(Event event)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
||||
{
|
||||
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
|
||||
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
|
||||
@@ -81,12 +80,15 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
|
||||
sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId()))
|
||||
&& (target->IsPlayer() || target->IsPet()))
|
||||
// Check if bot OR target is in prohibited zone/area (skip for duels)
|
||||
if ((target->IsPlayer() || target->IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != target) &&
|
||||
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
|
||||
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
||||
@@ -98,6 +100,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,6 +108,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -112,6 +116,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,6 +124,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -154,9 +160,8 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
}
|
||||
|
||||
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
||||
{
|
||||
sServerFacade->SetFacingTo(bot, target);
|
||||
}
|
||||
|
||||
botAI->ChangeEngine(BOT_STATE_COMBAT);
|
||||
|
||||
bot->Attack(target, shouldMelee);
|
||||
@@ -186,4 +191,4 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
|
||||
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
|
||||
|
||||
bool AttackDuelOpponentAction::Execute(Event event) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||
|
||||
@@ -224,42 +224,36 @@ bool BuyAction::Execute(Event event)
|
||||
|
||||
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
|
||||
{
|
||||
uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1);
|
||||
|
||||
if (!tItems)
|
||||
if (!tItems || !proto)
|
||||
return false;
|
||||
|
||||
uint32 itemId = proto->ItemId;
|
||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++)
|
||||
uint32 oldCount = bot->GetItemCount(itemId, false);
|
||||
|
||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot)
|
||||
{
|
||||
if (tItems->GetItem(slot)->item == itemId)
|
||||
if (tItems->GetItem(slot)->item != itemId)
|
||||
continue;
|
||||
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(10000000);
|
||||
|
||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(botMoney);
|
||||
|
||||
uint32 newCount = bot->GetItemCount(itemId, false);
|
||||
if (newCount > oldCount)
|
||||
{
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
bot->SetMoney(10000000);
|
||||
}
|
||||
|
||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
bot->SetMoney(botMoney);
|
||||
}
|
||||
|
||||
if (oldCount <
|
||||
AI_VALUE2(
|
||||
uint32, "item count",
|
||||
proto->Name1)) // BuyItem Always returns false (unless unique) so we have to check the item counts.
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
std::ostringstream out;
|
||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -122,21 +122,18 @@ bool CheckMountStateAction::Execute(Event /*event*/)
|
||||
bool shouldMount = false;
|
||||
|
||||
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||||
bool masterInCombat = master && master->IsInCombat();
|
||||
|
||||
if (currentTarget && (bot->IsInCombat() || masterInCombat))
|
||||
if (currentTarget)
|
||||
{
|
||||
// Use target-based logic if bot is in combat OR master is in combat and needs assistance
|
||||
float dismountDistance = CalculateDismountDistance();
|
||||
float mountDistance = CalculateMountDistance();
|
||||
float combatReach = bot->GetCombatReach() + currentTarget->GetCombatReach();
|
||||
float distanceToTarget = bot->GetExactDist(currentTarget);
|
||||
|
||||
shouldDismount = (distanceToTarget <= dismountDistance);
|
||||
shouldMount = (distanceToTarget > mountDistance);
|
||||
shouldDismount = (distanceToTarget <= dismountDistance + combatReach);
|
||||
shouldMount = (distanceToTarget > mountDistance + combatReach);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If neither bot nor master is in combat, prioritize master-following
|
||||
shouldMount = true;
|
||||
}
|
||||
|
||||
@@ -163,19 +160,10 @@ bool CheckMountStateAction::Execute(Event /*event*/)
|
||||
|
||||
else if (ShouldDismountForMaster(master) && bot->IsMounted())
|
||||
{
|
||||
// If master dismounted, stay mounted until close enough to assist
|
||||
if (StayMountedToCloseDistance())
|
||||
return false;
|
||||
|
||||
Dismount();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mount up to close distance to master if beneficial - allow mounting even if master is in combat
|
||||
// as long as the bot itself is not in combat and has no attackers
|
||||
else if (!bot->IsMounted() && noAttackers && !bot->IsInCombat() && ShouldMountToCloseDistance())
|
||||
return Mount();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -409,50 +397,6 @@ bool CheckMountStateAction::TryRandomMountFiltered(const std::map<int32, std::ve
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckMountStateAction::StayMountedToCloseDistance() const
|
||||
{
|
||||
// Keep the bot mounted while closing distance to a recently dismounted master.
|
||||
// Rationale: if the master dismounts far away, immediately dismounting slows the bot down
|
||||
// and delays assistance. Instead, remain mounted until within reasonable proximity
|
||||
// of the master, then dismount to help.
|
||||
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
float distToMaster = sServerFacade->GetDistance2d(bot, master);
|
||||
|
||||
// If master is in combat, dismount at combat assist range to help immediately
|
||||
if (master->IsInCombat())
|
||||
{
|
||||
float assistRange = CalculateDismountDistance();
|
||||
return distToMaster > assistRange;
|
||||
}
|
||||
|
||||
// If master is not in combat, use smaller proximity range for general following
|
||||
float masterProximityRange = 10.0f; // Close enough to be near master but not attack range
|
||||
return distToMaster > masterProximityRange;
|
||||
}
|
||||
|
||||
bool CheckMountStateAction::ShouldMountToCloseDistance() const
|
||||
{
|
||||
// Mount up to close distance to master if beneficial
|
||||
// Uses the same logic as CalculateMountDistance() which already considers the 2-second mount cast time
|
||||
// This handles cases where master is in combat but bot isn't, and bot needs to mount to reach master
|
||||
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
// Only mount to close distance when actively following
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return false;
|
||||
|
||||
float distToMaster = sServerFacade->GetDistance2d(bot, master);
|
||||
float mountDistance = CalculateMountDistance();
|
||||
|
||||
// Mount if distance is greater than the calculated mount distance threshold
|
||||
return distToMaster > mountDistance;
|
||||
}
|
||||
|
||||
float CheckMountStateAction::CalculateDismountDistance() const
|
||||
{
|
||||
// Warrior bots should dismount far enough to charge (because it's important for generating some initial rage),
|
||||
|
||||
@@ -60,8 +60,6 @@ private:
|
||||
bool TryPreferredMount(Player* master) const;
|
||||
uint32 GetMountType(Player* master) const;
|
||||
bool TryRandomMountFiltered(const std::map<int32, std::vector<uint32>>& spells, int32 masterSpeed) const;
|
||||
bool StayMountedToCloseDistance() const;
|
||||
bool ShouldMountToCloseDistance() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -311,7 +311,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target)
|
||||
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||
{
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
Player* gmaster = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* realMaster = botAI->GetMaster();
|
||||
AiObjectContext* context = botAI->GetAiObjectContext();
|
||||
|
||||
@@ -327,30 +327,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gmaster || bot == gmaster)
|
||||
if (!groupLeader || bot == groupLeader)
|
||||
return true;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return true;
|
||||
|
||||
if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||
if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||
return false;
|
||||
|
||||
Formation* formation = AI_VALUE(Formation*, "formation");
|
||||
float distance = gmaster->GetDistance2d(pos.getX(), pos.getY());
|
||||
float distance = groupLeader->GetDistance2d(pos.getX(), pos.getY());
|
||||
|
||||
if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
|
||||
{
|
||||
Player* player = gmaster;
|
||||
if (gmaster && !gmaster->isMoving() ||
|
||||
Player* player = groupLeader;
|
||||
if (groupLeader && !groupLeader->isMoving() ||
|
||||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((inDungeon || !gmaster->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == gmaster && distance > 5.0f)
|
||||
if ((inDungeon || !groupLeader->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == groupLeader && distance > 5.0f)
|
||||
return false;
|
||||
|
||||
if (!gmaster->isMoving() && distance < 25.0f)
|
||||
if (!groupLeader->isMoving() && distance < 25.0f)
|
||||
return true;
|
||||
|
||||
if (distance < formation->GetMaxDistance())
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "LootObjectStack.h"
|
||||
#include "NewRpgStrategy.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
#include "PossibleRpgTargetsValue.h"
|
||||
#include "PvpTriggers.h"
|
||||
#include "ServerFacade.h"
|
||||
@@ -87,9 +88,7 @@ bool DropTargetAction::Execute(Event event)
|
||||
{
|
||||
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
|
||||
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
||||
{
|
||||
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
|
||||
}
|
||||
}
|
||||
bot->AttackStop();
|
||||
|
||||
@@ -142,6 +141,23 @@ bool AttackRtiTargetAction::Execute(Event event)
|
||||
{
|
||||
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
|
||||
|
||||
// Fallback: if the "rti target" value did not resolve a valid unit yet,
|
||||
// try to resolve the raid icon directly from the group.
|
||||
if (!rtiTarget)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
std::string const rti = AI_VALUE(std::string, "rti");
|
||||
int32 const index = RtiTargetValue::GetRtiIndex(rti);
|
||||
if (index >= 0)
|
||||
{
|
||||
ObjectGuid const guid = group->GetTargetIcon(index);
|
||||
if (!guid.IsEmpty())
|
||||
rtiTarget = botAI->GetUnit(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
|
||||
{
|
||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
|
||||
@@ -153,9 +169,7 @@ bool AttackRtiTargetAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
botAI->TellError("I dont see my rti attack target");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
|
||||
void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
|
||||
{
|
||||
// Tell the master where we are going.
|
||||
if (!bot->GetGroup() || (botAI->GetGroupMaster() == bot))
|
||||
if (!bot->GetGroup() || (botAI->GetGroupLeader() == bot))
|
||||
ReportTravelTarget(newTarget, oldTarget);
|
||||
|
||||
// If we are heading to a creature/npc clear it from the ignore list.
|
||||
|
||||
@@ -70,7 +70,7 @@ bool FollowAction::isUseful()
|
||||
if (!target.empty())
|
||||
fTarget = AI_VALUE(Unit*, target);
|
||||
else
|
||||
fTarget = AI_VALUE(Unit*, "master target");
|
||||
fTarget = AI_VALUE(Unit*, "group leader");
|
||||
|
||||
if (fTarget)
|
||||
{
|
||||
@@ -114,9 +114,9 @@ bool FollowAction::CanDeadFollow(Unit* target)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeToMasterAction::Execute(Event event)
|
||||
bool FleeToGroupLeaderAction::Execute(Event event)
|
||||
{
|
||||
Unit* fTarget = AI_VALUE(Unit*, "master target");
|
||||
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||
bool canFollow = Follow(fTarget);
|
||||
if (!canFollow)
|
||||
{
|
||||
@@ -146,22 +146,22 @@ bool FleeToMasterAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeToMasterAction::isUseful()
|
||||
bool FleeToGroupLeaderAction::isUseful()
|
||||
{
|
||||
if (!botAI->GetGroupMaster())
|
||||
if (!botAI->GetGroupLeader())
|
||||
return false;
|
||||
|
||||
if (botAI->GetGroupMaster() == bot)
|
||||
if (botAI->GetGroupLeader() == bot)
|
||||
return false;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (target && botAI->GetGroupMaster()->GetTarget() == target->GetGUID())
|
||||
if (target && botAI->GetGroupLeader()->GetTarget() == target->GetGUID())
|
||||
return false;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return false;
|
||||
|
||||
Unit* fTarget = AI_VALUE(Unit*, "master target");
|
||||
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||
|
||||
if (!CanDeadFollow(fTarget))
|
||||
return false;
|
||||
|
||||
@@ -20,10 +20,10 @@ public:
|
||||
bool CanDeadFollow(Unit* target);
|
||||
};
|
||||
|
||||
class FleeToMasterAction : public FollowAction
|
||||
class FleeToGroupLeaderAction : public FollowAction
|
||||
{
|
||||
public:
|
||||
FleeToMasterAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to master") {}
|
||||
FleeToGroupLeaderAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to group leader") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
@@ -58,6 +58,14 @@ Player* GuidManageAction::GetPlayer(Event event)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GuidManageAction::SendPacket(WorldPacket const& packet)
|
||||
{
|
||||
// make a heap copy because QueuePacket takes ownership
|
||||
WorldPacket* data = new WorldPacket(packet);
|
||||
|
||||
bot->GetSession()->QueuePacket(data);
|
||||
}
|
||||
|
||||
bool GuidManageAction::Execute(Event event)
|
||||
{
|
||||
Player* player = GetPlayer(event);
|
||||
@@ -84,12 +92,6 @@ bool GuildInviteAction::isUseful()
|
||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_INVITE);
|
||||
}
|
||||
|
||||
void GuildInviteAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildInviteByName data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildInviteOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildInviteAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return !member->GetGuildId() && (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) ||
|
||||
@@ -101,12 +103,6 @@ bool GuildPromoteAction::isUseful()
|
||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_PROMOTE);
|
||||
}
|
||||
|
||||
void GuildPromoteAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildPromoteMember data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildPromoteOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildPromoteAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member) - 1;
|
||||
@@ -117,12 +113,6 @@ bool GuildDemoteAction::isUseful()
|
||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_DEMOTE);
|
||||
}
|
||||
|
||||
void GuildDemoteAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildDemoteMember data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildDemoteOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildDemoteAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
|
||||
@@ -133,12 +123,6 @@ bool GuildRemoveAction::isUseful()
|
||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_REMOVE);
|
||||
}
|
||||
|
||||
void GuildRemoveAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildOfficerRemoveMember data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildRemoveOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildRemoveAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
bool isUseful() override { return false; }
|
||||
|
||||
protected:
|
||||
virtual void SendPacket(WorldPacket data){};
|
||||
virtual void SendPacket(WorldPacket const& packet);
|
||||
virtual Player* GetPlayer(Event event);
|
||||
virtual bool PlayerIsValid(Player* member);
|
||||
virtual uint8 GetRankId(Player* member);
|
||||
@@ -44,7 +44,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
@@ -59,7 +58,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
@@ -74,7 +72,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
@@ -89,7 +86,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include "Event.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Log.h"
|
||||
#include "PlayerbotOperations.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "ServerFacade.h"
|
||||
|
||||
bool InviteToGroupAction::Invite(Player* inviter, Player* player)
|
||||
@@ -27,7 +29,10 @@ bool InviteToGroupAction::Invite(Player* inviter, Player* player)
|
||||
{
|
||||
if (GET_PLAYERBOT_AI(player) && !GET_PLAYERBOT_AI(player)->IsRealPlayer())
|
||||
if (!group->isRaidGroup() && group->GetMembersCount() > 4)
|
||||
group->ConvertToRaid();
|
||||
{
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(inviter->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
}
|
||||
|
||||
WorldPacket p;
|
||||
@@ -89,7 +94,10 @@ bool InviteNearbyToGroupAction::Execute(Event event)
|
||||
// When inviting the 5th member of the group convert to raid for future invites.
|
||||
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
||||
bot->GetGroup()->GetMembersCount() > 3)
|
||||
group->ConvertToRaid();
|
||||
{
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->inviteChat && sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
{
|
||||
@@ -133,7 +141,7 @@ bool InviteNearbyToGroupAction::isUseful()
|
||||
if (group->isRaidGroup() && group->IsFull())
|
||||
return false;
|
||||
|
||||
if (botAI->GetGroupMaster() != bot)
|
||||
if (botAI->GetGroupLeader() != bot)
|
||||
return false;
|
||||
|
||||
uint32 memberCount = group->GetMembersCount();
|
||||
@@ -221,7 +229,8 @@ bool InviteGuildToGroupAction::Execute(Event event)
|
||||
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
||||
bot->GetGroup()->GetMembersCount() > 3)
|
||||
{
|
||||
group->ConvertToRaid();
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->inviteChat &&
|
||||
@@ -362,7 +371,10 @@ bool LfgAction::Execute(Event event)
|
||||
if (param.empty() || param == "5" || group->isRaidGroup())
|
||||
return false; // Group or raid is full so stop trying.
|
||||
else
|
||||
group->ConvertToRaid(); // We want a raid but are in a group so convert and continue.
|
||||
{
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(requester->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
}
|
||||
|
||||
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
|
||||
|
||||
@@ -109,22 +109,22 @@ bool LeaveFarAwayAction::isUseful()
|
||||
if (!bot->GetGroup())
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* trueMaster = botAI->GetMaster();
|
||||
if (!master || (bot == master && !botAI->IsRealPlayer()))
|
||||
if (!groupLeader || (bot == groupLeader && !botAI->IsRealPlayer()))
|
||||
return false;
|
||||
|
||||
PlayerbotAI* masterBotAI = nullptr;
|
||||
if (master)
|
||||
masterBotAI = GET_PLAYERBOT_AI(master);
|
||||
if (master && !masterBotAI)
|
||||
PlayerbotAI* groupLeaderBotAI = nullptr;
|
||||
if (groupLeader)
|
||||
groupLeaderBotAI = GET_PLAYERBOT_AI(groupLeader);
|
||||
if (groupLeader && !groupLeaderBotAI)
|
||||
return false;
|
||||
|
||||
if (trueMaster && !GET_PLAYERBOT_AI(trueMaster))
|
||||
return false;
|
||||
|
||||
if (botAI->IsAlt() &&
|
||||
(!masterBotAI || masterBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player master.
|
||||
(!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player groupLeader.
|
||||
return false;
|
||||
|
||||
if (botAI->GetGrouperType() == GrouperType::SOLO)
|
||||
@@ -138,19 +138,19 @@ bool LeaveFarAwayAction::isUseful()
|
||||
if (dCount > 4 && !botAI->HasRealPlayerMaster())
|
||||
return true;
|
||||
|
||||
if (bot->GetGuildId() == master->GetGuildId())
|
||||
if (bot->GetGuildId() == groupLeader->GetGuildId())
|
||||
{
|
||||
if (bot->GetLevel() > master->GetLevel() + 5)
|
||||
if (bot->GetLevel() > groupLeader->GetLevel() + 5)
|
||||
{
|
||||
if (AI_VALUE(bool, "should get money"))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (abs(int32(master->GetLevel() - bot->GetLevel())) > 4)
|
||||
if (abs(int32(groupLeader->GetLevel() - bot->GetLevel())) > 4)
|
||||
return true;
|
||||
|
||||
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance2d(master) >= 2 * sPlayerbotAIConfig->rpgDistance)
|
||||
if (bot->GetMapId() != groupLeader->GetMapId() || bot->GetDistance2d(groupLeader) >= 2 * sPlayerbotAIConfig->rpgDistance)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,90 +7,43 @@
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotSpellCache.h"
|
||||
|
||||
std::map<uint32, SkillLineAbilityEntry const*> ListSpellsAction::skillSpells;
|
||||
std::set<uint32> ListSpellsAction::vendorItems;
|
||||
using SpellListEntry = std::pair<uint32, std::string>;
|
||||
|
||||
bool CompareSpells(const std::pair<uint32, std::string>& s1, const std::pair<uint32, std::string>& s2)
|
||||
// CHANGE: Simplified and cheap comparator used in MapUpdater worker thread.
|
||||
// It now avoids scanning the entire SkillLineAbilityStore for each comparison
|
||||
// and only relies on spell school and spell name to keep sorting fast and bounded.
|
||||
// lhs = the left element, rhs = the right element.
|
||||
static bool CompareSpells(SpellListEntry const& lhSpell, SpellListEntry const& rhSpell)
|
||||
{
|
||||
SpellInfo const* si1 = sSpellMgr->GetSpellInfo(s1.first);
|
||||
SpellInfo const* si2 = sSpellMgr->GetSpellInfo(s2.first);
|
||||
if (!si1 || !si2)
|
||||
SpellInfo const* lhSpellInfo = sSpellMgr->GetSpellInfo(lhSpell.first);
|
||||
SpellInfo const* rhSpellInfo = sSpellMgr->GetSpellInfo(rhSpell.first);
|
||||
|
||||
if (!lhSpellInfo || !rhSpellInfo)
|
||||
{
|
||||
LOG_ERROR("playerbots", "SpellInfo missing. {} {}", s1.first, s2.first);
|
||||
return false;
|
||||
}
|
||||
uint32 p1 = si1->SchoolMask * 20000;
|
||||
uint32 p2 = si2->SchoolMask * 20000;
|
||||
|
||||
uint32 skill1 = 0, skill2 = 0;
|
||||
uint32 skillValue1 = 0, skillValue2 = 0;
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
{
|
||||
if (skillLine->Spell == s1.first)
|
||||
{
|
||||
skill1 = skillLine->SkillLine;
|
||||
skillValue1 = skillLine->TrivialSkillLineRankLow;
|
||||
}
|
||||
|
||||
if (skillLine->Spell == s2.first)
|
||||
{
|
||||
skill2 = skillLine->SkillLine;
|
||||
skillValue2 = skillLine->TrivialSkillLineRankLow;
|
||||
}
|
||||
}
|
||||
|
||||
if (skill1 && skill2)
|
||||
break;
|
||||
LOG_ERROR("playerbots", "SpellInfo missing for spell {} or {}", lhSpell.first, rhSpell.first);
|
||||
// Fallback: order by spell id to keep comparator strict and deterministic.
|
||||
return lhSpell.first < rhSpell.first;
|
||||
}
|
||||
|
||||
p1 += skill1 * 500;
|
||||
p2 += skill2 * 500;
|
||||
uint32 lhsKey = lhSpellInfo->SchoolMask;
|
||||
uint32 rhsKey = rhSpellInfo->SchoolMask;
|
||||
|
||||
p1 += skillValue1;
|
||||
p2 += skillValue2;
|
||||
|
||||
if (p1 == p2)
|
||||
if (lhsKey == rhsKey)
|
||||
{
|
||||
return strcmp(si1->SpellName[0], si2->SpellName[0]) > 0;
|
||||
}
|
||||
// Defensive check: if DBC data is broken and spell names are nullptr,
|
||||
// fall back to id ordering instead of risking a crash in std::strcmp.
|
||||
if (!lhSpellInfo->SpellName[0] || !rhSpellInfo->SpellName[0])
|
||||
return lhSpell.first < rhSpell.first;
|
||||
|
||||
return p1 > p2;
|
||||
return std::strcmp(lhSpellInfo->SpellName[0], rhSpellInfo->SpellName[0]) > 0;
|
||||
}
|
||||
return lhsKey > rhsKey;
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::string filter)
|
||||
{
|
||||
if (skillSpells.empty())
|
||||
{
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
skillSpells[skillLine->Spell] = skillLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendorItems.empty())
|
||||
{
|
||||
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
|
||||
if (results)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
int32 entry = fields[0].Get<int32>();
|
||||
if (entry <= 0)
|
||||
continue;
|
||||
|
||||
vendorItems.insert(entry);
|
||||
} while (results->NextRow());
|
||||
}
|
||||
}
|
||||
|
||||
std::ostringstream posOut;
|
||||
std::ostringstream negOut;
|
||||
|
||||
uint32 skill = 0;
|
||||
|
||||
std::vector<std::string> ss = split(filter, ' ');
|
||||
@@ -99,13 +52,15 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
skill = chat->parseSkill(ss[0]);
|
||||
if (skill != SKILL_NONE)
|
||||
{
|
||||
filter = ss.size() > 1 ? filter = ss[1] : "";
|
||||
filter = ss.size() > 1 ? ss[1] : "";
|
||||
}
|
||||
|
||||
if (ss[0] == "first" && ss[1] == "aid")
|
||||
// Guard access to ss[1]/ss[2] to avoid out-of-bounds
|
||||
// when the player only types "first" without "aid".
|
||||
if (ss[0] == "first" && ss.size() > 1 && ss[1] == "aid")
|
||||
{
|
||||
skill = SKILL_FIRST_AID;
|
||||
filter = ss.size() > 2 ? filter = ss[2] : "";
|
||||
filter = ss.size() > 2 ? ss[2] : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,26 +70,57 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
|
||||
uint32 minLevel = 0;
|
||||
uint32 maxLevel = 0;
|
||||
if (filter.find("-") != std::string::npos)
|
||||
if (filter.find('-') != std::string::npos)
|
||||
{
|
||||
std::vector<std::string> ff = split(filter, '-');
|
||||
minLevel = atoi(ff[0].c_str());
|
||||
maxLevel = atoi(ff[1].c_str());
|
||||
filter = "";
|
||||
if (ff.size() >= 2)
|
||||
{
|
||||
minLevel = std::atoi(ff[0].c_str());
|
||||
maxLevel = std::atoi(ff[1].c_str());
|
||||
if (minLevel > maxLevel)
|
||||
std::swap(minLevel, maxLevel);
|
||||
}
|
||||
filter.clear();
|
||||
}
|
||||
|
||||
bool craftableOnly = false;
|
||||
if (filter.find("+") != std::string::npos)
|
||||
bool canCraftNow = false;
|
||||
if (filter.find('+') != std::string::npos)
|
||||
{
|
||||
craftableOnly = true;
|
||||
canCraftNow = true;
|
||||
|
||||
// Support "+<skill>" syntax (e.g. "spells +tailoring" or "spells tailoring+").
|
||||
// If no explicit skill was detected yet, try to parse the filter (without '+')
|
||||
// as a profession/skill name so that craftable-only filters still work with skills.
|
||||
if (skill == SKILL_NONE)
|
||||
{
|
||||
std::string skillFilter = filter;
|
||||
|
||||
// Remove '+' before trying to interpret the first token as a skill name.
|
||||
skillFilter.erase(remove(skillFilter.begin(), skillFilter.end(), '+'), skillFilter.end());
|
||||
|
||||
std::vector<std::string> skillTokens = split(skillFilter, ' ');
|
||||
if (!skillTokens.empty())
|
||||
{
|
||||
uint32 parsedSkill = chat->parseSkill(skillTokens[0]);
|
||||
if (parsedSkill != SKILL_NONE)
|
||||
{
|
||||
skill = parsedSkill;
|
||||
|
||||
// Any remaining text after the skill token becomes the "name" filter
|
||||
// (e.g. "spells +tailoring cloth" -> skill = tailoring, filter = "cloth").
|
||||
filter = skillTokens.size() > 1 ? skillTokens[1] : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally remove '+' from the filter that will be used for name/range parsing.
|
||||
filter.erase(remove(filter.begin(), filter.end(), '+'), filter.end());
|
||||
}
|
||||
|
||||
uint32 slot = chat->parseSlot(filter);
|
||||
if (slot != EQUIPMENT_SLOT_END)
|
||||
filter = "";
|
||||
filter.clear();
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> spells;
|
||||
std::vector<SpellListEntry> spells;
|
||||
for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr)
|
||||
{
|
||||
if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active)
|
||||
@@ -150,7 +136,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
if (spellInfo->IsPassive())
|
||||
continue;
|
||||
|
||||
SkillLineAbilityEntry const* skillLine = skillSpells[itr->first];
|
||||
SkillLineAbilityEntry const* skillLine = sPlayerbotSpellCache->GetSkillLine(itr->first);
|
||||
if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill))
|
||||
continue;
|
||||
|
||||
@@ -162,7 +148,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
continue;
|
||||
|
||||
bool first = true;
|
||||
int32 craftCount = -1;
|
||||
int32 craftsPossible = -1;
|
||||
std::ostringstream materials;
|
||||
for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x)
|
||||
{
|
||||
@@ -189,12 +175,12 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
|
||||
FindItemByIdVisitor visitor(itemid);
|
||||
uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor);
|
||||
bool buyable = (vendorItems.find(itemid) != vendorItems.end());
|
||||
bool buyable = sPlayerbotSpellCache->IsItemBuyable(itemid);
|
||||
if (!buyable)
|
||||
{
|
||||
uint32 craftable = reagentsInInventory / reagentsRequired;
|
||||
if (craftCount < 0 || craftCount > craftable)
|
||||
craftCount = craftable;
|
||||
if (craftsPossible < 0 || craftsPossible > static_cast<int32>(craftable))
|
||||
craftsPossible = static_cast<int32>(craftable);
|
||||
}
|
||||
|
||||
if (reagentsInInventory)
|
||||
@@ -205,8 +191,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
}
|
||||
}
|
||||
|
||||
if (craftCount < 0)
|
||||
craftCount = 0;
|
||||
if (craftsPossible < 0)
|
||||
craftsPossible = 0;
|
||||
|
||||
std::ostringstream out;
|
||||
bool filtered = false;
|
||||
@@ -218,8 +204,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
{
|
||||
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(spellInfo->Effects[i].ItemType))
|
||||
{
|
||||
if (craftCount)
|
||||
out << "|cffffff00(x" << craftCount << ")|r ";
|
||||
if (craftsPossible)
|
||||
out << "|cffffff00(x" << craftsPossible << ")|r ";
|
||||
|
||||
out << chat->FormatItem(proto);
|
||||
|
||||
@@ -246,7 +232,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
if (filtered)
|
||||
continue;
|
||||
|
||||
if (craftableOnly && !craftCount)
|
||||
if (canCraftNow && !craftsPossible)
|
||||
continue;
|
||||
|
||||
out << materials.str();
|
||||
@@ -275,10 +261,9 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
continue;
|
||||
|
||||
if (itr->first == 0)
|
||||
{
|
||||
LOG_ERROR("playerbots", "?! {}", itr->first);
|
||||
}
|
||||
spells.push_back(std::pair<uint32, std::string>(itr->first, out.str()));
|
||||
|
||||
spells.emplace_back(itr->first, out.str());
|
||||
alreadySeenList += spellInfo->SpellName[0];
|
||||
alreadySeenList += ",";
|
||||
}
|
||||
@@ -294,25 +279,28 @@ bool ListSpellsAction::Execute(Event event)
|
||||
|
||||
std::string const filter = event.getParam();
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> spells = GetSpellList(filter);
|
||||
std::vector<SpellListEntry> spells = GetSpellList(filter);
|
||||
|
||||
if (spells.empty())
|
||||
{
|
||||
// CHANGE: Give early feedback when no spells match the filter.
|
||||
botAI->TellMaster("No spells found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
botAI->TellMaster("=== Spells ===");
|
||||
|
||||
std::sort(spells.begin(), spells.end(), CompareSpells);
|
||||
|
||||
uint32 count = 0;
|
||||
for (std::vector<std::pair<uint32, std::string>>::iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
{
|
||||
// CHANGE: Send the full spell list again so client-side addons
|
||||
// (e.g. Multibot / Unbot) can reconstruct the
|
||||
// complete spellbook for configuration. The heavy part that caused
|
||||
// freezes before was the old CompareSpells implementation scanning
|
||||
// the entire SkillLineAbility DBC on every comparison. With the new
|
||||
// cheap comparator above, sending all lines here is safe and keeps
|
||||
// behaviour compatible with existing addons.
|
||||
for (std::vector<SpellListEntry>::const_iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
botAI->TellMasterNoFacing(i->second);
|
||||
|
||||
// if (++count >= 50)
|
||||
// {
|
||||
// std::ostringstream msg;
|
||||
// msg << (spells.size() - 50) << " more...";
|
||||
// botAI->TellMasterNoFacing(msg.str());
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,8 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
virtual std::vector<std::pair<uint32, std::string>> GetSpellList(std::string filter = "");
|
||||
|
||||
private:
|
||||
static std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
|
||||
static std::set<uint32> vendorItems;
|
||||
static void InitSpellCaches();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
|
||||
WorldLocation location = *target->getPosition();
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat())
|
||||
if (group && !urand(0, 1) && bot == botAI->GetGroupLeader() && !bot->IsInCombat())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
|
||||
@@ -946,98 +946,120 @@ bool MovementAction::IsWaitingForLastMove(MovementPriority priority)
|
||||
|
||||
bool MovementAction::IsMovingAllowed()
|
||||
{
|
||||
// do not allow if not vehicle driver
|
||||
if (botAI->IsInVehicle() && !botAI->IsInVehicle(true))
|
||||
// Most common checks: confused, stunned, fleeing, jumping, charging. All these
|
||||
// states are set when handling certain aura effects. We don't check against
|
||||
// UNIT_STATE_ROOT here, because this state is used by vehicles.
|
||||
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
|
||||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
// Death state (w/o spirit release) and Spirit of Redemption aura (priest)
|
||||
if ((bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || bot->HasSpiritOfRedemptionAura())
|
||||
return false;
|
||||
|
||||
// Common CC effects, ordered by frequency: rooted > frozen > polymorphed
|
||||
if (bot->IsRooted() || bot->isFrozen() || bot->IsPolymorphed())
|
||||
return false;
|
||||
|
||||
// Check for the MM controlled slot types: feared, confused, fleeing, etc.
|
||||
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;
|
||||
// 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).
|
||||
// We need to check charmed state AFTER vehicle one, cuz that's how it works:
|
||||
// passengers are set to charmed by vehicle with CHARM_TYPE_VEHICLE.
|
||||
if ((bot->GetVehicle() && !botAI->IsInVehicle(true)) || bot->IsCharmed())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); }
|
||||
|
||||
void MovementAction::UpdateMovementState()
|
||||
{
|
||||
// state flags
|
||||
const float gLvlZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||
const bool onGround = bot->GetPositionZ() < gLvlZ + 1.f;
|
||||
const bool wantsToFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
|
||||
const auto master = botAI ? botAI->GetMaster() : nullptr; // real or not
|
||||
const bool masterIsFlying = master && master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
const auto liquidState = bot->GetLiquidData().Status; // default LIQUID_MAP_NO_WATER
|
||||
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 isCurrentlyRestricted = // see if the bot is currently slowed, rooted, or otherwise unable to move
|
||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL) ||
|
||||
bot->IsRooted() ||
|
||||
bot->isFrozen() ||
|
||||
bot->IsPolymorphed();
|
||||
|
||||
// handle water state
|
||||
if (isWaterArea)
|
||||
// no update movement flags while movement is current restricted.
|
||||
if (!isCurrentlyRestricted && bot->IsAlive())
|
||||
{
|
||||
// water walking
|
||||
if (wantsToWaterWalk && !isWaterWalking && !isUnderWater && !isFlying)
|
||||
// state flags
|
||||
const auto master = botAI ? botAI->GetMaster() : nullptr; // real player or not
|
||||
const bool masterIsFlying = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) : true;
|
||||
const bool masterIsSwimming = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) : true;
|
||||
const auto liquidState = bot->GetLiquidData().Status; // default LIQUID_MAP_NO_WATER
|
||||
const float gZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||
const bool wantsToFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
|
||||
const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
const bool isWaterArea = liquidState != LIQUID_MAP_NO_WATER;
|
||||
const bool isUnderWater = liquidState == LIQUID_MAP_UNDER_WATER;
|
||||
const bool isInWater = liquidState == LIQUID_MAP_IN_WATER;
|
||||
const bool isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
const bool isSwimming = bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
const bool wantsToWaterWalk = bot->HasWaterWalkAura();
|
||||
const bool wantsToSwim = isInWater || isUnderWater;
|
||||
const bool onGroundZ = (bot->GetPositionZ() < gZ + 1.f) && !isWaterArea;
|
||||
bool movementFlagsUpdated = false;
|
||||
|
||||
// handle water state
|
||||
if (isWaterArea && !isFlying)
|
||||
{
|
||||
// water walking
|
||||
if (wantsToWaterWalk && !isWaterWalking && !masterIsSwimming)
|
||||
{
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
movementFlagsUpdated = true;
|
||||
}
|
||||
// swimming
|
||||
else if (wantsToSwim && !isSwimming && masterIsSwimming)
|
||||
{
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
movementFlagsUpdated = true;
|
||||
}
|
||||
}
|
||||
else if (isSwimming || isWaterWalking)
|
||||
{
|
||||
// reset water flags
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
// swimming
|
||||
else if (wantsToSwim && !isSwimming && !wantsToWaterWalk && !isFlying)
|
||||
{
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
bot->SendMovementFlagUpdate();
|
||||
movementFlagsUpdated = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// reset flags, if not will inherit incorrect walk speed here and there
|
||||
// when transistions between land and water.
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||
bot->SendMovementFlagUpdate();
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// handle flying state
|
||||
if (wantsToFly && !isFlying && masterIsFlying)
|
||||
{
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
else if ((!wantsToFly || onGround) && isFlying)
|
||||
{
|
||||
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
|
||||
// Save current state for the next check
|
||||
wasMovementRestricted = isCurrentlyRestricted;
|
||||
|
||||
// Temporary speed increase in group
|
||||
@@ -1089,7 +1111,7 @@ void MovementAction::UpdateMovementState()
|
||||
// {
|
||||
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
|
||||
// {
|
||||
// if (pTarget != botAI->GetGroupMaster())
|
||||
// if (pTarget != botAI->GetGroupLeader())
|
||||
// return;
|
||||
|
||||
// if (!bot->IsWithinMeleeRange(pTarget))
|
||||
@@ -1820,25 +1842,12 @@ void MovementAction::DoMovePoint(Unit* unit, float x, float y, float z, bool gen
|
||||
if (!mm)
|
||||
return;
|
||||
|
||||
// enable flying
|
||||
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
|
||||
{
|
||||
unit->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
||||
unit->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||||
}
|
||||
else
|
||||
{
|
||||
unit->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
||||
unit->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||||
}
|
||||
|
||||
// enable water walking
|
||||
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING))
|
||||
{
|
||||
float gLvlZ = unit->GetMapWaterOrGroundLevel(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ());
|
||||
unit->UpdatePosition(unit->GetPositionX(), unit->GetPositionY(), gLvlZ, false);
|
||||
// z = gLvlZ; do not overwrite Z axex, otherwise you wont be able to steer the bots into swimming when water
|
||||
// walking.
|
||||
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();
|
||||
@@ -2663,7 +2672,7 @@ bool DisperseSetAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "master target")); }
|
||||
bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "group leader")); }
|
||||
|
||||
bool MoveToLootAction::Execute(Event event)
|
||||
{
|
||||
|
||||
@@ -6,16 +6,17 @@
|
||||
#include "PassLeadershipToMasterAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "PlayerbotOperations.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
|
||||
bool PassLeadershipToMasterAction::Execute(Event event)
|
||||
{
|
||||
if (Player* master = GetMaster())
|
||||
if (master && master != bot && bot->GetGroup() && bot->GetGroup()->IsMember(master->GetGUID()))
|
||||
{
|
||||
WorldPacket p(SMSG_GROUP_SET_LEADER, 8);
|
||||
p << master->GetGUID();
|
||||
bot->GetSession()->HandleGroupSetLeaderOpcode(p);
|
||||
auto setLeaderOp = std::make_unique<GroupSetLeaderOperation>(bot->GetGUID(), master->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(setLeaderOp));
|
||||
|
||||
if (!message.empty())
|
||||
botAI->TellMasterNoFacing(message);
|
||||
|
||||
@@ -18,14 +18,14 @@ bool PetsAction::Execute(Event event)
|
||||
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
|
||||
std::string param = event.getParam();
|
||||
if (param.empty() && !defaultCmd.empty())
|
||||
{
|
||||
param = defaultCmd;
|
||||
}
|
||||
|
||||
if (param.empty())
|
||||
{
|
||||
// If no parameter is provided, show usage instructions and return.
|
||||
botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_usage_error", "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ bool PetsAction::Execute(Event event)
|
||||
// If no pets or guardians are found, notify and return.
|
||||
if (targets.empty())
|
||||
{
|
||||
botAI->TellError("You have no pet or guardian pet.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_no_pet_error", "You have no pet or guardian pet.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -63,42 +65,54 @@ bool PetsAction::Execute(Event event)
|
||||
if (param == "aggressive")
|
||||
{
|
||||
react = REACT_AGGRESSIVE;
|
||||
stanceText = "aggressive";
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_aggressive", "aggressive", {});
|
||||
}
|
||||
else if (param == "defensive")
|
||||
{
|
||||
react = REACT_DEFENSIVE;
|
||||
stanceText = "defensive";
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_defensive", "defensive", {});
|
||||
}
|
||||
else if (param == "passive")
|
||||
{
|
||||
react = REACT_PASSIVE;
|
||||
stanceText = "passive";
|
||||
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_passive", "passive", {});
|
||||
}
|
||||
// The "stance" command simply reports the current stance of each pet/guardian.
|
||||
else if (param == "stance")
|
||||
{
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
std::string type = target->IsPet() ? "pet" : "guardian";
|
||||
std::string type = target->IsPet() ?
|
||||
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_pet", "pet", {}) :
|
||||
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_guardian", "guardian", {});
|
||||
std::string name = target->GetName();
|
||||
std::string stance;
|
||||
switch (target->GetReactState())
|
||||
{
|
||||
case REACT_AGGRESSIVE:
|
||||
stance = "aggressive";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_aggressive", "aggressive", {});
|
||||
break;
|
||||
case REACT_DEFENSIVE:
|
||||
stance = "defensive";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_defensive", "defensive", {});
|
||||
break;
|
||||
case REACT_PASSIVE:
|
||||
stance = "passive";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_passive", "passive", {});
|
||||
break;
|
||||
default:
|
||||
stance = "unknown";
|
||||
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_unknown", "unknown", {});
|
||||
break;
|
||||
}
|
||||
botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + ".");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_report", "Current stance of %type \"%name\": %stance.",
|
||||
{{"type", type}, {"name", name}, {"stance", stance}});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -113,25 +127,38 @@ bool PetsAction::Execute(Event event)
|
||||
{
|
||||
ObjectGuid masterTargetGuid = master->GetTarget();
|
||||
if (!masterTargetGuid.IsEmpty())
|
||||
{
|
||||
targetUnit = botAI->GetUnit(masterTargetGuid);
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid target is selected, show an error and return.
|
||||
if (!targetUnit)
|
||||
{
|
||||
botAI->TellError("No valid target selected by master.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_no_target_error", "No valid target selected by master.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (!targetUnit->IsAlive())
|
||||
{
|
||||
botAI->TellError("Target is not alive.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_target_dead_error", "Target is not alive.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (!bot->IsValidAttackTarget(targetUnit))
|
||||
{
|
||||
botAI->TellError("Target is not a valid attack target for the bot.");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_invalid_target_error", "Target is not a valid attack target for the bot.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) &&
|
||||
(targetUnit->IsPlayer() || targetUnit->IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != targetUnit))
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -182,9 +209,17 @@ bool PetsAction::Execute(Event event)
|
||||
}
|
||||
// Inform the master if the command succeeded or failed.
|
||||
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to attack your target.");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_attack_success", "Pet commanded to attack your target.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
else if (!didAttack)
|
||||
botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {});
|
||||
botAI->TellError(text);
|
||||
}
|
||||
return didAttack;
|
||||
}
|
||||
// The "follow" command makes all pets/guardians follow the bot.
|
||||
@@ -192,7 +227,11 @@ bool PetsAction::Execute(Event event)
|
||||
{
|
||||
botAI->PetFollow();
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to follow.");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_follow_success", "Pet commanded to follow.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// The "stay" command causes all pets/guardians to stop and stay in place.
|
||||
@@ -229,14 +268,20 @@ bool PetsAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to stay.");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stay_success", "Pet commanded to stay.", {});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Unknown command: show usage instructions and return.
|
||||
else
|
||||
{
|
||||
botAI->TellError("Unknown pet command: " + param +
|
||||
". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_unknown_command_error", "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||
{{"param", param}});
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -251,7 +296,12 @@ bool PetsAction::Execute(Event event)
|
||||
|
||||
// Inform the master of the new stance if debug is enabled.
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet stance set to " + stanceText + ".");
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_stance_set_success", "Pet stance set to %stance.",
|
||||
{{"stance", stanceText}});
|
||||
botAI->TellMaster(text);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,10 @@ bool RandomBotUpdateAction::Execute(Event event)
|
||||
if (!sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
return false;
|
||||
|
||||
if (bot->GetGroup() && botAI->GetGroupMaster())
|
||||
if (bot->GetGroup() && botAI->GetGroupLeader())
|
||||
{
|
||||
PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster());
|
||||
if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer())
|
||||
PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader());
|
||||
if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ReadyCheckAction.h"
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "ReadyCheckAction.h"
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
@@ -27,14 +30,17 @@ std::string const formatPercent(std::string const name, uint8 value, float perce
|
||||
class ReadyChecker
|
||||
{
|
||||
public:
|
||||
virtual ~ReadyChecker() = default;
|
||||
virtual bool Check(PlayerbotAI* botAI, AiObjectContext* context) = 0;
|
||||
virtual std::string const getName() = 0;
|
||||
virtual bool PrintAlways() { return true; }
|
||||
|
||||
static std::vector<ReadyChecker*> checkers;
|
||||
static std::vector<std::unique_ptr<ReadyChecker>> checkers;
|
||||
static std::once_flag initFlag;
|
||||
};
|
||||
|
||||
std::vector<ReadyChecker*> ReadyChecker::checkers;
|
||||
std::vector<std::unique_ptr<ReadyChecker>> ReadyChecker::checkers;
|
||||
std::once_flag ReadyChecker::initFlag;
|
||||
|
||||
class HealthChecker : public ReadyChecker
|
||||
{
|
||||
@@ -160,25 +166,30 @@ bool ReadyCheckAction::Execute(Event event)
|
||||
|
||||
bool ReadyCheckAction::ReadyCheck()
|
||||
{
|
||||
if (ReadyChecker::checkers.empty())
|
||||
{
|
||||
ReadyChecker::checkers.push_back(new HealthChecker());
|
||||
ReadyChecker::checkers.push_back(new ManaChecker());
|
||||
ReadyChecker::checkers.push_back(new DistanceChecker());
|
||||
ReadyChecker::checkers.push_back(new HunterChecker());
|
||||
std::call_once(
|
||||
ReadyChecker::initFlag,
|
||||
[]()
|
||||
{
|
||||
ReadyChecker::checkers.reserve(8);
|
||||
|
||||
ReadyChecker::checkers.push_back(new ItemCountChecker("food", "Food"));
|
||||
ReadyChecker::checkers.push_back(new ManaPotionChecker("drink", "Water"));
|
||||
ReadyChecker::checkers.push_back(new ItemCountChecker("healing potion", "Hpot"));
|
||||
ReadyChecker::checkers.push_back(new ManaPotionChecker("mana potion", "Mpot"));
|
||||
}
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<HealthChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<DistanceChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<HunterChecker>());
|
||||
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("food", "Food"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("drink", "Water"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("healing potion", "Hpot"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("mana potion", "Mpot"));
|
||||
});
|
||||
|
||||
bool result = true;
|
||||
for (std::vector<ReadyChecker*>::iterator i = ReadyChecker::checkers.begin(); i != ReadyChecker::checkers.end();
|
||||
++i)
|
||||
for (auto const& checkerPtr : ReadyChecker::checkers)
|
||||
{
|
||||
ReadyChecker* checker = *i;
|
||||
bool ok = checker->Check(botAI, context);
|
||||
if (!checkerPtr)
|
||||
continue;
|
||||
|
||||
bool ok = checkerPtr->Check(botAI, context);
|
||||
result = result && ok;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,15 +168,15 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
||||
if (!bot->GetGroup())
|
||||
return true;
|
||||
|
||||
Player* groupMaster = botAI->GetGroupMaster();
|
||||
if (!groupMaster || groupMaster == bot)
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
if (!groupLeader || groupLeader == bot)
|
||||
return true;
|
||||
|
||||
if (!botAI->HasActivePlayerMaster())
|
||||
return true;
|
||||
|
||||
if (botAI->HasActivePlayerMaster() &&
|
||||
groupMaster->GetMapId() == bot->GetMapId() &&
|
||||
groupLeader->GetMapId() == bot->GetMapId() &&
|
||||
bot->GetMap() &&
|
||||
(bot->GetMap()->IsRaid() || bot->GetMap()->IsDungeon()))
|
||||
{
|
||||
@@ -184,7 +184,7 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
||||
}
|
||||
|
||||
return sServerFacade->IsDistanceGreaterThan(
|
||||
AI_VALUE2(float, "distance", "master target"),
|
||||
AI_VALUE2(float, "distance", "group leader"),
|
||||
sPlayerbotAIConfig->sightDistance);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,4 +16,4 @@ bool ResetInstancesAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResetInstancesAction::isUseful() { return botAI->GetGroupMaster() == bot; };
|
||||
bool ResetInstancesAction::isUseful() { return botAI->GetGroupLeader() == bot; };
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
|
||||
bool ReviveFromCorpseAction::Execute(Event event)
|
||||
{
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Corpse* corpse = bot->GetCorpse();
|
||||
|
||||
// follow master when master revives
|
||||
// follow group Leader when group Leader revives
|
||||
WorldPacket& p = event.getPacket();
|
||||
if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && master && !corpse && bot->IsAlive())
|
||||
if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && groupLeader && !corpse && bot->IsAlive())
|
||||
{
|
||||
if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
sPlayerbotAIConfig->farDistance))
|
||||
{
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
@@ -43,10 +43,10 @@ bool ReviveFromCorpseAction::Execute(Event event)
|
||||
// time(nullptr))
|
||||
// return false;
|
||||
|
||||
if (master)
|
||||
if (groupLeader)
|
||||
{
|
||||
if (!GET_PLAYERBOT_AI(master) && master->isDead() && master->GetCorpse() &&
|
||||
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
if (!GET_PLAYERBOT_AI(groupLeader) && groupLeader->isDead() && groupLeader->GetCorpse() &&
|
||||
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
sPlayerbotAIConfig->farDistance))
|
||||
return false;
|
||||
}
|
||||
@@ -79,15 +79,15 @@ bool FindCorpseAction::Execute(Event event)
|
||||
if (bot->InBattleground())
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Corpse* corpse = bot->GetCorpse();
|
||||
if (!corpse)
|
||||
return false;
|
||||
|
||||
// if (master)
|
||||
// if (groupLeader)
|
||||
// {
|
||||
// if (!GET_PLAYERBOT_AI(master) &&
|
||||
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
|
||||
// if (!GET_PLAYERBOT_AI(groupLeader) &&
|
||||
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
|
||||
// sPlayerbotAIConfig->farDistance)) return false;
|
||||
// }
|
||||
|
||||
@@ -110,20 +110,20 @@ bool FindCorpseAction::Execute(Event event)
|
||||
WorldPosition botPos(bot);
|
||||
WorldPosition corpsePos(corpse);
|
||||
WorldPosition moveToPos = corpsePos;
|
||||
WorldPosition masterPos(master);
|
||||
WorldPosition leaderPos(groupLeader);
|
||||
|
||||
float reclaimDist = CORPSE_RECLAIM_RADIUS - 5.0f;
|
||||
float corpseDist = botPos.distance(corpsePos);
|
||||
int64 deadTime = time(nullptr) - corpse->GetGhostTime();
|
||||
|
||||
bool moveToMaster = master && master != bot && masterPos.fDist(corpsePos) < reclaimDist;
|
||||
bool moveToLeader = groupLeader && groupLeader != bot && leaderPos.fDist(corpsePos) < reclaimDist;
|
||||
|
||||
// Should we ressurect? If so, return false.
|
||||
if (corpseDist < reclaimDist)
|
||||
{
|
||||
if (moveToMaster) // We are near master.
|
||||
if (moveToLeader) // We are near group leader.
|
||||
{
|
||||
if (botPos.fDist(masterPos) < sPlayerbotAIConfig->spellDistance)
|
||||
if (botPos.fDist(leaderPos) < sPlayerbotAIConfig->spellDistance)
|
||||
return false;
|
||||
}
|
||||
else if (deadTime > 8 * MINUTE) // We have walked too long already.
|
||||
@@ -140,8 +140,8 @@ bool FindCorpseAction::Execute(Event event)
|
||||
// If we are getting close move to a save ressurrection spot instead of just the corpse.
|
||||
if (corpseDist < sPlayerbotAIConfig->reactDistance)
|
||||
{
|
||||
if (moveToMaster)
|
||||
moveToPos = masterPos;
|
||||
if (moveToLeader)
|
||||
moveToPos = leaderPos;
|
||||
else
|
||||
{
|
||||
FleeManager manager(bot, reclaimDist, 0.0, urand(0, 1), moveToPos);
|
||||
@@ -215,12 +215,12 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
|
||||
if (!startZone && ClosestGrave)
|
||||
return ClosestGrave;
|
||||
|
||||
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupMaster() && botAI->GetGroupMaster() != bot)
|
||||
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupLeader() && botAI->GetGroupLeader() != bot)
|
||||
{
|
||||
Player* master = botAI->GetGroupMaster();
|
||||
if (master && master != bot)
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
if (groupLeader && groupLeader != bot)
|
||||
{
|
||||
ClosestGrave = sGraveyard->GetClosestGraveyard(master, bot->GetTeamId());
|
||||
ClosestGrave = sGraveyard->GetClosestGraveyard(groupLeader, bot->GetTeamId());
|
||||
|
||||
if (ClosestGrave)
|
||||
return ClosestGrave;
|
||||
|
||||
@@ -35,8 +35,8 @@ bool RewardAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
Unit* mtar = AI_VALUE(Unit*, "master target");
|
||||
if (mtar && Reward(itemId, mtar))
|
||||
Unit* groupLeaderUnit = AI_VALUE(Unit*, "group leader");
|
||||
if (groupLeaderUnit && Reward(itemId, groupLeaderUnit))
|
||||
return true;
|
||||
|
||||
botAI->TellError("Cannot talk to quest giver");
|
||||
|
||||
@@ -76,7 +76,7 @@ void RpgHelper::setFacing(GuidPosition guidPosition)
|
||||
|
||||
void RpgHelper::setDelay(bool waitForGroup)
|
||||
{
|
||||
if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupMaster() == bot && bot->GetGroup()))
|
||||
if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupLeader() == bot && bot->GetGroup()))
|
||||
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay);
|
||||
else
|
||||
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay / 5);
|
||||
|
||||
@@ -22,8 +22,8 @@ bool SecurityCheckAction::Execute(Event event)
|
||||
ItemQualities threshold = group->GetLootThreshold();
|
||||
if (method == MASTER_LOOT || method == FREE_FOR_ALL || threshold > ITEM_QUALITY_UNCOMMON)
|
||||
{
|
||||
if ((botAI->GetGroupMaster()->GetSession()->GetSecurity() == SEC_PLAYER) &&
|
||||
(!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupMaster()->GetGuildId()))
|
||||
if ((botAI->GetGroupLeader()->GetSession()->GetSecurity() == SEC_PLAYER) &&
|
||||
(!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupLeader()->GetGuildId()))
|
||||
{
|
||||
botAI->TellError("I will play with this loot type only if I'm in your guild :/");
|
||||
botAI->ChangeStrategy("+passive,+stay", BOT_STATE_NON_COMBAT);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "RTSCValues.h"
|
||||
#include "RtscAction.h"
|
||||
#include "PositionValue.h"
|
||||
#include "ByteBuffer.h"
|
||||
|
||||
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
|
||||
bool important)
|
||||
@@ -31,27 +32,52 @@ Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z,
|
||||
|
||||
bool SeeSpellAction::Execute(Event event)
|
||||
{
|
||||
WorldPacket p(event.getPacket()); //
|
||||
// RTSC packet data
|
||||
WorldPacket p(event.getPacket());
|
||||
uint8 castCount;
|
||||
uint32 spellId;
|
||||
uint8 castCount, castFlags;
|
||||
uint8 castFlags;
|
||||
|
||||
// check RTSC header size = castCount (uint8) + spellId (uint32) + castFlags (uint8)
|
||||
uint32 const rtscHeaderSize = sizeof(uint8) + sizeof(uint32) + sizeof(uint8);
|
||||
if (p.size() < rtscHeaderSize)
|
||||
{
|
||||
LOG_WARN("playerbots", "SeeSpellAction: Corrupt RTSC packet size={}, expected>={}", p.size(), rtscHeaderSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
|
||||
p.rpos(0);
|
||||
p >> castCount >> spellId >> castFlags;
|
||||
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
// read RTSC packet data
|
||||
p.rpos(0); // set read position to start
|
||||
p >> castCount >> spellId >> castFlags;
|
||||
|
||||
// if (!botAI->HasStrategy("RTSC", botAI->GetState()))
|
||||
// return false;
|
||||
|
||||
if (spellId != RTSC_MOVE_SPELL)
|
||||
return false;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
|
||||
// should not throw exception,just defensive measure to prevent any crashes when core function breaks.
|
||||
SpellCastTargets targets;
|
||||
targets.Read(p, botAI->GetMaster());
|
||||
try
|
||||
{
|
||||
targets.Read(p, master);
|
||||
if (!targets.GetDst())
|
||||
{
|
||||
// do not dereference a null destination; ignore malformed RTSC packets instead of crashing
|
||||
LOG_WARN("playerbots", "SeeSpellAction: (malformed) RTSC payload does not contain full targets data");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ByteBufferException const&)
|
||||
{
|
||||
// ignore malformed RTSC packets instead of crashing
|
||||
LOG_WARN("playerbots", "SeeSpellAction: Failed deserialization (malformed) RTSC payload");
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPosition spellPosition(master->GetMapId(), targets.GetDst()->_position);
|
||||
SET_AI_VALUE(WorldPosition, "see spell location", spellPosition);
|
||||
|
||||
@@ -22,7 +22,7 @@ bool OutOfReactRangeAction::Execute(Event event)
|
||||
|
||||
bool OutOfReactRangeAction::isUseful()
|
||||
{
|
||||
bool canFollow = Follow(AI_VALUE(Unit*, "master target"));
|
||||
bool canFollow = Follow(AI_VALUE(Unit*, "group leader"));
|
||||
if (!canFollow)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -64,7 +64,7 @@ bool MoveToDarkPortalAction::Execute(Event event)
|
||||
{
|
||||
if (bot->GetGroup())
|
||||
if (bot->GetGroup()->GetLeaderGUID() != bot->GetGUID() &&
|
||||
!GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupMaster()))
|
||||
!GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupLeader()))
|
||||
return false;
|
||||
|
||||
if (bot->GetLevel() > 57)
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "AoeValues.h"
|
||||
#include "TargetValue.h"
|
||||
|
||||
NextAction** CastAbolishPoisonAction::getAlternatives()
|
||||
{
|
||||
@@ -30,6 +33,32 @@ bool CastEntanglingRootsCcAction::Execute(Event event) { return botAI->CastSpell
|
||||
Value<Unit*>* CastHibernateCcAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", "hibernate"); }
|
||||
|
||||
bool CastHibernateCcAction::Execute(Event event) { return botAI->CastSpell("hibernate", GetTarget()); }
|
||||
bool CastStarfallAction::isUseful()
|
||||
{
|
||||
if (!CastSpellAction::isUseful())
|
||||
return false;
|
||||
|
||||
// Avoid breaking CC
|
||||
WorldLocation aoePos = *context->GetValue<WorldLocation>("aoe position");
|
||||
Unit* ccTarget = context->GetValue<Unit*>("current cc target")->Get();
|
||||
if (ccTarget && ccTarget->IsAlive())
|
||||
{
|
||||
float dist2d = sServerFacade->GetDistance2d(ccTarget, aoePos.GetPositionX(), aoePos.GetPositionY());
|
||||
if (sServerFacade->IsDistanceLessOrEqualThan(dist2d, sPlayerbotAIConfig->aoeRadius))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid single-target usage on initial pull
|
||||
uint8 aoeCount = *context->GetValue<uint8>("aoe count");
|
||||
if (aoeCount < 2)
|
||||
{
|
||||
Unit* target = context->GetValue<Unit*>("current target")->Get();
|
||||
if (!target || (!botAI->HasAura("moonfire", target) && !botAI->HasAura("insect swarm", target)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NextAction** CastReviveAction::getPrerequisites()
|
||||
{
|
||||
|
||||
@@ -144,6 +144,8 @@ class CastStarfallAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastStarfallAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "starfall") {}
|
||||
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class CastHurricaneAction : public CastSpellAction
|
||||
|
||||
@@ -11,7 +11,6 @@ void GroupStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("invite nearby", 4.0f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("invite guild", 4.0f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("leave far away", 4.0f), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("seldom", NextAction::array(0, new NextAction("reset instances", 1.0f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("leave far away", 4.0f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("seldom", NextAction::array(0, new NextAction("reset instances", 1.0f), nullptr)));
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "WorldPacketHandlerStrategy.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
PassTroughStrategy::InitTriggers(triggers);
|
||||
@@ -69,7 +67,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
||||
triggers.push_back(new TriggerNode("questgiver quest details", NextAction::array(0, new NextAction("turn in query quest", relevance), nullptr)));
|
||||
|
||||
// loot roll
|
||||
triggers.push_back(new TriggerNode("very often", NextAction::array(0, new NextAction("loot roll", 10.0f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("very often", NextAction::array(0, new NextAction("loot roll", relevance), nullptr)));
|
||||
}
|
||||
|
||||
WorldPacketHandlerStrategy::WorldPacketHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
|
||||
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
RaidStrategyContext() : NamedObjectContext<Strategy>(false, true)
|
||||
{
|
||||
creators["aq20"] = &RaidStrategyContext::aq20;
|
||||
creators["mc"] = &RaidStrategyContext::mc;
|
||||
creators["moltencore"] = &RaidStrategyContext::moltencore;
|
||||
creators["bwl"] = &RaidStrategyContext::bwl;
|
||||
creators["karazhan"] = &RaidStrategyContext::karazhan;
|
||||
creators["magtheridon"] = &RaidStrategyContext::magtheridon;
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
|
||||
private:
|
||||
static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); }
|
||||
static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); }
|
||||
static Strategy* moltencore(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); }
|
||||
static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); }
|
||||
static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); }
|
||||
static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); }
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace GruulsLairHelpers
|
||||
SPELL_SPELL_SHIELD = 33054,
|
||||
|
||||
// Hunter
|
||||
SPELL_MISDIRECTION = 34477,
|
||||
SPELL_MISDIRECTION = 35079,
|
||||
|
||||
// Warlock
|
||||
SPELL_BANISH = 18647, // Rank 2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H
|
||||
#define _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H
|
||||
#ifndef _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H
|
||||
|
||||
#include "RaidKarazhanActions.h"
|
||||
#include "NamedObjectContext.h"
|
||||
@@ -9,77 +9,255 @@ class RaidKarazhanActionContext : public NamedObjectContext<Action>
|
||||
public:
|
||||
RaidKarazhanActionContext()
|
||||
{
|
||||
creators["karazhan attumen the huntsman stack behind"] = &RaidKarazhanActionContext::karazhan_attumen_the_huntsman_stack_behind;
|
||||
// Trash
|
||||
creators["mana warp stun creature before warp breach"] =
|
||||
&RaidKarazhanActionContext::mana_warp_stun_creature_before_warp_breach;
|
||||
|
||||
creators["karazhan moroes mark target"] = &RaidKarazhanActionContext::karazhan_moroes_mark_target;
|
||||
// Attumen the Huntsman
|
||||
creators["attumen the huntsman mark target"] =
|
||||
&RaidKarazhanActionContext::attumen_the_huntsman_mark_target;
|
||||
|
||||
creators["karazhan maiden of virtue position boss"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_boss;
|
||||
creators["karazhan maiden of virtue position ranged"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_ranged;
|
||||
creators["attumen the huntsman split bosses"] =
|
||||
&RaidKarazhanActionContext::attumen_the_huntsman_split_bosses;
|
||||
|
||||
creators["karazhan big bad wolf position boss"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_position_boss;
|
||||
creators["karazhan big bad wolf run away"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_run_away;
|
||||
creators["attumen the huntsman stack behind"] =
|
||||
&RaidKarazhanActionContext::attumen_the_huntsman_stack_behind;
|
||||
|
||||
creators["karazhan romulo and julianne mark target"] = &RaidKarazhanActionContext::karazhan_romulo_and_julianne_mark_target;
|
||||
creators["attumen the huntsman manage dps timer"] =
|
||||
&RaidKarazhanActionContext::attumen_the_huntsman_manage_dps_timer;
|
||||
|
||||
creators["karazhan wizard of oz mark target"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_mark_target;
|
||||
creators["karazhan wizard of oz scorch strawman"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_scorch_strawman;
|
||||
// Moroes
|
||||
creators["moroes main tank attack boss"] =
|
||||
&RaidKarazhanActionContext::moroes_main_tank_attack_boss;
|
||||
|
||||
creators["karazhan the curator mark target"] = &RaidKarazhanActionContext::karazhan_the_curator_mark_target;
|
||||
creators["karazhan the curator position boss"] = &RaidKarazhanActionContext::karazhan_the_curator_position_boss;
|
||||
creators["karazhan the curator spread ranged"] = &RaidKarazhanActionContext::karazhan_the_curator_spread_ranged;
|
||||
creators["moroes mark target"] =
|
||||
&RaidKarazhanActionContext::moroes_mark_target;
|
||||
|
||||
creators["karazhan terestian illhoof mark target"] = &RaidKarazhanActionContext::karazhan_terestian_illhoof_mark_target;
|
||||
// Maiden of Virtue
|
||||
creators["maiden of virtue move boss to healer"] =
|
||||
&RaidKarazhanActionContext::maiden_of_virtue_move_boss_to_healer;
|
||||
|
||||
creators["karazhan shade of aran arcane explosion run away"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_arcane_explosion_run_away;
|
||||
creators["karazhan shade of aran flame wreath stop movement"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_flame_wreath_stop_movement;
|
||||
creators["karazhan shade of aran mark conjured elemental"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_mark_conjured_elemental;
|
||||
creators["karazhan shade of aran spread ranged"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_spread_ranged;
|
||||
creators["maiden of virtue position ranged"] =
|
||||
&RaidKarazhanActionContext::maiden_of_virtue_position_ranged;
|
||||
|
||||
creators["karazhan netherspite block red beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_red_beam;
|
||||
creators["karazhan netherspite block blue beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_blue_beam;
|
||||
creators["karazhan netherspite block green beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_green_beam;
|
||||
creators["karazhan netherspite avoid beam and void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_avoid_beam_and_void_zone;
|
||||
creators["karazhan netherspite banish phase avoid void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_banish_phase_avoid_void_zone;
|
||||
// The Big Bad Wolf
|
||||
creators["big bad wolf position boss"] =
|
||||
&RaidKarazhanActionContext::big_bad_wolf_position_boss;
|
||||
|
||||
creators["karazhan prince malchezaar non tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_non_tank_avoid_hazard;
|
||||
creators["karazhan prince malchezaar tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_tank_avoid_hazard;
|
||||
creators["big bad wolf run away from boss"] =
|
||||
&RaidKarazhanActionContext::big_bad_wolf_run_away_from_boss;
|
||||
|
||||
// Romulo and Julianne
|
||||
creators["romulo and julianne mark target"] =
|
||||
&RaidKarazhanActionContext::romulo_and_julianne_mark_target;
|
||||
|
||||
// The Wizard of Oz
|
||||
creators["wizard of oz mark target"] =
|
||||
&RaidKarazhanActionContext::wizard_of_oz_mark_target;
|
||||
|
||||
creators["wizard of oz scorch strawman"] =
|
||||
&RaidKarazhanActionContext::wizard_of_oz_scorch_strawman;
|
||||
|
||||
// The Curator
|
||||
creators["the curator mark astral flare"] =
|
||||
&RaidKarazhanActionContext::the_curator_mark_astral_flare;
|
||||
|
||||
creators["the curator position boss"] =
|
||||
&RaidKarazhanActionContext::the_curator_position_boss;
|
||||
|
||||
creators["the curator spread ranged"] =
|
||||
&RaidKarazhanActionContext::the_curator_spread_ranged;
|
||||
|
||||
// Terestian Illhoof
|
||||
creators["terestian illhoof mark target"] =
|
||||
&RaidKarazhanActionContext::terestian_illhoof_mark_target;
|
||||
|
||||
// Shade of Aran
|
||||
creators["shade of aran run away from arcane explosion"] =
|
||||
&RaidKarazhanActionContext::shade_of_aran_run_away_from_arcane_explosion;
|
||||
|
||||
creators["shade of aran stop moving during flame wreath"] =
|
||||
&RaidKarazhanActionContext::shade_of_aran_stop_moving_during_flame_wreath;
|
||||
|
||||
creators["shade of aran mark conjured elemental"] =
|
||||
&RaidKarazhanActionContext::shade_of_aran_mark_conjured_elemental;
|
||||
|
||||
creators["shade of aran ranged maintain distance"] =
|
||||
&RaidKarazhanActionContext::shade_of_aran_ranged_maintain_distance;
|
||||
|
||||
// Netherspite
|
||||
creators["netherspite block red beam"] =
|
||||
&RaidKarazhanActionContext::netherspite_block_red_beam;
|
||||
|
||||
creators["netherspite block blue beam"] =
|
||||
&RaidKarazhanActionContext::netherspite_block_blue_beam;
|
||||
|
||||
creators["netherspite block green beam"] =
|
||||
&RaidKarazhanActionContext::netherspite_block_green_beam;
|
||||
|
||||
creators["netherspite avoid beam and void zone"] =
|
||||
&RaidKarazhanActionContext::netherspite_avoid_beam_and_void_zone;
|
||||
|
||||
creators["netherspite banish phase avoid void zone"] =
|
||||
&RaidKarazhanActionContext::netherspite_banish_phase_avoid_void_zone;
|
||||
|
||||
creators["netherspite manage timers and trackers"] =
|
||||
&RaidKarazhanActionContext::netherspite_manage_timers_and_trackers;
|
||||
|
||||
// Prince Malchezaar
|
||||
creators["prince malchezaar enfeebled avoid hazard"] =
|
||||
&RaidKarazhanActionContext::prince_malchezaar_enfeebled_avoid_hazard;
|
||||
|
||||
creators["prince malchezaar non tank avoid infernal"] =
|
||||
&RaidKarazhanActionContext::prince_malchezaar_non_tank_avoid_infernal;
|
||||
|
||||
creators["prince malchezaar main tank movement"] =
|
||||
&RaidKarazhanActionContext::prince_malchezaar_main_tank_movement;
|
||||
|
||||
// Nightbane
|
||||
creators["nightbane ground phase position boss"] =
|
||||
&RaidKarazhanActionContext::nightbane_ground_phase_position_boss;
|
||||
|
||||
creators["nightbane ground phase rotate ranged positions"] =
|
||||
&RaidKarazhanActionContext::nightbane_ground_phase_rotate_ranged_positions;
|
||||
|
||||
creators["nightbane cast fear ward on main tank"] =
|
||||
&RaidKarazhanActionContext::nightbane_cast_fear_ward_on_main_tank;
|
||||
|
||||
creators["nightbane control pet aggression"] =
|
||||
&RaidKarazhanActionContext::nightbane_control_pet_aggression;
|
||||
|
||||
creators["nightbane flight phase movement"] =
|
||||
&RaidKarazhanActionContext::nightbane_flight_phase_movement;
|
||||
|
||||
creators["nightbane manage timers and trackers"] =
|
||||
&RaidKarazhanActionContext::nightbane_manage_timers_and_trackers;
|
||||
}
|
||||
|
||||
private:
|
||||
static Action* karazhan_attumen_the_huntsman_stack_behind(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanStackBehindAction(botAI); }
|
||||
// Trash
|
||||
static Action* mana_warp_stun_creature_before_warp_breach(
|
||||
PlayerbotAI* botAI) { return new ManaWarpStunCreatureBeforeWarpBreachAction(botAI); }
|
||||
|
||||
static Action* karazhan_moroes_mark_target(PlayerbotAI* botAI) { return new KarazhanMoroesMarkTargetAction(botAI); }
|
||||
// Attumen the Huntsman
|
||||
static Action* attumen_the_huntsman_mark_target(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanMarkTargetAction(botAI); }
|
||||
|
||||
static Action* karazhan_maiden_of_virtue_position_boss(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionBossAction(botAI); }
|
||||
static Action* karazhan_maiden_of_virtue_position_ranged(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionRangedAction(botAI); }
|
||||
static Action* attumen_the_huntsman_split_bosses(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanSplitBossesAction(botAI); }
|
||||
|
||||
static Action* karazhan_big_bad_wolf_position_boss(PlayerbotAI* botAI) { return new KarazhanBigBadWolfPositionBossAction(botAI); }
|
||||
static Action* karazhan_big_bad_wolf_run_away(PlayerbotAI* botAI) { return new KarazhanBigBadWolfRunAwayAction(botAI); }
|
||||
static Action* attumen_the_huntsman_stack_behind(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanStackBehindAction(botAI); }
|
||||
|
||||
static Action* karazhan_romulo_and_julianne_mark_target(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneMarkTargetAction(botAI); }
|
||||
static Action* attumen_the_huntsman_manage_dps_timer(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanManageDpsTimerAction(botAI); }
|
||||
|
||||
static Action* karazhan_wizard_of_oz_mark_target(PlayerbotAI* botAI) { return new KarazhanWizardOfOzMarkTargetAction(botAI); }
|
||||
static Action* karazhan_wizard_of_oz_scorch_strawman(PlayerbotAI* botAI) { return new KarazhanWizardOfOzScorchStrawmanAction(botAI); }
|
||||
// Moroes
|
||||
static Action* moroes_main_tank_attack_boss(
|
||||
PlayerbotAI* botAI) { return new MoroesMainTankAttackBossAction(botAI); }
|
||||
|
||||
static Action* karazhan_the_curator_mark_target(PlayerbotAI* botAI) { return new KarazhanTheCuratorMarkTargetAction(botAI); }
|
||||
static Action* karazhan_the_curator_position_boss(PlayerbotAI* botAI) { return new KarazhanTheCuratorPositionBossAction(botAI); }
|
||||
static Action* karazhan_the_curator_spread_ranged(PlayerbotAI* botAI) { return new KarazhanTheCuratorSpreadRangedAction(botAI); }
|
||||
static Action* moroes_mark_target(
|
||||
PlayerbotAI* botAI) { return new MoroesMarkTargetAction(botAI); }
|
||||
|
||||
static Action* karazhan_terestian_illhoof_mark_target(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofMarkTargetAction(botAI); }
|
||||
// Maiden of Virtue
|
||||
static Action* maiden_of_virtue_move_boss_to_healer(
|
||||
PlayerbotAI* botAI) { return new MaidenOfVirtueMoveBossToHealerAction(botAI); }
|
||||
|
||||
static Action* karazhan_shade_of_aran_arcane_explosion_run_away(PlayerbotAI* botAI) { return new KarazhanShadeOfAranArcaneExplosionRunAwayAction(botAI); }
|
||||
static Action* karazhan_shade_of_aran_flame_wreath_stop_movement(PlayerbotAI* botAI) { return new KarazhanShadeOfAranFlameWreathStopMovementAction(botAI); }
|
||||
static Action* karazhan_shade_of_aran_mark_conjured_elemental(PlayerbotAI* botAI) { return new KarazhanShadeOfAranMarkConjuredElementalAction(botAI); }
|
||||
static Action* karazhan_shade_of_aran_spread_ranged(PlayerbotAI* botAI) { return new KarazhanShadeOfAranSpreadRangedAction(botAI); }
|
||||
static Action* maiden_of_virtue_position_ranged(
|
||||
PlayerbotAI* botAI) { return new MaidenOfVirtuePositionRangedAction(botAI); }
|
||||
|
||||
static Action* karazhan_netherspite_block_red_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockRedBeamAction(botAI); }
|
||||
static Action* karazhan_netherspite_block_blue_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockBlueBeamAction(botAI); }
|
||||
static Action* karazhan_netherspite_block_green_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockGreenBeamAction(botAI); }
|
||||
static Action* karazhan_netherspite_avoid_beam_and_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteAvoidBeamAndVoidZoneAction(botAI); }
|
||||
static Action* karazhan_netherspite_banish_phase_avoid_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(botAI); }
|
||||
// The Big Bad Wolf
|
||||
static Action* big_bad_wolf_position_boss(
|
||||
PlayerbotAI* botAI) { return new BigBadWolfPositionBossAction(botAI); }
|
||||
|
||||
static Action* karazhan_prince_malchezaar_non_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarNonTankAvoidHazardAction(botAI); }
|
||||
static Action* karazhan_prince_malchezaar_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTankAvoidHazardAction(botAI); }
|
||||
static Action* big_bad_wolf_run_away_from_boss(
|
||||
PlayerbotAI* botAI) { return new BigBadWolfRunAwayFromBossAction(botAI); }
|
||||
|
||||
// Romulo and Julianne
|
||||
static Action* romulo_and_julianne_mark_target(
|
||||
PlayerbotAI* botAI) { return new RomuloAndJulianneMarkTargetAction(botAI); }
|
||||
|
||||
// The Wizard of Oz
|
||||
static Action* wizard_of_oz_mark_target(
|
||||
PlayerbotAI* botAI) { return new WizardOfOzMarkTargetAction(botAI); }
|
||||
|
||||
static Action* wizard_of_oz_scorch_strawman(
|
||||
PlayerbotAI* botAI) { return new WizardOfOzScorchStrawmanAction(botAI); }
|
||||
|
||||
// The Curator
|
||||
static Action* the_curator_mark_astral_flare(
|
||||
PlayerbotAI* botAI) { return new TheCuratorMarkAstralFlareAction(botAI); }
|
||||
|
||||
static Action* the_curator_position_boss(
|
||||
PlayerbotAI* botAI) { return new TheCuratorPositionBossAction(botAI); }
|
||||
|
||||
static Action* the_curator_spread_ranged(
|
||||
PlayerbotAI* botAI) { return new TheCuratorSpreadRangedAction(botAI); }
|
||||
|
||||
// Terestian Illhoof
|
||||
static Action* terestian_illhoof_mark_target(
|
||||
PlayerbotAI* botAI) { return new TerestianIllhoofMarkTargetAction(botAI); }
|
||||
|
||||
// Shade of Aran
|
||||
static Action* shade_of_aran_run_away_from_arcane_explosion(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranRunAwayFromArcaneExplosionAction(botAI); }
|
||||
|
||||
static Action* shade_of_aran_stop_moving_during_flame_wreath(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranStopMovingDuringFlameWreathAction(botAI); }
|
||||
|
||||
static Action* shade_of_aran_mark_conjured_elemental(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranMarkConjuredElementalAction(botAI); }
|
||||
|
||||
static Action* shade_of_aran_ranged_maintain_distance(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranRangedMaintainDistanceAction(botAI); }
|
||||
|
||||
// Netherspite
|
||||
static Action* netherspite_block_red_beam(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBlockRedBeamAction(botAI); }
|
||||
|
||||
static Action* netherspite_block_blue_beam(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBlockBlueBeamAction(botAI); }
|
||||
|
||||
static Action* netherspite_block_green_beam(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBlockGreenBeamAction(botAI); }
|
||||
|
||||
static Action* netherspite_avoid_beam_and_void_zone(
|
||||
PlayerbotAI* botAI) { return new NetherspiteAvoidBeamAndVoidZoneAction(botAI); }
|
||||
|
||||
static Action* netherspite_banish_phase_avoid_void_zone(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBanishPhaseAvoidVoidZoneAction(botAI); }
|
||||
|
||||
static Action* netherspite_manage_timers_and_trackers(
|
||||
PlayerbotAI* botAI) { return new NetherspiteManageTimersAndTrackersAction(botAI); }
|
||||
|
||||
// Prince Malchezaar
|
||||
static Action* prince_malchezaar_enfeebled_avoid_hazard(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarEnfeebledAvoidHazardAction(botAI); }
|
||||
|
||||
static Action* prince_malchezaar_non_tank_avoid_infernal(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarNonTankAvoidInfernalAction(botAI); }
|
||||
|
||||
static Action* prince_malchezaar_main_tank_movement(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarMainTankMovementAction(botAI); }
|
||||
|
||||
// Nightbane
|
||||
static Action* nightbane_ground_phase_position_boss(
|
||||
PlayerbotAI* botAI) { return new NightbaneGroundPhasePositionBossAction(botAI); }
|
||||
|
||||
static Action* nightbane_ground_phase_rotate_ranged_positions(
|
||||
PlayerbotAI* botAI) { return new NightbaneGroundPhaseRotateRangedPositionsAction(botAI); }
|
||||
|
||||
static Action* nightbane_cast_fear_ward_on_main_tank(
|
||||
PlayerbotAI* botAI) { return new NightbaneCastFearWardOnMainTankAction(botAI); }
|
||||
|
||||
static Action* nightbane_control_pet_aggression(
|
||||
PlayerbotAI* botAI) { return new NightbaneControlPetAggressionAction(botAI); }
|
||||
|
||||
static Action* nightbane_flight_phase_movement(
|
||||
PlayerbotAI* botAI) { return new NightbaneFlightPhaseMovementAction(botAI); }
|
||||
|
||||
static Action* nightbane_manage_timers_and_trackers(
|
||||
PlayerbotAI* botAI) { return new NightbaneManageTimersAndTrackersAction(botAI); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,217 +2,321 @@
|
||||
#define _PLAYERBOT_RAIDKARAZHANACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "MovementActions.h"
|
||||
|
||||
class KarazhanAttumenTheHuntsmanStackBehindAction : public MovementAction
|
||||
class ManaWarpStunCreatureBeforeWarpBreachAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanAttumenTheHuntsmanStackBehindAction(PlayerbotAI* botAI, std::string const name = "karazhan attumen the huntsman stack behind") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanMoroesMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanMoroesMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan moroes mark target") : Action(botAI, name) {}
|
||||
|
||||
ManaWarpStunCreatureBeforeWarpBreachAction(
|
||||
PlayerbotAI* botAI, std::string const name = "mana warp stun creature before warp breach") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanMaidenOfVirtuePositionBossAction : public MovementAction
|
||||
class AttumenTheHuntsmanMarkTargetAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanMaidenOfVirtuePositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position boss") : MovementAction(botAI, name) {}
|
||||
|
||||
AttumenTheHuntsmanMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "attumen the huntsman mark target") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanMaidenOfVirtuePositionRangedAction : public MovementAction
|
||||
class AttumenTheHuntsmanSplitBossesAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanMaidenOfVirtuePositionRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position ranged") : MovementAction(botAI, name) {}
|
||||
|
||||
AttumenTheHuntsmanSplitBossesAction(
|
||||
PlayerbotAI* botAI, std::string const name = "attumen the huntsman split bosses") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanBigBadWolfPositionBossAction : public MovementAction
|
||||
class AttumenTheHuntsmanStackBehindAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanBigBadWolfPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf position boss") : MovementAction(botAI, name) {}
|
||||
|
||||
AttumenTheHuntsmanStackBehindAction(
|
||||
PlayerbotAI* botAI, std::string const name = "attumen the huntsman stack behind") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanBigBadWolfRunAwayAction : public MovementAction
|
||||
class AttumenTheHuntsmanManageDpsTimerAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanBigBadWolfRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf run away") : MovementAction(botAI, name) {}
|
||||
|
||||
AttumenTheHuntsmanManageDpsTimerAction(
|
||||
PlayerbotAI* botAI, std::string const name = "attumen the huntsman manage dps timer") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MoroesMainTankAttackBossAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
MoroesMainTankAttackBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "moroes main tank attack boss") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MoroesMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
MoroesMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "moroes mark target") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MaidenOfVirtueMoveBossToHealerAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
MaidenOfVirtueMoveBossToHealerAction(
|
||||
PlayerbotAI* botAI, std::string const name = "maiden of virtue move boss to healer") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MaidenOfVirtuePositionRangedAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MaidenOfVirtuePositionRangedAction(
|
||||
PlayerbotAI* botAI, std::string const name = "maiden of virtue position ranged") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class BigBadWolfPositionBossAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
BigBadWolfPositionBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "big bad wolf position boss") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class BigBadWolfRunAwayFromBossAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
BigBadWolfRunAwayFromBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "big bad wolf run away from boss") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class RomuloAndJulianneMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
RomuloAndJulianneMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "romulo and julianne mark target") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class WizardOfOzMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
WizardOfOzMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "wizard of oz mark target") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class WizardOfOzScorchStrawmanAction : public Action
|
||||
{
|
||||
public:
|
||||
WizardOfOzScorchStrawmanAction(
|
||||
PlayerbotAI* botAI, std::string const name = "wizard of oz scorch strawman") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TheCuratorMarkAstralFlareAction : public Action
|
||||
{
|
||||
public:
|
||||
TheCuratorMarkAstralFlareAction(
|
||||
PlayerbotAI* botAI, std::string const name = "the curator mark astral flare") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TheCuratorPositionBossAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
TheCuratorPositionBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "the curator position boss") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TheCuratorSpreadRangedAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
TheCuratorSpreadRangedAction(
|
||||
PlayerbotAI* botAI, std::string const name = "the curator spread ranged") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TerestianIllhoofMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
TerestianIllhoofMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "terestian illhoof mark target") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ShadeOfAranRunAwayFromArcaneExplosionAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
ShadeOfAranRunAwayFromArcaneExplosionAction(
|
||||
PlayerbotAI* botAI, std::string const name = "shade of aran run away from arcane explosion") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ShadeOfAranStopMovingDuringFlameWreathAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
ShadeOfAranStopMovingDuringFlameWreathAction(
|
||||
PlayerbotAI* botAI, std::string const name = "shade of aran stop moving during flame wreath") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ShadeOfAranMarkConjuredElementalAction : public Action
|
||||
{
|
||||
public:
|
||||
ShadeOfAranMarkConjuredElementalAction(
|
||||
PlayerbotAI* botAI, std::string const name = "shade of aran mark conjured elemental") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ShadeOfAranRangedMaintainDistanceAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
ShadeOfAranRangedMaintainDistanceAction(
|
||||
PlayerbotAI* botAI, std::string const name = "shade of aran ranged maintain distance") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class NetherspiteBlockRedBeamAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
NetherspiteBlockRedBeamAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite block red beam") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
private:
|
||||
size_t currentIndex = 0;
|
||||
Position GetPositionOnBeam(Unit* netherspite, Unit* portal, float distanceFromBoss);
|
||||
std::unordered_map<ObjectGuid, bool> _wasBlockingRedBeam;
|
||||
};
|
||||
|
||||
class KarazhanRomuloAndJulianneMarkTargetAction : public Action
|
||||
class NetherspiteBlockBlueBeamAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanRomuloAndJulianneMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan romulo and julianne mark target") : Action(botAI, name) {}
|
||||
NetherspiteBlockBlueBeamAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite block blue beam") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<ObjectGuid, bool> _wasBlockingBlueBeam;
|
||||
};
|
||||
|
||||
class NetherspiteBlockGreenBeamAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
NetherspiteBlockGreenBeamAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite block green beam") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<ObjectGuid, bool> _wasBlockingGreenBeam;
|
||||
};
|
||||
|
||||
class NetherspiteAvoidBeamAndVoidZoneAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
NetherspiteAvoidBeamAndVoidZoneAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite avoid beam and void zone") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
struct BeamAvoid
|
||||
{
|
||||
Unit* portal;
|
||||
float minDist, maxDist;
|
||||
};
|
||||
bool IsAwayFromBeams(float x, float y, const std::vector<BeamAvoid>& beams, Unit* netherspite);
|
||||
};
|
||||
|
||||
class NetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
NetherspiteBanishPhaseAvoidVoidZoneAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite banish phase avoid void zone") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanWizardOfOzMarkTargetAction : public Action
|
||||
class NetherspiteManageTimersAndTrackersAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanWizardOfOzMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz mark target") : Action(botAI, name) {}
|
||||
|
||||
NetherspiteManageTimersAndTrackersAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite manage timers and trackers") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanWizardOfOzScorchStrawmanAction : public Action
|
||||
class PrinceMalchezaarEnfeebledAvoidHazardAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanWizardOfOzScorchStrawmanAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz scorch strawman") : Action(botAI, name) {}
|
||||
|
||||
PrinceMalchezaarEnfeebledAvoidHazardAction(
|
||||
PlayerbotAI* botAI, std::string const name = "prince malchezaar enfeebled avoid hazard") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanTheCuratorMarkTargetAction : public Action
|
||||
class PrinceMalchezaarNonTankAvoidInfernalAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanTheCuratorMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator mark target") : Action(botAI, name) {}
|
||||
|
||||
PrinceMalchezaarNonTankAvoidInfernalAction(
|
||||
PlayerbotAI* botAI, std::string const name = "prince malchezaar non tank avoid infernal") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanTheCuratorPositionBossAction : public MovementAction
|
||||
class PrinceMalchezaarMainTankMovementAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanTheCuratorPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator position boss") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanTheCuratorSpreadRangedAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanTheCuratorSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator spread ranged") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanTerestianIllhoofMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanTerestianIllhoofMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan terestian illhoof mark target") : Action(botAI, name) {}
|
||||
|
||||
PrinceMalchezaarMainTankMovementAction(
|
||||
PlayerbotAI* botAI, std::string const name = "prince malchezaar main tank movement") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranArcaneExplosionRunAwayAction : public MovementAction
|
||||
class NightbaneGroundPhasePositionBossAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranArcaneExplosionRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran arcane explosion run away") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranFlameWreathStopMovementAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranFlameWreathStopMovementAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran flame wreath stop bot") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneGroundPhasePositionBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane ground phase position boss") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranMarkConjuredElementalAction : public Action
|
||||
class NightbaneGroundPhaseRotateRangedPositionsAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranMarkConjuredElementalAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran mark conjured elemental") : Action(botAI, name) {}
|
||||
|
||||
NightbaneGroundPhaseRotateRangedPositionsAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane ground phase rotate ranged positions") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranSpreadRangedAction : public MovementAction
|
||||
class NightbaneCastFearWardOnMainTankAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran spread ranged") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneCastFearWardOnMainTankAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane cast fear ward on main tank") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBlockRedBeamAction : public MovementAction
|
||||
class NightbaneControlPetAggressionAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBlockRedBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block red beam") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneControlPetAggressionAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane control pet aggression") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBlockBlueBeamAction : public MovementAction
|
||||
class NightbaneFlightPhaseMovementAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBlockBlueBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block blue beam") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneFlightPhaseMovementAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane flight phase movement") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBlockGreenBeamAction : public MovementAction
|
||||
class NightbaneManageTimersAndTrackersAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBlockGreenBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block green beam") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneManageTimersAndTrackersAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane manage timers and trackers") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteAvoidBeamAndVoidZoneAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteAvoidBeamAndVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite avoid beam and void zone") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite banish phase avoid void zone") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanPrinceMalchezaarNonTankAvoidHazardAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanPrinceMalchezaarNonTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar non-tank avoid hazard") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanPrinceMalchezaarTankAvoidHazardAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanPrinceMalchezaarTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar tank avoid hazard") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,316 +1,356 @@
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#include "RaidKarazhanHelpers.h"
|
||||
#include "RaidKarazhanActions.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "Position.h"
|
||||
#include "Spell.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
|
||||
const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION = Position(-10945.881f, -2103.782f, 92.712f);
|
||||
const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8] =
|
||||
namespace KarazhanHelpers
|
||||
{
|
||||
{ -10931.178f, -2116.580f, 92.179f },
|
||||
{ -10925.828f, -2102.425f, 92.180f },
|
||||
{ -10933.089f, -2088.5017f, 92.180f },
|
||||
{ -10947.59f, -2082.8147f, 92.180f },
|
||||
{ -10960.912f, -2090.4368f, 92.179f },
|
||||
{ -10966.017f, -2105.288f, 92.175f },
|
||||
{ -10959.242f, -2119.6172f, 92.180f },
|
||||
{ -10944.495f, -2123.857f, 92.180f },
|
||||
};
|
||||
// Attumen the Huntsman
|
||||
std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
|
||||
// Big Bad Wolf
|
||||
std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex;
|
||||
// Netherspite
|
||||
std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer;
|
||||
std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer;
|
||||
std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways;
|
||||
// Nightbane
|
||||
std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer;
|
||||
std::unordered_map<ObjectGuid, uint8> nightbaneTankStep;
|
||||
std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
|
||||
std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
|
||||
std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
|
||||
|
||||
const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION = Position(-10913.391f, -1773.508f, 90.477f);
|
||||
const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4] =
|
||||
{
|
||||
{ -10875.456f, -1779.036f, 90.477f },
|
||||
{ -10872.281f, -1751.638f, 90.477f },
|
||||
{ -10910.492f, -1747.401f, 90.477f },
|
||||
{ -10913.391f, -1773.508f, 90.477f },
|
||||
};
|
||||
|
||||
const Position KARAZHAN_THE_CURATOR_BOSS_POSITION = Position(-11139.463f, -1884.645f, 165.765f);
|
||||
|
||||
void RaidKarazhanHelpers::MarkTargetWithSkull(Unit* target)
|
||||
{
|
||||
if (!target)
|
||||
const Position MAIDEN_OF_VIRTUE_BOSS_POSITION = { -10945.881f, -2103.782f, 92.712f };
|
||||
const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8] =
|
||||
{
|
||||
return;
|
||||
}
|
||||
{ -10931.178f, -2116.580f, 92.179f },
|
||||
{ -10925.828f, -2102.425f, 92.180f },
|
||||
{ -10933.089f, -2088.502f, 92.180f },
|
||||
{ -10947.590f, -2082.815f, 92.180f },
|
||||
{ -10960.912f, -2090.437f, 92.179f },
|
||||
{ -10966.017f, -2105.288f, 92.175f },
|
||||
{ -10959.242f, -2119.617f, 92.180f },
|
||||
{ -10944.495f, -2123.857f, 92.180f },
|
||||
};
|
||||
|
||||
if (Group* group = bot->GetGroup())
|
||||
const Position BIG_BAD_WOLF_BOSS_POSITION = { -10913.391f, -1773.508f, 90.477f };
|
||||
const Position BIG_BAD_WOLF_RUN_POSITION[4] =
|
||||
{
|
||||
constexpr uint8_t skullIconId = 7;
|
||||
ObjectGuid skullGuid = group->GetTargetIcon(skullIconId);
|
||||
{ -10875.456f, -1779.036f, 90.477f },
|
||||
{ -10872.281f, -1751.638f, 90.477f },
|
||||
{ -10910.492f, -1747.401f, 90.477f },
|
||||
{ -10913.391f, -1773.508f, 90.477f },
|
||||
};
|
||||
|
||||
if (skullGuid != target->GetGUID())
|
||||
const Position THE_CURATOR_BOSS_POSITION = { -11139.463f, -1884.645f, 165.765f };
|
||||
|
||||
const Position NIGHTBANE_TRANSITION_BOSS_POSITION = { -11160.646f, -1932.773f, 91.473f }; // near some ribs
|
||||
const Position NIGHTBANE_FINAL_BOSS_POSITION = { -11173.530f, -1940.707f, 91.473f };
|
||||
const Position NIGHTBANE_RANGED_POSITION1 = { -11145.949f, -1970.927f, 91.473f };
|
||||
const Position NIGHTBANE_RANGED_POSITION2 = { -11143.594f, -1954.981f, 91.473f };
|
||||
const Position NIGHTBANE_RANGED_POSITION3 = { -11159.778f, -1961.031f, 91.473f };
|
||||
const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel
|
||||
const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f };
|
||||
|
||||
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
group->SetTargetIcon(skullIconId, bot->GetGUID(), target->GetGUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Unit* RaidKarazhanHelpers::GetFirstAliveUnit(const std::vector<Unit*>& units)
|
||||
{
|
||||
for (Unit* unit : units)
|
||||
{
|
||||
if (unit && unit->IsAlive())
|
||||
{
|
||||
return unit;
|
||||
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
|
||||
if (currentGuid != target->GetGUID())
|
||||
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* RaidKarazhanHelpers::GetFirstAliveUnitByEntry(uint32 entry)
|
||||
{
|
||||
const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||
|
||||
for (auto const& npcGuid : npcs)
|
||||
void MarkTargetWithSkull(Player* bot, Unit* target)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
|
||||
}
|
||||
|
||||
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
|
||||
void MarkTargetWithSquare(Player* bot, Unit* target)
|
||||
{
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
|
||||
}
|
||||
|
||||
void MarkTargetWithStar(Player* bot, Unit* target)
|
||||
{
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
|
||||
}
|
||||
|
||||
void MarkTargetWithCircle(Player* bot, Unit* target)
|
||||
{
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
|
||||
}
|
||||
|
||||
void MarkTargetWithMoon(Player* bot, Unit* target)
|
||||
{
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
|
||||
}
|
||||
|
||||
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
|
||||
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
|
||||
|
||||
if (currentRti != rtiName || currentTarget != target)
|
||||
{
|
||||
return unit;
|
||||
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
|
||||
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* RaidKarazhanHelpers::GetNearestPlayerInRadius(float radius)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
// Only one bot is needed to set/reset instance-wide timers
|
||||
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
|
||||
if (!member || !member->IsAlive() || member == bot)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bot->GetExactDist2d(member) < radius)
|
||||
{
|
||||
return member;
|
||||
Player* member = ref->GetSource();
|
||||
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
|
||||
return member == bot;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool RaidKarazhanHelpers::IsFlameWreathActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
Spell* currentSpell = boss ? boss->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr;
|
||||
if (currentSpell && currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH)
|
||||
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
for (Unit* unit : units)
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (!member || !member->IsAlive())
|
||||
if (unit && unit->IsAlive())
|
||||
return unit;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry)
|
||||
{
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
|
||||
return unit;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
|
||||
{
|
||||
Unit* nearestPlayer = nullptr;
|
||||
float nearestDistance = radius;
|
||||
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (member->HasAura(SPELL_AURA_FLAME_WREATH))
|
||||
{
|
||||
return true;
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || member == bot)
|
||||
continue;
|
||||
|
||||
float distance = bot->GetExactDist2d(member);
|
||||
if (distance < nearestDistance)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearestPlayer = member;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nearestPlayer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Red beam blockers: tank bots, no Nether Exhaustion Red
|
||||
std::vector<Player*> RaidKarazhanHelpers::GetRedBlockers()
|
||||
{
|
||||
std::vector<Player*> redBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) ||
|
||||
member->HasAura(SPELL_NETHER_EXHAUSTION_RED))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
redBlockers.push_back(member);
|
||||
}
|
||||
}
|
||||
Unit* aran = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "shade of aran")->Get();
|
||||
Spell* currentSpell = aran ? aran->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr;
|
||||
|
||||
return redBlockers;
|
||||
}
|
||||
if (currentSpell && currentSpell->m_spellInfo &&
|
||||
currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH_CAST)
|
||||
return true;
|
||||
|
||||
// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and ≤25 stacks of Blue Beam debuff
|
||||
std::vector<Player*> RaidKarazhanHelpers::GetBlueBlockers()
|
||||
{
|
||||
std::vector<Player*> blueBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool isDps = botAI->IsDps(member);
|
||||
bool isWarrior = member->getClass() == CLASS_WARRIOR;
|
||||
bool isRogue = member->getClass() == CLASS_ROGUE;
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE);
|
||||
Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF);
|
||||
bool overStack = blueBuff && blueBuff->GetStackAmount() >= 26;
|
||||
if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack)
|
||||
{
|
||||
blueBlockers.push_back(member);
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (member->HasAura(SPELL_FLAME_WREATH_AURA))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return blueBlockers;
|
||||
}
|
||||
|
||||
// Green beam blockers:
|
||||
// (1) Rogue and non-tank Warrior bots, no Nether Exhaustion Green
|
||||
// (2) Healer bots, no Nether Exhaustion Green and ≤25 stacks of Green Beam debuff
|
||||
std::vector<Player*> RaidKarazhanHelpers::GetGreenBlockers()
|
||||
{
|
||||
std::vector<Player*> greenBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
// Red beam blockers: tank bots, no Nether Exhaustion Red
|
||||
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
std::vector<Player*> redBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
|
||||
Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF);
|
||||
bool overStack = greenBuff && greenBuff->GetStackAmount() >= 26;
|
||||
bool isRogue = member->getClass() == CLASS_ROGUE;
|
||||
bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member);
|
||||
bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion;
|
||||
bool isHealer = botAI->IsHeal(member);
|
||||
bool eligibleHealer = isHealer && !hasExhaustion && !overStack;
|
||||
if (eligibleRogueWarrior || eligibleHealer)
|
||||
{
|
||||
greenBlockers.push_back(member);
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) ||
|
||||
member->HasAura(SPELL_NETHER_EXHAUSTION_RED))
|
||||
continue;
|
||||
|
||||
redBlockers.push_back(member);
|
||||
}
|
||||
}
|
||||
|
||||
return redBlockers;
|
||||
}
|
||||
|
||||
return greenBlockers;
|
||||
}
|
||||
|
||||
Position RaidKarazhanHelpers::GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss)
|
||||
{
|
||||
float bx = boss->GetPositionX();
|
||||
float by = boss->GetPositionY();
|
||||
float bz = boss->GetPositionZ();
|
||||
float px = portal->GetPositionX();
|
||||
float py = portal->GetPositionY();
|
||||
|
||||
float dx = px - bx;
|
||||
float dy = py - by;
|
||||
float length = sqrt(dx*dx + dy*dy);
|
||||
if (length == 0.0f)
|
||||
// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and <24 stacks of Blue Beam debuff
|
||||
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
return Position(bx, by, bz);
|
||||
std::vector<Player*> blueBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
continue;
|
||||
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE);
|
||||
Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF);
|
||||
bool overStack = blueBuff && blueBuff->GetStackAmount() >= 24;
|
||||
|
||||
bool isDps = botAI->IsDps(member);
|
||||
bool isWarrior = member->getClass() == CLASS_WARRIOR;
|
||||
bool isRogue = member->getClass() == CLASS_ROGUE;
|
||||
|
||||
if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack)
|
||||
blueBlockers.push_back(member);
|
||||
}
|
||||
}
|
||||
|
||||
return blueBlockers;
|
||||
}
|
||||
|
||||
dx /= length;
|
||||
dy /= length;
|
||||
float targetX = bx + dx * distanceFromBoss;
|
||||
float targetY = by + dy * distanceFromBoss;
|
||||
float targetZ = bz;
|
||||
|
||||
return Position(targetX, targetY, targetZ);
|
||||
}
|
||||
|
||||
std::tuple<Player*, Player*, Player*> RaidKarazhanHelpers::GetCurrentBeamBlockers()
|
||||
{
|
||||
static ObjectGuid currentRedBlocker;
|
||||
static ObjectGuid currentGreenBlocker;
|
||||
static ObjectGuid currentBlueBlocker;
|
||||
|
||||
Player* redBlocker = nullptr;
|
||||
Player* greenBlocker = nullptr;
|
||||
Player* blueBlocker = nullptr;
|
||||
|
||||
std::vector<Player*> redBlockers = GetRedBlockers();
|
||||
if (!redBlockers.empty())
|
||||
// Green beam blockers:
|
||||
// (1) Prioritize Rogues and non-tank Warrior bots, no Nether Exhaustion Green
|
||||
// (2) Then assign Healer bots, no Nether Exhaustion Green and <24 stacks of Green Beam debuff
|
||||
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* p)
|
||||
std::vector<Player*> greenBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
return p && p->GetGUID() == currentRedBlocker;
|
||||
});
|
||||
if (it != redBlockers.end())
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
continue;
|
||||
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
|
||||
bool isRogue = member->getClass() == CLASS_ROGUE;
|
||||
bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member);
|
||||
bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion;
|
||||
|
||||
if (eligibleRogueWarrior)
|
||||
greenBlockers.push_back(member);
|
||||
}
|
||||
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
continue;
|
||||
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
|
||||
Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF);
|
||||
bool overStack = greenBuff && greenBuff->GetStackAmount() >= 24;
|
||||
bool isHealer = botAI->IsHeal(member);
|
||||
bool eligibleHealer = isHealer && !hasExhaustion && !overStack;
|
||||
|
||||
if (eligibleHealer)
|
||||
greenBlockers.push_back(member);
|
||||
}
|
||||
}
|
||||
|
||||
return greenBlockers;
|
||||
}
|
||||
|
||||
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
static ObjectGuid currentRedBlocker;
|
||||
static ObjectGuid currentGreenBlocker;
|
||||
static ObjectGuid currentBlueBlocker;
|
||||
|
||||
Player* redBlocker = nullptr;
|
||||
Player* greenBlocker = nullptr;
|
||||
Player* blueBlocker = nullptr;
|
||||
|
||||
std::vector<Player*> redBlockers = GetRedBlockers(botAI, bot);
|
||||
if (!redBlockers.empty())
|
||||
{
|
||||
redBlocker = *it;
|
||||
auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* player)
|
||||
{
|
||||
return player && player->GetGUID() == currentRedBlocker;
|
||||
});
|
||||
|
||||
if (it != redBlockers.end())
|
||||
redBlocker = *it;
|
||||
else
|
||||
redBlocker = redBlockers.front();
|
||||
|
||||
currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
redBlocker = redBlockers.front();
|
||||
currentRedBlocker = ObjectGuid::Empty;
|
||||
redBlocker = nullptr;
|
||||
}
|
||||
currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentRedBlocker = ObjectGuid::Empty;
|
||||
redBlocker = nullptr;
|
||||
}
|
||||
|
||||
std::vector<Player*> greenBlockers = GetGreenBlockers();
|
||||
if (!greenBlockers.empty())
|
||||
{
|
||||
auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* p)
|
||||
std::vector<Player*> greenBlockers = GetGreenBlockers(botAI, bot);
|
||||
if (!greenBlockers.empty())
|
||||
{
|
||||
return p && p->GetGUID() == currentGreenBlocker;
|
||||
});
|
||||
if (it != greenBlockers.end())
|
||||
{
|
||||
greenBlocker = *it;
|
||||
auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* player)
|
||||
{
|
||||
return player && player->GetGUID() == currentGreenBlocker;
|
||||
});
|
||||
|
||||
if (it != greenBlockers.end())
|
||||
greenBlocker = *it;
|
||||
else
|
||||
greenBlocker = greenBlockers.front();
|
||||
|
||||
currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
greenBlocker = greenBlockers.front();
|
||||
currentGreenBlocker = ObjectGuid::Empty;
|
||||
greenBlocker = nullptr;
|
||||
}
|
||||
currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentGreenBlocker = ObjectGuid::Empty;
|
||||
greenBlocker = nullptr;
|
||||
}
|
||||
|
||||
std::vector<Player*> blueBlockers = GetBlueBlockers();
|
||||
std::vector<Player*> blueBlockers = GetBlueBlockers(botAI, bot);
|
||||
if (!blueBlockers.empty())
|
||||
{
|
||||
auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* p)
|
||||
auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* player)
|
||||
{
|
||||
return p && p->GetGUID() == currentBlueBlocker;
|
||||
return player && player->GetGUID() == currentBlueBlocker;
|
||||
});
|
||||
|
||||
if (it != blueBlockers.end())
|
||||
{
|
||||
blueBlocker = *it;
|
||||
}
|
||||
else
|
||||
{
|
||||
blueBlocker = blueBlockers.front();
|
||||
}
|
||||
|
||||
currentBlueBlocker = blueBlocker ? blueBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
@@ -319,91 +359,132 @@ std::tuple<Player*, Player*, Player*> RaidKarazhanHelpers::GetCurrentBeamBlocker
|
||||
blueBlocker = nullptr;
|
||||
}
|
||||
|
||||
return std::make_tuple(redBlocker, greenBlocker, blueBlocker);
|
||||
}
|
||||
|
||||
std::vector<Unit*> RaidKarazhanHelpers::GetAllVoidZones()
|
||||
{
|
||||
std::vector<Unit*> voidZones;
|
||||
const float radius = 30.0f;
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (!unit || unit->GetEntry() != NPC_VOID_ZONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float dist = bot->GetExactDist2d(unit);
|
||||
if (dist < radius)
|
||||
{
|
||||
voidZones.push_back(unit);
|
||||
}
|
||||
return std::make_tuple(redBlocker, greenBlocker, blueBlocker);
|
||||
}
|
||||
|
||||
return voidZones;
|
||||
}
|
||||
|
||||
bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z,
|
||||
const std::vector<Unit*>& hazards, float hazardRadius)
|
||||
{
|
||||
for (Unit* hazard : hazards)
|
||||
std::vector<Unit*> GetAllVoidZones(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
float dist = std::sqrt(std::pow(x - hazard->GetPositionX(), 2) + std::pow(y - hazard->GetPositionY(), 2));
|
||||
if (dist < hazardRadius)
|
||||
std::vector<Unit*> voidZones;
|
||||
const float radius = 30.0f;
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
{
|
||||
return false;
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (!unit || unit->GetEntry() != NPC_VOID_ZONE)
|
||||
continue;
|
||||
|
||||
float dist = bot->GetExactDist2d(unit);
|
||||
if (dist < radius)
|
||||
voidZones.push_back(unit);
|
||||
}
|
||||
|
||||
return voidZones;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<Unit*> RaidKarazhanHelpers::GetSpawnedInfernals() const
|
||||
{
|
||||
std::vector<Unit*> infernals;
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
bool IsSafePosition(float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL)
|
||||
for (Unit* hazard : hazards)
|
||||
{
|
||||
infernals.push_back(unit);
|
||||
float dist = hazard->GetExactDist2d(x, y);
|
||||
if (dist < hazardRadius)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return infernals;
|
||||
}
|
||||
|
||||
bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Position& target, const std::vector<Unit*>& hazards, float hazardRadius, float stepSize)
|
||||
{
|
||||
float sx = start.GetPositionX();
|
||||
float sy = start.GetPositionY();
|
||||
float sz = start.GetPositionZ();
|
||||
float tx = target.GetPositionX();
|
||||
float ty = target.GetPositionY();
|
||||
float tz = target.GetPositionZ();
|
||||
float totalDist = std::sqrt(std::pow(tx - sx, 2) + std::pow(ty - sy, 2));
|
||||
if (totalDist == 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize)
|
||||
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI)
|
||||
{
|
||||
float t = checkDist / totalDist;
|
||||
float checkX = sx + (tx - sx) * t;
|
||||
float checkY = sy + (ty - sy) * t;
|
||||
float checkZ = sz + (tz - sz) * t;
|
||||
for (Unit* hazard : hazards)
|
||||
std::vector<Unit*> infernals;
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
{
|
||||
float hazardDist = std::sqrt(std::pow(checkX - hazard->GetPositionX(), 2) + std::pow(checkY - hazard->GetPositionY(), 2));
|
||||
if (hazardDist < hazardRadius)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL)
|
||||
infernals.push_back(unit);
|
||||
}
|
||||
|
||||
return infernals;
|
||||
}
|
||||
|
||||
return true;
|
||||
bool IsStraightPathSafe(const Position& start, const Position& target, const std::vector<Unit*>& hazards,
|
||||
float hazardRadius, float stepSize)
|
||||
{
|
||||
float sx = start.GetPositionX();
|
||||
float sy = start.GetPositionY();
|
||||
float sz = start.GetPositionZ();
|
||||
float tx = target.GetPositionX();
|
||||
float ty = target.GetPositionY();
|
||||
float tz = target.GetPositionZ();
|
||||
|
||||
const float totalDist = start.GetExactDist2d(target.GetPositionX(), target.GetPositionY());
|
||||
if (totalDist == 0.0f)
|
||||
return true;
|
||||
|
||||
for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize)
|
||||
{
|
||||
float t = checkDist / totalDist;
|
||||
float checkX = sx + (tx - sx) * t;
|
||||
float checkY = sy + (ty - sy) * t;
|
||||
float checkZ = sz + (tz - sz) * t;
|
||||
for (Unit* hazard : hazards)
|
||||
{
|
||||
const float hx = checkX - hazard->GetPositionX();
|
||||
const float hy = checkY - hazard->GetPositionY();
|
||||
if ((hx*hx + hy*hy) < hazardRadius * hazardRadius)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryFindSafePositionWithSafePath(
|
||||
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ,
|
||||
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles,
|
||||
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ)
|
||||
{
|
||||
float bestMoveDist = std::numeric_limits<float>::max();
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; i < numAngles; ++i)
|
||||
{
|
||||
float angle = (2.0f * M_PI * i) / numAngles;
|
||||
float dx = cos(angle);
|
||||
float dy = sin(angle);
|
||||
|
||||
for (float dist = stepSize; dist <= maxSampleDist; dist += stepSize)
|
||||
{
|
||||
float x = centerX + dx * dist;
|
||||
float y = centerY + dy * dist;
|
||||
float z = centerZ;
|
||||
float destX = x, destY = y, destZ = z;
|
||||
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, centerX, centerY, centerZ,
|
||||
destX, destY, destZ, true))
|
||||
continue;
|
||||
|
||||
if (!IsSafePosition(destX, destY, destZ, hazards, safeDistance))
|
||||
continue;
|
||||
|
||||
if (requireSafePath)
|
||||
{
|
||||
if (!IsStraightPathSafe(Position(originX, originY, originZ), Position(destX, destY, destZ),
|
||||
hazards, safeDistance, stepSize))
|
||||
continue;
|
||||
}
|
||||
|
||||
const float moveDist = Position(originX, originY, originZ).GetExactDist2d(destX, destY);
|
||||
if (moveDist < bestMoveDist)
|
||||
{
|
||||
bestMoveDist = moveDist;
|
||||
bestDestX = destX;
|
||||
bestDestY = destY;
|
||||
bestDestZ = destZ;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +1,136 @@
|
||||
#ifndef _PLAYERBOT_RAIDKARAZHANHELPERS_H_
|
||||
#define _PLAYERBOT_RAIDKARAZHANHELPERS_H_
|
||||
|
||||
#include <ctime>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "AiObject.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Position.h"
|
||||
#include "Unit.h"
|
||||
|
||||
enum KarazhanSpells
|
||||
namespace KarazhanHelpers
|
||||
{
|
||||
// Maiden of Virtue
|
||||
SPELL_REPENTANCE = 29511,
|
||||
enum KarazhanSpells
|
||||
{
|
||||
// Maiden of Virtue
|
||||
SPELL_REPENTANCE = 29511,
|
||||
|
||||
// Opera Event
|
||||
SPELL_LITTLE_RED_RIDING_HOOD = 30756,
|
||||
// Opera Event
|
||||
SPELL_LITTLE_RED_RIDING_HOOD = 30756,
|
||||
|
||||
// Shade of Aran
|
||||
SPELL_FLAME_WREATH = 30004,
|
||||
SPELL_AURA_FLAME_WREATH = 29946,
|
||||
SPELL_ARCANE_EXPLOSION = 29973,
|
||||
SPELL_WARLOCK_BANISH = 18647, // Rank 2
|
||||
// The Curator
|
||||
SPELL_CURATOR_EVOCATION = 30254,
|
||||
|
||||
// Netherspite
|
||||
SPELL_GREEN_BEAM_DEBUFF = 30422,
|
||||
SPELL_BLUE_BEAM_DEBUFF = 30423,
|
||||
SPELL_NETHER_EXHAUSTION_RED = 38637,
|
||||
SPELL_NETHER_EXHAUSTION_GREEN = 38638,
|
||||
SPELL_NETHER_EXHAUSTION_BLUE = 38639,
|
||||
SPELL_NETHERSPITE_BANISHED = 39833,
|
||||
// Shade of Aran
|
||||
SPELL_FLAME_WREATH_CAST = 30004,
|
||||
SPELL_FLAME_WREATH_AURA = 29946,
|
||||
SPELL_ARCANE_EXPLOSION = 29973,
|
||||
|
||||
// Prince Malchezaar
|
||||
SPELL_ENFEEBLE = 30843,
|
||||
};
|
||||
// Netherspite
|
||||
SPELL_RED_BEAM_DEBUFF = 30421, // "Nether Portal - Perseverance" (player aura)
|
||||
SPELL_GREEN_BEAM_DEBUFF = 30422, // "Nether Portal - Serenity" (player aura)
|
||||
SPELL_BLUE_BEAM_DEBUFF = 30423, // "Nether Portal - Dominance" (player aura)
|
||||
SPELL_GREEN_BEAM_HEAL = 30467, // "Nether Portal - Serenity" (Netherspite aura)
|
||||
SPELL_NETHER_EXHAUSTION_RED = 38637,
|
||||
SPELL_NETHER_EXHAUSTION_GREEN = 38638,
|
||||
SPELL_NETHER_EXHAUSTION_BLUE = 38639,
|
||||
SPELL_NETHERSPITE_BANISHED = 39833, // "Vortex Shade Black"
|
||||
|
||||
// Prince Malchezaar
|
||||
SPELL_ENFEEBLE = 30843,
|
||||
|
||||
// Nightbane
|
||||
SPELL_CHARRED_EARTH = 30129,
|
||||
SPELL_BELLOWING_ROAR = 36922,
|
||||
SPELL_RAIN_OF_BONES = 37091,
|
||||
|
||||
// Warlock
|
||||
SPELL_WARLOCK_BANISH = 18647,
|
||||
|
||||
// Priest
|
||||
SPELL_FEAR_WARD = 6346,
|
||||
};
|
||||
|
||||
enum KarazhanNPCs
|
||||
{
|
||||
// Trash
|
||||
NPC_SPECTRAL_RETAINER = 16410,
|
||||
NPC_MANA_WARP = 16530,
|
||||
|
||||
// Attumen the Huntsman
|
||||
NPC_ATTUMEN_THE_HUNTSMAN = 15550,
|
||||
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
|
||||
|
||||
// Shade of Aran
|
||||
NPC_CONJURED_ELEMENTAL = 17167,
|
||||
|
||||
// Netherspite
|
||||
NPC_VOID_ZONE = 16697,
|
||||
NPC_GREEN_PORTAL = 17367, // "Nether Portal - Serenity <Healing Portal>"
|
||||
NPC_BLUE_PORTAL = 17368, // "Nether Portal - Dominance <Damage Portal>"
|
||||
NPC_RED_PORTAL = 17369, // "Nether Portal - Perseverance <Tanking Portal>"
|
||||
|
||||
// Prince Malchezaar
|
||||
NPC_NETHERSPITE_INFERNAL = 17646,
|
||||
};
|
||||
|
||||
const uint32 KARAZHAN_MAP_ID = 532;
|
||||
const float NIGHTBANE_FLIGHT_Z = 95.0f;
|
||||
|
||||
enum KarazhanNpcs
|
||||
{
|
||||
// Attumen the Huntsman
|
||||
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
|
||||
|
||||
// Terestian Illhoof
|
||||
NPC_KILREK = 17229,
|
||||
NPC_DEMON_CHAINS = 17248,
|
||||
|
||||
// Shade of Aran
|
||||
NPC_CONJURED_ELEMENTAL = 17167,
|
||||
|
||||
extern std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
|
||||
// Big Bad Wolf
|
||||
extern std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex;
|
||||
// Netherspite
|
||||
NPC_VOID_ZONE = 16697,
|
||||
NPC_RED_PORTAL = 17369,
|
||||
NPC_BLUE_PORTAL = 17368,
|
||||
NPC_GREEN_PORTAL = 17367,
|
||||
extern std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer;
|
||||
extern std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer;
|
||||
extern std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways;
|
||||
// Nightbane
|
||||
extern std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer;
|
||||
extern std::unordered_map<ObjectGuid, uint8> nightbaneTankStep;
|
||||
extern std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
|
||||
extern std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
|
||||
extern std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
|
||||
|
||||
// Prince Malchezaar
|
||||
NPC_NETHERSPITE_INFERNAL = 17646,
|
||||
};
|
||||
extern const Position MAIDEN_OF_VIRTUE_BOSS_POSITION;
|
||||
extern const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8];
|
||||
extern const Position BIG_BAD_WOLF_BOSS_POSITION;
|
||||
extern const Position BIG_BAD_WOLF_RUN_POSITION[4];
|
||||
extern const Position THE_CURATOR_BOSS_POSITION;
|
||||
extern const Position NIGHTBANE_TRANSITION_BOSS_POSITION;
|
||||
extern const Position NIGHTBANE_FINAL_BOSS_POSITION;
|
||||
extern const Position NIGHTBANE_RANGED_POSITION1;
|
||||
extern const Position NIGHTBANE_RANGED_POSITION2;
|
||||
extern const Position NIGHTBANE_RANGED_POSITION3;
|
||||
extern const Position NIGHTBANE_FLIGHT_STACK_POSITION;
|
||||
extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION;
|
||||
|
||||
extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION;
|
||||
extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8];
|
||||
extern const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION;
|
||||
extern const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4];
|
||||
extern const Position KARAZHAN_THE_CURATOR_BOSS_POSITION;
|
||||
|
||||
class RaidKarazhanHelpers : public AiObject
|
||||
{
|
||||
public:
|
||||
explicit RaidKarazhanHelpers(PlayerbotAI* botAI) : AiObject(botAI) {}
|
||||
|
||||
void MarkTargetWithSkull(Unit* /*target*/);
|
||||
Unit* GetFirstAliveUnit(const std::vector<Unit*>& /*units*/);
|
||||
Unit* GetFirstAliveUnitByEntry(uint32 /*entry*/);
|
||||
Unit* GetNearestPlayerInRadius(float /*radius*/ = 5.0f);
|
||||
bool IsFlameWreathActive();
|
||||
Position GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss);
|
||||
std::vector<Player*> GetRedBlockers();
|
||||
std::vector<Player*> GetBlueBlockers();
|
||||
std::vector<Player*> GetGreenBlockers();
|
||||
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers();
|
||||
std::vector<Unit*> GetAllVoidZones();
|
||||
bool IsSafePosition (float x, float y, float z,
|
||||
const std::vector<Unit*>& hazards, float hazardRadius);
|
||||
std::vector<Unit*> GetSpawnedInfernals() const;
|
||||
bool IsStraightPathSafe(const Position& start, const Position& target,
|
||||
const std::vector<Unit*>& hazards, float hazardRadius, float stepSize);
|
||||
};
|
||||
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
|
||||
void MarkTargetWithSkull(Player* bot, Unit* target);
|
||||
void MarkTargetWithSquare(Player* bot, Unit* target);
|
||||
void MarkTargetWithStar(Player* bot, Unit* target);
|
||||
void MarkTargetWithCircle(Player* bot, Unit* target);
|
||||
void MarkTargetWithMoon(Player* bot, Unit* target);
|
||||
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
|
||||
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
|
||||
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
|
||||
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
|
||||
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
|
||||
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot);
|
||||
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot);
|
||||
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot);
|
||||
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot);
|
||||
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot);
|
||||
std::vector<Unit*> GetAllVoidZones(PlayerbotAI *botAI, Player* bot);
|
||||
bool IsSafePosition (float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius);
|
||||
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI);
|
||||
bool IsStraightPathSafe(
|
||||
const Position& start, const Position& target,
|
||||
const std::vector<Unit*>& hazards, float hazardRadius, float stepSize);
|
||||
bool TryFindSafePositionWithSafePath(
|
||||
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ,
|
||||
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles,
|
||||
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,265 +1,362 @@
|
||||
#include "RaidKarazhanMultipliers.h"
|
||||
#include "RaidKarazhanActions.h"
|
||||
#include "RaidKarazhanHelpers.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "AttackAction.h"
|
||||
#include "DruidBearActions.h"
|
||||
#include "DruidCatActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "DruidActions.h"
|
||||
#include "FollowActions.h"
|
||||
#include "GenericActions.h"
|
||||
#include "HunterActions.h"
|
||||
#include "MageActions.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PriestActions.h"
|
||||
#include "ReachTargetActions.h"
|
||||
#include "RogueActions.h"
|
||||
#include "WarriorActions.h"
|
||||
#include "ShamanActions.h"
|
||||
|
||||
static bool IsChargeAction(Action* action)
|
||||
{
|
||||
return dynamic_cast<CastChargeAction*>(action) ||
|
||||
dynamic_cast<CastInterceptAction*>(action) ||
|
||||
dynamic_cast<CastFeralChargeBearAction*>(action) ||
|
||||
dynamic_cast<CastFeralChargeCatAction*>(action);
|
||||
}
|
||||
using namespace KarazhanHelpers;
|
||||
|
||||
float KarazhanAttumenTheHuntsmanMultiplier::GetValue(Action* action)
|
||||
// Keep tanks from jumping back and forth between Attumen and Midnight
|
||||
float AttumenTheHuntsmanDisableTankAssistMultiplier::GetValue(Action* action)
|
||||
{
|
||||
RaidKarazhanHelpers karazhanHelper(botAI);
|
||||
Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
if (boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot) &&
|
||||
(dynamic_cast<MovementAction*>(action) &&
|
||||
!dynamic_cast<KarazhanAttumenTheHuntsmanStackBehindAction*>(action)))
|
||||
{
|
||||
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
|
||||
if (!midnight)
|
||||
return 1.0f;
|
||||
|
||||
Unit* attumen = AI_VALUE2(Unit*, "find target", "attumen the huntsman");
|
||||
if (!attumen)
|
||||
return 1.0f;
|
||||
|
||||
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Try to get rid of jittering when bots are stacked behind Attumen
|
||||
float AttumenTheHuntsmanStayStackedMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
if (!attumenMounted)
|
||||
return 1.0f;
|
||||
|
||||
if (!botAI->IsMainTank(bot) && attumenMounted->GetVictim() != bot)
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action) ||
|
||||
dynamic_cast<CastBlinkBackAction*>(action) ||
|
||||
dynamic_cast<CastDisengageAction*>(action) ||
|
||||
dynamic_cast<CastReachTargetSpellAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanBigBadWolfMultiplier::GetValue(Action* action)
|
||||
// Give the main tank 8 seconds to grab aggro when Attumen mounts Midnight
|
||||
// In reality it's shorter because it takes Attumen a few seconds to aggro after mounting
|
||||
float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf");
|
||||
if (!boss)
|
||||
{
|
||||
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
if (!attumenMounted)
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
|
||||
const uint32 instanceId = attumenMounted->GetMap()->GetInstanceId();
|
||||
const time_t now = std::time(nullptr);
|
||||
const uint8 dpsWaitSeconds = 8;
|
||||
|
||||
auto it = attumenDpsWaitTimer.find(instanceId);
|
||||
if (it == attumenDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
|
||||
{
|
||||
if ((dynamic_cast<MovementAction*>(action) && !dynamic_cast<KarazhanBigBadWolfRunAwayAction*>(action)) ||
|
||||
(dynamic_cast<AttackAction*>(action)))
|
||||
if (!botAI->IsMainTank(bot))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanShadeOfAranMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
if (!boss)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION))
|
||||
{
|
||||
if (IsChargeAction(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (dynamic_cast<MovementAction*>(action))
|
||||
{
|
||||
const float safeDistance = 20.0f;
|
||||
if (bot->GetDistance2d(boss) >= safeDistance)
|
||||
{
|
||||
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
|
||||
!dynamic_cast<CastHealingSpellAction*>(action)))
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool flameWreathActive = boss->HasAura(SPELL_FLAME_WREATH);
|
||||
if (!flameWreathActive && bot->GetGroup())
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// The assist tank should stay on the boss to be 2nd on aggro and tank Hateful Bolts
|
||||
float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
if (!curator)
|
||||
return 1.0f;
|
||||
|
||||
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Save Bloodlust/Heroism for Evocation (100% increased damage)
|
||||
float TheCuratorDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
if (!curator)
|
||||
return 1.0f;
|
||||
|
||||
if (!curator->HasAura(SPELL_CURATOR_EVOCATION))
|
||||
{
|
||||
for (GroupReference* itr = bot->GetGroup()->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
if (dynamic_cast<CastBloodlustAction*>(action) ||
|
||||
dynamic_cast<CastHeroismAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Don't charge back in when running from Arcane Explosion
|
||||
float ShadeOfAranArcaneExplosionDisableChargeMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
if (!aran)
|
||||
return 1.0f;
|
||||
|
||||
if (aran->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION))
|
||||
{
|
||||
if (dynamic_cast<CastReachTargetSpellAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
if (bot->GetDistance2d(aran) >= 20.0f)
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (member && member->HasAura(SPELL_AURA_FLAME_WREATH))
|
||||
{
|
||||
flameWreathActive = true;
|
||||
break;
|
||||
}
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action) ||
|
||||
dynamic_cast<FollowAction*>(action) ||
|
||||
dynamic_cast<ReachTargetAction*>(action) ||
|
||||
dynamic_cast<AvoidAoeAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
if (flameWreathActive)
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// I will not move when Flame Wreath is cast or the raid blows up
|
||||
float ShadeOfAranFlameWreathDisableMovementMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
if (!aran)
|
||||
return 1.0f;
|
||||
|
||||
if (IsFlameWreathActive(botAI, bot))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action) ||
|
||||
dynamic_cast<FollowAction*>(action) ||
|
||||
dynamic_cast<ReachTargetAction*>(action) ||
|
||||
dynamic_cast<AvoidAoeAction*>(action) ||
|
||||
dynamic_cast<CastKillingSpreeAction*>(action) ||
|
||||
dynamic_cast<CastBlinkBackAction*>(action) ||
|
||||
dynamic_cast<CastDisengageAction*>(action) ||
|
||||
dynamic_cast<CastReachTargetSpellAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Try to rid of the jittering when blocking beams
|
||||
float NetherspiteKeepBlockingBeamMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return 1.0f;
|
||||
|
||||
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
|
||||
|
||||
if (bot == redBlocker)
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (bot == blueBlocker)
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<ReachTargetAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (bot == greenBlocker)
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<ReachTargetAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action) ||
|
||||
dynamic_cast<CastKillingSpreeAction*>(action) ||
|
||||
dynamic_cast<CastReachTargetSpellAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Give tanks 5 seconds to get aggro during phase transitions
|
||||
float NetherspiteWaitForDpsMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return 1.0f;
|
||||
|
||||
const uint32 instanceId = netherspite->GetMap()->GetInstanceId();
|
||||
const time_t now = std::time(nullptr);
|
||||
const uint8 dpsWaitSeconds = 5;
|
||||
|
||||
auto it = netherspiteDpsWaitTimer.find(instanceId);
|
||||
if (it == netherspiteDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
|
||||
{
|
||||
if (!botAI->IsTank(bot))
|
||||
{
|
||||
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
|
||||
!dynamic_cast<CastHealingSpellAction*>(action)))
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action)
|
||||
// Disable standard "avoid aoe" strategy, which may interfere with scripted avoidance
|
||||
float PrinceMalchezaarDisableAvoidAoeMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!boss || !boss->IsAlive())
|
||||
{
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!malchezaar)
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<CastKillingSpreeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
RaidKarazhanHelpers karazhanHelper(botAI);
|
||||
auto [redBlocker /*unused*/, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers();
|
||||
bool isBlocker = (bot == greenBlocker || bot == blueBlocker);
|
||||
if (isBlocker)
|
||||
{
|
||||
Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f);
|
||||
Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f);
|
||||
bool inBeam = false;
|
||||
for (Unit* portal : {bluePortal, greenPortal})
|
||||
{
|
||||
if (!portal)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float bx = boss->GetPositionX(), by = boss->GetPositionY();
|
||||
float px = portal->GetPositionX(), py = portal->GetPositionY();
|
||||
float dx = px - bx, dy = py - by;
|
||||
float length = sqrt(dx*dx + dy*dy);
|
||||
if (length == 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
dx /= length; dy /= length;
|
||||
float botdx = bot->GetPositionX() - bx, botdy = bot->GetPositionY() - by;
|
||||
float t = (botdx * dx + botdy * dy);
|
||||
float beamX = bx + dx * t, beamY = by + dy * t;
|
||||
float distToBeam = sqrt(pow(bot->GetPositionX() - beamX, 2) + pow(bot->GetPositionY() - beamY, 2));
|
||||
if (distToBeam < 0.3f && t > 0.0f && t < length)
|
||||
{
|
||||
inBeam = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inBeam)
|
||||
{
|
||||
std::vector<Unit*> voidZones = karazhanHelper.GetAllVoidZones();
|
||||
bool inVoidZone = false;
|
||||
for (Unit* vz : voidZones)
|
||||
{
|
||||
if (bot->GetExactDist2d(vz) < 4.0f)
|
||||
{
|
||||
inVoidZone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inVoidZone)
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!boss || !boss->IsAlive())
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (dynamic_cast<AvoidAoeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Don't run back into Shadow Nova when Enfeebled
|
||||
float PrinceMalchezaarEnfeebleKeepDistanceMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!malchezaar)
|
||||
return 1.0f;
|
||||
|
||||
if (bot->HasAura(SPELL_ENFEEBLE))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) &&
|
||||
!dynamic_cast<PrinceMalchezaarEnfeebledAvoidHazardAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
RaidKarazhanHelpers karazhanHelper(botAI);
|
||||
auto [redBlocker, greenBlocker /*unused*/, blueBlocker /*unused*/] = karazhanHelper.GetCurrentBeamBlockers();
|
||||
static std::map<ObjectGuid, uint32> beamMoveTimes;
|
||||
static std::map<ObjectGuid, bool> lastBeamMoveSideways;
|
||||
ObjectGuid botGuid = bot->GetGUID();
|
||||
Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f);
|
||||
if (bot == redBlocker && boss && redPortal)
|
||||
{
|
||||
Position blockingPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f);
|
||||
float bx = boss->GetPositionX();
|
||||
float by = boss->GetPositionY();
|
||||
float px = redPortal->GetPositionX();
|
||||
float py = redPortal->GetPositionY();
|
||||
float dx = px - bx;
|
||||
float dy = py - by;
|
||||
float length = sqrt(dx*dx + dy*dy);
|
||||
if (length != 0.0f)
|
||||
{
|
||||
dx /= length;
|
||||
dy /= length;
|
||||
float perpDx = -dy;
|
||||
float perpDy = dx;
|
||||
Position sidewaysPos(blockingPos.GetPositionX() + perpDx * 3.0f,
|
||||
blockingPos.GetPositionY() + perpDy * 3.0f,
|
||||
blockingPos.GetPositionZ());
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
uint32 intervalSecs = 5;
|
||||
if (beamMoveTimes[botGuid] == 0)
|
||||
{
|
||||
beamMoveTimes[botGuid] = time(nullptr);
|
||||
lastBeamMoveSideways[botGuid] = false;
|
||||
}
|
||||
if (time(nullptr) - beamMoveTimes[botGuid] >= intervalSecs)
|
||||
{
|
||||
lastBeamMoveSideways[botGuid] = !lastBeamMoveSideways[botGuid];
|
||||
beamMoveTimes[botGuid] = time(nullptr);
|
||||
}
|
||||
Position targetPos = lastBeamMoveSideways[botGuid] ? sidewaysPos : blockingPos;
|
||||
float distToTarget = bot->GetExactDist2d(targetPos.GetPositionX(), targetPos.GetPositionY());
|
||||
const float positionTolerance = 1.5f;
|
||||
if (distToTarget < positionTolerance)
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
// Wait until Phase 3 to use Bloodlust/Heroism
|
||||
float PrinceMalchezaarDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!malchezaar)
|
||||
return 1.0f;
|
||||
|
||||
if (malchezaar->GetHealthPct() > 30.0f)
|
||||
{
|
||||
if (dynamic_cast<CastBloodlustAction*>(action) ||
|
||||
dynamic_cast<CastHeroismAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Pets tend to run out of bounds and cause skeletons to spawn off the map
|
||||
// Pets also tend to pull adds from inside of the tower through the floor
|
||||
// This multiplier DOES NOT impact Hunter and Warlock pets
|
||||
// Hunter and Warlock pets are addressed in ControlPetAggressionAction
|
||||
float NightbaneDisablePetsMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return 1.0f;
|
||||
|
||||
if (dynamic_cast<CastForceOfNatureAction*>(action) ||
|
||||
dynamic_cast<CastFeralSpiritAction*>(action) ||
|
||||
dynamic_cast<CastFireElementalTotemAction*>(action) ||
|
||||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
|
||||
dynamic_cast<CastSummonWaterElementalAction*>(action) ||
|
||||
dynamic_cast<CastShadowfiendAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
|
||||
{
|
||||
if (dynamic_cast<PetAttackAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Give the main tank 8 seconds to get aggro during phase transitions
|
||||
float NightbaneWaitForDpsMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
|
||||
return 1.0f;
|
||||
|
||||
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
|
||||
const time_t now = std::time(nullptr);
|
||||
const uint8 dpsWaitSeconds = 8;
|
||||
|
||||
auto it = nightbaneDpsWaitTimer.find(instanceId);
|
||||
if (it == nightbaneDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
|
||||
{
|
||||
if (!botAI->IsMainTank(bot))
|
||||
{
|
||||
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
|
||||
!dynamic_cast<CastHealingSpellAction*>(action)))
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanPrinceMalchezaarMultiplier::GetValue(Action* action)
|
||||
// The "avoid aoe" strategy must be disabled for the main tank
|
||||
// Otherwise, the main tank will spin Nightbane to avoid Charred Earth and wipe the raid
|
||||
// It is also disabled for all bots during the flight phase
|
||||
float NightbaneDisableAvoidAoeMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!boss || !boss->IsAlive())
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (dynamic_cast<AvoidAoeAction*>(action))
|
||||
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z || botAI->IsMainTank(bot))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (botAI->IsMelee(bot) && bot->HasAura(SPELL_ENFEEBLE) &&
|
||||
!dynamic_cast<KarazhanPrinceMalchezaarNonTankAvoidHazardAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (botAI->IsRanged(bot) && bot->HasAura(SPELL_ENFEEBLE) &&
|
||||
(dynamic_cast<MovementAction*>(action) &&
|
||||
!dynamic_cast<KarazhanPrinceMalchezaarNonTankAvoidHazardAction*>(action)))
|
||||
{
|
||||
return 0.0f;
|
||||
if (dynamic_cast<AvoidAoeAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Disable some movement actions that conflict with the strategies
|
||||
float NightbaneDisableMovementMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return 1.0f;
|
||||
|
||||
if (dynamic_cast<CastBlinkBackAction*>(action) ||
|
||||
dynamic_cast<CastDisengageAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
// Disable CombatFormationMoveAction for all bots except:
|
||||
// (1) main tank and (2) only during the ground phase, other melee
|
||||
if (botAI->IsRanged(bot) ||
|
||||
(botAI->IsMelee(bot) && !botAI->IsMainTank(bot) &&
|
||||
nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z))
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
|
||||
@@ -3,45 +3,131 @@
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class KarazhanAttumenTheHuntsmanMultiplier : public Multiplier
|
||||
class AttumenTheHuntsmanDisableTankAssistMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanAttumenTheHuntsmanMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan attumen the huntsman multiplier") {}
|
||||
AttumenTheHuntsmanDisableTankAssistMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman disable tank assist multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanBigBadWolfMultiplier : public Multiplier
|
||||
class AttumenTheHuntsmanStayStackedMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanBigBadWolfMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan big bad wolf multiplier") {}
|
||||
AttumenTheHuntsmanStayStackedMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman stay stacked multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranMultiplier : public Multiplier
|
||||
class AttumenTheHuntsmanWaitForDpsMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan shade of aran multiplier") {}
|
||||
AttumenTheHuntsmanWaitForDpsMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman wait for dps multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBlueAndGreenBeamMultiplier : public Multiplier
|
||||
class TheCuratorDisableTankAssistMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBlueAndGreenBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite blue and green beam multiplier") {}
|
||||
TheCuratorDisableTankAssistMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "the curator disable tank assist multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteRedBeamMultiplier : public Multiplier
|
||||
class TheCuratorDelayBloodlustAndHeroismMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteRedBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite red beam multiplier") {}
|
||||
TheCuratorDelayBloodlustAndHeroismMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "the curator delay bloodlust and heroism multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanPrinceMalchezaarMultiplier : public Multiplier
|
||||
class ShadeOfAranArcaneExplosionDisableChargeMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanPrinceMalchezaarMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan prince malchezaar multiplier") {}
|
||||
ShadeOfAranArcaneExplosionDisableChargeMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran arcane explosion disable charge multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class ShadeOfAranFlameWreathDisableMovementMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
ShadeOfAranFlameWreathDisableMovementMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran flame wreath disable movement multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NetherspiteKeepBlockingBeamMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NetherspiteKeepBlockingBeamMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite keep blocking beam multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NetherspiteWaitForDpsMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NetherspiteWaitForDpsMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite wait for dps multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class PrinceMalchezaarDisableAvoidAoeMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarDisableAvoidAoeMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar disable avoid aoe multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class PrinceMalchezaarEnfeebleKeepDistanceMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarEnfeebleKeepDistanceMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar enfeeble keep distance multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class PrinceMalchezaarDelayBloodlustAndHeroismMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar delay bloodlust and heroism multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NightbaneDisablePetsMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NightbaneDisablePetsMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable pets multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NightbaneWaitForDpsMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NightbaneWaitForDpsMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane wait for dps multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NightbaneDisableAvoidAoeMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NightbaneDisableAvoidAoeMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable avoid aoe multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NightbaneDisableMovementMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NightbaneDisableMovementMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable movement multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,79 +3,160 @@
|
||||
|
||||
void RaidKarazhanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan attumen the huntsman", NextAction::array(0,
|
||||
new NextAction("karazhan attumen the huntsman stack behind", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Trash
|
||||
triggers.push_back(new TriggerNode("mana warp is about to explode",
|
||||
NextAction::array(0, new NextAction("mana warp stun creature before warp breach", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan moroes", NextAction::array(0,
|
||||
new NextAction("karazhan moroes mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Attumen the Huntsman
|
||||
triggers.push_back(new TriggerNode("attumen the huntsman need target priority",
|
||||
NextAction::array(0, new NextAction("attumen the huntsman mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("attumen the huntsman attumen spawned",
|
||||
NextAction::array(0, new NextAction("attumen the huntsman split bosses", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("attumen the huntsman attumen is mounted",
|
||||
NextAction::array(0, new NextAction("attumen the huntsman stack behind", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("attumen the huntsman boss wipes aggro when mounting",
|
||||
NextAction::array(0, new NextAction("attumen the huntsman manage dps timer", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan maiden of virtue", NextAction::array(0,
|
||||
new NextAction("karazhan maiden of virtue position ranged", ACTION_RAID + 1),
|
||||
new NextAction("karazhan maiden of virtue position boss", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Moroes
|
||||
triggers.push_back(new TriggerNode("moroes boss engaged by main tank",
|
||||
NextAction::array(0, new NextAction("moroes main tank attack boss", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("moroes need target priority",
|
||||
NextAction::array(0, new NextAction("moroes mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan big bad wolf", NextAction::array(0,
|
||||
new NextAction("karazhan big bad wolf run away", ACTION_EMERGENCY + 6),
|
||||
new NextAction("karazhan big bad wolf position boss", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Maiden of Virtue
|
||||
triggers.push_back(new TriggerNode("maiden of virtue healers are stunned by repentance",
|
||||
NextAction::array(0, new NextAction("maiden of virtue move boss to healer", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("maiden of virtue holy wrath deals chain damage",
|
||||
NextAction::array(0, new NextAction("maiden of virtue position ranged", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan romulo and julianne", NextAction::array(0,
|
||||
new NextAction("karazhan romulo and julianne mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// The Big Bad Wolf
|
||||
triggers.push_back(new TriggerNode("big bad wolf boss is chasing little red riding hood",
|
||||
NextAction::array(0, new NextAction("big bad wolf run away from boss", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("big bad wolf boss engaged by tank",
|
||||
NextAction::array(0, new NextAction("big bad wolf position boss", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan wizard of oz", NextAction::array(0,
|
||||
new NextAction("karazhan wizard of oz scorch strawman", ACTION_RAID + 2),
|
||||
new NextAction("karazhan wizard of oz mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Romulo and Julianne
|
||||
triggers.push_back(new TriggerNode("romulo and julianne both bosses revived",
|
||||
NextAction::array(0, new NextAction("romulo and julianne mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan the curator", NextAction::array(0,
|
||||
new NextAction("karazhan the curator spread ranged", ACTION_RAID + 2),
|
||||
new NextAction("karazhan the curator position boss", ACTION_RAID + 2),
|
||||
new NextAction("karazhan the curator mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// The Wizard of Oz
|
||||
triggers.push_back(new TriggerNode("wizard of oz need target priority",
|
||||
NextAction::array(0, new NextAction("wizard of oz mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("wizard of oz strawman is vulnerable to fire",
|
||||
NextAction::array(0, new NextAction("wizard of oz scorch strawman", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan terestian illhoof", NextAction::array(0,
|
||||
new NextAction("karazhan terestian illhoof mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// The Curator
|
||||
triggers.push_back(new TriggerNode("the curator astral flare spawned",
|
||||
NextAction::array(0, new NextAction("the curator mark astral flare", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("the curator boss engaged by tanks",
|
||||
NextAction::array(0, new NextAction("the curator position boss", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("the curator astral flares cast arcing sear",
|
||||
NextAction::array(0, new NextAction("the curator spread ranged", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan shade of aran", NextAction::array(0,
|
||||
new NextAction("karazhan shade of aran flame wreath stop movement", ACTION_EMERGENCY + 7),
|
||||
new NextAction("karazhan shade of aran arcane explosion run away", ACTION_EMERGENCY + 6),
|
||||
new NextAction("karazhan shade of aran spread ranged", ACTION_RAID + 2),
|
||||
new NextAction("karazhan shade of aran mark conjured elemental", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Terestian Illhoof
|
||||
triggers.push_back(new TriggerNode("terestian illhoof need target priority",
|
||||
NextAction::array(0, new NextAction("terestian illhoof mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan netherspite", NextAction::array(0,
|
||||
new NextAction("karazhan netherspite block red beam", ACTION_EMERGENCY + 8),
|
||||
new NextAction("karazhan netherspite block blue beam", ACTION_EMERGENCY + 8),
|
||||
new NextAction("karazhan netherspite block green beam", ACTION_EMERGENCY + 8),
|
||||
new NextAction("karazhan netherspite avoid beam and void zone", ACTION_EMERGENCY + 7),
|
||||
new NextAction("karazhan netherspite banish phase avoid void zone", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Shade of Aran
|
||||
triggers.push_back(new TriggerNode("shade of aran arcane explosion is casting",
|
||||
NextAction::array(0, new NextAction("shade of aran run away from arcane explosion", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("shade of aran flame wreath is active",
|
||||
NextAction::array(0, new NextAction("shade of aran stop moving during flame wreath", ACTION_EMERGENCY + 7), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("shade of aran conjured elementals summoned",
|
||||
NextAction::array(0, new NextAction("shade of aran mark conjured elemental", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("shade of aran boss uses counterspell and blizzard",
|
||||
NextAction::array(0, new NextAction("shade of aran ranged maintain distance", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan prince malchezaar", NextAction::array(0,
|
||||
new NextAction("karazhan prince malchezaar non tank avoid hazard", ACTION_EMERGENCY + 6),
|
||||
new NextAction("karazhan prince malchezaar tank avoid hazard", ACTION_EMERGENCY + 6),
|
||||
nullptr)));
|
||||
// Netherspite
|
||||
triggers.push_back(new TriggerNode("netherspite red beam is active",
|
||||
NextAction::array(0, new NextAction("netherspite block red beam", ACTION_EMERGENCY + 8), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite blue beam is active",
|
||||
NextAction::array(0, new NextAction("netherspite block blue beam", ACTION_EMERGENCY + 8), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite green beam is active",
|
||||
NextAction::array(0, new NextAction("netherspite block green beam", ACTION_EMERGENCY + 8), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite bot is not beam blocker",
|
||||
NextAction::array(0, new NextAction("netherspite avoid beam and void zone", ACTION_EMERGENCY + 7), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite boss is banished",
|
||||
NextAction::array(0, new NextAction("netherspite banish phase avoid void zone", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite need to manage timers and trackers",
|
||||
NextAction::array(0, new NextAction("netherspite manage timers and trackers", ACTION_EMERGENCY + 10), nullptr)
|
||||
));
|
||||
|
||||
// Prince Malchezaar
|
||||
triggers.push_back(new TriggerNode("prince malchezaar bot is enfeebled",
|
||||
NextAction::array(0, new NextAction("prince malchezaar enfeebled avoid hazard", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("prince malchezaar infernals are spawned",
|
||||
NextAction::array(0, new NextAction("prince malchezaar non tank avoid infernal", ACTION_EMERGENCY + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("prince malchezaar boss engaged by main tank",
|
||||
NextAction::array(0, new NextAction("prince malchezaar main tank movement", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
|
||||
// Nightbane
|
||||
triggers.push_back(new TriggerNode("nightbane boss engaged by main tank",
|
||||
NextAction::array(0, new NextAction("nightbane ground phase position boss", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane ranged bots are in charred earth",
|
||||
NextAction::array(0, new NextAction("nightbane ground phase rotate ranged positions", ACTION_EMERGENCY + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane main tank is susceptible to fear",
|
||||
NextAction::array(0, new NextAction("nightbane cast fear ward on main tank", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane pets ignore collision to chase flying boss",
|
||||
NextAction::array(0, new NextAction("nightbane control pet aggression", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane boss is flying",
|
||||
NextAction::array(0, new NextAction("nightbane flight phase movement", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane need to manage timers and trackers",
|
||||
NextAction::array(0, new NextAction("nightbane manage timers and trackers", ACTION_EMERGENCY + 10), nullptr)
|
||||
));
|
||||
}
|
||||
|
||||
void RaidKarazhanStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
||||
{
|
||||
multipliers.push_back(new KarazhanShadeOfAranMultiplier(botAI));
|
||||
multipliers.push_back(new KarazhanNetherspiteBlueAndGreenBeamMultiplier(botAI));
|
||||
multipliers.push_back(new KarazhanNetherspiteRedBeamMultiplier(botAI));
|
||||
multipliers.push_back(new KarazhanPrinceMalchezaarMultiplier(botAI));
|
||||
multipliers.push_back(new AttumenTheHuntsmanDisableTankAssistMultiplier(botAI));
|
||||
multipliers.push_back(new AttumenTheHuntsmanStayStackedMultiplier(botAI));
|
||||
multipliers.push_back(new AttumenTheHuntsmanWaitForDpsMultiplier(botAI));
|
||||
multipliers.push_back(new TheCuratorDisableTankAssistMultiplier(botAI));
|
||||
multipliers.push_back(new TheCuratorDelayBloodlustAndHeroismMultiplier(botAI));
|
||||
multipliers.push_back(new ShadeOfAranArcaneExplosionDisableChargeMultiplier(botAI));
|
||||
multipliers.push_back(new ShadeOfAranFlameWreathDisableMovementMultiplier(botAI));
|
||||
multipliers.push_back(new NetherspiteKeepBlockingBeamMultiplier(botAI));
|
||||
multipliers.push_back(new NetherspiteWaitForDpsMultiplier(botAI));
|
||||
multipliers.push_back(new PrinceMalchezaarDisableAvoidAoeMultiplier(botAI));
|
||||
multipliers.push_back(new PrinceMalchezaarEnfeebleKeepDistanceMultiplier(botAI));
|
||||
multipliers.push_back(new PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(botAI));
|
||||
multipliers.push_back(new NightbaneDisablePetsMultiplier(botAI));
|
||||
multipliers.push_back(new NightbaneWaitForDpsMultiplier(botAI));
|
||||
multipliers.push_back(new NightbaneDisableAvoidAoeMultiplier(botAI));
|
||||
multipliers.push_back(new NightbaneDisableMovementMultiplier(botAI));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class RaidKarazhanStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
RaidKarazhanStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
RaidKarazhanStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
|
||||
|
||||
std::string const getName() override { return "karazhan"; }
|
||||
|
||||
|
||||
@@ -9,31 +9,255 @@ class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger>
|
||||
public:
|
||||
RaidKarazhanTriggerContext()
|
||||
{
|
||||
creators["karazhan attumen the huntsman"] = &RaidKarazhanTriggerContext::karazhan_attumen_the_huntsman;
|
||||
creators["karazhan moroes"] = &RaidKarazhanTriggerContext::karazhan_moroes;
|
||||
creators["karazhan maiden of virtue"] = &RaidKarazhanTriggerContext::karazhan_maiden_of_virtue;
|
||||
creators["karazhan big bad wolf"] = &RaidKarazhanTriggerContext::karazhan_big_bad_wolf;
|
||||
creators["karazhan romulo and julianne"] = &RaidKarazhanTriggerContext::karazhan_romulo_and_julianne;
|
||||
creators["karazhan wizard of oz"] = &RaidKarazhanTriggerContext::karazhan_wizard_of_oz;
|
||||
creators["karazhan the curator"] = &RaidKarazhanTriggerContext::karazhan_the_curator;
|
||||
creators["karazhan terestian illhoof"] = &RaidKarazhanTriggerContext::karazhan_terestian_illhoof;
|
||||
creators["karazhan shade of aran"] = &RaidKarazhanTriggerContext::karazhan_shade_of_aran;
|
||||
creators["karazhan netherspite"] = &RaidKarazhanTriggerContext::karazhan_netherspite;
|
||||
creators["karazhan prince malchezaar"] = &RaidKarazhanTriggerContext::karazhan_prince_malchezaar;
|
||||
// Trash
|
||||
creators["mana warp is about to explode"] =
|
||||
&RaidKarazhanTriggerContext::mana_warp_is_about_to_explode;
|
||||
|
||||
// Attumen the Huntsman
|
||||
creators["attumen the huntsman need target priority"] =
|
||||
&RaidKarazhanTriggerContext::attumen_the_huntsman_need_target_priority;
|
||||
|
||||
creators["attumen the huntsman attumen spawned"] =
|
||||
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_spawned;
|
||||
|
||||
creators["attumen the huntsman attumen is mounted"] =
|
||||
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_is_mounted;
|
||||
|
||||
creators["attumen the huntsman boss wipes aggro when mounting"] =
|
||||
&RaidKarazhanTriggerContext::attumen_the_huntsman_boss_wipes_aggro_when_mounting;
|
||||
|
||||
// Moroes
|
||||
creators["moroes boss engaged by main tank"] =
|
||||
&RaidKarazhanTriggerContext::moroes_boss_engaged_by_main_tank;
|
||||
|
||||
creators["moroes need target priority"] =
|
||||
&RaidKarazhanTriggerContext::moroes_need_target_priority;
|
||||
|
||||
// Maiden of Virtue
|
||||
creators["maiden of virtue healers are stunned by repentance"] =
|
||||
&RaidKarazhanTriggerContext::maiden_of_virtue_healers_are_stunned_by_repentance;
|
||||
|
||||
creators["maiden of virtue holy wrath deals chain damage"] =
|
||||
&RaidKarazhanTriggerContext::maiden_of_virtue_holy_wrath_deals_chain_damage;
|
||||
|
||||
// The Big Bad Wolf
|
||||
creators["big bad wolf boss engaged by tank"] =
|
||||
&RaidKarazhanTriggerContext::big_bad_wolf_boss_engaged_by_tank;
|
||||
|
||||
creators["big bad wolf boss is chasing little red riding hood"] =
|
||||
&RaidKarazhanTriggerContext::big_bad_wolf_boss_is_chasing_little_red_riding_hood;
|
||||
|
||||
// Romulo and Julianne
|
||||
creators["romulo and julianne both bosses revived"] =
|
||||
&RaidKarazhanTriggerContext::romulo_and_julianne_both_bosses_revived;
|
||||
|
||||
// The Wizard of Oz
|
||||
creators["wizard of oz need target priority"] =
|
||||
&RaidKarazhanTriggerContext::wizard_of_oz_need_target_priority;
|
||||
|
||||
creators["wizard of oz strawman is vulnerable to fire"] =
|
||||
&RaidKarazhanTriggerContext::wizard_of_oz_strawman_is_vulnerable_to_fire;
|
||||
|
||||
// The Curator
|
||||
creators["the curator astral flare spawned"] =
|
||||
&RaidKarazhanTriggerContext::the_curator_astral_flare_spawned;
|
||||
|
||||
creators["the curator boss engaged by tanks"] =
|
||||
&RaidKarazhanTriggerContext::the_curator_boss_engaged_by_tanks;
|
||||
|
||||
creators["the curator astral flares cast arcing sear"] =
|
||||
&RaidKarazhanTriggerContext::the_curator_astral_flares_cast_arcing_sear;
|
||||
|
||||
// Terestian Illhoof
|
||||
creators["terestian illhoof need target priority"] =
|
||||
&RaidKarazhanTriggerContext::terestian_illhoof_need_target_priority;
|
||||
|
||||
// Shade of Aran
|
||||
creators["shade of aran arcane explosion is casting"] =
|
||||
&RaidKarazhanTriggerContext::shade_of_aran_arcane_explosion_is_casting;
|
||||
|
||||
creators["shade of aran flame wreath is active"] =
|
||||
&RaidKarazhanTriggerContext::shade_of_aran_flame_wreath_is_active;
|
||||
|
||||
creators["shade of aran conjured elementals summoned"] =
|
||||
&RaidKarazhanTriggerContext::shade_of_aran_conjured_elementals_summoned;
|
||||
|
||||
creators["shade of aran boss uses counterspell and blizzard"] =
|
||||
&RaidKarazhanTriggerContext::shade_of_aran_boss_uses_counterspell_and_blizzard;
|
||||
|
||||
// Netherspite
|
||||
creators["netherspite red beam is active"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_red_beam_is_active;
|
||||
|
||||
creators["netherspite blue beam is active"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_blue_beam_is_active;
|
||||
|
||||
creators["netherspite green beam is active"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_green_beam_is_active;
|
||||
|
||||
creators["netherspite bot is not beam blocker"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_bot_is_not_beam_blocker;
|
||||
|
||||
creators["netherspite boss is banished"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_boss_is_banished;
|
||||
|
||||
creators["netherspite need to manage timers and trackers"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_need_to_manage_timers_and_trackers;
|
||||
|
||||
// Prince Malchezaar
|
||||
creators["prince malchezaar bot is enfeebled"] =
|
||||
&RaidKarazhanTriggerContext::prince_malchezaar_bot_is_enfeebled;
|
||||
|
||||
creators["prince malchezaar infernals are spawned"] =
|
||||
&RaidKarazhanTriggerContext::prince_malchezaar_infernals_are_spawned;
|
||||
|
||||
creators["prince malchezaar boss engaged by main tank"] =
|
||||
&RaidKarazhanTriggerContext::prince_malchezaar_boss_engaged_by_main_tank;
|
||||
|
||||
// Nightbane
|
||||
creators["nightbane boss engaged by main tank"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_boss_engaged_by_main_tank;
|
||||
|
||||
creators["nightbane ranged bots are in charred earth"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_ranged_bots_are_in_charred_earth;
|
||||
|
||||
creators["nightbane main tank is susceptible to fear"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_main_tank_is_susceptible_to_fear;
|
||||
|
||||
creators["nightbane pets ignore collision to chase flying boss"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_pets_ignore_collision_to_chase_flying_boss;
|
||||
|
||||
creators["nightbane boss is flying"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_boss_is_flying;
|
||||
|
||||
creators["nightbane need to manage timers and trackers"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_need_to_manage_timers_and_trackers;
|
||||
}
|
||||
|
||||
private:
|
||||
static Trigger* karazhan_attumen_the_huntsman(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanTrigger(botAI); }
|
||||
static Trigger* karazhan_moroes(PlayerbotAI* botAI) { return new KarazhanMoroesTrigger(botAI); }
|
||||
static Trigger* karazhan_maiden_of_virtue(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtueTrigger(botAI); }
|
||||
static Trigger* karazhan_big_bad_wolf(PlayerbotAI* botAI) { return new KarazhanBigBadWolfTrigger(botAI); }
|
||||
static Trigger* karazhan_romulo_and_julianne(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneTrigger(botAI); }
|
||||
static Trigger* karazhan_wizard_of_oz(PlayerbotAI* botAI) { return new KarazhanWizardOfOzTrigger(botAI); }
|
||||
static Trigger* karazhan_the_curator(PlayerbotAI* botAI) { return new KarazhanTheCuratorTrigger(botAI); }
|
||||
static Trigger* karazhan_terestian_illhoof(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofTrigger(botAI); }
|
||||
static Trigger* karazhan_shade_of_aran(PlayerbotAI* botAI) { return new KarazhanShadeOfAranTrigger(botAI); }
|
||||
static Trigger* karazhan_netherspite(PlayerbotAI* botAI) { return new KarazhanNetherspiteTrigger(botAI); }
|
||||
static Trigger* karazhan_prince_malchezaar(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTrigger(botAI); }
|
||||
// Trash
|
||||
static Trigger* mana_warp_is_about_to_explode(
|
||||
PlayerbotAI* botAI) { return new ManaWarpIsAboutToExplodeTrigger(botAI); }
|
||||
|
||||
// Attumen the Huntsman
|
||||
static Trigger* attumen_the_huntsman_need_target_priority(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanNeedTargetPriorityTrigger(botAI); }
|
||||
|
||||
static Trigger* attumen_the_huntsman_attumen_spawned(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenSpawnedTrigger(botAI); }
|
||||
|
||||
static Trigger* attumen_the_huntsman_attumen_is_mounted(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenIsMountedTrigger(botAI); }
|
||||
|
||||
static Trigger* attumen_the_huntsman_boss_wipes_aggro_when_mounting(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(botAI); }
|
||||
|
||||
// Moroes
|
||||
static Trigger* moroes_boss_engaged_by_main_tank(
|
||||
PlayerbotAI* botAI) { return new MoroesBossEngagedByMainTankTrigger(botAI); }
|
||||
|
||||
static Trigger* moroes_need_target_priority(
|
||||
PlayerbotAI* botAI) { return new MoroesNeedTargetPriorityTrigger(botAI); }
|
||||
|
||||
// Maiden of Virtue
|
||||
static Trigger* maiden_of_virtue_healers_are_stunned_by_repentance(
|
||||
PlayerbotAI* botAI) { return new MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(botAI); }
|
||||
|
||||
static Trigger* maiden_of_virtue_holy_wrath_deals_chain_damage(
|
||||
PlayerbotAI* botAI) { return new MaidenOfVirtueHolyWrathDealsChainDamageTrigger(botAI); }
|
||||
|
||||
// The Big Bad Wolf
|
||||
static Trigger* big_bad_wolf_boss_engaged_by_tank(
|
||||
PlayerbotAI* botAI) { return new BigBadWolfBossEngagedByTankTrigger(botAI); }
|
||||
|
||||
static Trigger* big_bad_wolf_boss_is_chasing_little_red_riding_hood(
|
||||
PlayerbotAI* botAI) { return new BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(botAI); }
|
||||
|
||||
// Romulo and Julianne
|
||||
static Trigger* romulo_and_julianne_both_bosses_revived(
|
||||
PlayerbotAI* botAI) { return new RomuloAndJulianneBothBossesRevivedTrigger(botAI); }
|
||||
|
||||
// The Wizard of Oz
|
||||
static Trigger* wizard_of_oz_need_target_priority(
|
||||
PlayerbotAI* botAI) { return new WizardOfOzNeedTargetPriorityTrigger(botAI); }
|
||||
|
||||
static Trigger* wizard_of_oz_strawman_is_vulnerable_to_fire(
|
||||
PlayerbotAI* botAI) { return new WizardOfOzStrawmanIsVulnerableToFireTrigger(botAI); }
|
||||
|
||||
// The Curator
|
||||
static Trigger* the_curator_astral_flare_spawned(
|
||||
PlayerbotAI* botAI) { return new TheCuratorAstralFlareSpawnedTrigger(botAI); }
|
||||
|
||||
static Trigger* the_curator_boss_engaged_by_tanks(
|
||||
PlayerbotAI* botAI) { return new TheCuratorBossEngagedByTanksTrigger(botAI); }
|
||||
|
||||
static Trigger* the_curator_astral_flares_cast_arcing_sear(
|
||||
PlayerbotAI* botAI) { return new TheCuratorBossAstralFlaresCastArcingSearTrigger(botAI); }
|
||||
|
||||
// Terestian Illhoof
|
||||
static Trigger* terestian_illhoof_need_target_priority(
|
||||
PlayerbotAI* botAI) { return new TerestianIllhoofNeedTargetPriorityTrigger(botAI); }
|
||||
|
||||
// Shade of Aran
|
||||
static Trigger* shade_of_aran_arcane_explosion_is_casting(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranArcaneExplosionIsCastingTrigger(botAI); }
|
||||
|
||||
static Trigger* shade_of_aran_flame_wreath_is_active(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranFlameWreathIsActiveTrigger(botAI); }
|
||||
|
||||
static Trigger* shade_of_aran_conjured_elementals_summoned(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranConjuredElementalsSummonedTrigger(botAI); }
|
||||
|
||||
static Trigger* shade_of_aran_boss_uses_counterspell_and_blizzard(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(botAI); }
|
||||
|
||||
// Netherspite
|
||||
static Trigger* netherspite_red_beam_is_active(
|
||||
PlayerbotAI* botAI) { return new NetherspiteRedBeamIsActiveTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_blue_beam_is_active(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBlueBeamIsActiveTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_green_beam_is_active(
|
||||
PlayerbotAI* botAI) { return new NetherspiteGreenBeamIsActiveTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_bot_is_not_beam_blocker(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBotIsNotBeamBlockerTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_boss_is_banished(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBossIsBanishedTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_need_to_manage_timers_and_trackers(
|
||||
PlayerbotAI* botAI) { return new NetherspiteNeedToManageTimersAndTrackersTrigger(botAI); }
|
||||
|
||||
// Prince Malchezaar
|
||||
static Trigger* prince_malchezaar_bot_is_enfeebled(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarBotIsEnfeebledTrigger(botAI); }
|
||||
|
||||
static Trigger* prince_malchezaar_infernals_are_spawned(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarInfernalsAreSpawnedTrigger(botAI); }
|
||||
|
||||
static Trigger* prince_malchezaar_boss_engaged_by_main_tank(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarBossEngagedByMainTankTrigger(botAI); }
|
||||
|
||||
// Nightbane
|
||||
static Trigger* nightbane_boss_engaged_by_main_tank(
|
||||
PlayerbotAI* botAI) { return new NightbaneBossEngagedByMainTankTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_ranged_bots_are_in_charred_earth(
|
||||
PlayerbotAI* botAI) { return new NightbaneRangedBotsAreInCharredEarthTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_main_tank_is_susceptible_to_fear(
|
||||
PlayerbotAI* botAI) { return new NightbaneMainTankIsSusceptibleToFearTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_pets_ignore_collision_to_chase_flying_boss(
|
||||
PlayerbotAI* botAI) { return new NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_boss_is_flying(
|
||||
PlayerbotAI* botAI) { return new NightbaneBossIsFlyingTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_need_to_manage_timers_and_trackers(
|
||||
PlayerbotAI* botAI) { return new NightbaneNeedToManageTimersAndTrackersTrigger(botAI); }
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -3,17 +3,64 @@
|
||||
#include "RaidKarazhanActions.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool KarazhanAttumenTheHuntsmanTrigger::IsActive()
|
||||
{
|
||||
RaidKarazhanHelpers helpers(botAI);
|
||||
Unit* boss = helpers.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
using namespace KarazhanHelpers;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
bool ManaWarpIsAboutToExplodeTrigger::IsActive()
|
||||
{
|
||||
Unit* manaWarp = AI_VALUE2(Unit*, "find target", "mana warp");
|
||||
return manaWarp && manaWarp->GetHealthPct() < 15;
|
||||
}
|
||||
|
||||
bool KarazhanMoroesTrigger::IsActive()
|
||||
bool AttumenTheHuntsmanNeedTargetPriorityTrigger::IsActive()
|
||||
{
|
||||
if (botAI->IsHeal(bot))
|
||||
return false;
|
||||
|
||||
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
|
||||
return midnight != nullptr;
|
||||
}
|
||||
|
||||
bool AttumenTheHuntsmanAttumenSpawnedTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsAssistTankOfIndex(bot, 0))
|
||||
return false;
|
||||
|
||||
Unit* attumen = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN);
|
||||
return attumen != nullptr;
|
||||
}
|
||||
|
||||
bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
|
||||
{
|
||||
if (botAI->IsMainTank(bot))
|
||||
return false;
|
||||
|
||||
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
return attumenMounted && attumenMounted->GetVictim() != bot;
|
||||
}
|
||||
|
||||
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
|
||||
{
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
|
||||
return midnight != nullptr;
|
||||
}
|
||||
|
||||
bool MoroesBossEngagedByMainTankTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsMainTank(bot))
|
||||
return false;
|
||||
|
||||
Unit* moroes = AI_VALUE2(Unit*, "find target", "moroes");
|
||||
return moroes != nullptr;
|
||||
}
|
||||
|
||||
bool MoroesNeedTargetPriorityTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsDps(bot))
|
||||
return false;
|
||||
|
||||
Unit* dorothea = AI_VALUE2(Unit*, "find target", "baroness dorothea millstipe");
|
||||
Unit* catriona = AI_VALUE2(Unit*, "find target", "lady catriona von'indi");
|
||||
Unit* keira = AI_VALUE2(Unit*, "find target", "lady keira berrybuck");
|
||||
@@ -21,39 +68,67 @@ bool KarazhanMoroesTrigger::IsActive()
|
||||
Unit* robin = AI_VALUE2(Unit*, "find target", "lord robin daris");
|
||||
Unit* crispin = AI_VALUE2(Unit*, "find target", "lord crispin ference");
|
||||
|
||||
return ((moroes && moroes->IsAlive()) ||
|
||||
(dorothea && dorothea->IsAlive()) ||
|
||||
(catriona && catriona->IsAlive()) ||
|
||||
(keira && keira->IsAlive()) ||
|
||||
(rafe && rafe->IsAlive()) ||
|
||||
(robin && robin->IsAlive()) ||
|
||||
(crispin && crispin->IsAlive()));
|
||||
Unit* target = GetFirstAliveUnit({ dorothea, catriona, keira, rafe, robin, crispin });
|
||||
return target != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanMaidenOfVirtueTrigger::IsActive()
|
||||
bool MaidenOfVirtueHealersAreStunnedByRepentanceTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue");
|
||||
if (!botAI->IsTank(bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue");
|
||||
return maiden && maiden->GetVictim() == bot;
|
||||
}
|
||||
|
||||
bool KarazhanBigBadWolfTrigger::IsActive()
|
||||
bool MaidenOfVirtueHolyWrathDealsChainDamageTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf");
|
||||
if (!botAI->IsRanged(bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue");
|
||||
return maiden != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanRomuloAndJulianneTrigger::IsActive()
|
||||
bool BigBadWolfBossEngagedByTankTrigger::IsActive()
|
||||
{
|
||||
Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne");
|
||||
if (!botAI->IsTank(bot) || bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
|
||||
return false;
|
||||
|
||||
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
|
||||
return wolf != nullptr;
|
||||
}
|
||||
|
||||
bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
|
||||
{
|
||||
if (!bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
|
||||
return false;
|
||||
|
||||
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
|
||||
return wolf != nullptr;
|
||||
}
|
||||
|
||||
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
|
||||
{
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
|
||||
if (!romulo)
|
||||
return false;
|
||||
|
||||
return julianne && julianne->IsAlive() && romulo && romulo->IsAlive();
|
||||
Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne");
|
||||
if (!julianne)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KarazhanWizardOfOzTrigger::IsActive()
|
||||
bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
|
||||
{
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
|
||||
Unit* tito = AI_VALUE2(Unit*, "find target", "tito");
|
||||
Unit* roar = AI_VALUE2(Unit*, "find target", "roar");
|
||||
@@ -61,45 +136,250 @@ bool KarazhanWizardOfOzTrigger::IsActive()
|
||||
Unit* tinhead = AI_VALUE2(Unit*, "find target", "tinhead");
|
||||
Unit* crone = AI_VALUE2(Unit*, "find target", "the crone");
|
||||
|
||||
return ((dorothee && dorothee->IsAlive()) ||
|
||||
(tito && tito->IsAlive()) ||
|
||||
(roar && roar->IsAlive()) ||
|
||||
(strawman && strawman->IsAlive()) ||
|
||||
(tinhead && tinhead->IsAlive()) ||
|
||||
(crone && crone->IsAlive()));
|
||||
Unit* target = GetFirstAliveUnit({ dorothee, tito, roar, strawman, tinhead, crone });
|
||||
return target != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanTheCuratorTrigger::IsActive()
|
||||
bool WizardOfOzStrawmanIsVulnerableToFireTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
if (bot->getClass() != CLASS_MAGE)
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman");
|
||||
return strawman && strawman->IsAlive();
|
||||
}
|
||||
|
||||
bool KarazhanTerestianIllhoofTrigger::IsActive()
|
||||
bool TheCuratorAstralFlareSpawnedTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "terestian illhoof");
|
||||
if (!botAI->IsDps(bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* flare = AI_VALUE2(Unit*, "find target", "astral flare");
|
||||
return flare != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanShadeOfAranTrigger::IsActive()
|
||||
bool TheCuratorBossEngagedByTanksTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
return curator != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanNetherspiteTrigger::IsActive()
|
||||
bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!botAI->IsRanged(bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
return curator != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanPrinceMalchezaarTrigger::IsActive()
|
||||
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
|
||||
return illhoof != nullptr;
|
||||
}
|
||||
|
||||
bool ShadeOfAranArcaneExplosionIsCastingTrigger::IsActive()
|
||||
{
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
return aran && aran->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION) &&
|
||||
!IsFlameWreathActive(botAI, bot);
|
||||
}
|
||||
|
||||
bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
|
||||
{
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
return aran && IsFlameWreathActive(botAI, bot);
|
||||
}
|
||||
|
||||
// Exclusion of Banish is so the player may Banish elementals if they wish
|
||||
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
|
||||
{
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
|
||||
return elemental && elemental->IsAlive() &&
|
||||
!elemental->HasAura(SPELL_WARLOCK_BANISH);
|
||||
}
|
||||
|
||||
bool ShadeOfAranBossUsesCounterspellAndBlizzardTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsRanged(bot))
|
||||
return false;
|
||||
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
return aran && !(aran->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) &&
|
||||
!IsFlameWreathActive(botAI, bot);
|
||||
}
|
||||
|
||||
bool NetherspiteRedBeamIsActiveTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f);
|
||||
return redPortal != nullptr;
|
||||
}
|
||||
|
||||
bool NetherspiteBlueBeamIsActiveTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f);
|
||||
return bluePortal != nullptr;
|
||||
}
|
||||
|
||||
bool NetherspiteGreenBeamIsActiveTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f);
|
||||
return greenPortal != nullptr;
|
||||
}
|
||||
|
||||
bool NetherspiteBotIsNotBeamBlockerTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
|
||||
return bot != redBlocker && bot != blueBlocker && bot != greenBlocker;
|
||||
}
|
||||
|
||||
bool NetherspiteBossIsBanishedTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || !netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
std::vector<Unit*> voidZones = GetAllVoidZones(botAI, bot);
|
||||
for (Unit* vz : voidZones)
|
||||
{
|
||||
if (bot->GetExactDist2d(vz) < 4.0f)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsTank(bot) && !IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
return netherspite != nullptr;
|
||||
}
|
||||
|
||||
bool PrinceMalchezaarBotIsEnfeebledTrigger::IsActive()
|
||||
{
|
||||
return bot->HasAura(SPELL_ENFEEBLE);
|
||||
}
|
||||
|
||||
bool PrinceMalchezaarInfernalsAreSpawnedTrigger::IsActive()
|
||||
{
|
||||
if (botAI->IsMainTank(bot) || bot->HasAura(SPELL_ENFEEBLE))
|
||||
return false;
|
||||
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
return malchezaar != nullptr;
|
||||
}
|
||||
|
||||
bool PrinceMalchezaarBossEngagedByMainTankTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsMainTank(bot))
|
||||
return false;
|
||||
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
return malchezaar != nullptr;
|
||||
}
|
||||
|
||||
bool NightbaneBossEngagedByMainTankTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsMainTank(bot))
|
||||
return false;
|
||||
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
|
||||
}
|
||||
|
||||
bool NightbaneRangedBotsAreInCharredEarthTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsRanged(bot))
|
||||
return false;
|
||||
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
|
||||
}
|
||||
|
||||
bool NightbaneMainTankIsSusceptibleToFearTrigger::IsActive()
|
||||
{
|
||||
if (bot->getClass() != CLASS_PRIEST)
|
||||
return false;
|
||||
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return false;
|
||||
|
||||
Player* mainTank = nullptr;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (member && botAI->IsMainTank(member))
|
||||
{
|
||||
mainTank = member;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mainTank && !mainTank->HasAura(SPELL_FEAR_WARD) &&
|
||||
botAI->CanCastSpell("fear ward", mainTank);
|
||||
}
|
||||
|
||||
bool NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger::IsActive()
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return false;
|
||||
|
||||
Pet* pet = bot->GetPet();
|
||||
return pet && pet->IsAlive();
|
||||
}
|
||||
|
||||
bool NightbaneBossIsFlyingTrigger::IsActive()
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z)
|
||||
return false;
|
||||
|
||||
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
|
||||
const time_t now = std::time(nullptr);
|
||||
const uint8 flightPhaseDurationSeconds = 35;
|
||||
|
||||
return nightbaneFlightPhaseStartTimer.find(instanceId) != nightbaneFlightPhaseStartTimer.end() &&
|
||||
(now - nightbaneFlightPhaseStartTimer[instanceId] < flightPhaseDurationSeconds);
|
||||
}
|
||||
|
||||
bool NightbaneNeedToManageTimersAndTrackersTrigger::IsActive()
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
return nightbane != nullptr;
|
||||
}
|
||||
|
||||
@@ -3,80 +3,298 @@
|
||||
|
||||
#include "Trigger.h"
|
||||
|
||||
class KarazhanAttumenTheHuntsmanTrigger : public Trigger
|
||||
class ManaWarpIsAboutToExplodeTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanAttumenTheHuntsmanTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan attumen the huntsman") {}
|
||||
ManaWarpIsAboutToExplodeTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "mana warp is about to explode") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanMoroesTrigger : public Trigger
|
||||
class AttumenTheHuntsmanNeedTargetPriorityTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanMoroesTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan moroes") {}
|
||||
AttumenTheHuntsmanNeedTargetPriorityTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman need target priority") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanMaidenOfVirtueTrigger : public Trigger
|
||||
class AttumenTheHuntsmanAttumenSpawnedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanMaidenOfVirtueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan maiden of virtue") {}
|
||||
AttumenTheHuntsmanAttumenSpawnedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen spawned") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanBigBadWolfTrigger : public Trigger
|
||||
class AttumenTheHuntsmanAttumenIsMountedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanBigBadWolfTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan big bad wolf") {}
|
||||
AttumenTheHuntsmanAttumenIsMountedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen is mounted") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanRomuloAndJulianneTrigger : public Trigger
|
||||
class AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanRomuloAndJulianneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan romulo and julianne") {}
|
||||
AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman boss wipes aggro when mounting") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanWizardOfOzTrigger : public Trigger
|
||||
class MoroesBossEngagedByMainTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanWizardOfOzTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan wizard of oz") {}
|
||||
MoroesBossEngagedByMainTankTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "moroes boss engaged by main tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanTheCuratorTrigger : public Trigger
|
||||
class MoroesNeedTargetPriorityTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanTheCuratorTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan the curator") {}
|
||||
MoroesNeedTargetPriorityTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "moroes need target priority") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanTerestianIllhoofTrigger : public Trigger
|
||||
class MaidenOfVirtueHealersAreStunnedByRepentanceTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanTerestianIllhoofTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan terestian illhoof") {}
|
||||
MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue healers are stunned by repentance") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranTrigger : public Trigger
|
||||
class MaidenOfVirtueHolyWrathDealsChainDamageTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan shade of aran") {}
|
||||
MaidenOfVirtueHolyWrathDealsChainDamageTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue holy wrath deals chain damage") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteTrigger : public Trigger
|
||||
class BigBadWolfBossEngagedByTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan netherspite") {}
|
||||
BigBadWolfBossEngagedByTankTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss engaged by tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanPrinceMalchezaarTrigger : public Trigger
|
||||
class BigBadWolfBossIsChasingLittleRedRidingHoodTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanPrinceMalchezaarTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan prince malchezaar") {}
|
||||
BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss is chasing little red riding hood") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class RomuloAndJulianneBothBossesRevivedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
RomuloAndJulianneBothBossesRevivedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "romulo and julianne both bosses revived") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class WizardOfOzNeedTargetPriorityTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
WizardOfOzNeedTargetPriorityTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz need target priority") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class WizardOfOzStrawmanIsVulnerableToFireTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
WizardOfOzStrawmanIsVulnerableToFireTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz strawman is vulnerable to fire") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
class TheCuratorAstralFlareSpawnedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TheCuratorAstralFlareSpawnedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flare spawned") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class TheCuratorBossEngagedByTanksTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TheCuratorBossEngagedByTanksTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "the curator boss engaged by tanks") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class TheCuratorBossAstralFlaresCastArcingSearTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TheCuratorBossAstralFlaresCastArcingSearTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flares cast arcing sear") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class TerestianIllhoofNeedTargetPriorityTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TerestianIllhoofNeedTargetPriorityTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "terestian illhoof need target priority") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ShadeOfAranArcaneExplosionIsCastingTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ShadeOfAranArcaneExplosionIsCastingTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran arcane explosion is casting") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ShadeOfAranFlameWreathIsActiveTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ShadeOfAranFlameWreathIsActiveTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran flame wreath is active") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ShadeOfAranConjuredElementalsSummonedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ShadeOfAranConjuredElementalsSummonedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran conjured elementals summoned") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ShadeOfAranBossUsesCounterspellAndBlizzardTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran boss uses counterspell and blizzard") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteRedBeamIsActiveTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteRedBeamIsActiveTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite red beam is active") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteBlueBeamIsActiveTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteBlueBeamIsActiveTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite blue beam is active") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteGreenBeamIsActiveTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteGreenBeamIsActiveTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite green beam is active") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteBotIsNotBeamBlockerTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteBotIsNotBeamBlockerTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite bot is not beam blocker") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteBossIsBanishedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteBossIsBanishedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite boss is banished") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteNeedToManageTimersAndTrackersTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteNeedToManageTimersAndTrackersTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite need to manage timers and trackers") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class PrinceMalchezaarBotIsEnfeebledTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarBotIsEnfeebledTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar bot is enfeebled") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class PrinceMalchezaarInfernalsAreSpawnedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarInfernalsAreSpawnedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar infernals are spawned") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class PrinceMalchezaarBossEngagedByMainTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarBossEngagedByMainTankTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar boss engaged by main tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneBossEngagedByMainTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneBossEngagedByMainTankTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss engaged by main tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneRangedBotsAreInCharredEarthTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneRangedBotsAreInCharredEarthTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane ranged bots are in charred earth") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneMainTankIsSusceptibleToFearTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneMainTankIsSusceptibleToFearTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane main tank is susceptible to fear") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane pets ignore collision to chase flying boss") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneBossIsFlyingTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneBossIsFlyingTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss is flying") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneNeedToManageTimersAndTrackersTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneNeedToManageTimersAndTrackersTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane need to manage timers and trackers") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -401,14 +401,20 @@ std::unordered_map<ObjectGuid, bool> MagtheridonSpreadRangedAction::hasReachedIn
|
||||
|
||||
bool MagtheridonSpreadRangedAction::Execute(Event event)
|
||||
{
|
||||
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
|
||||
if (!magtheridon)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
return false;
|
||||
|
||||
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
|
||||
|
||||
// Wait for 6 seconds after Magtheridon activates to spread
|
||||
const uint8 spreadWaitSeconds = 6;
|
||||
auto it = magtheridonSpreadWaitTimer.find(bot->GetMapId());
|
||||
if (it == magtheridonSpreadWaitTimer.end() ||
|
||||
auto it = spreadWaitTimer.find(instanceId);
|
||||
if (it == spreadWaitTimer.end() ||
|
||||
(time(nullptr) - it->second) < spreadWaitSeconds)
|
||||
return false;
|
||||
|
||||
@@ -416,8 +422,8 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
|
||||
if (cubeIt != botToCubeAssignment.end())
|
||||
{
|
||||
time_t now = time(nullptr);
|
||||
auto timerIt = magtheridonBlastNovaTimer.find(bot->GetMapId());
|
||||
if (timerIt != magtheridonBlastNovaTimer.end())
|
||||
auto timerIt = blastNovaTimer.find(instanceId);
|
||||
if (timerIt != blastNovaTimer.end())
|
||||
{
|
||||
time_t lastBlastNova = timerIt->second;
|
||||
if (now - lastBlastNova >= 49)
|
||||
@@ -559,8 +565,8 @@ bool MagtheridonUseManticronCubeAction::HandleCubeRelease(Unit* magtheridon, Gam
|
||||
|
||||
bool MagtheridonUseManticronCubeAction::ShouldActivateCubeLogic(Unit* magtheridon)
|
||||
{
|
||||
auto timerIt = magtheridonBlastNovaTimer.find(bot->GetMapId());
|
||||
if (timerIt == magtheridonBlastNovaTimer.end())
|
||||
auto timerIt = blastNovaTimer.find(magtheridon->GetMap()->GetInstanceId());
|
||||
if (timerIt == blastNovaTimer.end())
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
@@ -650,52 +656,38 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
|
||||
if (!magtheridon)
|
||||
return false;
|
||||
|
||||
uint32 mapId = magtheridon->GetMapId();
|
||||
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
|
||||
const time_t now = time(nullptr);
|
||||
|
||||
bool blastNovaActive = magtheridon->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA);
|
||||
bool lastBlastNova = lastBlastNovaState[mapId];
|
||||
bool lastBlastNova = lastBlastNovaState[instanceId];
|
||||
|
||||
if (lastBlastNova && !blastNovaActive && IsMapIDTimerManager(botAI, bot))
|
||||
magtheridonBlastNovaTimer[mapId] = time(nullptr);
|
||||
lastBlastNovaState[mapId] = blastNovaActive;
|
||||
if (lastBlastNova && !blastNovaActive && IsInstanceTimerManager(botAI, bot))
|
||||
blastNovaTimer[instanceId] = now;
|
||||
|
||||
if (IsMapIDTimerManager(botAI, bot))
|
||||
lastBlastNovaState[instanceId] = blastNovaActive;
|
||||
|
||||
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
|
||||
{
|
||||
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
|
||||
if (IsInstanceTimerManager(botAI, bot))
|
||||
{
|
||||
if (magtheridonSpreadWaitTimer.find(mapId) == magtheridonSpreadWaitTimer.end())
|
||||
magtheridonSpreadWaitTimer[mapId] = time(nullptr);
|
||||
|
||||
if (magtheridonBlastNovaTimer.find(mapId) == magtheridonBlastNovaTimer.end())
|
||||
magtheridonBlastNovaTimer[mapId] = time(nullptr);
|
||||
|
||||
if (magtheridonAggroWaitTimer.find(mapId) == magtheridonAggroWaitTimer.end())
|
||||
magtheridonAggroWaitTimer[mapId] = time(nullptr);
|
||||
spreadWaitTimer.try_emplace(instanceId, now);
|
||||
blastNovaTimer.try_emplace(instanceId, now);
|
||||
dpsWaitTimer.try_emplace(instanceId, now);
|
||||
}
|
||||
}
|
||||
|
||||
if (magtheridon->HasAura(SPELL_SHADOW_CAGE))
|
||||
else
|
||||
{
|
||||
if (!MagtheridonSpreadRangedAction::initialPositions.empty())
|
||||
MagtheridonSpreadRangedAction::initialPositions.clear();
|
||||
MagtheridonSpreadRangedAction::initialPositions.clear();
|
||||
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
|
||||
botToCubeAssignment.clear();
|
||||
|
||||
if (!MagtheridonSpreadRangedAction::hasReachedInitialPosition.empty())
|
||||
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
|
||||
|
||||
if (!botToCubeAssignment.empty())
|
||||
botToCubeAssignment.clear();
|
||||
|
||||
if (IsMapIDTimerManager(botAI, bot))
|
||||
if (IsInstanceTimerManager(botAI, bot))
|
||||
{
|
||||
if (magtheridonSpreadWaitTimer.find(mapId) != magtheridonSpreadWaitTimer.end())
|
||||
magtheridonSpreadWaitTimer.erase(mapId);
|
||||
|
||||
if (magtheridonBlastNovaTimer.find(mapId) != magtheridonBlastNovaTimer.end())
|
||||
magtheridonBlastNovaTimer.erase(mapId);
|
||||
|
||||
if (magtheridonAggroWaitTimer.find(mapId) != magtheridonAggroWaitTimer.end())
|
||||
magtheridonAggroWaitTimer.erase(mapId);
|
||||
spreadWaitTimer.erase(instanceId);
|
||||
blastNovaTimer.erase(instanceId);
|
||||
dpsWaitTimer.erase(instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -170,9 +170,9 @@ namespace MagtheridonHelpers
|
||||
}
|
||||
|
||||
std::unordered_map<uint32, bool> lastBlastNovaState;
|
||||
std::unordered_map<uint32, time_t> magtheridonBlastNovaTimer;
|
||||
std::unordered_map<uint32, time_t> magtheridonSpreadWaitTimer;
|
||||
std::unordered_map<uint32, time_t> magtheridonAggroWaitTimer;
|
||||
std::unordered_map<uint32, time_t> blastNovaTimer;
|
||||
std::unordered_map<uint32, time_t> spreadWaitTimer;
|
||||
std::unordered_map<uint32, time_t> dpsWaitTimer;
|
||||
|
||||
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z)
|
||||
{
|
||||
@@ -209,14 +209,14 @@ namespace MagtheridonHelpers
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot)
|
||||
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (member && member->IsAlive() && !botAI->IsMainTank(member) && GET_PLAYERBOT_AI(member))
|
||||
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
|
||||
return member == bot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace MagtheridonHelpers
|
||||
SPELL_FEAR = 6215,
|
||||
|
||||
// Hunter
|
||||
SPELL_MISDIRECTION = 34477,
|
||||
SPELL_MISDIRECTION = 35079,
|
||||
};
|
||||
|
||||
enum MagtheridonNPCs
|
||||
@@ -54,7 +54,7 @@ namespace MagtheridonHelpers
|
||||
void MarkTargetWithCross(Player* bot, Unit* target);
|
||||
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
|
||||
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z);
|
||||
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot);
|
||||
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
|
||||
|
||||
struct Location
|
||||
{
|
||||
@@ -82,9 +82,9 @@ namespace MagtheridonHelpers
|
||||
std::vector<CubeInfo> GetAllCubeInfosByDbGuids(Map* map, const std::vector<uint32>& cubeDbGuids);
|
||||
void AssignBotsToCubesByGuidAndCoords(Group* group, const std::vector<CubeInfo>& cubes, PlayerbotAI* botAI);
|
||||
extern std::unordered_map<uint32, bool> lastBlastNovaState;
|
||||
extern std::unordered_map<uint32, time_t> magtheridonBlastNovaTimer;
|
||||
extern std::unordered_map<uint32, time_t> magtheridonSpreadWaitTimer;
|
||||
extern std::unordered_map<uint32, time_t> magtheridonAggroWaitTimer;
|
||||
extern std::unordered_map<uint32, time_t> blastNovaTimer;
|
||||
extern std::unordered_map<uint32, time_t> spreadWaitTimer;
|
||||
extern std::unordered_map<uint32, time_t> dpsWaitTimer;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -41,10 +41,10 @@ float MagtheridonWaitToAttackMultiplier::GetValue(Action* action)
|
||||
if (!magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE))
|
||||
return 1.0f;
|
||||
|
||||
const uint8 aggroWaitSeconds = 6;
|
||||
auto it = magtheridonAggroWaitTimer.find(bot->GetMapId());
|
||||
if (it == magtheridonAggroWaitTimer.end() ||
|
||||
(time(nullptr) - it->second) < aggroWaitSeconds)
|
||||
const uint8 dpsWaitSeconds = 6;
|
||||
auto it = dpsWaitTimer.find(magtheridon->GetMap()->GetInstanceId());
|
||||
if (it == dpsWaitTimer.end() ||
|
||||
(time(nullptr) - it->second) < dpsWaitSeconds)
|
||||
{
|
||||
if (!botAI->IsMainTank(bot) && (dynamic_cast<AttackAction*>(action) ||
|
||||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action))))
|
||||
|
||||
@@ -10,13 +10,39 @@ class RaidMcActionContext : public NamedObjectContext<Action>
|
||||
public:
|
||||
RaidMcActionContext()
|
||||
{
|
||||
creators["mc check should move from group"] = &RaidMcActionContext::check_should_move_from_group;
|
||||
creators["mc lucifron shadow resistance"] = &RaidMcActionContext::lucifron_shadow_resistance;
|
||||
creators["mc magmadar fire resistance"] = &RaidMcActionContext::magmadar_fire_resistance;
|
||||
creators["mc gehennas shadow resistance"] = &RaidMcActionContext::gehennas_shadow_resistance;
|
||||
creators["mc garr fire resistance"] = &RaidMcActionContext::garr_fire_resistance;
|
||||
creators["mc baron geddon fire resistance"] = &RaidMcActionContext::baron_geddon_fire_resistance;
|
||||
creators["mc move from group"] = &RaidMcActionContext::check_should_move_from_group;
|
||||
creators["mc move from baron geddon"] = &RaidMcActionContext::move_from_baron_geddon;
|
||||
creators["mc shazzrah move away"] = &RaidMcActionContext::shazzrah_move_away;
|
||||
creators["mc sulfuron harbinger fire resistance"] = &RaidMcActionContext::sulfuron_harbinger_fire_resistance;
|
||||
creators["mc golemagg fire resistance"] = &RaidMcActionContext::golemagg_fire_resistance;
|
||||
creators["mc golemagg mark boss"] = &RaidMcActionContext::golemagg_mark_boss;
|
||||
creators["mc golemagg main tank attack golemagg"] = &RaidMcActionContext::golemagg_main_tank_attack_golemagg;
|
||||
creators["mc golemagg assist tank attack core rager"] = &RaidMcActionContext::golemagg_assist_tank_attack_core_rager;
|
||||
creators["mc majordomo shadow resistance"] = &RaidMcActionContext::majordomo_shadow_resistance;
|
||||
creators["mc ragnaros fire resistance"] = &RaidMcActionContext::ragnaros_fire_resistance;
|
||||
}
|
||||
|
||||
private:
|
||||
static Action* check_should_move_from_group(PlayerbotAI* ai) { return new McCheckShouldMoveFromGroupAction(ai); }
|
||||
static Action* move_from_baron_geddon(PlayerbotAI* ai) { return new McMoveFromBaronGeddonAction(ai); }
|
||||
static Action* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "lucifron"); }
|
||||
static Action* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "magmadar"); }
|
||||
static Action* gehennas_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "gehennas"); }
|
||||
static Action* garr_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "garr"); }
|
||||
static Action* baron_geddon_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "baron geddon"); }
|
||||
static Action* check_should_move_from_group(PlayerbotAI* botAI) { return new McMoveFromGroupAction(botAI); }
|
||||
static Action* move_from_baron_geddon(PlayerbotAI* botAI) { return new McMoveFromBaronGeddonAction(botAI); }
|
||||
static Action* shazzrah_move_away(PlayerbotAI* botAI) { return new McShazzrahMoveAwayAction(botAI); }
|
||||
static Action* sulfuron_harbinger_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "sulfuron harbinger"); }
|
||||
static Action* golemagg_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "golemagg the incinerator"); }
|
||||
static Action* golemagg_mark_boss(PlayerbotAI* botAI) { return new McGolemaggMarkBossAction(botAI); }
|
||||
static Action* golemagg_main_tank_attack_golemagg(PlayerbotAI* botAI) { return new McGolemaggMainTankAttackGolemaggAction(botAI); }
|
||||
static Action* golemagg_assist_tank_attack_core_rager(PlayerbotAI* botAI) { return new McGolemaggAssistTankAttackCoreRagerAction(botAI); }
|
||||
static Action* majordomo_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "majordomo executus"); }
|
||||
static Action* ragnaros_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "ragnaros"); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,43 +1,215 @@
|
||||
#include "RaidMcActions.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
#include "RaidMcTriggers.h"
|
||||
#include "RaidMcHelpers.h"
|
||||
|
||||
bool McCheckShouldMoveFromGroupAction::Execute(Event event)
|
||||
static constexpr float LIVING_BOMB_DISTANCE = 20.0f;
|
||||
static constexpr float INFERNO_DISTANCE = 20.0f;
|
||||
|
||||
// don't get hit by Arcane Explosion but still be in casting range
|
||||
static constexpr float ARCANE_EXPLOSION_DISTANCE = 26.0f;
|
||||
|
||||
// dedicated tank positions; prevents assist tanks from positioning Core Ragers on steep walls on pull
|
||||
static const Position GOLEMAGG_TANK_POSITION{795.7308, -994.8848, -207.18661};
|
||||
static const Position CORE_RAGER_TANK_POSITION{846.6453, -1019.0639, -198.9819};
|
||||
|
||||
static constexpr float GOLEMAGGS_TRUST_DISTANCE = 30.0f;
|
||||
static constexpr float CORE_RAGER_STEP_DISTANCE = 5.0f;
|
||||
|
||||
using namespace MoltenCoreHelpers;
|
||||
|
||||
bool McMoveFromGroupAction::Execute(Event event)
|
||||
{
|
||||
if (bot->HasAura(20475)) // barron geddon's living bomb
|
||||
{
|
||||
if (!botAI->HasStrategy("move from group", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
// add/remove from both for now as it will make it more obvious to
|
||||
// player if this strat remains on after fight somehow
|
||||
botAI->ChangeStrategy("+move from group", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("+move from group", BOT_STATE_COMBAT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (botAI->HasStrategy("move from group", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
// add/remove from both for now as it will make it more obvious to
|
||||
// player if this strat remains on after fight somehow
|
||||
botAI->ChangeStrategy("-move from group", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("-move from group", BOT_STATE_COMBAT);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return MoveFromGroup(LIVING_BOMB_DISTANCE);
|
||||
}
|
||||
|
||||
bool McMoveFromBaronGeddonAction::Execute(Event event)
|
||||
{
|
||||
const float radius = 25.0f; // more than should be needed but bots keep trying to run back in
|
||||
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
|
||||
{
|
||||
long distToTravel = radius - bot->GetDistance(boss);
|
||||
float distToTravel = INFERNO_DISTANCE - bot->GetDistance2d(boss);
|
||||
if (distToTravel > 0)
|
||||
{
|
||||
// float angle = bot->GetAngle(boss) + M_PI;
|
||||
// return Move(angle, distToTravel);
|
||||
// Stop current spell first
|
||||
bot->AttackStop();
|
||||
bot->InterruptNonMeleeSpells(false);
|
||||
|
||||
return MoveAway(boss, distToTravel);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool McShazzrahMoveAwayAction::Execute(Event event)
|
||||
{
|
||||
if (Unit* boss = AI_VALUE2(Unit*, "find target", "shazzrah"))
|
||||
{
|
||||
float distToTravel = ARCANE_EXPLOSION_DISTANCE - bot->GetDistance2d(boss);
|
||||
if (distToTravel > 0)
|
||||
return MoveAway(boss, distToTravel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool McGolemaggMarkBossAction::Execute(Event event)
|
||||
{
|
||||
if (Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator"))
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
ObjectGuid currentSkullGuid = group->GetTargetIcon(RtiTargetValue::skullIndex);
|
||||
if (currentSkullGuid.IsEmpty() || currentSkullGuid != boss->GetGUID())
|
||||
{
|
||||
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool McGolemaggTankAction::MoveUnitToPosition(Unit* target, const Position& tankPosition, float maxDistance,
|
||||
float stepDistance)
|
||||
{
|
||||
if (bot->GetVictim() != target)
|
||||
return Attack(target);
|
||||
if (target->GetVictim() == bot)
|
||||
{
|
||||
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.GetPositionX(), tankPosition.GetPositionY());
|
||||
if (distanceToTankPosition > maxDistance)
|
||||
{
|
||||
float dX = tankPosition.GetPositionX() - bot->GetPositionX();
|
||||
float dY = tankPosition.GetPositionY() - bot->GetPositionY();
|
||||
float dist = sqrt(dX * dX + dY * dY);
|
||||
float moveX = bot->GetPositionX() + (dX / dist) * stepDistance;
|
||||
float moveY = bot->GetPositionY() + (dY / dist) * stepDistance;
|
||||
return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false,
|
||||
false, false, MovementPriority::MOVEMENT_COMBAT, true,
|
||||
true);
|
||||
}
|
||||
}
|
||||
else if (botAI->DoSpecificAction("taunt spell", Event(), true))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool McGolemaggTankAction::FindCoreRagers(Unit*& coreRager1, Unit*& coreRager2) const
|
||||
{
|
||||
coreRager1 = coreRager2 = nullptr;
|
||||
for (auto const& target : AI_VALUE(GuidVector, "possible targets no los"))
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
if (unit && unit->IsAlive() && unit->GetEntry() == NPC_CORE_RAGER)
|
||||
{
|
||||
if (coreRager1 == nullptr)
|
||||
coreRager1 = unit;
|
||||
else if (coreRager2 == nullptr)
|
||||
{
|
||||
coreRager2 = unit;
|
||||
break; // There should be no third Core Rager.
|
||||
}
|
||||
}
|
||||
}
|
||||
return coreRager1 != nullptr && coreRager2 != nullptr;
|
||||
}
|
||||
|
||||
bool McGolemaggMainTankAttackGolemaggAction::Execute(Event event)
|
||||
{
|
||||
// At this point, we know we are not the last living tank in the group.
|
||||
if (Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator"))
|
||||
{
|
||||
Unit* coreRager1;
|
||||
Unit* coreRager2;
|
||||
if (!FindCoreRagers(coreRager1, coreRager2))
|
||||
return false; // safety check
|
||||
|
||||
// We only need to move if the Core Ragers still have Golemagg's Trust
|
||||
if (coreRager1->HasAura(SPELL_GOLEMAGGS_TRUST) || coreRager2->HasAura(SPELL_GOLEMAGGS_TRUST))
|
||||
return MoveUnitToPosition(boss, GOLEMAGG_TANK_POSITION, boss->GetCombatReach());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool McGolemaggAssistTankAttackCoreRagerAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator");
|
||||
if (!boss)
|
||||
return false;
|
||||
|
||||
// Step 0: Filter additional assist tanks. We only need 2.
|
||||
bool isFirstAssistTank = PlayerbotAI::IsAssistTankOfIndex(bot, 0, true);
|
||||
bool isSecondAssistTank = PlayerbotAI::IsAssistTankOfIndex(bot, 1, true);
|
||||
if (!isFirstAssistTank && !isSecondAssistTank)
|
||||
return Attack(boss);
|
||||
|
||||
// Step 1: Find both Core Ragers
|
||||
Unit* coreRager1;
|
||||
Unit* coreRager2;
|
||||
if (!FindCoreRagers(coreRager1, coreRager2))
|
||||
return false; // safety check
|
||||
|
||||
// Step 2: Assign Core Rager to bot
|
||||
Unit* myCoreRager = nullptr;
|
||||
Unit* otherCoreRager = nullptr;
|
||||
if (isFirstAssistTank)
|
||||
{
|
||||
myCoreRager = coreRager1;
|
||||
otherCoreRager = coreRager2;
|
||||
}
|
||||
else // isSecondAssistTank is always true here
|
||||
{
|
||||
myCoreRager = coreRager2;
|
||||
otherCoreRager = coreRager1;
|
||||
}
|
||||
|
||||
// Step 3: Select the right target
|
||||
if (myCoreRager->GetVictim() != bot)
|
||||
{
|
||||
// Step 3.1: My Core Rager isn't attacking me. Attack until it does.
|
||||
if (bot->GetVictim() != myCoreRager)
|
||||
return Attack(myCoreRager);
|
||||
return botAI->DoSpecificAction("taunt spell", event, true);
|
||||
}
|
||||
|
||||
Unit* otherCoreRagerVictim = otherCoreRager->GetVictim();
|
||||
if (otherCoreRagerVictim) // Core Rager victim can be NULL
|
||||
{
|
||||
// Step 3.2: Check if the other Core Rager isn't attacking its assist tank.
|
||||
Player* otherCoreRagerPlayerVictim = otherCoreRagerVictim->ToPlayer();
|
||||
if (otherCoreRagerPlayerVictim &&
|
||||
!PlayerbotAI::IsAssistTankOfIndex(otherCoreRagerPlayerVictim, 0, true) &&
|
||||
!PlayerbotAI::IsAssistTankOfIndex(otherCoreRagerPlayerVictim, 1, true))
|
||||
{
|
||||
// Assume we are the only assist tank or the other assist tank is dead => pick up other Core Rager!
|
||||
if (bot->GetVictim() != otherCoreRager)
|
||||
return Attack(otherCoreRager);
|
||||
return botAI->DoSpecificAction("taunt spell", event, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (bot->GetVictim() != myCoreRager)
|
||||
return Attack(myCoreRager); // Step 3.3: Attack our Core Rager in case we previously switched in 3.2.
|
||||
|
||||
// Step 4: Prevent Golemagg's Trust on Core Ragers
|
||||
if (myCoreRager->HasAura(SPELL_GOLEMAGGS_TRUST) ||
|
||||
(otherCoreRagerVictim == bot && otherCoreRager->HasAura(SPELL_GOLEMAGGS_TRUST)))
|
||||
{
|
||||
// Step 4.1: Move Core Ragers to dedicated tank position (only if Golemagg is far enough away from said position)
|
||||
float bossDistanceToCoreRagerTankPosition = boss->GetExactDist2d(
|
||||
CORE_RAGER_TANK_POSITION.GetPositionX(), CORE_RAGER_TANK_POSITION.GetPositionY());
|
||||
if (bossDistanceToCoreRagerTankPosition > GOLEMAGGS_TRUST_DISTANCE)
|
||||
{
|
||||
float distanceToTankPosition = bot->GetExactDist2d(CORE_RAGER_TANK_POSITION.GetPositionX(),
|
||||
CORE_RAGER_TANK_POSITION.GetPositionY());
|
||||
if (distanceToTankPosition > CORE_RAGER_STEP_DISTANCE)
|
||||
return MoveUnitToPosition(myCoreRager, CORE_RAGER_TANK_POSITION, CORE_RAGER_STEP_DISTANCE);
|
||||
}
|
||||
|
||||
// Step 4.2: if boss is too close to tank position, or we are already there, move away from Golemagg to try to out-range Golemagg's Trust
|
||||
return MoveAway(boss, CORE_RAGER_STEP_DISTANCE, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#ifndef _PLAYERBOT_RAIDMCACTIONS_H
|
||||
#define _PLAYERBOT_RAIDMCACTIONS_H
|
||||
|
||||
#include "AttackAction.h"
|
||||
#include "MovementActions.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
class McCheckShouldMoveFromGroupAction : public Action
|
||||
class McMoveFromGroupAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
McCheckShouldMoveFromGroupAction(PlayerbotAI* botAI, std::string const name = "mc check should move from group")
|
||||
: Action(botAI, name) {}
|
||||
McMoveFromGroupAction(PlayerbotAI* botAI, std::string const name = "mc move from group")
|
||||
: MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
@@ -21,4 +22,46 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class McShazzrahMoveAwayAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
McShazzrahMoveAwayAction(PlayerbotAI* botAI, std::string const name = "mc shazzrah move away")
|
||||
: MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class McGolemaggMarkBossAction : public Action
|
||||
{
|
||||
public:
|
||||
McGolemaggMarkBossAction(PlayerbotAI* botAI, std::string const name = "mc golemagg mark boss")
|
||||
: Action(botAI, name) {};
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class McGolemaggTankAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
McGolemaggTankAction(PlayerbotAI* botAI, std::string const name)
|
||||
: AttackAction(botAI, name) {}
|
||||
protected:
|
||||
bool MoveUnitToPosition(Unit* target, const Position& tankPosition, float maxDistance, float stepDistance = 3.0f);
|
||||
bool FindCoreRagers(Unit*& coreRager1, Unit*& coreRager2) const;
|
||||
};
|
||||
|
||||
class McGolemaggMainTankAttackGolemaggAction : public McGolemaggTankAction
|
||||
{
|
||||
public:
|
||||
McGolemaggMainTankAttackGolemaggAction(PlayerbotAI* botAI, std::string const name = "mc golemagg main tank attack golemagg")
|
||||
: McGolemaggTankAction(botAI, name) {};
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class McGolemaggAssistTankAttackCoreRagerAction : public McGolemaggTankAction
|
||||
{
|
||||
public:
|
||||
McGolemaggAssistTankAttackCoreRagerAction(PlayerbotAI* botAI, std::string const name = "mc golemagg assist tank attack core rager")
|
||||
: McGolemaggTankAction(botAI, name) {};
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
22
src/strategy/raids/moltencore/RaidMcHelpers.h
Normal file
22
src/strategy/raids/moltencore/RaidMcHelpers.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef _PLAYERBOT_RAIDMCHELPERS_H
|
||||
#define _PLAYERBOT_RAIDMCHELPERS_H
|
||||
|
||||
namespace MoltenCoreHelpers
|
||||
{
|
||||
enum MoltenCoreNPCs
|
||||
{
|
||||
// Golemagg
|
||||
NPC_CORE_RAGER = 11672,
|
||||
};
|
||||
enum MoltenCoreSpells
|
||||
{
|
||||
// Baron Geddon
|
||||
SPELL_INFERNO = 19695,
|
||||
SPELL_LIVING_BOMB = 20475,
|
||||
|
||||
// Golemagg
|
||||
SPELL_GOLEMAGGS_TRUST = 20553,
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
117
src/strategy/raids/moltencore/RaidMcMultipliers.cpp
Normal file
117
src/strategy/raids/moltencore/RaidMcMultipliers.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "RaidMcMultipliers.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "DruidActions.h"
|
||||
#include "HunterActions.h"
|
||||
#include "PaladinActions.h"
|
||||
#include "ShamanActions.h"
|
||||
#include "WarriorActions.h"
|
||||
#include "DKActions.h"
|
||||
#include "RaidMcActions.h"
|
||||
#include "RaidMcHelpers.h"
|
||||
|
||||
using namespace MoltenCoreHelpers;
|
||||
|
||||
static bool IsDpsBotWithAoeAction(Player* bot, Action* action)
|
||||
{
|
||||
if (PlayerbotAI::IsDps(bot))
|
||||
{
|
||||
if (dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastConsecrationAction*>(action) ||
|
||||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<CastWhirlwindAction*>(action) ||
|
||||
dynamic_cast<CastMagmaTotemAction*>(action) || dynamic_cast<CastExplosiveTrapAction*>(action) ||
|
||||
dynamic_cast<CastDeathAndDecayAction*>(action))
|
||||
return true;
|
||||
|
||||
if (auto castSpellAction = dynamic_cast<CastSpellAction*>(action))
|
||||
{
|
||||
if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float GarrDisableDpsAoeMultiplier::GetValue(Action* action)
|
||||
{
|
||||
if (AI_VALUE2(Unit*, "find target", "garr"))
|
||||
{
|
||||
if (IsDpsBotWithAoeAction(bot, action))
|
||||
return 0.0f;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
static bool IsAllowedGeddonMovementAction(Action* action)
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) &&
|
||||
!dynamic_cast<McMoveFromGroupAction*>(action) &&
|
||||
!dynamic_cast<McMoveFromBaronGeddonAction*>(action))
|
||||
return false;
|
||||
|
||||
if (dynamic_cast<CastReachTargetSpellAction*>(action))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float BaronGeddonAbilityMultiplier::GetValue(Action* action)
|
||||
{
|
||||
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
|
||||
{
|
||||
if (boss->HasAura(SPELL_INFERNO))
|
||||
{
|
||||
if (!IsAllowedGeddonMovementAction(action))
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// No check for Baron Geddon, because bots may have the bomb even after Geddon died.
|
||||
if (bot->HasAura(SPELL_LIVING_BOMB))
|
||||
{
|
||||
if (!IsAllowedGeddonMovementAction(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
static bool IsSingleLivingTankInGroup(Player* bot)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (!member || !member->IsAlive() || member == bot)
|
||||
continue;
|
||||
if (PlayerbotAI::IsTank(member))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float GolemaggMultiplier::GetValue(Action* action)
|
||||
{
|
||||
if (AI_VALUE2(Unit*, "find target", "golemagg the incinerator"))
|
||||
{
|
||||
if (PlayerbotAI::IsTank(bot) && IsSingleLivingTankInGroup(bot))
|
||||
{
|
||||
// Only one tank => Pick up Golemagg and the two Core Ragers
|
||||
if (dynamic_cast<McGolemaggMainTankAttackGolemaggAction*>(action) ||
|
||||
dynamic_cast<McGolemaggAssistTankAttackCoreRagerAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
if (PlayerbotAI::IsAssistTank(bot))
|
||||
{
|
||||
// The first two assist tanks manage the Core Ragers. The remaining assist tanks attack the boss.
|
||||
if (dynamic_cast<TankAssistAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
if (IsDpsBotWithAoeAction(bot, action))
|
||||
return 0.0f;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
27
src/strategy/raids/moltencore/RaidMcMultipliers.h
Normal file
27
src/strategy/raids/moltencore/RaidMcMultipliers.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef _PLAYERBOT_RAIDMCMULTIPLIERS_H
|
||||
#define _PLAYERBOT_RAIDMCMULTIPLIERS_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class GarrDisableDpsAoeMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
GarrDisableDpsAoeMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "garr disable dps aoe multiplier") {}
|
||||
float GetValue(Action* action) override;
|
||||
};
|
||||
|
||||
class BaronGeddonAbilityMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
BaronGeddonAbilityMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "baron geddon ability multiplier") {}
|
||||
float GetValue(Action* action) override;
|
||||
};
|
||||
|
||||
class GolemaggMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
GolemaggMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "golemagg multiplier") {}
|
||||
float GetValue(Action* action) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,13 +1,81 @@
|
||||
#include "RaidMcStrategy.h"
|
||||
|
||||
#include "RaidMcMultipliers.h"
|
||||
#include "Strategy.h"
|
||||
|
||||
void RaidMcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
// Lucifron
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc lucifron shadow resistance",
|
||||
NextAction::array(0, new NextAction("mc lucifron shadow resistance", ACTION_RAID), nullptr)));
|
||||
|
||||
// Magmadar
|
||||
// TODO: Fear ward / tremor totem, or general anti-fear strat development. Same as King Dred (Drak'Tharon) and faction commander (Nexus).
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc magmadar fire resistance",
|
||||
NextAction::array(0, new NextAction("mc magmadar fire resistance", ACTION_RAID), nullptr)));
|
||||
|
||||
// Gehennas
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc gehennas shadow resistance",
|
||||
NextAction::array(0, new NextAction("mc gehennas shadow resistance", ACTION_RAID), nullptr)));
|
||||
|
||||
// Garr
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc garr fire resistance",
|
||||
NextAction::array(0, new NextAction("mc garr fire resistance", ACTION_RAID), nullptr)));
|
||||
|
||||
// Baron Geddon
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc baron geddon fire resistance",
|
||||
NextAction::array(0, new NextAction("mc baron geddon fire resistance", ACTION_RAID), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc living bomb debuff",
|
||||
NextAction::array(0, new NextAction("mc check should move from group", ACTION_RAID), nullptr)));
|
||||
NextAction::array(0, new NextAction("mc move from group", ACTION_RAID), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc baron geddon inferno",
|
||||
NextAction::array(0, new NextAction("mc move from baron geddon", ACTION_RAID), nullptr)));
|
||||
|
||||
// Shazzrah
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc shazzrah ranged",
|
||||
NextAction::array(0, new NextAction("mc shazzrah move away", ACTION_RAID), nullptr)));
|
||||
|
||||
// Sulfuron Harbinger
|
||||
// Alternatively, shadow resistance is also possible.
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc sulfuron harbinger fire resistance",
|
||||
NextAction::array(0, new NextAction("mc sulfuron harbinger fire resistance", ACTION_RAID), nullptr)));
|
||||
|
||||
// Golemagg the Incinerator
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc golemagg fire resistance",
|
||||
NextAction::array(0, new NextAction("mc golemagg fire resistance", ACTION_RAID), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc golemagg mark boss",
|
||||
NextAction::array(0, new NextAction("mc golemagg mark boss", ACTION_RAID), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc golemagg is main tank",
|
||||
NextAction::array(0, new NextAction("mc golemagg main tank attack golemagg", ACTION_RAID), nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc golemagg is assist tank",
|
||||
NextAction::array(0, new NextAction("mc golemagg assist tank attack core rager", ACTION_RAID), nullptr)));
|
||||
|
||||
// Majordomo Executus
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc majordomo shadow resistance",
|
||||
NextAction::array(0, new NextAction("mc majordomo shadow resistance", ACTION_RAID), nullptr)));
|
||||
|
||||
// Ragnaros
|
||||
triggers.push_back(
|
||||
new TriggerNode("mc ragnaros fire resistance",
|
||||
NextAction::array(0, new NextAction("mc ragnaros fire resistance", ACTION_RAID), nullptr)));
|
||||
}
|
||||
|
||||
void RaidMcStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
||||
{
|
||||
multipliers.push_back(new GarrDisableDpsAoeMultiplier(botAI));
|
||||
multipliers.push_back(new BaronGeddonAbilityMultiplier(botAI));
|
||||
multipliers.push_back(new GolemaggMultiplier(botAI));
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
class RaidMcStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
RaidMcStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
virtual std::string const getName() override { return "mc"; }
|
||||
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
// virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
RaidMcStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
|
||||
std::string const getName() override { return "moltencore"; }
|
||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||
void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -10,13 +10,39 @@ class RaidMcTriggerContext : public NamedObjectContext<Trigger>
|
||||
public:
|
||||
RaidMcTriggerContext()
|
||||
{
|
||||
creators["mc lucifron shadow resistance"] = &RaidMcTriggerContext::lucifron_shadow_resistance;
|
||||
creators["mc magmadar fire resistance"] = &RaidMcTriggerContext::magmadar_fire_resistance;
|
||||
creators["mc gehennas shadow resistance"] = &RaidMcTriggerContext::gehennas_shadow_resistance;
|
||||
creators["mc garr fire resistance"] = &RaidMcTriggerContext::garr_fire_resistance;
|
||||
creators["mc baron geddon fire resistance"] = &RaidMcTriggerContext::baron_geddon_fire_resistance;
|
||||
creators["mc living bomb debuff"] = &RaidMcTriggerContext::living_bomb_debuff;
|
||||
creators["mc baron geddon inferno"] = &RaidMcTriggerContext::baron_geddon_inferno;
|
||||
creators["mc shazzrah ranged"] = &RaidMcTriggerContext::shazzrah_ranged;
|
||||
creators["mc sulfuron harbinger fire resistance"] = &RaidMcTriggerContext::sulfuron_harbinger_fire_resistance;
|
||||
creators["mc golemagg fire resistance"] = &RaidMcTriggerContext::golemagg_fire_resistance;
|
||||
creators["mc golemagg mark boss"] = &RaidMcTriggerContext::golemagg_mark_boss;
|
||||
creators["mc golemagg is main tank"] = &RaidMcTriggerContext::golemagg_is_main_tank;
|
||||
creators["mc golemagg is assist tank"] = &RaidMcTriggerContext::golemagg_is_assist_tank;
|
||||
creators["mc majordomo shadow resistance"] = &RaidMcTriggerContext::majordomo_shadow_resistance;
|
||||
creators["mc ragnaros fire resistance"] = &RaidMcTriggerContext::ragnaros_fire_resistance;
|
||||
}
|
||||
|
||||
private:
|
||||
static Trigger* living_bomb_debuff(PlayerbotAI* ai) { return new McLivingBombDebuffTrigger(ai); }
|
||||
static Trigger* baron_geddon_inferno(PlayerbotAI* ai) { return new McBaronGeddonInfernoTrigger(ai); }
|
||||
static Trigger* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "lucifron"); }
|
||||
static Trigger* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "magmadar"); }
|
||||
static Trigger* gehennas_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "gehennas"); }
|
||||
static Trigger* garr_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "garr"); }
|
||||
static Trigger* baron_geddon_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "baron geddon"); }
|
||||
static Trigger* living_bomb_debuff(PlayerbotAI* botAI) { return new McLivingBombDebuffTrigger(botAI); }
|
||||
static Trigger* baron_geddon_inferno(PlayerbotAI* botAI) { return new McBaronGeddonInfernoTrigger(botAI); }
|
||||
static Trigger* shazzrah_ranged(PlayerbotAI* botAI) { return new McShazzrahRangedTrigger(botAI); }
|
||||
static Trigger* sulfuron_harbinger_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "sulfuron harbinger"); }
|
||||
static Trigger* golemagg_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "golemagg the incinerator"); }
|
||||
static Trigger* golemagg_mark_boss(PlayerbotAI* botAI) { return new McGolemaggMarkBossTrigger(botAI); }
|
||||
static Trigger* golemagg_is_main_tank(PlayerbotAI* botAI) { return new McGolemaggIsMainTankTrigger(botAI); }
|
||||
static Trigger* golemagg_is_assist_tank(PlayerbotAI* botAI) { return new McGolemaggIsAssistTankTrigger(botAI); }
|
||||
static Trigger* majordomo_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "majordomo executus"); }
|
||||
static Trigger* ragnaros_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "ragnaros"); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,22 +1,40 @@
|
||||
#include "RaidMcTriggers.h"
|
||||
|
||||
#include "SharedDefines.h"
|
||||
#include "RaidMcHelpers.h"
|
||||
|
||||
using namespace MoltenCoreHelpers;
|
||||
|
||||
bool McLivingBombDebuffTrigger::IsActive()
|
||||
{
|
||||
// if bot has barron geddon's living bomb, we need to add strat, otherwise we need to remove
|
||||
// only do when fighting baron geddon (to avoid modifying strat set by player outside this fight)
|
||||
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
|
||||
{
|
||||
if (boss->IsInCombat())
|
||||
return bot->HasAura(20475) != botAI->HasStrategy("move from group", BotState::BOT_STATE_COMBAT);
|
||||
}
|
||||
return false;
|
||||
// No check for Baron Geddon, because bots may have the bomb even after Geddon died.
|
||||
return bot->HasAura(SPELL_LIVING_BOMB);
|
||||
}
|
||||
|
||||
bool McBaronGeddonInfernoTrigger::IsActive()
|
||||
{
|
||||
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
|
||||
return boss->HasAura(19695);
|
||||
return boss->HasAura(SPELL_INFERNO);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool McShazzrahRangedTrigger::IsActive()
|
||||
{
|
||||
return AI_VALUE2(Unit*, "find target", "shazzrah") && PlayerbotAI::IsRanged(bot);
|
||||
}
|
||||
|
||||
bool McGolemaggMarkBossTrigger::IsActive()
|
||||
{
|
||||
// any tank may mark the boss
|
||||
return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsTank(bot);
|
||||
}
|
||||
|
||||
bool McGolemaggIsMainTankTrigger::IsActive()
|
||||
{
|
||||
return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsMainTank(bot);
|
||||
}
|
||||
|
||||
bool McGolemaggIsAssistTankTrigger::IsActive()
|
||||
{
|
||||
return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsAssistTank(bot);
|
||||
}
|
||||
|
||||
@@ -19,4 +19,32 @@ public:
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class McShazzrahRangedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
McShazzrahRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc shazzrah ranged") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class McGolemaggMarkBossTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
McGolemaggMarkBossTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg mark boss") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class McGolemaggIsMainTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
McGolemaggIsMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg is main tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class McGolemaggIsAssistTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
McGolemaggIsAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg is assist tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,8 +22,8 @@ bool UnstealthTrigger::IsActive()
|
||||
return botAI->HasAura("stealth", bot) && !AI_VALUE(uint8, "attacker count") &&
|
||||
(AI_VALUE2(bool, "moving", "self target") &&
|
||||
((botAI->GetMaster() &&
|
||||
sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), 10.0f) &&
|
||||
AI_VALUE2(bool, "moving", "master target")) ||
|
||||
sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "group leader"), 10.0f) &&
|
||||
AI_VALUE2(bool, "moving", "group leader")) ||
|
||||
!AI_VALUE(uint8, "attacker count")));
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ PartyMemberToHealOutOfSpellRangeTrigger::PartyMemberToHealOutOfSpellRangeTrigger
|
||||
|
||||
bool FarFromMasterTrigger::IsActive()
|
||||
{
|
||||
return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), distance);
|
||||
return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "group leader"), distance);
|
||||
}
|
||||
|
||||
bool TooCloseToCreatureTrigger::TooCloseToCreature(uint32 creatureId, float range, bool alive)
|
||||
|
||||
@@ -7,4 +7,14 @@
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool NoRtiTrigger::IsActive() { return !AI_VALUE(Unit*, "rti target"); }
|
||||
bool NoRtiTrigger::IsActive()
|
||||
{
|
||||
// Do not auto-react to raid icons while out of combat.
|
||||
// Out-of-combat RTI usage (explicit chat commands) is handled by chat triggers,
|
||||
// not by this generic trigger.
|
||||
if (!bot->IsInCombat())
|
||||
return false;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "rti target");
|
||||
return target != nullptr;
|
||||
}
|
||||
|
||||
@@ -33,18 +33,14 @@ GuidVector AttackersValue::Calculate()
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
if (unit && IsValidTarget(unit, bot))
|
||||
{
|
||||
targets.insert(unit);
|
||||
}
|
||||
}
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
ObjectGuid skullGuid = group->GetTargetIcon(7);
|
||||
Unit* skullTarget = botAI->GetUnit(skullGuid);
|
||||
if (skullTarget && IsValidTarget(skullTarget, bot))
|
||||
{
|
||||
targets.insert(skullTarget);
|
||||
}
|
||||
}
|
||||
|
||||
for (Unit* unit : targets)
|
||||
@@ -61,9 +57,7 @@ GuidVector AttackersValue::Calculate()
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(guid);
|
||||
if (unit && unit->IsPlayer() && IsValidTarget(unit, bot))
|
||||
{
|
||||
result.push_back(unit->GetGUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,13 +101,11 @@ void AttackersValue::AddAttackersOf(Player* player, std::unordered_set<Unit*>& t
|
||||
{
|
||||
ThreatMgr* threatMgr = ref->GetSource();
|
||||
Unit* attacker = threatMgr->GetOwner();
|
||||
Unit* victim = attacker->GetVictim();
|
||||
|
||||
if (player->IsValidAttackTarget(attacker) &&
|
||||
player->GetDistance2d(attacker) < sPlayerbotAIConfig->sightDistance)
|
||||
{
|
||||
targets.insert(attacker);
|
||||
}
|
||||
|
||||
ref = ref->next();
|
||||
}
|
||||
}
|
||||
@@ -142,57 +134,108 @@ bool AttackersValue::hasRealThreat(Unit* attacker)
|
||||
(attacker->GetThreatMgr().getCurrentVictim() || dynamic_cast<Player*>(attacker));
|
||||
}
|
||||
|
||||
bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float range)
|
||||
bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float /*range*/)
|
||||
{
|
||||
Creature* c = attacker->ToCreature();
|
||||
bool rti = false;
|
||||
if (attacker && bot->GetGroup())
|
||||
rti = bot->GetGroup()->GetTargetIcon(7) == attacker->GetGUID();
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
if (!botAI)
|
||||
return false;
|
||||
|
||||
bool leaderHasThreat = false;
|
||||
if (attacker && bot->GetGroup() && botAI->GetMaster())
|
||||
leaderHasThreat = attacker->GetThreatMgr().GetThreat(botAI->GetMaster());
|
||||
|
||||
bool isMemberBotGroup = false;
|
||||
if (bot->GetGroup() && botAI->GetMaster())
|
||||
{
|
||||
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(botAI->GetMaster());
|
||||
if (masterBotAI && !masterBotAI->IsRealPlayer())
|
||||
isMemberBotGroup = true;
|
||||
}
|
||||
// Basic check
|
||||
if (!attacker)
|
||||
return false;
|
||||
|
||||
// bool inCannon = botAI->IsInVehicle(false, true);
|
||||
// bool enemy = botAI->GetAiObjectContext()->GetValue<Unit*>("enemy player target")->Get();
|
||||
|
||||
return attacker && attacker->IsVisible() && attacker->IsInWorld() && attacker->GetMapId() == bot->GetMapId() &&
|
||||
!attacker->isDead() &&
|
||||
!attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2) &&
|
||||
// (inCannon || !attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) &&
|
||||
// attacker->CanSeeOrDetect(bot) &&
|
||||
// !(attacker->HasUnitState(UNIT_STATE_STUNNED) && botAI->HasAura("shackle undead", attacker)) &&
|
||||
// !((attacker->IsPolymorphed() || botAI->HasAura("sap", attacker) || /*attacker->IsCharmed() ||*/
|
||||
// attacker->isFeared()) && !rti) &&
|
||||
/*!sServerFacade->IsInRoots(attacker) &&*/
|
||||
!attacker->IsFriendlyTo(bot) && !attacker->HasSpiritOfRedemptionAura() &&
|
||||
// !(attacker->GetGUID().IsPet() && enemy) &&
|
||||
!(attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) &&
|
||||
!attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) && !attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) &&
|
||||
bot->CanSeeOrDetect(attacker) &&
|
||||
!(sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) &&
|
||||
(attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet())) &&
|
||||
!(attacker->IsPlayer() && !attacker->IsPvP() && !attacker->IsFFAPvP() &&
|
||||
(!bot->duel || bot->duel->Opponent != attacker)) &&
|
||||
(!c ||
|
||||
(!c->IsInEvadeMode() &&
|
||||
((!isMemberBotGroup && botAI->HasStrategy("attack tagged", BOT_STATE_NON_COMBAT)) || leaderHasThreat ||
|
||||
(!c->hasLootRecipient() &&
|
||||
(!c->GetVictim() ||
|
||||
(c->GetVictim() &&
|
||||
((!c->GetVictim()->IsPlayer() || bot->IsInSameGroupWith(c->GetVictim()->ToPlayer())) ||
|
||||
(botAI->GetMaster() && c->GetVictim() == botAI->GetMaster()))))) ||
|
||||
c->isTappedBy(bot))));
|
||||
// Validity checks
|
||||
if (!attacker->IsVisible() || !attacker->IsInWorld() || attacker->GetMapId() != bot->GetMapId())
|
||||
return false;
|
||||
|
||||
if (attacker->isDead() || attacker->HasSpiritOfRedemptionAura())
|
||||
return false;
|
||||
|
||||
// Flag checks
|
||||
if (attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2))
|
||||
return false;
|
||||
|
||||
if (attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) || attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
|
||||
return false;
|
||||
|
||||
// Relationship checks
|
||||
if (attacker->IsFriendlyTo(bot))
|
||||
return false;
|
||||
|
||||
// Critter exception
|
||||
if (attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat())
|
||||
return false;
|
||||
|
||||
// Visibility check
|
||||
if (!bot->CanSeeOrDetect(attacker))
|
||||
return false;
|
||||
|
||||
// PvP prohibition checks (skip for duels)
|
||||
if ((attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != attacker) &&
|
||||
(sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) ||
|
||||
sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId())))
|
||||
{
|
||||
// This will stop aggresive pets from starting an attack.
|
||||
// This will stop currently attacking pets from continuing their attack.
|
||||
// This will first require the bot to change from a combat strat. It will
|
||||
// not be reached if the bot only switches targets, including NPC targets.
|
||||
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin();
|
||||
itr != bot->m_Controlled.end(); ++itr)
|
||||
{
|
||||
Creature* creature = dynamic_cast<Creature*>(*itr);
|
||||
if (creature && creature->GetVictim() == attacker)
|
||||
{
|
||||
creature->AttackStop();
|
||||
if (CharmInfo* charmInfo = creature->GetCharmInfo())
|
||||
charmInfo->SetIsCommandAttack(false);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unflagged player check
|
||||
if (attacker->IsPlayer() && !attacker->IsPvP() && !attacker->IsFFAPvP() &&
|
||||
(!bot->duel || bot->duel->Opponent != attacker))
|
||||
return false;
|
||||
|
||||
// Creature-specific checks
|
||||
Creature* c = attacker->ToCreature();
|
||||
if (c)
|
||||
{
|
||||
if (c->IsInEvadeMode())
|
||||
return false;
|
||||
|
||||
bool leaderHasThreat = false;
|
||||
if (bot->GetGroup() && botAI->GetMaster())
|
||||
leaderHasThreat = attacker->GetThreatMgr().GetThreat(botAI->GetMaster());
|
||||
|
||||
bool isMemberBotGroup = false;
|
||||
if (bot->GetGroup() && botAI->GetMaster())
|
||||
{
|
||||
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(botAI->GetMaster());
|
||||
if (masterBotAI && !masterBotAI->IsRealPlayer())
|
||||
isMemberBotGroup = true;
|
||||
}
|
||||
|
||||
bool canAttack = (!isMemberBotGroup && botAI->HasStrategy("attack tagged", BOT_STATE_NON_COMBAT)) ||
|
||||
leaderHasThreat ||
|
||||
(!c->hasLootRecipient() &&
|
||||
(!c->GetVictim() ||
|
||||
(c->GetVictim() &&
|
||||
((!c->GetVictim()->IsPlayer() || bot->IsInSameGroupWith(c->GetVictim()->ToPlayer())) ||
|
||||
(botAI->GetMaster() && c->GetVictim() == botAI->GetMaster()))))) ||
|
||||
c->isTappedBy(bot);
|
||||
|
||||
if (!canAttack)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AttackersValue::IsValidTarget(Unit* attacker, Player* bot)
|
||||
|
||||
@@ -21,11 +21,37 @@ bool IsSameLocation(WorldLocation const& a, WorldLocation const& b)
|
||||
|
||||
bool Formation::IsNullLocation(WorldLocation const& loc) { return IsSameLocation(loc, Formation::NullLocation); }
|
||||
|
||||
bool ValidateTargetContext(Unit* a, Unit* b, Map*& outMap)
|
||||
{
|
||||
if (!a || !b || a == b)
|
||||
return false;
|
||||
|
||||
if (!a->IsInWorld() || !b->IsInWorld())
|
||||
return false;
|
||||
|
||||
if (a->IsDuringRemoveFromWorld() || b->IsDuringRemoveFromWorld())
|
||||
return false;
|
||||
|
||||
Map* map = a->GetMap();
|
||||
if (!map || map != b->GetMap())
|
||||
return false;
|
||||
|
||||
outMap = map;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateTargetContext(Unit* a, Unit* b)
|
||||
{
|
||||
Map* unused = nullptr;
|
||||
return ValidateTargetContext(a, b, unused);
|
||||
}
|
||||
|
||||
WorldLocation MoveAheadFormation::GetLocation()
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master || master == bot)
|
||||
return WorldLocation();
|
||||
if (!ValidateTargetContext(master, bot))
|
||||
return Formation::NullLocation;
|
||||
|
||||
WorldLocation loc = GetLocationInternal();
|
||||
if (Formation::IsNullLocation(loc))
|
||||
@@ -40,7 +66,7 @@ WorldLocation MoveAheadFormation::GetLocation()
|
||||
// float ori = master->GetOrientation();
|
||||
// float x1 = x + sPlayerbotAIConfig->tooCloseDistance * cos(ori);
|
||||
// float y1 = y + sPlayerbotAIConfig->tooCloseDistance * sin(ori);
|
||||
// float ground = master->GetMap()->GetHeight(x1, y1, z);
|
||||
// float ground = map->GetHeight(x1, y1, z);
|
||||
// if (ground > INVALID_HEIGHT)
|
||||
// {
|
||||
// x = x1;
|
||||
@@ -48,7 +74,7 @@ WorldLocation MoveAheadFormation::GetLocation()
|
||||
// }
|
||||
// }
|
||||
|
||||
// float ground = master->GetMap()->GetHeight(x, y, z);
|
||||
// float ground = map->GetHeight(x, y, z);
|
||||
// if (ground <= INVALID_HEIGHT)
|
||||
// return Formation::NullLocation;
|
||||
|
||||
@@ -62,7 +88,7 @@ class MeleeFormation : public FollowFormation
|
||||
public:
|
||||
MeleeFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "melee") {}
|
||||
|
||||
std::string const GetTargetName() override { return "master target"; }
|
||||
std::string const GetTargetName() override { return "group leader"; }
|
||||
};
|
||||
|
||||
class QueueFormation : public FollowFormation
|
||||
@@ -81,16 +107,17 @@ public:
|
||||
WorldLocation GetLocationInternal() override
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return WorldLocation();
|
||||
Map* map = nullptr;
|
||||
if (!ValidateTargetContext(master, bot, map))
|
||||
return Formation::NullLocation;
|
||||
|
||||
float range = sPlayerbotAIConfig->followDistance;
|
||||
float angle = GetFollowAngle();
|
||||
float x = master->GetPositionX() + cos(angle) * range;
|
||||
float y = master->GetPositionY() + sin(angle) * range;
|
||||
float z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range;
|
||||
y = master->GetPositionY() + sin(angle) * range;
|
||||
@@ -111,8 +138,9 @@ public:
|
||||
WorldLocation GetLocationInternal() override
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return WorldLocation();
|
||||
Map* map = nullptr;
|
||||
if (!ValidateTargetContext(master, bot, map))
|
||||
return Formation::NullLocation;
|
||||
|
||||
float range = sPlayerbotAIConfig->followDistance;
|
||||
float angle = GetFollowAngle();
|
||||
@@ -120,49 +148,28 @@ public:
|
||||
time_t now = time(nullptr);
|
||||
if (!lastChangeTime || now - lastChangeTime >= 3)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return WorldLocation();
|
||||
lastChangeTime = now;
|
||||
|
||||
float range = sPlayerbotAIConfig->followDistance;
|
||||
float angle = GetFollowAngle();
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if (!lastChangeTime || now - lastChangeTime >= 3)
|
||||
{
|
||||
lastChangeTime = now;
|
||||
dx = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
|
||||
dy = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
|
||||
dr = sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
float x = master->GetPositionX() + cos(angle) * range + dx;
|
||||
float y = master->GetPositionY() + sin(angle) * range + dy;
|
||||
float z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
|
||||
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + dx;
|
||||
y = master->GetPositionY() + sin(angle) * range + dy;
|
||||
z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
master->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
// bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
// bot->GetPositionZ(), x, y, z);
|
||||
return WorldLocation(master->GetMapId(), x, y, z);
|
||||
dx = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
|
||||
dy = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
|
||||
dr = std::sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
float x = master->GetPositionX() + cos(angle) * range + dx;
|
||||
float y = master->GetPositionY() + sin(angle) * range + dy;
|
||||
float x = master->GetPositionX() + std::cos(angle) * range + dx;
|
||||
float y = master->GetPositionY() + std::sin(angle) * range + dy;
|
||||
float z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
|
||||
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + dx;
|
||||
y = master->GetPositionY() + sin(angle) * range + dy;
|
||||
// Recompute a clean fallback and clamp Z
|
||||
x = master->GetPositionX() + std::cos(angle) * range + dx;
|
||||
y = master->GetPositionY() + std::sin(angle) * range + dy;
|
||||
z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
|
||||
master->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
|
||||
return WorldLocation(master->GetMapId(), x, y, z);
|
||||
}
|
||||
|
||||
@@ -182,16 +189,18 @@ public:
|
||||
|
||||
WorldLocation GetLocation() override
|
||||
{
|
||||
float range = 2.0f;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
Player* master = GetMaster();
|
||||
if (!target && target != bot)
|
||||
|
||||
// Fix: if no target OR target is the bot, fall back to master
|
||||
if (!target || target == bot)
|
||||
target = master;
|
||||
|
||||
if (!target)
|
||||
Map* map = nullptr;
|
||||
if (!ValidateTargetContext(master, bot, map))
|
||||
return Formation::NullLocation;
|
||||
|
||||
float range = 2.0f;
|
||||
switch (bot->getClass())
|
||||
{
|
||||
case CLASS_HUNTER:
|
||||
@@ -214,14 +223,16 @@ public:
|
||||
float x = target->GetPositionX() + cos(angle) * range;
|
||||
float y = target->GetPositionY() + sin(angle) * range;
|
||||
float z = target->GetPositionZ();
|
||||
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
|
||||
target->GetPositionZ(), x, y, z))
|
||||
|
||||
if (!map->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
|
||||
target->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = target->GetPositionX() + cos(angle) * range;
|
||||
y = target->GetPositionY() + sin(angle) * range;
|
||||
z = target->GetPositionZ();
|
||||
target->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
|
||||
return WorldLocation(bot->GetMapId(), x, y, z);
|
||||
}
|
||||
};
|
||||
@@ -249,18 +260,18 @@ public:
|
||||
float orientation = master->GetOrientation();
|
||||
|
||||
std::vector<Player*> players;
|
||||
GroupReference* gref = group->GetFirstMember();
|
||||
while (gref)
|
||||
players.reserve(group->GetMembersCount());
|
||||
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (member != master)
|
||||
players.push_back(member);
|
||||
if (!member || member == master)
|
||||
continue;
|
||||
|
||||
gref = gref->next();
|
||||
players.push_back(member);
|
||||
}
|
||||
|
||||
players.insert(players.begin() + group->GetMembersCount() / 2, master);
|
||||
|
||||
players.insert(players.begin() + players.size() / 2, master);
|
||||
return MoveLine(players, 0.0f, x, y, z, orientation, range);
|
||||
}
|
||||
};
|
||||
@@ -289,19 +300,17 @@ public:
|
||||
|
||||
std::vector<Player*> tanks;
|
||||
std::vector<Player*> dps;
|
||||
GroupReference* gref = group->GetFirstMember();
|
||||
while (gref)
|
||||
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (member != master)
|
||||
{
|
||||
if (botAI->IsTank(member))
|
||||
tanks.push_back(member);
|
||||
else
|
||||
dps.push_back(member);
|
||||
}
|
||||
if (!member || member == master)
|
||||
continue;
|
||||
|
||||
gref = gref->next();
|
||||
if (botAI->IsTank(member))
|
||||
tanks.push_back(member);
|
||||
else
|
||||
dps.push_back(member);
|
||||
}
|
||||
|
||||
if (botAI->IsTank(master))
|
||||
@@ -310,25 +319,21 @@ public:
|
||||
dps.insert(dps.begin() + (dps.size() + 1) / 2, master);
|
||||
|
||||
if (botAI->IsTank(bot) && botAI->IsTank(master))
|
||||
{
|
||||
return MoveLine(tanks, 0.0f, x, y, z, orientation, range);
|
||||
}
|
||||
|
||||
if (!botAI->IsTank(bot) && !botAI->IsTank(master))
|
||||
{
|
||||
return MoveLine(dps, 0.0f, x, y, z, orientation, range);
|
||||
}
|
||||
|
||||
if (botAI->IsTank(bot) && !botAI->IsTank(master))
|
||||
{
|
||||
float diff = tanks.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
|
||||
float diff = (tanks.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
|
||||
return MoveLine(tanks, diff, x + cos(orientation) * range, y + sin(orientation) * range, z, orientation,
|
||||
range);
|
||||
}
|
||||
|
||||
if (!botAI->IsTank(bot) && botAI->IsTank(master))
|
||||
{
|
||||
float diff = dps.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
|
||||
float diff = (dps.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
|
||||
return MoveLine(dps, diff, x - cos(orientation) * range, y - sin(orientation) * range, z, orientation,
|
||||
range);
|
||||
}
|
||||
@@ -344,65 +349,69 @@ public:
|
||||
|
||||
WorldLocation GetLocation() override
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
Map* map = nullptr;
|
||||
if (!ValidateTargetContext(master, bot, map))
|
||||
return Formation::NullLocation;
|
||||
|
||||
float range = sPlayerbotAIConfig->farDistance;
|
||||
float followRange = sPlayerbotAIConfig->followDistance;
|
||||
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return Formation::NullLocation;
|
||||
|
||||
if (sServerFacade->GetDistance2d(bot, master) <= range)
|
||||
return Formation::NullLocation;
|
||||
|
||||
float angle = master->GetAngle(bot);
|
||||
float angleToBot = master->GetAngle(bot);
|
||||
float followAngle = GetFollowAngle();
|
||||
|
||||
float x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
|
||||
float y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
|
||||
float x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
|
||||
float y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
|
||||
float z = master->GetPositionZ();
|
||||
|
||||
float ground = master->GetMapHeight(x, y, z + 30.0f);
|
||||
if (ground <= INVALID_HEIGHT)
|
||||
{
|
||||
float minDist = 0, minX = 0, minY = 0;
|
||||
for (float angle = 0.0f; angle <= 2 * M_PI; angle += M_PI / 16.0f)
|
||||
float minDist = 0.f;
|
||||
float minX = 0.f, minY = 0.f;
|
||||
|
||||
for (float a = 0.0f; a <= 2 * M_PI; a += M_PI / 16.0f)
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
|
||||
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
|
||||
float dist = sServerFacade->GetDistance2d(bot, x, y);
|
||||
float ground = master->GetMapHeight(x, y, z + 30.0f);
|
||||
if (ground > INVALID_HEIGHT && (!minDist || minDist > dist))
|
||||
float tx = master->GetPositionX() + cos(a) * range + cos(followAngle) * followRange;
|
||||
float ty = master->GetPositionY() + sin(a) * range + sin(followAngle) * followRange;
|
||||
|
||||
float dist = sServerFacade->GetDistance2d(bot, tx, ty);
|
||||
float tg = master->GetMapHeight(tx, ty, z + 30.0f);
|
||||
|
||||
if (tg > INVALID_HEIGHT && (!minDist || dist < minDist))
|
||||
{
|
||||
minDist = dist;
|
||||
minX = x;
|
||||
minY = y;
|
||||
minX = tx;
|
||||
minY = ty;
|
||||
}
|
||||
}
|
||||
|
||||
if (minDist)
|
||||
if (!minDist)
|
||||
return Formation::NullLocation;
|
||||
|
||||
float lz = z;
|
||||
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), minX, minY, lz))
|
||||
{
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
|
||||
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
|
||||
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
|
||||
z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
master->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
return WorldLocation(bot->GetMapId(), minX, minY, z);
|
||||
lz = z + master->GetHoverHeight();
|
||||
master->UpdateAllowedPositionZ(minX, minY, lz);
|
||||
}
|
||||
|
||||
return Formation::NullLocation;
|
||||
return WorldLocation(bot->GetMapId(), minX, minY, lz);
|
||||
}
|
||||
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
|
||||
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
|
||||
x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
|
||||
y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
|
||||
z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
master->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
|
||||
return WorldLocation(bot->GetMapId(), x, y, z);
|
||||
}
|
||||
};
|
||||
@@ -653,31 +662,36 @@ WorldLocation MoveFormation::MoveSingleLine(std::vector<Player*> line, float dif
|
||||
float orientation, float range)
|
||||
{
|
||||
float count = line.size();
|
||||
float angle = orientation - M_PI / 2.0f;
|
||||
float x = cx + cos(angle) * (range * floor(count / 2.0f) + diff);
|
||||
float y = cy + sin(angle) * (range * floor(count / 2.0f) + diff);
|
||||
float angleLeft = orientation - M_PI / 2.0f;
|
||||
float x0 = cx + std::cos(angleLeft) * (range * std::floor(count / 2.0f) + diff);
|
||||
float y0 = cy + std::sin(angleLeft) * (range * std::floor(count / 2.0f) + diff);
|
||||
|
||||
uint32 index = 0;
|
||||
for (Player* member : line)
|
||||
{
|
||||
if (member == bot)
|
||||
{
|
||||
float angle = orientation + M_PI / 2.0f;
|
||||
float angleRight = orientation + M_PI / 2.0f;
|
||||
float radius = range * index;
|
||||
|
||||
float lx = x + cos(angle) * radius;
|
||||
float ly = y + sin(angle) * radius;
|
||||
float lx = x0 + std::cos(angleRight) * radius;
|
||||
float ly = y0 + std::sin(angleRight) * radius;
|
||||
float lz = cz;
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master || !master->GetMap()->CheckCollisionAndGetValidCoords(
|
||||
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), lx, ly, lz))
|
||||
Map* map = master ? master->GetMap() : nullptr;
|
||||
|
||||
// if not fully in world ignore collision corrections.
|
||||
if (!master || !map || !bot || map != bot->GetMap() || !master->IsInWorld() ||
|
||||
master->IsDuringRemoveFromWorld() || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
|
||||
{
|
||||
lx = x + cos(angle) * radius;
|
||||
ly = y + sin(angle) * radius;
|
||||
lz = cz;
|
||||
return WorldLocation(bot->GetMapId(), lx, ly, lz);
|
||||
}
|
||||
|
||||
// if fully loaded check collision and applies coordinate corrections if needed
|
||||
map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), lx, ly, lz);
|
||||
|
||||
return WorldLocation(bot->GetMapId(), lx, ly, lz);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "MasterTargetValue.h"
|
||||
#include "GroupLeaderValue.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
Unit* MasterTargetValue::Calculate() { return botAI->GetGroupMaster(); }
|
||||
Unit* GroupLeaderValue::Calculate() { return botAI->GetGroupLeader(); }
|
||||
@@ -3,18 +3,18 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_MASTERTARGETVALUE_H
|
||||
#define _PLAYERBOT_MASTERTARGETVALUE_H
|
||||
#ifndef _PLAYERBOT_GROUPLEADERVALUE_H
|
||||
#define _PLAYERBOT_GROUPLEADERVALUE_H
|
||||
|
||||
#include "Value.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
class Unit;
|
||||
|
||||
class MasterTargetValue : public UnitCalculatedValue
|
||||
class GroupLeaderValue : public UnitCalculatedValue
|
||||
{
|
||||
public:
|
||||
MasterTargetValue(PlayerbotAI* botAI, std::string const name = "master target") : UnitCalculatedValue(botAI, name)
|
||||
GroupLeaderValue(PlayerbotAI* botAI, std::string const name = "group leader") : UnitCalculatedValue(botAI, name)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ GuidVector GroupMembersValue::Calculate()
|
||||
|
||||
bool IsFollowingPartyValue::Calculate()
|
||||
{
|
||||
if (botAI->GetGroupMaster() == bot)
|
||||
if (botAI->GetGroupLeader() == bot)
|
||||
return true;
|
||||
|
||||
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
@@ -39,15 +39,15 @@ bool IsFollowingPartyValue::Calculate()
|
||||
|
||||
bool IsNearLeaderValue::Calculate()
|
||||
{
|
||||
Player* groupMaster = botAI->GetGroupMaster();
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
|
||||
if (!groupMaster)
|
||||
if (!groupLeader)
|
||||
return false;
|
||||
|
||||
if (groupMaster == bot)
|
||||
if (groupLeader == bot)
|
||||
return true;
|
||||
|
||||
return sServerFacade->GetDistance2d(bot, botAI->GetGroupMaster()) < sPlayerbotAIConfig->sightDistance;
|
||||
return sServerFacade->GetDistance2d(bot, botAI->GetGroupLeader()) < sPlayerbotAIConfig->sightDistance;
|
||||
}
|
||||
|
||||
bool BoolANDValue::Calculate()
|
||||
@@ -154,8 +154,8 @@ bool GroupReadyValue::Calculate()
|
||||
|
||||
// We only wait for members that are in range otherwise we might be waiting for bots stuck in dead loops
|
||||
// forever.
|
||||
if (botAI->GetGroupMaster() &&
|
||||
sServerFacade->GetDistance2d(member, botAI->GetGroupMaster()) > sPlayerbotAIConfig->sightDistance)
|
||||
if (botAI->GetGroupLeader() &&
|
||||
sServerFacade->GetDistance2d(member, botAI->GetGroupLeader()) > sPlayerbotAIConfig->sightDistance)
|
||||
continue;
|
||||
|
||||
if (member->GetHealthPct() < sPlayerbotAIConfig->almostFullHealth)
|
||||
|
||||
@@ -80,7 +80,7 @@ ItemUsage ItemUsageValue::Calculate()
|
||||
return ITEM_USAGE_USE;
|
||||
|
||||
if (proto->Class == ITEM_CLASS_CONSUMABLE &&
|
||||
(proto->MaxCount == 0 || AI_VALUE2(uint32, "item count", proto->Name1) < proto->MaxCount))
|
||||
(proto->MaxCount == 0 || bot->GetItemCount(itemId, false) < proto->MaxCount))
|
||||
{
|
||||
std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA));
|
||||
|
||||
@@ -520,7 +520,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr
|
||||
{
|
||||
if (quest->RequiredItemId[i] == proto->ItemId)
|
||||
{
|
||||
if (AI_VALUE2(uint32, "item count", proto->Name1) >= quest->RequiredItemCount[i])
|
||||
if (player->GetItemCount(proto->ItemId, false) >= quest->RequiredItemCount[i])
|
||||
continue;
|
||||
|
||||
return true; // Item is directly required for a quest
|
||||
@@ -549,7 +549,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr
|
||||
{
|
||||
if (quest->RequiredItemId[j] == createdItemId)
|
||||
{
|
||||
if (AI_VALUE2(uint32, "item count", createdItemId) >= quest->RequiredItemCount[j])
|
||||
if (player->GetItemCount(createdItemId, false) >= quest->RequiredItemCount[j])
|
||||
continue;
|
||||
|
||||
return true; // Item is useful because it creates a required quest item
|
||||
|
||||
@@ -64,5 +64,15 @@ Unit* RtiTargetValue::Calculate()
|
||||
sPlayerbotAIConfig->sightDistance))
|
||||
return nullptr;
|
||||
|
||||
// Also prevent chasing raid icon targets that are too far away from the master,
|
||||
// even if they are technically visible to the bot.
|
||||
if (Player* master = botAI->GetMaster())
|
||||
{
|
||||
if (master->IsInWorld() && master->GetMapId() == unit->GetMapId() &&
|
||||
sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(master, unit),
|
||||
sPlayerbotAIConfig->sightDistance))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "Formations.h"
|
||||
#include "GrindTargetValue.h"
|
||||
#include "GroupValues.h"
|
||||
#include "GroupLeaderValue.h"
|
||||
#include "GuildValues.h"
|
||||
#include "HasAvailableLootValue.h"
|
||||
#include "HasTotemValue.h"
|
||||
@@ -51,7 +52,6 @@
|
||||
#include "LootStrategyValue.h"
|
||||
#include "MaintenanceValues.h"
|
||||
#include "ManaSaveLevelValue.h"
|
||||
#include "MasterTargetValue.h"
|
||||
#include "NearestAdsValue.h"
|
||||
#include "NearestCorpsesValue.h"
|
||||
#include "NearestFriendlyPlayersValue.h"
|
||||
@@ -130,7 +130,7 @@ public:
|
||||
creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect;
|
||||
creators["current target"] = &ValueContext::current_target;
|
||||
creators["self target"] = &ValueContext::self_target;
|
||||
creators["master target"] = &ValueContext::master;
|
||||
creators["group leader"] = &ValueContext::group_leader;
|
||||
creators["line target"] = &ValueContext::line_target;
|
||||
creators["tank target"] = &ValueContext::tank_target;
|
||||
creators["dps target"] = &ValueContext::dps_target;
|
||||
@@ -439,7 +439,7 @@ private:
|
||||
static UntypedValue* current_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
|
||||
static UntypedValue* old_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
|
||||
static UntypedValue* self_target(PlayerbotAI* botAI) { return new SelfTargetValue(botAI); }
|
||||
static UntypedValue* master(PlayerbotAI* botAI) { return new MasterTargetValue(botAI); }
|
||||
static UntypedValue* group_leader(PlayerbotAI* botAI) { return new GroupLeaderValue(botAI); }
|
||||
static UntypedValue* line_target(PlayerbotAI* botAI) { return new LineTargetValue(botAI); }
|
||||
static UntypedValue* tank_target(PlayerbotAI* botAI) { return new TankTargetValue(botAI); }
|
||||
static UntypedValue* dps_target(PlayerbotAI* botAI) { return new DpsTargetValue(botAI); }
|
||||
|
||||
Reference in New Issue
Block a user