diff --git a/modules/mod-playerbots/README.md b/modules/mod-playerbots/README.md index 3c2e0b8f6b2a0f..6dd49eba559ef5 100644 --- a/modules/mod-playerbots/README.md +++ b/modules/mod-playerbots/README.md @@ -24,7 +24,7 @@ git clone https://github.com/liyunfan1223/mod-playerbots.git --branch=master ## Quick Start & Documentation -For a quick start and an extensive set of commands, please refer to documentation here: [Playerbots Documentation](https://github.com/liyunfan1223/mod-playerbots/wiki/Playerbot-Documentation). +For a quick start and an extensive overview of available addons, commands, and recommended configuration please refer to the [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki). Please be aware that documentation for some newly added commands is currently lacking as the project is still under development. @@ -50,9 +50,10 @@ It's essential to note that there is still a significant amount of work to be do ## Addon -For enhanced control over the bots and to simplify command usage, you can also make use of our addon: -- Chinese version: [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon). -- English version (maintained by @Revision): [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english). +For enhanced control over the bots and to simplify command usage, you can also make use of available Playerbots addons: +- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio) +- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) +- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) ## Frequently Asked Questions diff --git a/modules/mod-playerbots/conf/playerbots.conf.dist b/modules/mod-playerbots/conf/playerbots.conf.dist index 884ab9f1a3c57e..537588fa2a9978 100644 --- a/modules/mod-playerbots/conf/playerbots.conf.dist +++ b/modules/mod-playerbots/conf/playerbots.conf.dist @@ -424,7 +424,7 @@ AiPlayerbot.AutoGearCommand = 1 # default: 3 (rare) AiPlayerbot.AutoGearQualityLimit = 3 -# Equips gear score limitation for auto gear command (0 = no limit) +# Equips item level (not gearScore) limitation for auto gear command (0 = no limit) # default: 0 (no limit) AiPlayerbot.AutoGearScoreLimit = 0 @@ -728,7 +728,7 @@ AiPlayerbot.FastReactInBG = 1 # # All In seconds -AiPlayerbot.RandomBotUpdateInterval = 1 +AiPlayerbot.RandomBotUpdateInterval = 20 AiPlayerbot.RandomBotCountChangeMinInterval = 1800 AiPlayerbot.RandomBotCountChangeMaxInterval = 7200 AiPlayerbot.MinRandomBotInWorldTime = 3600 @@ -1454,13 +1454,16 @@ AiPlayerbot.RandombotsWalkingRPG = 0 AiPlayerbot.RandombotsWalkingRPG.InDoors = 0 # Specify percent of active bots -# The default is 10. With 10% of all bots going active or inactive each minute. +# The default is 10. With 10% of all bots going active or inactive each minute. Regardless +# This value is only applied to inactive areas where no real-players are detected, when +# real-players are nearby, friend, group, guild, bg, instances etc the value is always +# enforced to 100% AiPlayerbot.BotActiveAlone = 100 # Specify smart scaling is enabled or not. # The default is 1. When enabled (smart) scales the 'BotActiveAlone' value. # Only when botLevel is between WhenMinLevel and WhenMaxLevel. -AiPlayerbot.botActiveAloneSmartScale = 0 +AiPlayerbot.botActiveAloneSmartScale = 1 AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1 AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80 diff --git a/modules/mod-playerbots/sql/README.md b/modules/mod-playerbots/data/sql/README.md similarity index 100% rename from modules/mod-playerbots/sql/README.md rename to modules/mod-playerbots/data/sql/README.md diff --git a/modules/mod-playerbots/sql/characters/playerbots_arena_team_names.sql b/modules/mod-playerbots/data/sql/characters/playerbots_arena_team_names.sql similarity index 100% rename from modules/mod-playerbots/sql/characters/playerbots_arena_team_names.sql rename to modules/mod-playerbots/data/sql/characters/playerbots_arena_team_names.sql diff --git a/modules/mod-playerbots/sql/characters/playerbots_guild_names.sql b/modules/mod-playerbots/data/sql/characters/playerbots_guild_names.sql similarity index 100% rename from modules/mod-playerbots/sql/characters/playerbots_guild_names.sql rename to modules/mod-playerbots/data/sql/characters/playerbots_guild_names.sql diff --git a/modules/mod-playerbots/sql/characters/playerbots_names.sql b/modules/mod-playerbots/data/sql/characters/playerbots_names.sql similarity index 100% rename from modules/mod-playerbots/sql/characters/playerbots_names.sql rename to modules/mod-playerbots/data/sql/characters/playerbots_names.sql diff --git a/modules/mod-playerbots/sql/playerbots/archive/.gitkeep b/modules/mod-playerbots/data/sql/playerbots/archive/.gitkeep similarity index 100% rename from modules/mod-playerbots/sql/playerbots/archive/.gitkeep rename to modules/mod-playerbots/data/sql/playerbots/archive/.gitkeep diff --git a/modules/mod-playerbots/sql/playerbots/base/ai_playerbot_texts.sql b/modules/mod-playerbots/data/sql/playerbots/base/ai_playerbot_texts.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/ai_playerbot_texts.sql rename to modules/mod-playerbots/data/sql/playerbots/base/ai_playerbot_texts.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/ai_playerbot_texts_chance.sql b/modules/mod-playerbots/data/sql/playerbots/base/ai_playerbot_texts_chance.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/ai_playerbot_texts_chance.sql rename to modules/mod-playerbots/data/sql/playerbots/base/ai_playerbot_texts_chance.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_custom_strategy.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_custom_strategy.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_custom_strategy.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_custom_strategy.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_db_store.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_db_store.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_db_store.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_db_store.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_dungeon_suggestion_abbrevation.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_dungeon_suggestion_abbrevation.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_dungeon_suggestion_abbrevation.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_dungeon_suggestion_abbrevation.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_dungeon_suggestion_definition.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_dungeon_suggestion_definition.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_dungeon_suggestion_definition.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_dungeon_suggestion_definition.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_dungeon_suggestion_strategy.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_dungeon_suggestion_strategy.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_dungeon_suggestion_strategy.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_dungeon_suggestion_strategy.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_enchants.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_enchants.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_enchants.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_enchants.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_equip_cache.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_equip_cache.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_equip_cache.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_equip_cache.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_guild_tasks.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_guild_tasks.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_guild_tasks.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_guild_tasks.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_item_info_cache.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_item_info_cache.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_item_info_cache.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_item_info_cache.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_preferred_mounts.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_preferred_mounts.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_preferred_mounts.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_preferred_mounts.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_random_bots.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_random_bots.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_random_bots.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_random_bots.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_rarity_cache.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_rarity_cache.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_rarity_cache.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_rarity_cache.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_rnditem_cache.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_rnditem_cache.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_rnditem_cache.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_rnditem_cache.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_speech.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_speech.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_speech.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_speech.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_speech_probability.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_speech_probability.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_speech_probability.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_speech_probability.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_tele_cache.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_tele_cache.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_tele_cache.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_tele_cache.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_travelnode.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_travelnode.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_travelnode.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_travelnode.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_travelnode_link.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_travelnode_link.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_travelnode_link.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_travelnode_link.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_travelnode_path.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_travelnode_path.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_travelnode_path.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_travelnode_path.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_weightscale_data.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_weightscale_data.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_weightscale_data.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_weightscale_data.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/playerbots_weightscales.sql b/modules/mod-playerbots/data/sql/playerbots/base/playerbots_weightscales.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/playerbots_weightscales.sql rename to modules/mod-playerbots/data/sql/playerbots/base/playerbots_weightscales.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/updates.sql b/modules/mod-playerbots/data/sql/playerbots/base/updates.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/updates.sql rename to modules/mod-playerbots/data/sql/playerbots/base/updates.sql diff --git a/modules/mod-playerbots/sql/playerbots/base/updates_include.sql b/modules/mod-playerbots/data/sql/playerbots/base/updates_include.sql similarity index 92% rename from modules/mod-playerbots/sql/playerbots/base/updates_include.sql rename to modules/mod-playerbots/data/sql/playerbots/base/updates_include.sql index c13fa41846e46b..584e9e16da6887 100644 --- a/modules/mod-playerbots/sql/playerbots/base/updates_include.sql +++ b/modules/mod-playerbots/data/sql/playerbots/base/updates_include.sql @@ -24,9 +24,9 @@ CREATE TABLE IF NOT EXISTS `updates_include` ( DELETE FROM `updates_include`; /*!40000 ALTER TABLE `updates_include` DISABLE KEYS */; INSERT INTO `updates_include` (`path`, `state`) VALUES - ('$/sql/playerbots/updates', 'RELEASED'), - ('$/sql/playerbots/custom', 'CUSTOM'), - ('$/sql/playerbots/archive', 'ARCHIVED'); + ('$/data/sql/playerbots/updates', 'RELEASED'), + ('$/data/sql/playerbots/custom', 'CUSTOM'), + ('$/data/sql/playerbots/archive', 'ARCHIVED'); /*!40000 ALTER TABLE `updates_include` ENABLE KEYS */; /*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; diff --git a/modules/mod-playerbots/sql/playerbots/base/version_db_playerbots.sql b/modules/mod-playerbots/data/sql/playerbots/base/version_db_playerbots.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/base/version_db_playerbots.sql rename to modules/mod-playerbots/data/sql/playerbots/base/version_db_playerbots.sql diff --git a/modules/mod-playerbots/sql/playerbots/create/create_mysql.sql b/modules/mod-playerbots/data/sql/playerbots/create/create_mysql.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/create/create_mysql.sql rename to modules/mod-playerbots/data/sql/playerbots/create/create_mysql.sql diff --git a/modules/mod-playerbots/sql/playerbots/create/drop_mysql.sql b/modules/mod-playerbots/data/sql/playerbots/create/drop_mysql.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/create/drop_mysql.sql rename to modules/mod-playerbots/data/sql/playerbots/create/drop_mysql.sql diff --git a/modules/mod-playerbots/sql/playerbots/create/drop_mysql_8.sql b/modules/mod-playerbots/data/sql/playerbots/create/drop_mysql_8.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/create/drop_mysql_8.sql rename to modules/mod-playerbots/data/sql/playerbots/create/drop_mysql_8.sql diff --git a/modules/mod-playerbots/sql/playerbots/custom/README.md b/modules/mod-playerbots/data/sql/playerbots/custom/README.md similarity index 100% rename from modules/mod-playerbots/sql/playerbots/custom/README.md rename to modules/mod-playerbots/data/sql/playerbots/custom/README.md diff --git a/modules/mod-playerbots/sql/playerbots/updates/README.md b/modules/mod-playerbots/data/sql/playerbots/updates/README.md similarity index 100% rename from modules/mod-playerbots/sql/playerbots/updates/README.md rename to modules/mod-playerbots/data/sql/playerbots/updates/README.md diff --git a/modules/mod-playerbots/sql/playerbots/updates/db_playerbots/2024_08_07_00.sql b/modules/mod-playerbots/data/sql/playerbots/updates/db_playerbots/2024_08_07_00.sql similarity index 100% rename from modules/mod-playerbots/sql/playerbots/updates/db_playerbots/2024_08_07_00.sql rename to modules/mod-playerbots/data/sql/playerbots/updates/db_playerbots/2024_08_07_00.sql diff --git a/modules/mod-playerbots/data/sql/playerbots/updates/db_playerbots/2024_11_25_00.sql b/modules/mod-playerbots/data/sql/playerbots/updates/db_playerbots/2024_11_25_00.sql new file mode 100644 index 00000000000000..fef6c756f4b8c4 --- /dev/null +++ b/modules/mod-playerbots/data/sql/playerbots/updates/db_playerbots/2024_11_25_00.sql @@ -0,0 +1,11 @@ +UPDATE `updates_include` +SET `path` = '$/data/sql/playerbots/updates' +WHERE `state` = 'RELEASED'; + +UPDATE `updates_include` +SET `path` = '$/data/sql/playerbots/custom' +WHERE `state` = 'CUSTOM'; + +UPDATE `updates_include` +SET `path` = '$/data/sql/playerbots/archive' +WHERE `state` = 'ARCHIVED'; diff --git a/modules/mod-playerbots/sql/world/world_charsections_dbc.sql b/modules/mod-playerbots/data/sql/world/world_charsections_dbc.sql similarity index 100% rename from modules/mod-playerbots/sql/world/world_charsections_dbc.sql rename to modules/mod-playerbots/data/sql/world/world_charsections_dbc.sql diff --git a/modules/mod-playerbots/sql/world/world_emotetextsound_dbc.sql b/modules/mod-playerbots/data/sql/world/world_emotetextsound_dbc.sql similarity index 100% rename from modules/mod-playerbots/sql/world/world_emotetextsound_dbc.sql rename to modules/mod-playerbots/data/sql/world/world_emotetextsound_dbc.sql diff --git a/modules/mod-playerbots/sql/world/world_playerbots_rpg_races.sql b/modules/mod-playerbots/data/sql/world/world_playerbots_rpg_races.sql similarity index 100% rename from modules/mod-playerbots/sql/world/world_playerbots_rpg_races.sql rename to modules/mod-playerbots/data/sql/world/world_playerbots_rpg_races.sql diff --git a/modules/mod-playerbots/src/PlayerbotAI.cpp b/modules/mod-playerbots/src/PlayerbotAI.cpp index b9c7ff6060cb9a..44e234d542b124 100644 --- a/modules/mod-playerbots/src/PlayerbotAI.cpp +++ b/modules/mod-playerbots/src/PlayerbotAI.cpp @@ -32,7 +32,6 @@ #include "ObjectGuid.h" #include "PerformanceMonitor.h" #include "Player.h" -#include "GameTime.h" #include "PlayerbotAIConfig.h" #include "PlayerbotDbStore.h" #include "PlayerbotMgr.h" @@ -50,6 +49,7 @@ #include "Unit.h" #include "UpdateTime.h" #include "Vehicle.h" +#include "GameTime.h" std::vector PlayerbotAI::dispel_whitelist = { "mutating injection", @@ -241,7 +241,6 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) { return; } - // if (!GetMaster() || !GetMaster()->IsInWorld() || !GetMaster()->GetSession() || // GetMaster()->GetSession()->isLogingOut()) { // return; @@ -304,7 +303,6 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) // bot->GetMotionMaster()->Clear(); // bot->GetMotionMaster()->MoveIdle(); // } - // cheat options if (bot->IsAlive() && ((uint32)GetCheat() > 0 || (uint32)sPlayerbotAIConfig->botCheatMask > 0)) { @@ -3951,10 +3949,7 @@ Player* PlayerbotAI::GetGroupMaster() uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, float cyclePerMin) { - //deterministic seed - uint8 seedNumber = uint8(typeNumber); - std::mt19937 rng(seedNumber); - uint32 randseed = rng(); // Seed random number + uint32 randseed = rand32(); // Seed random number uint32 randnum = bot->GetGUID().GetCounter() + randseed; // Semi-random but fixed number for each bot. if (cyclePerMin > 0) @@ -3964,7 +3959,8 @@ uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, fl randnum += cycle; // Make the random number cylce. } - randnum = (randnum % (maxNum + 1)); // Loops the randomnumber at maxNum. Bassically removes all the numbers above 99. + randnum = + (randnum % (maxNum + 1)); // Loops the randomnumber at maxNum. Bassically removes all the numbers above 99. return randnum; // Now we have a number unique for each bot between 0 and maxNum that increases by cyclePerMin. } @@ -4102,74 +4098,141 @@ inline bool HasRealPlayers(Map* map) return false; } -ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) +inline bool ZoneHasRealPlayers(Player* bot) { - // First priority - priorities disabled or has player master. Always active. - if (HasRealPlayerMaster()) - return ActivePiorityType::HAS_REAL_PLAYER_MASTER; + Map* map = bot->GetMap(); + if (!bot || !map) + { + return false; + } - // Self bot in a group with a bot master. - if (IsRealPlayer()) - return ActivePiorityType::IS_REAL_PLAYER; + Map::PlayerList const& players = bot->GetMap()->GetPlayers(); + if (players.IsEmpty()) + { + return false; + } + + for (auto const& itr : players) + { + Player* player = itr.GetSource(); + if (!player || !player->IsVisible()) + { + continue; + } + if (player->GetZoneId() == bot->GetZoneId()) + { + PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); + if (!botAI || botAI->IsRealPlayer() || botAI->HasRealPlayerMaster()) + { + return true; + } + } + } + + return false; +} + +bool PlayerbotAI::AllowActive(ActivityType activityType) +{ + // only keep updating till initializing time has completed, + // which prevents unneeded expensive GameTime calls. + if (_isBotInitializing) + { + _isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.12; + + // no activity allowed during bot initialization + if (_isBotInitializing) + { + return false; + } + } + + // General exceptions + if (activityType == PACKET_ACTIVITY) + { + return true; + } + + // bg, raid, dungeon + if (!WorldPosition(bot).isOverworld()) + { + return true; + } + + // Is in combat. Defend yourself. + if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY) + { + if (bot->IsInCombat()) + { + return true; + } + } + + // bot zone has active players. + if (ZoneHasRealPlayers(bot)) + { + return true; + } + + // when in real guild + if (IsInRealGuild()) + { + return true; + } + + // Has player master. Always active. + if (GetMaster()) + { + PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(GetMaster()); + if (!masterBotAI || masterBotAI->IsRealPlayer()) + { + return true; + } + } + + // if grouped up Group* group = bot->GetGroup(); if (group) { 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) + { continue; + } - //IN_GROUP_WITH_REAL_PLAYER PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member); - if (!memberBotAI || memberBotAI->HasRealPlayerMaster()) - return ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER; + { + if (!memberBotAI || memberBotAI->HasRealPlayerMaster()) + { + return true; + } + } - //ALLOWED_PARTY_ACTIVITY if (group->IsLeader(member->GetGUID())) { if (!memberBotAI->AllowActivity(PARTY_ACTIVITY)) - return ActivePiorityType::ALLOWED_PARTY_ACTIVITY; + { + return false; + } } } } - //IN_INSTANCE - if (bot->IsBeingTeleported()) // Allow activity while teleportation. - return ActivePiorityType::IN_INSTANCE; - - //IN_INSTANCE - if (!WorldPosition(bot).isOverworld()) - return ActivePiorityType::IN_INSTANCE; - - //IN_COMBAT - if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY) + + // In bg queue. Speed up bg queue/join. + if (bot->InBattlegroundQueue()) { - // Is in combat, defend yourself. - if (bot->IsInCombat()) - return ActivePiorityType::IN_COMBAT; + return true; } - // IN_REACT_DISTANCE - if (HasPlayerNearby(sPlayerbotAIConfig->reactDistance)) - return ActivePiorityType::IN_REACT_DISTANCE; - - // NEARBY_PLAYER acitivity based on yards. - if (HasPlayerNearby(300.f)) - return ActivePiorityType::NEARBY_PLAYER_300; - if (HasPlayerNearby(600.f)) - return ActivePiorityType::NEARBY_PLAYER_600; - - //if (sPlayerbotAIConfig->IsFreeAltBot(bot) || HasStrategy("travel once", BotState::BOT_STATE_NON_COMBAT)) - // return ActivePiorityType::IS_ALWAYS_ACTIVE; - - if (bot->InBattlegroundQueue()) - return ActivePiorityType::IN_BG_QUEUE; - bool isLFG = false; if (group) { @@ -4183,122 +4246,76 @@ ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) isLFG = true; } if (isLFG) - return ActivePiorityType::IN_LFG; - - //IN_EMPTY_SERVER - if (sRandomPlayerbotMgr->GetPlayers().empty()) - return ActivePiorityType::IN_EMPTY_SERVER; - - //PLAYER_FRIEND (on friendlist of real player) (needs to be tested for stability) - /*for (auto& player : sRandomPlayerbotMgr->GetPlayers()) { - if (!player || !player->IsInWorld()) - continue; - - if (player->GetSocial() && - bot->GetGUID() && - player->GetSocial()->HasFriend(bot->GetGUID())) - return ActivePiorityType::PLAYER_FRIEND; - }*/ - - //PLAYER_GUILD (contains real player) - if (IsInRealGuild()) - return ActivePiorityType::PLAYER_GUILD; + return true; + } + // Player is near. Always active. + if (HasPlayerNearby(300.f)) + { + return true; + } - //IN_NOT_ACTIVE_MAP - if (Map* map = bot->GetMap()) + // HasFriend + for (auto& player : sRandomPlayerbotMgr->GetPlayers()) { - if (map->GetEntry()->IsWorldMap()) + if (!player || !player->IsInWorld() || !player->GetSocial() || !bot->GetGUID()) { - if (!HasRealPlayers(map)) - return ActivePiorityType::IN_NOT_ACTIVE_MAP; + continue; + } - if (!map->IsGridLoaded(bot->GetPositionX(), bot->GetPositionY())) - return ActivePiorityType::IN_NOT_ACTIVE_MAP; + if (player->GetSocial()->HasFriend(bot->GetGUID())) + { + return true; } } - //IN_ACTIVE_MAP - if (Map* map = bot->GetMap()) + // Force the bots to spread + if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY) { - if (map->GetEntry()->IsWorldMap()) + if (HasManyPlayersNearby(10, sPlayerbotAIConfig->sightDistance)) { - if (HasRealPlayers(map)) - return ActivePiorityType::IN_ACTIVE_MAP; + return true; } } - // IN_VERY_ACTIVE_AREA - if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY) + // Bots don't need to move using PathGenerator. + if (activityType == DETAILED_MOVE_ACTIVITY) { - if (HasManyPlayersNearby(20, sPlayerbotAIConfig->sightDistance)) - return ActivePiorityType::IN_VERY_ACTIVE_AREA; + return false; } - return ActivePiorityType::DEFAULT; -} - -bool PlayerbotAI::AllowActive(ActivityType activityType) -{ - // no activity allowed during bot initialization during first - // few minutes after starting the server based on maxRandomBots. - if (sRandomPlayerbotMgr->isBotInitializing()) + if (sPlayerbotAIConfig->botActiveAlone <= 0) + { return false; - - // General exceptions - if (activityType == PACKET_ACTIVITY) - return true; - - uint32 botActiveAlonePerc = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone; - uint32 mod = botActiveAlonePerc; - ActivePiorityType type = GetPriorityType(activityType); - - switch (type) + } + if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale) { - case ActivePiorityType::HAS_REAL_PLAYER_MASTER: - case ActivePiorityType::IS_REAL_PLAYER: - case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER: - case ActivePiorityType::IN_INSTANCE: - case ActivePiorityType::IN_COMBAT: - case ActivePiorityType::IN_REACT_DISTANCE: - case ActivePiorityType::NEARBY_PLAYER_300: - case ActivePiorityType::IS_ALWAYS_ACTIVE: - return true; - break; - case ActivePiorityType::ALLOWED_PARTY_ACTIVITY: - return false; - break; - case ActivePiorityType::IN_BG_QUEUE: - case ActivePiorityType::IN_LFG: - case ActivePiorityType::PLAYER_FRIEND: - case ActivePiorityType::PLAYER_GUILD: - case ActivePiorityType::IN_ACTIVE_MAP: - case ActivePiorityType::IN_NOT_ACTIVE_MAP: - case ActivePiorityType::IN_EMPTY_SERVER: - default: - break; + return true; } - // Bots do not need to move using PathGenerator. - //if (activityType == DETAILED_MOVE_ACTIVITY) return false; + // ####################################################################################### + // All mandatory conditations are checked to be active or not, from here the remaining + // situations are usable for scaling when enabled. + // ####################################################################################### - // All exceptions are now done, below is the code to have a specified % of bots - // active at all times. The default is 10%. With 0.1% of all bots going active - // or inactive each minute. - if (sPlayerbotAIConfig->botActiveAlone <= 0) return false; + // Below is code to have a specified % of bots active at all times. + // The default is 10%. With 0.1% of all bots going active or inactive each minute. + uint32 mod = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone; if (sPlayerbotAIConfig->botActiveAloneSmartScale && bot->GetLevel() >= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMinLevel && bot->GetLevel() <= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMaxLevel) { - mod = SmartScaleActivity(type, botActiveAlonePerc); + mod = AutoScaleActivity(mod); } - uint32 ActivityNumber = GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, - botActiveAlonePerc * static_cast(mod) / 100 * 0.01f); + uint32 ActivityNumber = + GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, + sPlayerbotAIConfig->botActiveAlone * static_cast(mod) / 100 * 0.01f); - // The given percentage of bots should be active and rotate 1% of those active bots each minute. - return ActivityNumber <= (botActiveAlonePerc * mod) / 100; + return ActivityNumber <= + (sPlayerbotAIConfig->botActiveAlone * mod) / + 100; // The given percentage of bots should be active and rotate 1% of those active bots each minute. } bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) @@ -4315,42 +4332,37 @@ bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) return allowed; } -uint32 PlayerbotAI::SmartScaleActivity(ActivePiorityType type, uint32 botActiveAlonePerc) +uint32 PlayerbotAI::AutoScaleActivity(uint32 mod) { - uint32 maxDiff = sWorldUpdateTime.GetMaxUpdateTime(); - if (maxDiff > 1000) return false; - switch (type) + uint32 maxDiff = sWorldUpdateTime.GetAverageUpdateTime(); + + if (maxDiff > 500) return 0; + if (maxDiff > 250) { - case ActivePiorityType::IN_BG_QUEUE: - case ActivePiorityType::IN_LFG: - if (maxDiff > 100) return 80; - if (maxDiff > 100) return 90; - break; - case ActivePiorityType::NEARBY_PLAYER_600: - if (maxDiff > 100) return 50; - if (maxDiff > 50) return 75; - break; - case ActivePiorityType::PLAYER_FRIEND: - case ActivePiorityType::PLAYER_GUILD: - case ActivePiorityType::IN_ACTIVE_MAP: - if (maxDiff > 200) return 30; - if (maxDiff > 100) return 50; - if (maxDiff > 50) return 75; - break; - case ActivePiorityType::IN_VERY_ACTIVE_AREA: - case ActivePiorityType::IN_NOT_ACTIVE_MAP: - if (maxDiff > 100) return 30; - if (maxDiff > 50) return 50; - break; - case ActivePiorityType::IN_EMPTY_SERVER: - return 30; - default: - if (maxDiff > 200) return 30; - if (maxDiff > 100) return 50; - break; + if (Map* map = bot->GetMap()) + { + if (map->GetEntry()->IsWorldMap()) + { + if (!HasRealPlayers(map)) + { + return 0; + } + + if (!map->IsGridLoaded(bot->GetPositionX(), bot->GetPositionY())) + { + return 0; + } + } + } + + return (mod * 1) / 10; } + if (maxDiff > 200) return (mod * 3) / 10; + if (maxDiff > 150) return (mod * 5) / 10; + if (maxDiff > 100) return (mod * 6) / 10; + if (maxDiff > 75) return (mod * 9) / 10; - return botActiveAlonePerc; + return mod; } bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); } @@ -5460,29 +5472,15 @@ bool PlayerbotAI::CanMove() if (IsInVehicle() && !IsInVehicle(true)) return false; - if (bot->isFrozen() || - bot->IsPolymorphed() || - (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) || - bot->IsBeingTeleported() || - bot->isInRoots() || - bot->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) || - bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || - bot->IsCharmed() || - bot->HasAuraType(SPELL_AURA_MOD_STUN) || - bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || - bot->HasUnitState(UNIT_STATE_LOST_CONTROL)) - + if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) || + bot->IsBeingTeleported() || bot->isInRoots() || bot->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) || + bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || bot->IsCharmed() || bot->HasAuraType(SPELL_AURA_MOD_STUN) || + bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || bot->HasUnitState(UNIT_STATE_LOST_CONTROL)) return false; return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE; } -bool PlayerbotAI::IsTaxiFlying() -{ - return bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && - bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING); -} - bool PlayerbotAI::IsInRealGuild() { if (!bot->GetGuildId()) diff --git a/modules/mod-playerbots/src/PlayerbotAI.h b/modules/mod-playerbots/src/PlayerbotAI.h index f7cf77ca8128c0..5477685e03559a 100644 --- a/modules/mod-playerbots/src/PlayerbotAI.h +++ b/modules/mod-playerbots/src/PlayerbotAI.h @@ -241,30 +241,6 @@ enum class GuilderType : uint8 VERY_LARGE = 250 }; -enum class ActivePiorityType : uint8 -{ - IS_REAL_PLAYER = 0, - HAS_REAL_PLAYER_MASTER = 1, - IN_GROUP_WITH_REAL_PLAYER = 2, - IN_INSTANCE = 3, - IS_ALWAYS_ACTIVE = 4, - IN_COMBAT = 5, - IN_BG_QUEUE = 6, - IN_LFG = 7, - IN_REACT_DISTANCE = 8, - NEARBY_PLAYER_300 = 9, - NEARBY_PLAYER_600 = 10, - NEARBY_PLAYER_900 = 11, - PLAYER_FRIEND = 12, - PLAYER_GUILD = 13, - IN_VERY_ACTIVE_AREA = 14, - IN_ACTIVE_MAP = 15, - IN_NOT_ACTIVE_MAP = 16, - IN_EMPTY_SERVER = 17, - ALLOWED_PARTY_ACTIVITY = 18, - DEFAULT -}; - enum ActivityType { GRIND_ACTIVITY = 1, @@ -274,8 +250,8 @@ enum ActivityType PACKET_ACTIVITY = 5, DETAILED_MOVE_ACTIVITY = 6, PARTY_ACTIVITY = 7, - REACT_ACTIVITY = 8, - ALL_ACTIVITY = 9, + ALL_ACTIVITY = 8, + MAX_ACTIVITY_TYPE }; @@ -549,10 +525,9 @@ class PlayerbotAI : public PlayerbotAIBase bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig->reactDistance); bool HasPlayerNearby(float range = sPlayerbotAIConfig->reactDistance); bool HasManyPlayersNearby(uint32 trigerrValue = 20, float range = sPlayerbotAIConfig->sightDistance); - ActivePiorityType GetPriorityType(ActivityType activityType); bool AllowActive(ActivityType activityType); bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false); - uint32 SmartScaleActivity(ActivePiorityType type, uint32 botActiveAlonePerc); + uint32 AutoScaleActivity(uint32 mod); // Check if player is safe to use. bool IsSafe(Player* player); @@ -579,7 +554,6 @@ class PlayerbotAI : public PlayerbotAIBase void ResetJumpDestination() { jumpDestination = Position(); } bool CanMove(); - bool IsTaxiFlying(); bool IsInRealGuild(); static std::vector dispel_whitelist; bool EqualLowercaseName(std::string s1, std::string s2); @@ -606,6 +580,7 @@ class PlayerbotAI : public PlayerbotAIBase void HandleCommands(); void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL); + bool _isBotInitializing = true; protected: Player* bot; diff --git a/modules/mod-playerbots/src/PlayerbotAIConfig.cpp b/modules/mod-playerbots/src/PlayerbotAIConfig.cpp index 6c721024b00c1e..53a7b7207f8921 100644 --- a/modules/mod-playerbots/src/PlayerbotAIConfig.cpp +++ b/modules/mod-playerbots/src/PlayerbotAIConfig.cpp @@ -156,7 +156,7 @@ bool PlayerbotAIConfig::Initialize() randomBotAutologin = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutologin", true); minRandomBots = sConfigMgr->GetOption("AiPlayerbot.MinRandomBots", 50); maxRandomBots = sConfigMgr->GetOption("AiPlayerbot.MaxRandomBots", 200); - randomBotUpdateInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotUpdateInterval", 1); + randomBotUpdateInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotUpdateInterval", 20); randomBotCountChangeMinInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotCountChangeMinInterval", 30 * MINUTE); randomBotCountChangeMaxInterval = @@ -465,7 +465,7 @@ bool PlayerbotAIConfig::Initialize() playerbotsXPrate = sConfigMgr->GetOption("AiPlayerbot.KillXPRate", 1); disableDeathKnightLogin = sConfigMgr->GetOption("AiPlayerbot.DisableDeathKnightLogin", 0); botActiveAlone = sConfigMgr->GetOption("AiPlayerbot.BotActiveAlone", 100); - botActiveAloneSmartScale = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScale", 0); + botActiveAloneSmartScale = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScale", 1); botActiveAloneSmartScaleWhenMinLevel = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1); botActiveAloneSmartScaleWhenMaxLevel = diff --git a/modules/mod-playerbots/src/PlayerbotSecurity.cpp b/modules/mod-playerbots/src/PlayerbotSecurity.cpp index 0a045cdb72b30c..8813cde4c2fa26 100644 --- a/modules/mod-playerbots/src/PlayerbotSecurity.cpp +++ b/modules/mod-playerbots/src/PlayerbotSecurity.cpp @@ -181,7 +181,7 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, { DenyReason reason = PLAYERBOT_DENY_NONE; PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup); - if (realLevel >= level) + if (realLevel >= level || from == bot) return true; PlayerbotAI* fromBotAI = GET_PLAYERBOT_AI(from); diff --git a/modules/mod-playerbots/src/Playerbots.cpp b/modules/mod-playerbots/src/Playerbots.cpp index eeacb85a8130ad..94b6b806240391 100644 --- a/modules/mod-playerbots/src/Playerbots.cpp +++ b/modules/mod-playerbots/src/Playerbots.cpp @@ -26,6 +26,7 @@ #include "RandomPlayerbotMgr.h" #include "ScriptMgr.h" #include "cs_playerbots.h" +#include "cmath" class PlayerbotsDatabaseScript : public DatabaseScript { @@ -96,6 +97,16 @@ class PlayerbotsPlayerScript : public PlayerScript { sPlayerbotsMgr->AddPlayerbotData(player, false); sRandomPlayerbotMgr->OnPlayerLogin(player); + + if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin) + { + std::string roundedTime = + std::to_string(std::ceil((sPlayerbotAIConfig->maxRandomBots * 0.13 / 60) * 10) / 10.0); + roundedTime = roundedTime.substr(0, roundedTime.find('.') + 2); + + ChatHandler(player->GetSession()).SendSysMessage( + "Playerbots: bot initialization at server startup will require '" + roundedTime + "' minutes."); + } } } diff --git a/modules/mod-playerbots/src/RandomPlayerbotMgr.cpp b/modules/mod-playerbots/src/RandomPlayerbotMgr.cpp index a04467076cee2c..cbb4e53e7aed59 100644 --- a/modules/mod-playerbots/src/RandomPlayerbotMgr.cpp +++ b/modules/mod-playerbots/src/RandomPlayerbotMgr.cpp @@ -40,7 +40,6 @@ #include "Unit.h" #include "UpdateTime.h" #include "World.h" -#include "GameTime.h" void PrintStatsThread() { sRandomPlayerbotMgr->PrintStats(); } @@ -164,12 +163,8 @@ RandomPlayerbotMgr::RandomPlayerbotMgr() : PlayerbotHolder(), processTicks(0) sPlayerbotCommandServer->Start(); PrepareTeleportCache(); } - if (!sPlayerbotAIConfig->randomBotAutologin) - { - setBotInitializing(false); - } - BattlegroundData.clear(); + BattlegroundData.clear(); BgCheckTimer = 0; LfgCheckTimer = 0; PlayersCheckTimer = 0; @@ -296,6 +291,15 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) if (!sPlayerbotAIConfig->randomBotAutologin || !sPlayerbotAIConfig->enabled) return; + /*if (sPlayerbotAIConfig->enablePrototypePerformanceDiff) + { + LOG_INFO("playerbots", "---------------------------------------"); + LOG_INFO("playerbots", + "PROTOTYPE: Playerbot performance enhancements are active. Issues and instability may occur."); + LOG_INFO("playerbots", "---------------------------------------"); + ScaleBotActivity(); + }*/ + uint32 maxAllowedBotCount = GetEventValue(0, "bot_count"); if (!maxAllowedBotCount || (maxAllowedBotCount < sPlayerbotAIConfig->minRandomBots || maxAllowedBotCount > sPlayerbotAIConfig->maxRandomBots)) @@ -313,17 +317,16 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) uint32 onlineBotFocus = 75; if (onlineBotCount < (uint32)(sPlayerbotAIConfig->minRandomBots * 90 / 100)) - { onlineBotFocus = 25; - } - setBotInitializing( - //onlineBotCount < maxAllowedBotCount && <-- these fields are incorrect when using bot amount min/max are not equal. - GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.12); + // only keep updating till initializing time has completed, + // which prevents unneeded expensive GameTime calls. + if (_isBotInitializing) + { + _isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.15; + } - // when server is balancing bots then boost (decrease value of) the nextCheckDelay till - // onlineBotCount reached the AllowedBotCount. - uint32 updateIntervalTurboBoost = isBotInitializing() ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; + uint32 updateIntervalTurboBoost = _isBotInitializing ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; SetNextCheckDelay(updateIntervalTurboBoost * (onlineBotFocus + 25) * 10); PerformanceMonitorOperation* pmo = sPerformanceMonitor->start( @@ -407,6 +410,26 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) } } +//void RandomPlayerbotMgr::ScaleBotActivity() +//{ +// float activityPercentage = getActivityPercentage(); +// +// // if (activityPercentage >= 100.0f || activityPercentage <= 0.0f) pid.reset(); //Stop integer buildup during +// // max/min activity +// +// // % increase/decrease wanted diff , avg diff +// float activityPercentageMod = pid.calculate( +// sRandomPlayerbotMgr->GetPlayers().empty() ? sPlayerbotAIConfig->diffEmpty : sPlayerbotAIConfig->diffWithPlayer, +// sWorldUpdateTime.GetAverageUpdateTime()); +// +// activityPercentage = activityPercentageMod + 50; +// +// // Cap the percentage between 0 and 100. +// activityPercentage = std::max(0.0f, std::min(100.0f, activityPercentage)); +// +// setActivityPercentage(activityPercentage); +//} + uint32 RandomPlayerbotMgr::AddRandomBots() { uint32 maxAllowedBotCount = GetEventValue(0, "bot_count"); @@ -996,7 +1019,6 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) if (isLogginIn) return false; - uint32 randomTime; if (!player) { @@ -1004,21 +1026,21 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) randomTime = urand(1, 2); SetEventValue(bot, "login", 1, randomTime); - uint32 updateIntervalTurboBoost = isBotInitializing() ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; - randomTime = urand(std::max(5, static_cast(updateIntervalTurboBoost * 0.5)), - std::max(12, static_cast(updateIntervalTurboBoost * 2))); + uint32 randomBotUpdateInterval = _isBotInitializing ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; + randomTime = urand(std::max(5, static_cast(randomBotUpdateInterval * 0.5)), + std::max(12, static_cast(randomBotUpdateInterval * 2))); SetEventValue(bot, "update", 1, randomTime); // do not randomize or teleport immediately after server start (prevent lagging) if (!GetEventValue(bot, "randomize")) { - randomTime = urand(3, std::max(4, static_cast(updateIntervalTurboBoost * 0.4))); + randomTime = urand(3, std::max(4, static_cast(randomBotUpdateInterval * 0.4))); ScheduleRandomize(bot, randomTime); } if (!GetEventValue(bot, "teleport")) { - randomTime = urand(std::max(7, static_cast(updateIntervalTurboBoost * 0.7)), - std::max(14, static_cast(updateIntervalTurboBoost * 1.4))); + randomTime = urand(std::max(7, static_cast(randomBotUpdateInterval * 0.7)), + std::max(14, static_cast(randomBotUpdateInterval * 1.4))); ScheduleTeleport(bot, randomTime); } @@ -1088,9 +1110,6 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) bool RandomPlayerbotMgr::ProcessBot(Player* player) { - if (!player || !player->IsInWorld() || player->IsBeingTeleported() || player->GetSession()->isLogingOut()) - return false; - uint32 bot = player->GetGUID().GetCounter(); if (player->InBattleground()) @@ -1245,7 +1264,9 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& if (botAI) { // ignore when in when taxi with boat/zeppelin and has players nearby - if (botAI->IsTaxiFlying() && botAI->HasPlayerNearby()) + if (bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && + bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING) && + botAI->HasPlayerNearby()) return; } @@ -2222,6 +2243,15 @@ void RandomPlayerbotMgr::OnBotLoginInternal(Player* const bot) { LOG_INFO("playerbots", "{}/{} Bot {} logged in", playerBots.size(), sRandomPlayerbotMgr->GetMaxAllowedBotCount(), bot->GetName().c_str()); + + if (sPlayerbotAIConfig->randomBotFixedLevel) + { + bot->SetPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN); + } + else + { + bot->RemovePlayerFlag(PLAYER_FLAGS_NO_XP_GAIN); + } } void RandomPlayerbotMgr::OnPlayerLogin(Player* player) diff --git a/modules/mod-playerbots/src/RandomPlayerbotMgr.h b/modules/mod-playerbots/src/RandomPlayerbotMgr.h index 104054130da58a..f7ebf9aa025f66 100644 --- a/modules/mod-playerbots/src/RandomPlayerbotMgr.h +++ b/modules/mod-playerbots/src/RandomPlayerbotMgr.h @@ -103,7 +103,8 @@ class RandomPlayerbotMgr : public PlayerbotHolder void LogPlayerLocation(); void UpdateAIInternal(uint32 elapsed, bool minimal = false) override; -//private: +private: + //void ScaleBotActivity(); public: uint32 activeBots = 0; @@ -163,11 +164,11 @@ class RandomPlayerbotMgr : public PlayerbotHolder return BattleMastersCache; } + float getActivityMod() { return activityMod; } + float getActivityPercentage() { return activityMod * 100.0f; } + void setActivityPercentage(float percentage) { activityMod = percentage / 100.0f; } static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; } - bool isBotInitializing() const { return _isBotInitializing; } - void setBotInitializing(bool completed) { _isBotInitializing = completed; } - void PrepareAddclassCache(); std::map> addclassCache; protected: @@ -176,6 +177,7 @@ class RandomPlayerbotMgr : public PlayerbotHolder private: // pid values are set in constructor botPID pid = botPID(1, 50, -50, 0, 0, 0); + float activityMod = 0.25; bool _isBotInitializing = true; uint32 GetEventValue(uint32 bot, std::string const event); std::string const GetEventData(uint32 bot, std::string const event); diff --git a/modules/mod-playerbots/src/strategy/AiObjectContext.cpp b/modules/mod-playerbots/src/strategy/AiObjectContext.cpp index b2ede465d5b627..fc791244bd03b3 100644 --- a/modules/mod-playerbots/src/strategy/AiObjectContext.cpp +++ b/modules/mod-playerbots/src/strategy/AiObjectContext.cpp @@ -62,6 +62,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) actionContexts.Add(new WotlkDungeonOccActionContext()); actionContexts.Add(new WotlkDungeonUPActionContext()); actionContexts.Add(new WotlkDungeonCoSActionContext()); + actionContexts.Add(new WotlkDungeonFoSActionContext()); triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new ChatTriggerContext()); @@ -84,6 +85,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) triggerContexts.Add(new WotlkDungeonOccTriggerContext()); triggerContexts.Add(new WotlkDungeonUPTriggerContext()); triggerContexts.Add(new WotlkDungeonCoSTriggerContext()); + triggerContexts.Add(new WotlkDungeonFosTriggerContext()); valueContexts.Add(new ValueContext()); diff --git a/modules/mod-playerbots/src/strategy/actions/AcceptQuestAction.cpp b/modules/mod-playerbots/src/strategy/actions/AcceptQuestAction.cpp index 259e31dbc2380c..740850394af364 100644 --- a/modules/mod-playerbots/src/strategy/actions/AcceptQuestAction.cpp +++ b/modules/mod-playerbots/src/strategy/actions/AcceptQuestAction.cpp @@ -17,8 +17,8 @@ bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver) if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) { - LOG_INFO("playerbots", "{} => Quest [ {} ] accepted", bot->GetName(), quest->GetTitle()); - bot->Say("Quest [ " + text_quest + " ] accepted", LANG_UNIVERSAL); + LOG_INFO("playerbots", "{} => Quest [{}] accepted", bot->GetName(), quest->GetTitle()); + bot->Say("Quest [" + text_quest + "] accepted", LANG_UNIVERSAL); } return true; @@ -86,7 +86,7 @@ bool AcceptQuestAction::Execute(Event event) if (hasAccept) { std::stringstream ss; - ss << "AcceptQuestAction {" << qInfo->GetTitle() << "} - {" << std::to_string(qInfo->GetQuestId()) << "}"; + ss << "AcceptQuestAction [" << qInfo->GetTitle() << "] - [" << std::to_string(qInfo->GetQuestId()) << "]"; LOG_INFO("playerbots", "{}", ss.str().c_str()); // botAI->TellMaster(ss.str()); } @@ -167,6 +167,9 @@ bool ConfirmQuestAction::Execute(Event event) p >> quest; Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest); + if (!qInfo) + return false; + quest = qInfo->GetQuestId(); if (!bot->CanTakeQuest(qInfo, false)) { diff --git a/modules/mod-playerbots/src/strategy/actions/DropQuestAction.cpp b/modules/mod-playerbots/src/strategy/actions/DropQuestAction.cpp index 2a23c01998f394..ffdbb942ee4e65 100644 --- a/modules/mod-playerbots/src/strategy/actions/DropQuestAction.cpp +++ b/modules/mod-playerbots/src/strategy/actions/DropQuestAction.cpp @@ -118,7 +118,7 @@ bool CleanQuestLogAction::Execute(Event event) } // Check if the quest is trivial (grey) for the bot - if ((botLevel - questLevel) >= trivialLevel) + if ((botLevel - questLevel) > trivialLevel) { // Output only if "debug rpg" strategy is enabled if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) diff --git a/modules/mod-playerbots/src/strategy/actions/EquipAction.cpp b/modules/mod-playerbots/src/strategy/actions/EquipAction.cpp index 85591cf88446c4..f9258d77e24e5b 100644 --- a/modules/mod-playerbots/src/strategy/actions/EquipAction.cpp +++ b/modules/mod-playerbots/src/strategy/actions/EquipAction.cpp @@ -9,6 +9,7 @@ #include "ItemCountValue.h" #include "ItemUsageValue.h" #include "Playerbots.h" +#include "StatsWeightCalculator.h" bool EquipAction::Execute(Event event) { @@ -62,16 +63,17 @@ void EquipAction::EquipItem(Item* item) { uint8 bagIndex = item->GetBagSlot(); uint8 slot = item->GetSlot(); - uint32 itemId = item->GetTemplate()->ItemId; + const ItemTemplate* itemProto = item->GetTemplate(); + uint32 itemId = itemProto->ItemId; - if (item->GetTemplate()->InventoryType == INVTYPE_AMMO) + if (itemProto->InventoryType == INVTYPE_AMMO) { bot->SetAmmo(itemId); } else { - bool equipedBag = false; - if (item->GetTemplate()->Class == ITEM_CLASS_CONTAINER) + bool equippedBag = false; + if (itemProto->Class == ITEM_CLASS_CONTAINER) { Bag* pBag = (Bag*)&item; uint8 newBagSlot = GetSmallestBagSlot(); @@ -80,20 +82,76 @@ void EquipAction::EquipItem(Item* item) uint16 src = ((bagIndex << 8) | slot); uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | newBagSlot); bot->SwapItem(src, dst); - equipedBag = true; + equippedBag = true; } } - if (!equipedBag) + if (!equippedBag) { - WorldPacket packet(CMSG_AUTOEQUIP_ITEM, 2); - packet << bagIndex << slot; - bot->GetSession()->HandleAutoEquipItemOpcode(packet); + uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); + bool have2HWeapon = false; + bool isValidTGWeapon = false; + if (dstSlot == EQUIPMENT_SLOT_MAINHAND) + { + Item* currentWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + have2HWeapon = currentWeapon && currentWeapon->GetTemplate()->InventoryType == INVTYPE_2HWEAPON; + isValidTGWeapon = itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2; + } + + if (dstSlot == EQUIPMENT_SLOT_FINGER1 || + dstSlot == EQUIPMENT_SLOT_TRINKET1 || + (dstSlot == EQUIPMENT_SLOT_MAINHAND && bot->CanDualWield() && + ((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon)))) + { + Item* const equippedItems[2] = { + bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot), + bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot + 1) + }; + + if (equippedItems[0]) + { + if (equippedItems[1]) + { + // Both slots are full - determine worst item to replace + StatsWeightCalculator calculator(bot); + calculator.SetItemSetBonus(false); + calculator.SetOverflowPenalty(false); + + // float newItemScore = calculator.CalculateItem(itemId); + float equippedItemScore[2] = { + equippedItemScore[0] = calculator.CalculateItem(equippedItems[0]->GetTemplate()->ItemId), + equippedItemScore[1] = calculator.CalculateItem(equippedItems[1]->GetTemplate()->ItemId) + }; + + // Second item is worse than first, equip candidate item in second slot + if (equippedItemScore[0] > equippedItemScore[1]) + { + dstSlot++; + } + } + else // No item equipped in slot 2, equip in that slot instead of replacing first item + { + dstSlot++; + } + } + } + + WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2); + ObjectGuid itemguid = item->GetGUID(); + + packet << itemguid << dstSlot; + bot->GetSession()->HandleAutoEquipItemSlotOpcode(packet); + + // WorldPacket packet(CMSG_AUTOEQUIP_ITEM, 2); + // packet << bagIndex << slot; + // bot->GetSession()->HandleAutoEquipItemOpcode(packet); } } std::ostringstream out; - out << "equipping " << chat->FormatItem(item->GetTemplate()); + out << "equipping " << chat->FormatItem(itemProto); botAI->TellMaster(out); } diff --git a/modules/mod-playerbots/src/strategy/actions/InventoryChangeFailureAction.cpp b/modules/mod-playerbots/src/strategy/actions/InventoryChangeFailureAction.cpp index 238fa945a07739..31a182c934053f 100644 --- a/modules/mod-playerbots/src/strategy/actions/InventoryChangeFailureAction.cpp +++ b/modules/mod-playerbots/src/strategy/actions/InventoryChangeFailureAction.cpp @@ -75,7 +75,7 @@ bool InventoryChangeFailureAction::Execute(Event event) messages[EQUIP_ERR_BAG_FULL4] = messages[EQUIP_ERR_BAG_FULL]; messages[EQUIP_ERR_ITEM_SOLD_OUT] = messages[EQUIP_ERR_ITEM_IS_CURRENTLY_SOLD_OUT]; messages[EQUIP_ERR_OBJECT_IS_BUSY] = "This object is busy"; - messages[EQUIP_ERR_NOT_IN_COMBAT] = "I am not in combat"; + messages[EQUIP_ERR_NOT_IN_COMBAT] = "I am in combat"; messages[EQUIP_ERR_NOT_WHILE_DISARMED] = "Cannot do while disarmed"; messages[EQUIP_ERR_BAG_FULL6] = messages[EQUIP_ERR_BAG_FULL]; messages[EQUIP_ERR_CANT_EQUIP_RANK] = "Not enough rank"; diff --git a/modules/mod-playerbots/src/strategy/actions/LfgActions.cpp b/modules/mod-playerbots/src/strategy/actions/LfgActions.cpp index 9a07f527b63738..fb3d008fad5f5c 100644 --- a/modules/mod-playerbots/src/strategy/actions/LfgActions.cpp +++ b/modules/mod-playerbots/src/strategy/actions/LfgActions.cpp @@ -69,8 +69,10 @@ uint32 LfgJoinAction::GetRoles() else return PLAYER_ROLE_DAMAGE; break; + default: return PLAYER_ROLE_DAMAGE; + break; } return PLAYER_ROLE_DAMAGE; @@ -83,14 +85,14 @@ bool LfgJoinAction::JoinLFG() if (state != LFG_STATE_NONE) return false; - ItemCountByQuality visitor; + /*ItemCountByQuality visitor; IterateItems(&visitor, ITERATE_ITEMS_IN_EQUIP); bool random = urand(0, 100) < 20; bool heroic = urand(0, 100) < 50 && (visitor.count[ITEM_QUALITY_EPIC] >= 3 || visitor.count[ITEM_QUALITY_RARE] >= 10) && bot->GetLevel() >= 70; bool rbotAId = !heroic && (urand(0, 100) < 50 && visitor.count[ITEM_QUALITY_EPIC] >= 5 && - (bot->GetLevel() == 60 || bot->GetLevel() == 70 || bot->GetLevel() == 80)); + (bot->GetLevel() == 60 || bot->GetLevel() == 70 || bot->GetLevel() == 80));*/ LfgDungeonSet list; std::vector selected; @@ -173,9 +175,9 @@ bool LfgRoleCheckAction::Execute(Event event) bool LfgAcceptAction::Execute(Event event) { - LfgState status = sLFGMgr->GetState(bot->GetGUID()); + /*LfgState status = sLFGMgr->GetState(bot->GetGUID()); if (status != LFG_STATE_PROPOSAL) - return false; + return false;*/ uint32 id = AI_VALUE(uint32, "lfg proposal"); if (id) @@ -188,7 +190,7 @@ bool LfgAcceptAction::Execute(Event event) LOG_INFO("playerbots", "Bot {} {}:{} <{}> is in combat and refuses LFG proposal {}", bot->GetGUID().ToString().c_str(), bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), bot->GetName().c_str(), id); - sLFGMgr->UpdateProposal(id, bot->GetGUID(), false); + sLFGMgr->UpdateProposal(id, bot->GetGUID(), true); return true; } @@ -271,9 +273,12 @@ bool LfgJoinAction::isUseful() if (bot->GetLevel() < 15) return false; + + // don't use if active player master + if (GET_PLAYERBOT_AI(bot)->IsRealPlayer()) + return false; - if ((botAI->GetMaster() && !GET_PLAYERBOT_AI(botAI->GetMaster())) || - bot->GetGroup() && bot->GetGroup()->GetLeaderGUID() != bot->GetGUID()) + if (bot->GetGroup() && bot->GetGroup()->GetLeaderGUID() != bot->GetGUID()) { // botAI->ChangeStrategy("-lfg", BOT_STATE_NON_COMBAT); return false; diff --git a/modules/mod-playerbots/src/strategy/dungeons/DungeonStrategyContext.h b/modules/mod-playerbots/src/strategy/dungeons/DungeonStrategyContext.h index 5b1914a275582d..aa266f66c98ebb 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/DungeonStrategyContext.h +++ b/modules/mod-playerbots/src/strategy/dungeons/DungeonStrategyContext.h @@ -14,6 +14,7 @@ #include "wotlk/oculus/OculusStrategy.h" #include "wotlk/utgardepinnacle/UtgardePinnacleStrategy.h" #include "wotlk/cullingofstratholme/CullingOfStratholmeStrategy.h" +#include "wotlk/forgeofsouls/ForgeOfSoulsStrategy.h" /* Full list/TODO: @@ -76,11 +77,12 @@ class DungeonStrategyContext : public NamedObjectContext static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonOccStrategy(botAI); } static Strategy* wotlk_up(PlayerbotAI* botAI) { return new WotlkDungeonUPStrategy(botAI); } static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonCoSStrategy(botAI); } + static Strategy* wotlk_fos(PlayerbotAI* botAI) { return new WotlkDungeonFoSStrategy(botAI); } // NYI from here down static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_hor(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_pos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } - static Strategy* wotlk_fos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } + }; #endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/DungeonStrategyUtils.h b/modules/mod-playerbots/src/strategy/dungeons/DungeonStrategyUtils.h index 2b738360f4fcd5..3f67f4b4d45b82 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/DungeonStrategyUtils.h +++ b/modules/mod-playerbots/src/strategy/dungeons/DungeonStrategyUtils.h @@ -1,7 +1,6 @@ #ifndef _PLAYERBOT_DUNGEONUTILS_H #define _PLAYERBOT_DUNGEONUTILS_H - template inline const T& DUNGEON_MODE(Player* bot, const T& normal5, const T& heroic10) { diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h index e6f2e00ac5a68c..7576973976de60 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h @@ -13,9 +13,9 @@ #include "oculus/OculusActionContext.h" #include "utgardepinnacle/UtgardePinnacleActionContext.h" #include "cullingofstratholme/CullingOfStratholmeActionContext.h" +#include "forgeofsouls/ForgeOfSoulsActionContext.h" // #include "trialofthechampion/TrialOfTheChampionActionContext.h" // #include "hallsofreflection/HallsOfReflectionActionContext.h" // #include "pitofsaron/PitOfSaronActionContext.h" -// #include "forgeofsouls/ForgeOfSoulsActionContext.h" #endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h index 455239abf41bab..8b4e979e160372 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h @@ -13,9 +13,9 @@ #include "oculus/OculusTriggerContext.h" #include "utgardepinnacle/UtgardePinnacleTriggerContext.h" #include "cullingofstratholme/CullingOfStratholmeTriggerContext.h" +#include "forgeofsouls/ForgeOfSoulsTriggerContext.h" // #include "trialofthechampion/TrialOfTheChampionTriggerContext.h" // #include "hallsofreflection/HallsOfReflectionTriggerContext.h" // #include "pitofsaron/PitOfSaronTriggerContext.h" -// #include "forgeofsouls/ForgeOfSoulsTriggerContext.h" #endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsActionContext.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsActionContext.h new file mode 100644 index 00000000000000..0ba0b1d2afcdb5 --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsActionContext.h @@ -0,0 +1,23 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONFOSACTIONCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONFOSACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "ForgeOfSoulsActions.h" + +class WotlkDungeonFoSActionContext : public NamedObjectContext +{ + public: + WotlkDungeonFoSActionContext() + { + creators["move from bronjahm"] = &WotlkDungeonFoSActionContext::move_from_bronjahm; + creators["attack corrupted soul fragment"] = &WotlkDungeonFoSActionContext::attack_corrupted_soul_fragment; + creators["bronjahm group position"] = &WotlkDungeonFoSActionContext::bronjahm_group_position; + } + private: + static Action* move_from_bronjahm(PlayerbotAI* ai) { return new MoveFromBronjahmAction(ai); } + static Action* attack_corrupted_soul_fragment(PlayerbotAI* ai) { return new AttackCorruptedSoulFragmentAction(ai); } + static Action* bronjahm_group_position(PlayerbotAI* ai) { return new BronjahmGroupPositionAction(ai); } +}; + +#endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsActions.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsActions.cpp new file mode 100644 index 00000000000000..0992bd3baedd84 --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsActions.cpp @@ -0,0 +1,87 @@ +#include "Playerbots.h" +#include "ForgeOfSoulsActions.h" +#include "ForgeOfSoulsStrategy.h" + +bool MoveFromBronjahmAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "bronjahm"); + if (!boss) + return false; + + + float distance = bot->GetExactDist2d(boss->GetPosition()); + float targetDis = 20.0f; + if (distance >= targetDis) + return false; + return MoveAway(boss, targetDis - distance); +} + + +bool AttackCorruptedSoulFragmentAction::Execute(Event event) +{ + Unit* fragment = nullptr; + + GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + + for (auto &target : targets) + { + Unit* unit = botAI->GetUnit(target); + if (unit && unit->GetEntry() == NPC_CORRUPTED_SOUL_FRAGMENT) + { + fragment = unit; + break; + } + } + + if (fragment && AI_VALUE(Unit*, "current target") != fragment) + return Attack(fragment); + + return false; + +} + + +bool BronjahmGroupPositionAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "bronjahm"); + if (!boss) + return false; + + if (botAI->IsTank(bot)) + { + bot->SetTarget(boss->GetGUID()); + if (AI_VALUE2(bool, "has aggro", "current target")) + if (bot->GetExactDist2d(BRONJAHM_TANK_POSITION) > 5.0f) + return MoveTo(bot->GetMapId(), BRONJAHM_TANK_POSITION.GetPositionX(), + BRONJAHM_TANK_POSITION.GetPositionY(), BRONJAHM_TANK_POSITION.GetPositionZ(), false, + false, false, true, MovementPriority::MOVEMENT_NORMAL); + else + return Attack(boss); + else + { + return Attack(boss); + } + } + else + { + float maxMovement = 10.0f; + + if (bot->GetExactDist2d(boss) > maxMovement) + { + if (bot->getClass() == CLASS_HUNTER) + { + return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 6.5f, maxMovement)); + } + else + { + return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 2.0f, maxMovement)); + } + } + else + return false; + } +} + +bool BronjahmGroupPositionAction::isUseful() { return true; } + + diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsActions.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsActions.h new file mode 100644 index 00000000000000..2388fe5031c56b --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsActions.h @@ -0,0 +1,37 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONFOSACTIONS_H +#define _PLAYERBOT_WOTLKDUNGEONFOSACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "ForgeOfSoulsTriggers.h" + +const Position BRONJAHM_TANK_POSITION = Position(5297.920f, 2506.698f, 686.068f); + +class MoveFromBronjahmAction : public MovementAction +{ +public: + MoveFromBronjahmAction(PlayerbotAI* ai) : MovementAction(ai, "move from bronjahm") {} + bool Execute(Event event) override; +}; + +class AttackCorruptedSoulFragmentAction : public AttackAction +{ +public: + AttackCorruptedSoulFragmentAction(PlayerbotAI* ai) : AttackAction(ai, "attack corrupted soul fragment") {} + bool Execute(Event event) override; +}; + +class BronjahmGroupPositionAction : public AttackAction +{ +public: + BronjahmGroupPositionAction(PlayerbotAI* ai) : AttackAction(ai, "bronjahm group position") {} + + bool Execute(Event event) override; + + bool isUseful() override; +}; + + +#endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsMultipliers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsMultipliers.cpp new file mode 100644 index 00000000000000..36f67fb0c2049c --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsMultipliers.cpp @@ -0,0 +1,50 @@ +#include "ForgeOfSoulsMultipliers.h" +#include "ForgeOfSoulsActions.h" +#include "GenericSpellActions.h" +#include "ChooseTargetActions.h" +#include "MovementActions.h" +#include "ForgeOfSoulsTriggers.h" +#include "ForgeOfSoulsActions.h" + + +float BronjahmMultiplier::GetValue(Action* action) { + Unit* boss = AI_VALUE2(Unit *, "find target", "bronjahm"); + if (!boss) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (boss->FindCurrentSpellBySpellId(SPELL_CORRUPT_SOUL) && + bot->HasAura(SPELL_CORRUPT_SOUL)) + { + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + } + return 1.0f; +} + +float AttackFragmentMultiplier::GetValue(Action* action) +{ + + Unit* fragment = nullptr; + + GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + + for (auto& target : targets) + { + Unit* unit = botAI->GetUnit(target); + if (unit && unit->GetEntry() == NPC_CORRUPTED_SOUL_FRAGMENT) + { + fragment = unit; + break; + } + } + + if (fragment && botAI->IsDps(bot) && dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsMultipliers.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsMultipliers.h new file mode 100644 index 00000000000000..99d7e47b649194 --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsMultipliers.h @@ -0,0 +1,24 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONFOSMULTIPLIERS_H +#define _PLAYERBOT_WOTLKDUNGEONFOSMULTIPLIERS_H + +#include "Multiplier.h" + +class BronjahmMultiplier : public Multiplier +{ + public: + BronjahmMultiplier(PlayerbotAI* ai) : Multiplier(ai, "bronjahm") {} + + public: + virtual float GetValue(Action* action); +}; + +class AttackFragmentMultiplier : public Multiplier +{ +public: + AttackFragmentMultiplier(PlayerbotAI* ai) : Multiplier(ai, "attack fragment") { } + + float GetValue(Action* action) override; +}; + + +#endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsStrategy.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsStrategy.cpp new file mode 100644 index 00000000000000..68b891579b0494 --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsStrategy.cpp @@ -0,0 +1,18 @@ +#include "ForgeOfSoulsStrategy.h" +#include "ForgeOfSoulsMultipliers.h" + +void WotlkDungeonFoSStrategy::InitTriggers(std::vector& triggers) { + triggers.push_back( + new TriggerNode("move from bronjahm", + NextAction::array(0, new NextAction("move from bronjahm", ACTION_MOVE + 5), nullptr))); + triggers.push_back(new TriggerNode( + "switch to soul fragment", NextAction::array(0, new NextAction("attack corrupted soul fragment", ACTION_RAID + 1), nullptr))); + triggers.push_back(new TriggerNode("bronjahm position", + NextAction::array(0, new NextAction("bronjahm group position", ACTION_RAID + 1), nullptr))); +} + +void WotlkDungeonFoSStrategy::InitMultipliers(std::vector& multipliers) +{ + multipliers.push_back(new BronjahmMultiplier(botAI)); + multipliers.push_back(new AttackFragmentMultiplier(botAI)); +} diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsStrategy.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsStrategy.h new file mode 100644 index 00000000000000..79d58334a9c3b7 --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsStrategy.h @@ -0,0 +1,16 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONFOSSTRATEGY_H +#define _PLAYERBOT_WOTLKDUNGEONFOSSTRATEGY_H +#include "Multiplier.h" +#include "Strategy.h" + +class WotlkDungeonFoSStrategy : public Strategy +{ +public: + WotlkDungeonFoSStrategy(PlayerbotAI* ai) : Strategy(ai) {} + std::string const getName() override { return "forge of souls"; } + void InitTriggers(std::vector &triggers) override; + void InitMultipliers(std::vector &multipliers) override; + +}; + +#endif // !_PLAYERBOT_WOTLKDUNGEONFOSSTRATEGY_H diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsTriggerContext.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsTriggerContext.h new file mode 100644 index 00000000000000..9cef63739e1642 --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsTriggerContext.h @@ -0,0 +1,24 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONFOSTRIGGERCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONFOSTRIGGERCONTEXT_H + +#include "NamedObjectContext.h" +#include "AiObjectContext.h" +#include "ForgeOfSoulsTriggers.h" + +class WotlkDungeonFosTriggerContext : public NamedObjectContext +{ +public: + WotlkDungeonFosTriggerContext() + { + creators["bronjahm position"] = &WotlkDungeonFosTriggerContext::bronjahm_position; + creators["move from bronjahm"] = &WotlkDungeonFosTriggerContext::move_from_bronjahm; + creators["switch to soul fragment"] = &WotlkDungeonFosTriggerContext::switch_to_soul_fragment; + } + +private: + static Trigger* move_from_bronjahm(PlayerbotAI* ai) { return new MoveFromBronjahmTrigger(ai); } + static Trigger* switch_to_soul_fragment(PlayerbotAI* ai) { return new SwitchToSoulFragment(ai); } + static Trigger* bronjahm_position(PlayerbotAI* ai) { return new BronjahmPositionTrigger(ai); } +}; + +#endif // !_PLAYERBOT_WOTLKDUNGEONFOSTRIGGERCONTEXT_H diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsTriggers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsTriggers.cpp new file mode 100644 index 00000000000000..c8a3aa0d18749f --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsTriggers.cpp @@ -0,0 +1,39 @@ +#include "Playerbots.h" +#include "ForgeOfSoulsTriggers.h" +#include "AiObject.h" +#include "AiObjectContext.h" + +bool MoveFromBronjahmTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "bronjahm"); + + if (boss && boss->FindCurrentSpellBySpellId(SPELL_CORRUPT_SOUL) && bot->HasAura(SPELL_CORRUPT_SOUL)) + return true; + + return false; +} + +bool SwitchToSoulFragment::IsActive() +{ + Unit* fragment = nullptr; + GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + + for (auto& target : targets) + { + Unit* unit = botAI->GetUnit(target); + if (unit && unit->GetEntry() == NPC_CORRUPTED_SOUL_FRAGMENT) + { + if (botAI->IsDps(bot)) + return true; + } + } + + return false; + +} + +bool BronjahmPositionTrigger::IsActive() +{ + + return bool(AI_VALUE2(Unit*, "find target", "bronjahm")); +} diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsTriggers.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsTriggers.h new file mode 100644 index 00000000000000..3ddf8f6681cca3 --- /dev/null +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/ForgeOfSoulsTriggers.h @@ -0,0 +1,40 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONFOSTRIGGERS_H +#define _PLAYERBOT_WOTLKDUNGEONFOSTRIGGERS_H + +#include "Trigger.h" +#include "PlayerbotAIConfig.h" +#include "GenericTriggers.h" +#include "DungeonStrategyUtils.h" + +enum ForgeOfSoulsBronjahmIDs +{ + // Boss1 + NPC_CORRUPTED_SOUL_FRAGMENT = 36535, + + SPELL_CORRUPT_SOUL = 68839 +}; + +class MoveFromBronjahmTrigger : public Trigger +{ +public: + MoveFromBronjahmTrigger(PlayerbotAI* ai) : Trigger(ai, "move from bronjahm") {} + + bool IsActive() override; +}; + +class SwitchToSoulFragment : public Trigger +{ +public: + SwitchToSoulFragment(PlayerbotAI* ai) : Trigger(ai, "switch to soul fragment") {} + + bool IsActive() override; +}; + +class BronjahmPositionTrigger : public Trigger +{ +public: + BronjahmPositionTrigger(PlayerbotAI* ai) : Trigger(ai, "bronjahm position") {} + bool IsActive() override; +}; + +#endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/TODO b/modules/mod-playerbots/src/strategy/dungeons/wotlk/forgeofsouls/TODO deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActionContext.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActionContext.h index bbece7ef0fe574..746fed39469004 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActionContext.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActionContext.h @@ -10,10 +10,12 @@ class WotlkDungeonGDActionContext : public NamedObjectContext public: WotlkDungeonGDActionContext() { creators["avoid poison nova"] = &WotlkDungeonGDActionContext::avoid_poison_nova; + creators["attack snake wrap"] = &WotlkDungeonGDActionContext::attack_snake_wrap; creators["avoid whirling slash"] = &WotlkDungeonGDActionContext::avoid_whirling_slash; } private: static Action* avoid_poison_nova(PlayerbotAI* ai) { return new AvoidPoisonNovaAction(ai); } + static Action* attack_snake_wrap(PlayerbotAI* ai) { return new AttackSnakeWrapAction(ai); } static Action* avoid_whirling_slash(PlayerbotAI* ai) { return new AvoidWhirlingSlashAction(ai); } }; diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActions.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActions.cpp index 122c47a1b91a44..4e9910993df90c 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActions.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActions.cpp @@ -19,6 +19,32 @@ bool AvoidPoisonNovaAction::Execute(Event event) return false; } +bool AttackSnakeWrapAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran"); + if (!boss) { return false; } + + Unit* snakeWrap = nullptr; + // Target is not findable from threat table using AI_VALUE2(), + // therefore need to search manually for the unit name + GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + + for (auto& target : targets) + { + Unit* unit = botAI->GetUnit(target); + if (unit && unit->GetEntry() == NPC_SNAKE_WRAP) + { + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + if (!currentTarget || currentTarget->GetEntry() != NPC_SNAKE_WRAP) + { + return Attack(unit); + } + } + } + + return false; +} + bool AvoidWhirlingSlashAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "gal'darah"); diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActions.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActions.h index e163248b11a1ca..095cc5cfa0f676 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActions.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakActions.h @@ -14,6 +14,13 @@ class AvoidPoisonNovaAction : public MovementAction bool Execute(Event event) override; }; +class AttackSnakeWrapAction : public AttackAction +{ +public: + AttackSnakeWrapAction(PlayerbotAI* ai) : AttackAction(ai, "attack snake wrap") {} + bool Execute(Event event) override; +}; + class AvoidWhirlingSlashAction : public MovementAction { public: diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakMultipliers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakMultipliers.cpp index a484a5931847fa..2d440425519388 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakMultipliers.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakMultipliers.cpp @@ -11,13 +11,38 @@ float SladranMultiplier::GetValue(Action* action) Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran"); if (!boss) { return 1.0f; } - if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_POISON_NOVA)) + if (boss->FindCurrentSpellBySpellId(SPELL_POISON_NOVA)) + { + if (dynamic_cast(action) && !dynamic_cast(action)) { - if (dynamic_cast(action) && !dynamic_cast(action)) - { - return 0.0f; - } + return 0.0f; } + } + + if (!botAI->IsDps(bot)) { return 1.0f; } + + if (action->getThreatType() == Action::ActionThreatType::Aoe) + { + return 0.0f; + } + + Unit* snakeWrap = nullptr; + GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + for (auto& target : targets) + { + Unit* unit = botAI->GetUnit(target); + if (unit && unit->GetEntry() == NPC_SNAKE_WRAP) + { + snakeWrap = unit; + break; + } + } + // Prevent auto-target acquisition during snake wraps + if (snakeWrap && dynamic_cast(action)) + { + return 0.0f; + } + return 1.0f; } diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakStrategy.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakStrategy.cpp index 5c29bd215aa436..c77c3d15366727 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakStrategy.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakStrategy.cpp @@ -13,13 +13,14 @@ void WotlkDungeonGDStrategy::InitTriggers(std::vector &triggers) // Will re-test in heroic, decent dps groups should be able to blast him down with no funky strats. triggers.push_back(new TriggerNode("poison nova", NextAction::array(0, new NextAction("avoid poison nova", ACTION_RAID + 5), nullptr))); + triggers.push_back(new TriggerNode("snake wrap", + NextAction::array(0, new NextAction("attack snake wrap", ACTION_RAID + 4), nullptr))); // Gal'darah triggers.push_back(new TriggerNode("whirling slash", NextAction::array(0, new NextAction("avoid whirling slash", ACTION_RAID + 5), nullptr))); // Eck the Ferocious (Heroic only) - // TODO } void WotlkDungeonGDStrategy::InitMultipliers(std::vector &multipliers) diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggerContext.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggerContext.h index fc744d1dce75d4..4160639ba10af7 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggerContext.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggerContext.h @@ -11,10 +11,12 @@ class WotlkDungeonGDTriggerContext : public NamedObjectContext WotlkDungeonGDTriggerContext() { creators["poison nova"] = &WotlkDungeonGDTriggerContext::poison_nova; + creators["snake wrap"] = &WotlkDungeonGDTriggerContext::snake_wrap; creators["whirling slash"] = &WotlkDungeonGDTriggerContext::whirling_slash; } private: static Trigger* poison_nova(PlayerbotAI* ai) { return new SladranPoisonNovaTrigger(ai); } + static Trigger* snake_wrap(PlayerbotAI* ai) { return new SladranSnakeWrapTrigger(ai); } static Trigger* whirling_slash(PlayerbotAI* ai) { return new GaldarahWhirlingSlashTrigger(ai); } }; diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggers.cpp index 875b41ddaef123..6229f0c8425cde 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggers.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggers.cpp @@ -8,7 +8,26 @@ bool SladranPoisonNovaTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran"); if (!boss) { return false; } - return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_POISON_NOVA); + return bool(boss->FindCurrentSpellBySpellId(SPELL_POISON_NOVA)); +} + +bool SladranSnakeWrapTrigger::IsActive() +{ + if (!botAI->IsDps(bot)) { return false; } + + // Target is not findable from threat table using AI_VALUE2(), + // therefore need to search manually for the unit name + GuidVector targets = AI_VALUE(GuidVector, "possible targets"); + + for (auto& target : targets) + { + Unit* unit = botAI->GetUnit(target); + if (unit && unit->GetEntry() == NPC_SNAKE_WRAP) + { + return true; + } + } + return false; } bool GaldarahWhirlingSlashTrigger::IsActive() diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggers.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggers.h index 4b2245bc2b8e60..73f8c2348690b0 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggers.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/gundrak/GundrakTriggers.h @@ -11,6 +11,7 @@ enum GundrakIDs // Slad'ran SPELL_POISON_NOVA_N = 55081, SPELL_POISON_NOVA_H = 59842, + NPC_SNAKE_WRAP = 29742, // Gal'darah SPELL_WHIRLING_SLASH_N = 55250, @@ -27,6 +28,13 @@ class SladranPoisonNovaTrigger : public Trigger bool IsActive() override; }; +class SladranSnakeWrapTrigger : public Trigger +{ +public: + SladranSnakeWrapTrigger(PlayerbotAI* ai) : Trigger(ai, "slad'ran snake wrap") {} + bool IsActive() override; +}; + class GaldarahWhirlingSlashTrigger : public Trigger { public: diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningActions.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningActions.cpp index 6bb82079d4e35f..db6f315f3a8607 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningActions.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningActions.cpp @@ -8,11 +8,11 @@ bool BjarngrimTargetAction::Execute(Event event) // Target is not findable from threat table using AI_VALUE2(), // therefore need to search manually for the unit name - GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + GuidVector npcs = AI_VALUE(GuidVector, "possible targets"); - for (auto i = targets.begin(); i != targets.end(); ++i) + for (auto& npc : npcs) { - Unit* unit = botAI->GetUnit(*i); + Unit* unit = botAI->GetUnit(npc); if (unit && unit->GetEntry() == NPC_STORMFORGED_LIEUTENANT) { target = unit; diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningTriggers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningTriggers.cpp index 6350acc503099e..7b7fff6c2d6775 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningTriggers.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningTriggers.cpp @@ -5,16 +5,17 @@ bool StormforgedLieutenantTrigger::IsActive() { - if (botAI->IsTank(bot) || botAI->IsHeal(bot)) { return false; } + if (!botAI->IsDps(bot)) { return false; } // Target is not findable from threat table using AI_VALUE2(), // therefore need to search manually for the unit name - GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + GuidVector targets = AI_VALUE(GuidVector, "possible targets"); - for (auto i = targets.begin(); i != targets.end(); ++i) + for (auto& target : targets) { - Unit* unit = botAI->GetUnit(*i); - if (unit && unit->GetEntry() == NPC_STORMFORGED_LIEUTENANT) + Unit* unit = botAI->GetUnit(target); + if (unit && unit->IsInCombat() && + unit->GetEntry() == NPC_STORMFORGED_LIEUTENANT) { return true; } diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp index e50ae07df0e066..115181e23b7f82 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp @@ -161,18 +161,17 @@ bool DrakeAttackAction::Execute(Event event) if (!target) { - GuidVector attackers = AI_VALUE(GuidVector, "attackers"); - for (auto& attacker : attackers) + GuidVector npcs = AI_VALUE(GuidVector, "possible targets"); + for (auto& npc : npcs) { - Unit* unit = botAI->GetUnit(attacker); - if (!unit) { continue; } + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsInCombat()) { continue; } - SET_AI_VALUE(Unit*, "current target", unit); target = unit; break; } } - + // Check this again to see if a target was assigned if (!target) { return false; } switch (vehicleBase->GetEntry()) diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp index 38c99280379e1d..5706d3b9cea540 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp @@ -49,18 +49,8 @@ bool GroupFlyingTrigger::IsActive() bool DrakeCombatTrigger::IsActive() { - Unit* vehicleBase = bot->GetVehicleBase(); - if (!vehicleBase) { return false; } - - GuidVector attackers = AI_VALUE(GuidVector, "attackers"); - for (auto& attacker : attackers) - { - Unit* target = botAI->GetUnit(attacker); - if (!target) { continue; } - - return true; - } - return false; + GuidVector targets = AI_VALUE(GuidVector, "possible targets"); + return !targets.empty(); } bool VarosCloudstriderTrigger::IsActive() @@ -76,7 +66,7 @@ bool UromArcaneExplosionTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom"); if (!boss) { return false; } - return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_EMPOWERED_ARCANE_EXPLOSION); + return bool(boss->FindCurrentSpellBySpellId(SPELL_EMPOWERED_ARCANE_EXPLOSION)); } bool UromTimeBombTrigger::IsActive() diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepActions.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepActions.cpp index a8966e8bb13eec..3db0f13c8fc72e 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepActions.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepActions.cpp @@ -11,9 +11,9 @@ bool AttackFrostTombAction::Execute(Event event) // therefore need to search manually for the unit name GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); - for (auto i = targets.begin(); i != targets.end(); ++i) + for (auto& target : targets) { - Unit* unit = botAI->GetUnit(*i); + Unit* unit = botAI->GetUnit(target); if (unit && unit->GetEntry() == NPC_FROST_TOMB) { frostTomb = unit; diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepMultipliers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepMultipliers.cpp index c13f898d17f028..0073ec1e1589bb 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepMultipliers.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepMultipliers.cpp @@ -9,9 +9,18 @@ float PrinceKelesethMultiplier::GetValue(Action* action) Unit* boss = AI_VALUE2(Unit*, "find target", "prince keleseth"); if (!boss) { return 1.0f; } + // Suppress auto-targeting behaviour only when a tomb is up if (dynamic_cast(action)) { - return 0.0f; + GuidVector members = AI_VALUE(GuidVector, "group members"); + for (auto& member : members) + { + Unit* unit = botAI->GetUnit(member); + if (unit && unit->HasAura(SPELL_FROST_TOMB)) + { + return 0.0f; + } + } } return 1.0f; } @@ -19,10 +28,11 @@ float PrinceKelesethMultiplier::GetValue(Action* action) float SkarvaldAndDalronnMultiplier::GetValue(Action* action) { // Only need to deal with Dalronn here. If he's dead, just fall back to normal dps strat - Unit* boss = AI_VALUE2(Unit*, "find target", "dalronn the controller"); - if (!boss) { return 1.0f; } + Unit* dalronn = AI_VALUE2(Unit*, "find target", "dalronn the controller"); + if (!dalronn) { return 1.0f; } - if (dynamic_cast(action)) + // Only suppress DpsAssistAction if Dalronn is alive + if (dalronn->isTargetableForAttack() && dynamic_cast(action)) { return 0.0f; } diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggerContext.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggerContext.h index 29cb6dd96865dd..c3dd0740fa8563 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggerContext.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggerContext.h @@ -20,7 +20,7 @@ class WotlkDungeonUKTriggerContext : public NamedObjectContext } private: static Trigger* keleseth_frost_tomb(PlayerbotAI* ai) { return new KelesethFrostTombTrigger(ai); } - static Trigger* dalronn_priority_target(PlayerbotAI* ai) { return new DalronnNontankTrigger(ai); } + static Trigger* dalronn_priority_target(PlayerbotAI* ai) { return new DalronnDpsTrigger(ai); } static Trigger* ingvar_staggering_roar(PlayerbotAI* ai) { return new IngvarStaggeringRoarTrigger(ai); } static Trigger* ingvar_dreadful_roar(PlayerbotAI* ai) { return new IngvarDreadfulRoarTrigger(ai); } static Trigger* ingvar_smash_tank(PlayerbotAI* ai) { return new IngvarSmashTankTrigger(ai); } diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggers.cpp index a3f03b94254b44..5bc430c32e9190 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggers.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggers.cpp @@ -17,11 +17,12 @@ bool KelesethFrostTombTrigger::IsActive() return false; } -bool DalronnNontankTrigger::IsActive() +bool DalronnDpsTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "dalronn the controller"); - if (!boss) { return false; } - + if (!boss || !boss->isTargetableForAttack()) { return false; } + + // This doesn't cause issues with healers currently and they will continue to heal even when included here return !botAI->IsTank(bot); } @@ -30,12 +31,9 @@ bool IngvarStaggeringRoarTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer"); if (!boss) { return false; } - if (boss->HasUnitState(UNIT_STATE_CASTING)) + if (boss->FindCurrentSpellBySpellId(SPELL_STAGGERING_ROAR)) { - if (boss->FindCurrentSpellBySpellId(SPELL_STAGGERING_ROAR)) - { - return true; - } + return true; } return false; } @@ -45,8 +43,7 @@ bool IngvarDreadfulRoarTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer"); if (!boss) { return false; } - if (boss->HasUnitState(UNIT_STATE_CASTING) && - boss->FindCurrentSpellBySpellId(SPELL_DREADFUL_ROAR)) + if (boss->FindCurrentSpellBySpellId(SPELL_DREADFUL_ROAR)) { return true; } @@ -58,14 +55,11 @@ bool IngvarSmashTankTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer"); if (!boss || !botAI->IsTank(bot)) { return false; } - if (boss->HasUnitState(UNIT_STATE_CASTING)) - { - if (boss->FindCurrentSpellBySpellId(SPELL_SMASH) || - boss->FindCurrentSpellBySpellId(SPELL_DARK_SMASH)) - { - return true; - } - } + if (boss->FindCurrentSpellBySpellId(SPELL_SMASH) || + boss->FindCurrentSpellBySpellId(SPELL_DARK_SMASH)) + { + return true; + } return false; } diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggers.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggers.h index 6ea92a15cfc701..237721a6e7b619 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggers.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardekeep/UtgardeKeepTriggers.h @@ -21,12 +21,14 @@ enum UtgardeKeepIDs SPELL_DREADFUL_ROAR_H = 59734, SPELL_WOE_STRIKE_N = 42730, SPELL_WOE_STRIKE_H = 59735, - SPELL_DARK_SMASH = 42723, + SPELL_DARK_SMASH_N = 42723, + SPELL_DARK_SMASH_H = 59709, }; #define SPELL_STAGGERING_ROAR DUNGEON_MODE(bot, SPELL_STAGGERING_ROAR_N, SPELL_STAGGERING_ROAR_H) #define SPELL_DREADFUL_ROAR DUNGEON_MODE(bot, SPELL_DREADFUL_ROAR_N, SPELL_DREADFUL_ROAR_H) #define SPELL_SMASH DUNGEON_MODE(bot, SPELL_SMASH_N, SPELL_SMASH_H) +#define SPELL_DARK_SMASH DUNGEON_MODE(bot, SPELL_DARK_SMASH_N, SPELL_DARK_SMASH_H) class KelesethFrostTombTrigger : public Trigger { @@ -35,10 +37,10 @@ class KelesethFrostTombTrigger : public Trigger bool IsActive() override; }; -class DalronnNontankTrigger : public Trigger +class DalronnDpsTrigger : public Trigger { public: - DalronnNontankTrigger(PlayerbotAI* ai) : Trigger(ai, "dalronn non-tank") {} + DalronnDpsTrigger(PlayerbotAI* ai) : Trigger(ai, "dalronn dps") {} bool IsActive() override; }; diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h index 5a9dff5b8337d5..9e7532b9aeb6c6 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h @@ -11,10 +11,12 @@ class WotlkDungeonUPActionContext : public NamedObjectContext WotlkDungeonUPActionContext() { creators["avoid freezing cloud"] = &WotlkDungeonUPActionContext::avoid_freezing_cloud; creators["avoid skadi whirlwind"] = &WotlkDungeonUPActionContext::avoid_whirlwind; + creators["stop attack"] = &WotlkDungeonUPActionContext::stop_attack; } private: static Action* avoid_freezing_cloud(PlayerbotAI* ai) { return new AvoidFreezingCloudAction(ai); } static Action* avoid_whirlwind(PlayerbotAI* ai) { return new AvoidSkadiWhirlwindAction(ai); } + static Action* stop_attack(PlayerbotAI* ai) { return new DropTargetAction(ai); } }; #endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.cpp index 82e5fd53ba66e1..9a950933e533f5 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.cpp @@ -82,3 +82,18 @@ float SkadiMultiplier::GetValue(Action* action) return 1.0f; } + +float YmironMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "king ymiron"); + if (!boss) { return 1.0f; } + + if (boss->FindCurrentSpellBySpellId(SPELL_BANE) || boss->HasAura(SPELL_BANE)) + { + if (dynamic_cast(action)) + { + return 0.0f; + } + } + return 1.0f; +} \ No newline at end of file diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.h index 3657667831ec04..06cb8578383169 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.h @@ -12,4 +12,13 @@ class SkadiMultiplier : public Multiplier virtual float GetValue(Action* action); }; +class YmironMultiplier : public Multiplier +{ + public: + YmironMultiplier(PlayerbotAI* ai) : Multiplier(ai, "king ymiron") {} + + public: + virtual float GetValue(Action* action); +}; + #endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.cpp index 6877ba2cf243f7..00ff192e601cda 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.cpp @@ -17,9 +17,12 @@ void WotlkDungeonUPStrategy::InitTriggers(std::vector &triggers) // King Ymiron // May need to avoid orb.. unclear if the generic avoid AoE does this well + triggers.push_back(new TriggerNode("ymiron bane", + NextAction::array(0, new NextAction("stop attack", ACTION_RAID + 5), nullptr))); } void WotlkDungeonUPStrategy::InitMultipliers(std::vector &multipliers) { multipliers.push_back(new SkadiMultiplier(botAI)); + multipliers.push_back(new YmironMultiplier(botAI)); } diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggerContext.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggerContext.h index 143f3df5a53111..69d51c0ef2e27e 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggerContext.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggerContext.h @@ -12,10 +12,12 @@ class WotlkDungeonUPTriggerContext : public NamedObjectContext { creators["freezing cloud"] = &WotlkDungeonUPTriggerContext::freezing_cloud; creators["skadi whirlwind"] = &WotlkDungeonUPTriggerContext::whirlwind; + creators["ymiron bane"] = &WotlkDungeonUPTriggerContext::bane; } private: static Trigger* freezing_cloud(PlayerbotAI* ai) { return new SkadiFreezingCloudTrigger(ai); } static Trigger* whirlwind(PlayerbotAI* ai) { return new SkadiWhirlwindTrigger(ai); } + static Trigger* bane(PlayerbotAI* ai) { return new YmironBaneTrigger(ai); } }; #endif diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp index 168d16e6988119..b9b21dc8469ebf 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp @@ -46,3 +46,11 @@ bool SkadiWhirlwindTrigger::IsActive() Unit* boss = AI_VALUE2(Unit*, "find target", "skadi the ruthless"); return boss && boss->HasAura(SPELL_SKADI_WHIRLWIND); } + +bool YmironBaneTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "king ymiron"); + if (!boss) { return false; } + + return boss->FindCurrentSpellBySpellId(SPELL_BANE) || boss->HasAura(SPELL_BANE); +} \ No newline at end of file diff --git a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.h b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.h index bd42c3d629e412..72bf59f8ceb343 100644 --- a/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.h +++ b/modules/mod-playerbots/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.h @@ -15,10 +15,15 @@ enum UtgardePinnacleIDs NPC_BREATH_TRIGGER = 28351, SPELL_SKADI_WHIRLWIND_N = 50228, SPELL_SKADI_WHIRLWIND_H = 59322, + + // King Ymiron + SPELL_BANE_N = 48294, + SPELL_BANE_H = 59301, }; #define SPELL_FREEZING_CLOUD DUNGEON_MODE(bot, SPELL_FREEZING_CLOUD_N, SPELL_FREEZING_CLOUD_H) #define SPELL_SKADI_WHIRLWIND DUNGEON_MODE(bot, SPELL_SKADI_WHIRLWIND_N, SPELL_SKADI_WHIRLWIND_H) +#define SPELL_BANE DUNGEON_MODE(bot, SPELL_BANE_N, SPELL_BANE_H) // const float SKADI_BREATH_CENTRELINE = -512.46875f; @@ -36,4 +41,11 @@ class SkadiWhirlwindTrigger : public Trigger bool IsActive() override; }; +class YmironBaneTrigger : public Trigger +{ +public: + YmironBaneTrigger(PlayerbotAI* ai) : Trigger(ai, "ymiron bane") {} + bool IsActive() override; +}; + #endif diff --git a/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxActions.cpp b/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxActions.cpp index a1019d9bd4096c..021bedb9399607 100644 --- a/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxActions.cpp +++ b/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxActions.cpp @@ -834,17 +834,14 @@ bool AnubrekhanPositionAction::Execute(Event event) { return false; } - EventMap* eventMap = &boss_ai->events; - uint32 locust = eventMap->GetNextEventTime(2); - uint32 timer = eventMap->GetTimer(); bool inPhase = botAI->HasAura("locust swarm", boss) || boss->GetCurrentSpell(CURRENT_GENERIC_SPELL); - if (inPhase || (locust && locust - timer <= 8000)) + if (inPhase) { if (botAI->IsMainTank(bot)) { uint32 nearest = FindNearestWaypoint(); uint32 next_point; - if (inPhase || (locust && locust - timer <= 3000)) + if (inPhase) { next_point = (nearest + 1) % intervals; } diff --git a/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxTriggers.cpp b/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxTriggers.cpp index 0ce93564e4e66b..f4575141e0ea63 100644 --- a/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxTriggers.cpp +++ b/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxTriggers.cpp @@ -50,26 +50,6 @@ bool BossEventTrigger::IsActive() return false; } -template -bool BossPhaseTrigger::IsActive() -{ - Unit* boss = AI_VALUE2(Unit*, "find target", boss_name); - if (!boss) - { - return false; - } - if (this->phase_mask == 0) - { - return true; - } - T* boss_ai = dynamic_cast(boss->GetAI()); - EventMap* eventMap = &boss_ai->events; - uint8 phase_mask = eventMap->GetPhaseMask(); - // bot->Yell("phase mask detected: " + to_string(phase_mask) + " compare with " + to_string(this->phase_mask), - // LANG_UNIVERSAL); - return phase_mask == this->phase_mask; -} - bool GrobbulusCloudTrigger::IsActive() { Unit* boss = AI_VALUE(Unit*, "boss target"); @@ -162,21 +142,6 @@ bool SapphironFlightTrigger::IsActive() return helper.IsPhaseFlight(); } -// bool SapphironGroundExceptMainTankTrigger::IsActive() -// { -// return BossPhaseTrigger::IsActive() && !botAI->IsMainTank(bot); -// } - -// bool SapphironFlightTrigger::IsActive() -// { -// return BossPhaseTrigger::IsActive(); -// } - -// bool SapphironGroundChillTrigger::IsActive() -// { -// return BossPhaseTrigger::IsActive() && !botAI->IsMainTank(bot) && botAI->HasAura("chill", bot); -// } - bool GluthTrigger::IsActive() { return helper.UpdateBossAI(); } bool GluthMainTankMortalWoundTrigger::IsActive() @@ -204,6 +169,15 @@ bool GluthMainTankMortalWoundTrigger::IsActive() bool KelthuzadTrigger::IsActive() { return helper.UpdateBossAI(); } +bool AnubrekhanTrigger::IsActive() { + Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan"); + if (!boss) + { + return false; + } + return true; +} + bool LoathebTrigger::IsActive() { return helper.UpdateBossAI(); } bool ThaddiusPhasePetTrigger::IsActive() @@ -234,4 +208,3 @@ bool ThaddiusPhaseThaddiusTrigger::IsActive() } template bool BossEventTrigger::IsActive(); -template bool BossPhaseTrigger::IsActive(); \ No newline at end of file diff --git a/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxTriggers.h b/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxTriggers.h index 3ad498a318a763..ec10c5ded3d2b7 100644 --- a/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxTriggers.h +++ b/modules/mod-playerbots/src/strategy/raids/naxxramas/RaidNaxxTriggers.h @@ -49,23 +49,6 @@ class BossEventTrigger : public Trigger uint32 boss_entry, event_id, last_event_time; }; -template -class BossPhaseTrigger : public Trigger -{ -public: - BossPhaseTrigger(PlayerbotAI* ai, std::string boss_name, uint32 phase_mask, std::string name = "boss event") - : Trigger(ai, name, 1) - { - this->boss_name = boss_name; - this->phase_mask = phase_mask; - } - virtual bool IsActive(); - -protected: - std::string boss_name; - uint32 phase_mask; -}; - class GrobbulusCloudTrigger : public BossEventTrigger { public: @@ -117,10 +100,11 @@ class KelthuzadTrigger : public Trigger KelthuzadBossHelper helper; }; -class AnubrekhanTrigger : public BossPhaseTrigger +class AnubrekhanTrigger : public Trigger { public: - AnubrekhanTrigger(PlayerbotAI* ai) : BossPhaseTrigger(ai, "anub'rekhan", 0, "anub'rekhan trigger") {} + AnubrekhanTrigger(PlayerbotAI* ai) : Trigger(ai, "anub'rekhan") {} + bool IsActive() override; }; class ThaddiusPhasePetTrigger : public Trigger @@ -194,12 +178,6 @@ class SapphironGroundTrigger : public Trigger SapphironBossHelper helper; }; -// class SapphironGroundExceptMainTankTrigger : public BossPhaseTrigger -// { -// public: -// SapphironGroundExceptMainTankTrigger(PlayerbotAI* ai) : BossPhaseTrigger(ai, "sapphiron", (1 << (2 - 1)), -// "sapphiron ground except main tank") {} virtual bool IsActive(); -// }; class SapphironFlightTrigger : public Trigger { @@ -211,20 +189,6 @@ class SapphironFlightTrigger : public Trigger SapphironBossHelper helper; }; -// class SapphironGroundChillTrigger : public BossPhaseTrigger -// { -// public: -// SapphironGroundChillTrigger(PlayerbotAI* ai) : BossPhaseTrigger(ai, "sapphiron", 0, "sapphiron chill") {} -// virtual bool IsActive(); -// }; - -// class KelthuzadPhaseTwoTrigger : public BossPhaseTrigger -// { -// public: -// KelthuzadPhaseTwoTrigger(PlayerbotAI* ai) : BossPhaseTrigger(ai, "kel'thuzad", 1 << (2 - 1), "kel'thuzad -// trigger") {} -// }; - class GluthTrigger : public Trigger { public: @@ -257,5 +221,4 @@ class LoathebTrigger : public Trigger LoathebBossHelper helper; }; -// template BossEventTrigger; #endif \ No newline at end of file diff --git a/modules/mod-playerbots/src/strategy/triggers/StuckTriggers.cpp b/modules/mod-playerbots/src/strategy/triggers/StuckTriggers.cpp index 5cd56c38fcf441..235cf7be6b358b 100644 --- a/modules/mod-playerbots/src/strategy/triggers/StuckTriggers.cpp +++ b/modules/mod-playerbots/src/strategy/triggers/StuckTriggers.cpp @@ -8,6 +8,7 @@ #include "CellImpl.h" #include "PathGenerator.h" #include "Playerbots.h" +#include "MMapFactory.h" bool MoveStuckTrigger::IsActive() { diff --git a/modules/mod-playerbots/src/strategy/values/Arrow.cpp b/modules/mod-playerbots/src/strategy/values/Arrow.cpp index 0a8afb50e03d67..b41f8b29896e51 100644 --- a/modules/mod-playerbots/src/strategy/values/Arrow.cpp +++ b/modules/mod-playerbots/src/strategy/values/Arrow.cpp @@ -22,10 +22,9 @@ WorldLocation ArrowFormation::GetLocationInternal() float offset = 0.f; Player* master = botAI->GetMaster(); - if (!master) - { + if (!botAI->IsSafe(master)) return Formation::NullLocation; - } + float orientation = master->GetOrientation(); MultiLineUnitPlacer placer(orientation); @@ -43,6 +42,9 @@ WorldLocation ArrowFormation::GetLocationInternal() offset += rangedLines * sPlayerbotAIConfig->followDistance; healers.PlaceUnits(&placer); healers.Move(-cos(orientation) * offset, -sin(orientation) * offset); + + if (!masterUnit || !botUnit) + return Formation::NullLocation; float x = master->GetPositionX() - masterUnit->GetX() + botUnit->GetX(); float y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY(); @@ -89,14 +91,15 @@ void ArrowFormation::FillSlotsExceptMaster() while (gref) { Player* member = gref->GetSource(); - - if (member == bot) - FindSlot(member)->AddLast(botUnit = new FormationUnit(index, false)); - else if (member != botAI->GetMaster()) - FindSlot(member)->AddLast(new FormationUnit(index, false)); - + if (botAI->IsSafe(member)) + { + if (member == bot) + FindSlot(member)->AddLast(botUnit = new FormationUnit(index, false)); + else if (member != botAI->GetMaster()) + FindSlot(member)->AddLast(new FormationUnit(index, false)); + ++index; + } gref = gref->next(); - ++index; } } diff --git a/modules/mod-playerbots/src/strategy/values/Formations.cpp b/modules/mod-playerbots/src/strategy/values/Formations.cpp index 2a38d9d112f880..77dc2264f4b9f4 100644 --- a/modules/mod-playerbots/src/strategy/values/Formations.cpp +++ b/modules/mod-playerbots/src/strategy/values/Formations.cpp @@ -437,6 +437,7 @@ float Formation::GetFollowAngle() { if (Player* member = ref->GetSource()) { + if (!member || member == bot || !member->IsAlive() || bot->GetMapId() != member->GetMapId()) continue; if (member != master && !botAI->IsTank(member) && !botAI->IsHeal(member)) { roster.insert(roster.begin() + roster.size() / 2, member); @@ -448,6 +449,7 @@ float Formation::GetFollowAngle() { if (Player* member = ref->GetSource()) { + if (!member || member == bot || !member->IsAlive() || bot->GetMapId() != member->GetMapId()) continue; if (member != master && botAI->IsHeal(member)) { roster.insert(roster.begin() + roster.size() / 2, member); @@ -460,6 +462,7 @@ float Formation::GetFollowAngle() { if (Player* member = ref->GetSource()) { + if (!member || member == bot || !member->IsAlive() || bot->GetMapId() != member->GetMapId()) continue; if (member != master && botAI->IsTank(member)) { if (left) diff --git a/modules/mod-playerbots/src/strategy/values/ItemUsageValue.cpp b/modules/mod-playerbots/src/strategy/values/ItemUsageValue.cpp index 9c6adbf94d479b..5c5e38e1d4896a 100644 --- a/modules/mod-playerbots/src/strategy/values/ItemUsageValue.cpp +++ b/modules/mod-playerbots/src/strategy/values/ItemUsageValue.cpp @@ -170,8 +170,38 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) pItem->RemoveFromUpdateQueueOf(bot); delete pItem; - if (result != EQUIP_ERR_OK) + if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) + { return ITEM_USAGE_NONE; + } + // Check is unique items are equipped or not + bool needToCheckUnique = false; + if (result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) + { + needToCheckUnique = true; + } + else if (itemProto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE) + { + needToCheckUnique = true; + } + + if (needToCheckUnique) + { + // Count the total number of the item (equipped + in bags) + uint32 totalItemCount = bot->GetItemCount(itemProto->ItemId, true); + + // Count the number of the item in bags only + uint32 bagItemCount = bot->GetItemCount(itemProto->ItemId, false); + + // Determine if the unique item is already equipped + bool isEquipped = (totalItemCount > bagItemCount); + + if (isEquipped) + { + return ITEM_USAGE_NONE; // Item is already equipped + } + // If not equipped, continue processing + } if (itemProto->Class == ITEM_CLASS_QUIVER) if (bot->getClass() != CLASS_HUNTER) @@ -205,102 +235,142 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) !sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), itemProto)) shouldEquip = false; - Item* oldItem = bot->GetItemByPos(dest); - - // No item equiped - if (!oldItem) + uint8 possibleSlots = 1; + uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); + // Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead + // This occurs with unique items that are already in the bots bags when CanEquipItem is called + if (dest == 0) { - if (shouldEquip) - return ITEM_USAGE_EQUIP; - else + if (dstSlot != NULL_SLOT) { - return ITEM_USAGE_BAD_EQUIP; + // Construct dest from dstSlot + dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot; } } - ItemTemplate const* oldItemProto = oldItem->GetTemplate(); - float oldScore = calculator.CalculateItem(oldItemProto->ItemId); - if (oldItem) + if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1) + { + possibleSlots = 2; + } + + // Check weapon case separately to keep things a bit cleaner + bool have2HWeapon = false; + bool isValidTGWeapon = false; + if (dstSlot == EQUIPMENT_SLOT_MAINHAND) { - // uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); - if (itemScore || oldScore) + Item* currentWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + have2HWeapon = currentWeapon && currentWeapon->GetTemplate()->InventoryType == INVTYPE_2HWEAPON; + isValidTGWeapon = itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || + itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2; + + if (bot->CanDualWield() && ((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon))) { - shouldEquip = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold; + possibleSlots = 2; } } - // Bigger quiver - if (itemProto->Class == ITEM_CLASS_QUIVER) + for (uint8 i = 0; i < possibleSlots; i++) { - if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots) + bool shouldEquipInSlot = shouldEquip; + Item* oldItem = bot->GetItemByPos(dest + i); + + // No item equipped + if (!oldItem) { - return ITEM_USAGE_EQUIP; + if (shouldEquipInSlot) + return ITEM_USAGE_EQUIP; + else + { + return ITEM_USAGE_BAD_EQUIP; + } } - else + + ItemTemplate const* oldItemProto = oldItem->GetTemplate(); + float oldScore = calculator.CalculateItem(oldItemProto->ItemId); + if (oldItem) { - return ITEM_USAGE_NONE; + // uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); + if (itemScore || oldScore) + { + shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold; + } } - } - bool existingShouldEquip = true; - if (oldItemProto->Class == ITEM_CLASS_WEAPON && !sRandomItemMgr->CanEquipWeapon(bot->getClass(), oldItemProto)) - existingShouldEquip = false; - - if (oldItemProto->Class == ITEM_CLASS_ARMOR && - !sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), oldItemProto)) - existingShouldEquip = false; - - // uint32 oldItemPower = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); - // uint32 newItemPower = sRandomItemMgr->GetLiveStatWeight(bot, itemProto->ItemId); - - // Compare items based on item level, quality or itemId. - bool isBetter = false; - if (itemScore > oldScore) - isBetter = true; - // else if (newItemPower == oldScore && itemProto->Quality > oldItemProto->Quality) - // isBetter = true; - // else if (newItemPower == oldScore && itemProto->Quality == oldItemProto->Quality && itemProto->ItemId > - // oldItemProto->ItemId) - // isBetter = true; - - Item* item = CurrentItem(itemProto); - bool itemIsBroken = - item && item->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0; - bool oldItemIsBroken = - oldItem->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && oldItem->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0; - - if (itemProto->ItemId != oldItemProto->ItemId && (shouldEquip || !existingShouldEquip) && isBetter) - { - switch (itemProto->Class) + // Bigger quiver + if (itemProto->Class == ITEM_CLASS_QUIVER) + { + if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots) + { + return ITEM_USAGE_EQUIP; + } + else + { + return ITEM_USAGE_NONE; + } + } + + bool existingShouldEquip = true; + if (oldItemProto->Class == ITEM_CLASS_WEAPON && !sRandomItemMgr->CanEquipWeapon(bot->getClass(), oldItemProto)) + existingShouldEquip = false; + + if (oldItemProto->Class == ITEM_CLASS_ARMOR && + !sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), oldItemProto)) + existingShouldEquip = false; + + // uint32 oldItemPower = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); + // uint32 newItemPower = sRandomItemMgr->GetLiveStatWeight(bot, itemProto->ItemId); + + // Compare items based on item level, quality or itemId. + bool isBetter = false; + if (itemScore > oldScore) + isBetter = true; + // else if (newItemPower == oldScore && itemProto->Quality > oldItemProto->Quality) + // isBetter = true; + // else if (newItemPower == oldScore && itemProto->Quality == oldItemProto->Quality && itemProto->ItemId > + // oldItemProto->ItemId) + // isBetter = true; + + Item* item = CurrentItem(itemProto); + bool itemIsBroken = + item && item->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0; + bool oldItemIsBroken = + oldItem->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && oldItem->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0; + + if (itemProto->ItemId != oldItemProto->ItemId && (shouldEquipInSlot || !existingShouldEquip) && isBetter) { - case ITEM_CLASS_ARMOR: - if (oldItemProto->SubClass <= itemProto->SubClass) + switch (itemProto->Class) + { + case ITEM_CLASS_ARMOR: + if (oldItemProto->SubClass <= itemProto->SubClass) + { + // Need to add some logic to check second slot before returning, but as it happens, all three of these + // return vals will result in an attempted equip action so it wouldn't have much effect currently + if (itemIsBroken && !oldItemIsBroken) + return ITEM_USAGE_BROKEN_EQUIP; + else if (shouldEquipInSlot) + return ITEM_USAGE_REPLACE; + else + return ITEM_USAGE_BAD_EQUIP; + + break; + } + default: { if (itemIsBroken && !oldItemIsBroken) return ITEM_USAGE_BROKEN_EQUIP; - else if (shouldEquip) - return ITEM_USAGE_REPLACE; + else if (shouldEquipInSlot) + return ITEM_USAGE_EQUIP; else return ITEM_USAGE_BAD_EQUIP; - - break; } - default: - { - if (itemIsBroken && !oldItemIsBroken) - return ITEM_USAGE_BROKEN_EQUIP; - else if (shouldEquip) - return ITEM_USAGE_EQUIP; - else - return ITEM_USAGE_BAD_EQUIP; } } - } - - // Item is not better but current item is broken and new one is not. - if (oldItemIsBroken && !itemIsBroken) - return ITEM_USAGE_EQUIP; + // Item is not better but current item is broken and new one is not. + if (oldItemIsBroken && !itemIsBroken) + return ITEM_USAGE_EQUIP; + } return ITEM_USAGE_NONE; }