diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 6da1c7a3..b644c6c9 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1263,11 +1263,6 @@ Playerbots.Updates.EnableDatabases = 1 # Command server port, 0 - disabled AiPlayerbot.CommandServerPort = 8888 -# Diff with/without player in server. The server will tune bot activity to reach the desired server tick speed (in ms).# PLAYERBOT SYSTEM SETTINGS # -AiPlayerbot.EnablePrototypePerformanceDiff = 0 -AiPlayerbot.DiffWithPlayer = 100 -AiPlayerbot.DiffEmpty = 200 - # # # @@ -1462,9 +1457,18 @@ AiPlayerbot.RandombotsWalkingRPG.InDoors = 0 # The default is 10. With 10% of all bots going active or inactive each minute. AiPlayerbot.BotActiveAlone = 100 -# Specify 1 for enabled, 0 for disabled. -# The default is 1. Automatically adjusts 'BotActiveAlone' percentage based on server latency. -AiPlayerbot.botActiveAloneAutoScale = 1 +# Specify smart scaling is enabled or not. +# The default is 1. When enabled (smart) scales the 'BotActiveAlone' value. +AiPlayerbot.botActiveAloneSmartScale = 1 + +# Only when botLevel is between WhenMinLevel and WhenMaxLevel. +AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1 +AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80 + +# The server will tune bot activity to reach the desired server tick speed (in ms) +# bots will only join battleground when there is no lag based on latency diffs below +AiPlayerbot.botActiveAloneSmartScaleDiffWithPlayer = 100 +AiPlayerbot.botActiveAloneSmartScaleDiffEmpty = 200 # Premade spell to avoid (undetected spells) # spellid-radius, ... diff --git a/sql/playerbots/base/playerbots_text.sql b/sql/playerbots/base/ai_playerbot_texts.sql similarity index 99% rename from sql/playerbots/base/playerbots_text.sql rename to sql/playerbots/base/ai_playerbot_texts.sql index 881e4671..e1c49907 100644 --- a/sql/playerbots/base/playerbots_text.sql +++ b/sql/playerbots/base/ai_playerbot_texts.sql @@ -464,7 +464,7 @@ INSERT INTO `ai_playerbot_texts` (`name`, `text`, `say_type`, `reply_type`, `tex -- %my_level ('suggest_something', 'Wanna party in %zone_name.', 0, 0, '', 'Je veux faire la fête dans %zone_name.', '', '', '', '¡Vamos a perrear a %zone_name!', '', 'Ищу группу в %zone_name.'), ('suggest_something', 'Anyone is looking for %my_role?', 0, 0, '', 'Quelqu\'un cherche un %my_role ?', '', '', '', '¿Alguien está buscando %my_role?', '', 'Кто-нибудь ищет %my_role?'), -('suggest_something', '%my_role is looking for quild.', 0, 0, '', '%my_role recherche une guilde.', '', '', '', '%my_role está buscando hermandad.', '', '%my_role ищу гильдию.'), +('suggest_something', '%my_role is looking for guild.', 0, 0, '', '%my_role recherche une guilde.', '', '', '', '%my_role está buscando hermandad.', '', '%my_role ищу гильдию.'), ('suggest_something', 'Looking for gold.', 0, 0, '', 'A la recherche de l\'or.', '', '', '', 'Buscando oro.', '', 'Дайте голды'), ('suggest_something', '%my_role wants to join a good guild.', 0, 0, '', '%my_role veut rejoindre une bonne guilde.', '', '', '', '%my_role quiere unirse a una buen hermandad.', '', '%my_role хочу в хорошую гильдию.'), ('suggest_something', 'Need a friend.', 0, 0, '', 'Besoin d\'un ami.', '', '', '', 'Necesito un amigo...', '', 'Ищу друга.'), @@ -1457,19 +1457,3 @@ INSERT INTO `ai_playerbot_texts` (`name`, `text`, `say_type`, `reply_type`, `tex ('dummy_end', 'dummy', 0, 0, '', '', '', '', '', '', '', ''); - -DROP TABLE IF EXISTS `ai_playerbot_texts_chance`; - -CREATE TABLE IF NOT EXISTS `ai_playerbot_texts_chance` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL, - `probability` bigint(20) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=UTF8; - -/*!40000 ALTER TABLE `ai_playerbot_texts_chance` DISABLE KEYS */; -INSERT INTO `ai_playerbot_texts_chance` (`id`, `name`, `probability`) VALUES - (1, 'taunt', 30), - (2, 'aoe', 75), - (3, 'loot', 20); -/*!40000 ALTER TABLE `ai_playerbot_texts_chance` ENABLE KEYS */; \ No newline at end of file diff --git a/sql/playerbots/base/ai_playerbot_texts_chance.sql b/sql/playerbots/base/ai_playerbot_texts_chance.sql new file mode 100644 index 00000000..81218dba --- /dev/null +++ b/sql/playerbots/base/ai_playerbot_texts_chance.sql @@ -0,0 +1,14 @@ +DROP TABLE IF EXISTS `ai_playerbot_texts_chance`; +CREATE TABLE IF NOT EXISTS `ai_playerbot_texts_chance` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `probability` bigint(20) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=UTF8; + +/*!40000 ALTER TABLE `ai_playerbot_texts_chance` DISABLE KEYS */; +INSERT INTO `ai_playerbot_texts_chance` (`id`, `name`, `probability`) VALUES + (1, 'taunt', 30), + (2, 'aoe', 75), + (3, 'loot', 20); +/*!40000 ALTER TABLE `ai_playerbot_texts_chance` ENABLE KEYS */; diff --git a/src/ChatHelper.cpp b/src/ChatHelper.cpp index 8f464d5d..7bd8e590 100644 --- a/src/ChatHelper.cpp +++ b/src/ChatHelper.cpp @@ -304,9 +304,13 @@ ItemIds ChatHelper::parseItems(std::string const text) std::string const ChatHelper::FormatQuest(Quest const* quest) { + if (!quest) + { + return "Invalid quest"; + } + std::ostringstream out; - out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << quest->GetTitle() - << "]|h|r"; + out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << quest->GetTitle() << "]|h|r"; return out.str(); } diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 68512241..023ec7b6 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -239,6 +239,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) { return; } + // if (!GetMaster() || !GetMaster()->IsInWorld() || !GetMaster()->GetSession() || // GetMaster()->GetSession()->isLogingOut()) { // return; @@ -301,6 +302,7 @@ 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)) { @@ -3926,7 +3928,10 @@ Player* PlayerbotAI::GetGroupMaster() uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, float cyclePerMin) { - uint32 randseed = rand32(); // Seed random number + //deterministic seed + uint8 seedNumber = uint8(typeNumber); + std::mt19937 rng(seedNumber); + uint32 randseed = rng(); // Seed random number uint32 randnum = bot->GetGUID().GetCounter() + randseed; // Semi-random but fixed number for each bot. if (cyclePerMin > 0) @@ -3936,8 +3941,7 @@ 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. } @@ -4075,18 +4079,15 @@ inline bool HasRealPlayers(Map* map) return false; } -bool PlayerbotAI::AllowActive(ActivityType activityType) +ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) { - // General exceptions - if (activityType == PACKET_ACTIVITY) - return true; + // First priority - priorities disabled or has player master. Always active. + if (HasRealPlayerMaster()) + return ActivePiorityType::HAS_REAL_PLAYER_MASTER; - if (GetMaster()) // Has player master. Always active. - { - PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(GetMaster()); - if (!masterBotAI || masterBotAI->IsRealPlayer()) - return true; - } + // Self bot in a group with a bot master. + if (IsRealPlayer()) + return ActivePiorityType::IS_REAL_PLAYER; Group* group = bot->GetGroup(); if (group) @@ -4100,21 +4101,49 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) if (member == bot) continue; + //IN_GROUP_WITH_REAL_PLAYER PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member); if (!memberBotAI || memberBotAI->HasRealPlayerMaster()) - return true; + return ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER; + //IN_GROUP_WITH_REAL_PLAYER if (group->IsLeader(member->GetGUID())) + { if (!memberBotAI->AllowActivity(PARTY_ACTIVITY)) - return false; + return ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER; + } } } - if (!WorldPosition(bot).isOverworld()) // bg, raid, dungeon - return true; + //IN_INSTANCE + if (bot->IsBeingTeleported()) // Allow activity while teleportation. + return ActivePiorityType::IN_INSTANCE; - if (bot->InBattlegroundQueue()) // In bg queue. Speed up bg queue/join. - return true; + //IN_INSTANCE + if (!WorldPosition(bot).isOverworld()) + return ActivePiorityType::IN_INSTANCE; + + //VISIBLE_FOR_PLAYER + if (HasPlayerNearby(sPlayerbotAIConfig->reactDistance)) + return ActivePiorityType::VISIBLE_FOR_PLAYER; + + //IN_COMBAT + if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY) + { + // Is in combat, defend yourself. + if (bot->IsInCombat()) + return ActivePiorityType::IN_COMBAT; + } + + //NEARBY_PLAYER + if (HasPlayerNearby(300.f)) + return ActivePiorityType::NEARBY_PLAYER; + + //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) @@ -4124,62 +4153,172 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) isLFG = true; } } - if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE) { isLFG = true; } - if (isLFG) - return true; + return ActivePiorityType::IN_LFG; - if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY) // Is in combat. Defend yourself. - if (bot->IsInCombat()) - return true; + //IN_EMPTY_SERVER + if (sRandomPlayerbotMgr->GetPlayers().empty()) + return ActivePiorityType::IN_EMPTY_SERVER; - if (HasPlayerNearby(300.f)) // Player is near. Always active. - return true; - - // friends always active - - // HasFriend sometimes cause crash, disable - // for (auto& player : sRandomPlayerbotMgr->GetPlayers()) - // { - // if (!player || !player->IsInWorld()) - // continue; - - // if (player->GetSocial()->HasFriend(bot->GetGUID())) - // return true; - // } - - if (activityType == OUT_OF_PARTY_ACTIVITY || - activityType == GRIND_ACTIVITY) // Many bots nearby. Do not do heavy area checks. - if (HasManyPlayersNearby()) - return false; - - // Bots don't need to move using PathGenerator. - if (activityType == DETAILED_MOVE_ACTIVITY) - return false; - - // All exceptions are now done. - // 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. - if (sPlayerbotAIConfig->botActiveAlone <= 0) - return false; - - uint32 mod = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone; - if (sPlayerbotAIConfig->botActiveAloneAutoScale) + //PLAYER_FRIEND (on friendlist of real player) (needs to be tested for stability) + /*for (auto& player : sRandomPlayerbotMgr->GetPlayers()) { - mod = AutoScaleActivity(mod); + 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; + + //IN_INACTIVE_MAP + if (bot->IsBeingTeleported() || !bot->IsInWorld() || !HasRealPlayers(bot->GetMap())) + return ActivePiorityType::IN_INACTIVE_MAP; + + //IN_ACTIVE_MAP + if (!bot->IsBeingTeleported() && bot->IsInWorld() && HasRealPlayers(bot->GetMap())) + return ActivePiorityType::IN_ACTIVE_MAP; + + // IN_ACTIVE_AREA + if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY) + { + if (HasManyPlayersNearby(20, sPlayerbotAIConfig->sightDistance)) + return ActivePiorityType::IN_ACTIVE_AREA; } - uint32 ActivityNumber = - GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, - sPlayerbotAIConfig->botActiveAlone * static_cast(mod) / 100 * 0.01f); + return ActivePiorityType::IN_ACTIVE_AREA; +} - return ActivityNumber <= - (sPlayerbotAIConfig->botActiveAlone * mod) / - 100; // The given percentage of bots should be active and rotate 1% of those active bots each minute. +// Returns the lower and upper bracket for bots to be active. +// Ie. { 10, 20 } means all bots in this bracket will be inactive below 10% activityMod, +// and will be active above 20% activityMod and scale between those values. +std::pair PlayerbotAI::GetPriorityBracket(ActivePiorityType type) +{ + switch (type) + { + case ActivePiorityType::HAS_REAL_PLAYER_MASTER: + case ActivePiorityType::IS_REAL_PLAYER: + case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER: + case ActivePiorityType::IN_INSTANCE: + case ActivePiorityType::VISIBLE_FOR_PLAYER: + return {0, 0}; + case ActivePiorityType::IS_ALWAYS_ACTIVE: + case ActivePiorityType::IN_COMBAT: + return {0, 10}; + case ActivePiorityType::IN_BG_QUEUE: + return {0, 20}; + case ActivePiorityType::IN_LFG: + return {0, 30}; + case ActivePiorityType::NEARBY_PLAYER: + return {0, 40}; + case ActivePiorityType::PLAYER_FRIEND: + case ActivePiorityType::PLAYER_GUILD: + return {0, 50}; + case ActivePiorityType::IN_ACTIVE_AREA: + case ActivePiorityType::IN_EMPTY_SERVER: + return {50, 100}; + case ActivePiorityType::IN_ACTIVE_MAP: + return {70, 100}; + case ActivePiorityType::IN_INACTIVE_MAP: + return {80, 100}; + default: + return {90, 100}; + } + + return {90, 100}; +} + +bool PlayerbotAI::AllowActive(ActivityType activityType) +{ + // General exceptions + if (activityType == PACKET_ACTIVITY) + return true; + + ActivePiorityType type = GetPriorityType(activityType); + if (activityType == DETAILED_MOVE_ACTIVITY) + { + switch (type) + { + case ActivePiorityType::HAS_REAL_PLAYER_MASTER: + case ActivePiorityType::IS_REAL_PLAYER: + case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER: + case ActivePiorityType::IN_INSTANCE: + case ActivePiorityType::VISIBLE_FOR_PLAYER: + case ActivePiorityType::IN_COMBAT: + case ActivePiorityType::NEARBY_PLAYER: + return true; + break; + case ActivePiorityType::IS_ALWAYS_ACTIVE: + case ActivePiorityType::IN_BG_QUEUE: + case ActivePiorityType::IN_LFG: + case ActivePiorityType::PLAYER_FRIEND: + case ActivePiorityType::PLAYER_GUILD: + case ActivePiorityType::IN_ACTIVE_AREA: + case ActivePiorityType::IN_EMPTY_SERVER: + case ActivePiorityType::IN_ACTIVE_MAP: + case ActivePiorityType::IN_INACTIVE_MAP: + default: + break; + } + } + else if (activityType == REACT_ACTIVITY) + { + switch (type) + { + case ActivePiorityType::HAS_REAL_PLAYER_MASTER: + case ActivePiorityType::IS_REAL_PLAYER: + case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER: + case ActivePiorityType::IN_INSTANCE: + case ActivePiorityType::VISIBLE_FOR_PLAYER: + case ActivePiorityType::IS_ALWAYS_ACTIVE: + case ActivePiorityType::IN_COMBAT: + return true; + break; + case ActivePiorityType::NEARBY_PLAYER: + case ActivePiorityType::IN_BG_QUEUE: + case ActivePiorityType::IN_LFG: + case ActivePiorityType::PLAYER_FRIEND: + case ActivePiorityType::PLAYER_GUILD: + case ActivePiorityType::IN_ACTIVE_AREA: + case ActivePiorityType::IN_EMPTY_SERVER: + case ActivePiorityType::IN_ACTIVE_MAP: + case ActivePiorityType::IN_INACTIVE_MAP: + default: + return false; + break; + } + } + + // GetPriorityBracket acitivity + float normalizedBotActiveAlone = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone; + float activePerc = normalizedBotActiveAlone; + if (sPlayerbotAIConfig->botActiveAloneSmartScale && + bot->GetLevel() >= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMinLevel && + bot->GetLevel() <= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMaxLevel) + { + std::pair priorityBracket = GetPriorityBracket(type); + if (!priorityBracket.second) return true; + float activityPercentage = sRandomPlayerbotMgr->getActivityPercentage(); + if (priorityBracket.first >= activityPercentage) return false; + if (priorityBracket.second <= activityPercentage && priorityBracket.second < 100) return true; + activePerc = (activityPercentage - priorityBracket.first) / (priorityBracket.second - priorityBracket.first); + activePerc *= (priorityBracket.second == 100) ? normalizedBotActiveAlone : 100; + } + + // The last number if the amount it cycles per min. Currently set to 1% of the active bots. + uint32 ActivityNumber = GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, activePerc * 0.01f); + + // The given percentage of bots should be active and rotate 1% of those active bots each minute. + return ActivityNumber <= (activePerc); } bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) @@ -4196,31 +4335,6 @@ bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) return allowed; } -uint32 PlayerbotAI::AutoScaleActivity(uint32 mod) -{ - uint32 maxDiff = sWorldUpdateTime.GetAverageUpdateTime(); - - if (maxDiff > 500) return 0; - if (maxDiff > 250) - { - if (Map* map = bot->GetMap()) - { - if (map->GetEntry()->IsWorldMap() && - (!HasRealPlayers(map) || - !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 > 80) return (mod * 9) / 10; - - return mod; -} - bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); } bool PlayerbotAI::IsOpposing(uint8 race1, uint8 race2) @@ -5328,15 +5442,29 @@ 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/src/PlayerbotAI.h b/src/PlayerbotAI.h index 6e82338b..d1a2f292 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -241,6 +241,27 @@ 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, + VISIBLE_FOR_PLAYER = 4, + IS_ALWAYS_ACTIVE = 5, + IN_COMBAT = 6, + IN_BG_QUEUE = 7, + IN_LFG = 8, + NEARBY_PLAYER = 9, + PLAYER_FRIEND = 10, + PLAYER_GUILD = 11, + IN_ACTIVE_AREA = 12, + IN_ACTIVE_MAP = 13, + IN_INACTIVE_MAP = 14, + IN_EMPTY_SERVER = 15, + MAX_TYPE +}; + enum ActivityType { GRIND_ACTIVITY = 1, @@ -250,8 +271,8 @@ enum ActivityType PACKET_ACTIVITY = 5, DETAILED_MOVE_ACTIVITY = 6, PARTY_ACTIVITY = 7, - ALL_ACTIVITY = 8, - + REACT_ACTIVITY = 8, + ALL_ACTIVITY = 9, MAX_ACTIVITY_TYPE }; @@ -525,9 +546,10 @@ public: 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); + std::pair GetPriorityBracket(ActivePiorityType type); bool AllowActive(ActivityType activityType); bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false); - uint32 AutoScaleActivity(uint32 mod); // Check if player is safe to use. bool IsSafe(Player* player); @@ -554,6 +576,7 @@ public: void ResetJumpDestination() { jumpDestination = Position(); } bool CanMove(); + bool IsTaxiFlying(); bool IsInRealGuild(); static std::vector dispel_whitelist; bool EqualLowercaseName(std::string s1, std::string s2); diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 10c680ae..816a345f 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -465,11 +465,15 @@ bool PlayerbotAIConfig::Initialize() playerbotsXPrate = sConfigMgr->GetOption("AiPlayerbot.KillXPRate", 1); disableDeathKnightLogin = sConfigMgr->GetOption("AiPlayerbot.DisableDeathKnightLogin", 0); botActiveAlone = sConfigMgr->GetOption("AiPlayerbot.BotActiveAlone", 100); - botActiveAloneAutoScale = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneAutoScale", true); - - enablePrototypePerformanceDiff = sConfigMgr->GetOption("AiPlayerbot.EnablePrototypePerformanceDiff", false); - diffWithPlayer = sConfigMgr->GetOption("AiPlayerbot.DiffWithPlayer", 100); - diffEmpty = sConfigMgr->GetOption("AiPlayerbot.DiffEmpty", 200); + botActiveAloneSmartScale = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScale", 1); + botActiveAloneSmartScaleWhenMinLevel = + sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1); + botActiveAloneSmartScaleWhenMaxLevel = + sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel", 80); + botActiveAloneSmartScaleDiffWithPlayer = + sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleDiffWithPlayer", 100); + botActiveAloneSmartScaleDiffEmpty = + sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleDiffEmpty", 200); randombotsWalkingRPG = sConfigMgr->GetOption("AiPlayerbot.RandombotsWalkingRPG", false); randombotsWalkingRPGInDoors = sConfigMgr->GetOption("AiPlayerbot.RandombotsWalkingRPG.InDoors", false); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index f8e3fd82..5ac3fa4a 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -263,11 +263,11 @@ public: uint32 playerbotsXPrate; bool disableDeathKnightLogin; uint32 botActiveAlone; - bool botActiveAloneAutoScale; - - uint32 enablePrototypePerformanceDiff; - uint32 diffWithPlayer; - uint32 diffEmpty; + bool botActiveAloneSmartScale; + uint32 botActiveAloneSmartScaleWhenMinLevel; + uint32 botActiveAloneSmartScaleWhenMaxLevel; + uint32 botActiveAloneSmartScaleDiffWithPlayer; + uint32 botActiveAloneSmartScaleDiffEmpty; bool freeMethodLoot; int32 lootRollLevel; diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 34e107d1..3cae1645 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -100,6 +100,8 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con Player* bot = botSession->GetPlayer(); if (!bot) { + // Log para debug + LOG_ERROR("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId); botSession->LogoutPlayer(true); delete botSession; botLoading.erase(holder.GetGuid()); @@ -108,6 +110,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con uint32 masterAccount = holder.GetMasterAccountId(); WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : nullptr; + + // Check if masterSession->GetPlayer() is valid + Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr; + if (masterSession && !masterPlayer) + { + LOG_ERROR("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount); + } + std::ostringstream out; bool allowed = false; if (botAccountId == masterAccount) @@ -115,7 +125,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con allowed = true; } else if (masterSession && sPlayerbotAIConfig->allowGuildBots && bot->GetGuildId() != 0 && - bot->GetGuildId() == masterSession->GetPlayer()->GetGuildId()) + bot->GetGuildId() == masterPlayer->GetGuildId()) { allowed = true; } @@ -129,10 +139,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con out << "Failure: You are not allowed to control bot " << bot->GetName().c_str(); } - if (allowed && masterSession) + if (allowed && masterSession && masterPlayer) { - Player* player = masterSession->GetPlayer(); - PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(player); + PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer); + if (!mgr) + { + LOG_ERROR("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue()); + } + uint32 count = mgr->GetPlayerbotsCount(); uint32 cls_count = mgr->GetPlayerbotsCountByClass(bot->getClass()); if (count >= sPlayerbotAIConfig->maxAddedBots) @@ -428,14 +442,17 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (!botAI) { + // Log a warning here to indicate that the botAI is null + LOG_ERROR("mod-playerbots", "PlayerbotAI is null for bot with GUID: {}", bot->GetGUID().GetRawValue()); return; } + Player* master = botAI->GetMaster(); - if (master) + if (!master) { - ObjectGuid masterGuid = master->GetGUID(); - if (master->GetGroup() && !master->GetGroup()->IsLeader(masterGuid)) - master->GetGroup()->ChangeLeader(masterGuid); + // Log a warning to indicate that the master is null + LOG_ERROR("mod-playerbots", "Master is null for bot with GUID: {}", bot->GetGUID().GetRawValue()); + return; } Group* group = bot->GetGroup(); diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 7d26a096..37acca8d 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -292,12 +292,8 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) if (!sPlayerbotAIConfig->randomBotAutologin || !sPlayerbotAIConfig->enabled) return; - if (sPlayerbotAIConfig->enablePrototypePerformanceDiff) + if (sPlayerbotAIConfig->botActiveAloneSmartScale) { - LOG_INFO("playerbots", "---------------------------------------"); - LOG_INFO("playerbots", - "PROTOTYPE: Playerbot performance enhancements are active. Issues and instability may occur."); - LOG_INFO("playerbots", "---------------------------------------"); ScaleBotActivity(); } @@ -414,8 +410,9 @@ void RandomPlayerbotMgr::ScaleBotActivity() // max/min activity // % increase/decrease wanted diff , avg diff - float activityPercentageMod = pid.calculate( - sRandomPlayerbotMgr->GetPlayers().empty() ? sPlayerbotAIConfig->diffEmpty : sPlayerbotAIConfig->diffWithPlayer, + float activityPercentageMod = pid.calculate(sRandomPlayerbotMgr->GetPlayers().empty() ? + sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty : + sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer, sWorldUpdateTime.GetAverageUpdateTime()); activityPercentage = activityPercentageMod + 50; @@ -1108,6 +1105,9 @@ 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()) @@ -1262,9 +1262,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& if (botAI) { // ignore when in when taxi with boat/zeppelin and has players nearby - if (bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && - bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING) && - botAI->HasPlayerNearby()) + if (botAI->IsTaxiFlying() && botAI->HasPlayerNearby()) return; } diff --git a/src/strategy/actions/BattleGroundJoinAction.cpp b/src/strategy/actions/BattleGroundJoinAction.cpp index e6842b23..349ae410 100644 --- a/src/strategy/actions/BattleGroundJoinAction.cpp +++ b/src/strategy/actions/BattleGroundJoinAction.cpp @@ -234,16 +234,15 @@ bool BGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battlegroun return false; TeamId teamId = bot->GetTeamId(); - bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() - ? sPlayerbotAIConfig->diffEmpty - : sPlayerbotAIConfig->diffWithPlayer) * - 1.1; + bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() ? + sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty : + sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer) * 1.1; uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2; uint32 TeamSize = bg->GetMaxPlayersPerTeam(); // If performance diff is enabled, only queue if there is no lag - if (sPlayerbotAIConfig->enablePrototypePerformanceDiff && !noLag) + if (sPlayerbotAIConfig->botActiveAloneSmartScale && !noLag) return false; // If the bot is in a group, only the leader can queue @@ -578,16 +577,15 @@ bool FreeBGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battleg return false; TeamId teamId = bot->GetTeamId(); - bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() - ? sPlayerbotAIConfig->diffEmpty - : sPlayerbotAIConfig->diffWithPlayer) * - 1.1; + bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() ? + sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty : + sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer) * 1.1; uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2; uint32 TeamSize = bg->GetMaxPlayersPerTeam(); // If performance diff is enabled, only queue if there is no lag - if (sPlayerbotAIConfig->enablePrototypePerformanceDiff && !noLag) + if (sPlayerbotAIConfig->botActiveAloneSmartScale && !noLag) return false; // If the bot is in a group, only the leader can queue diff --git a/src/strategy/actions/DropQuestAction.cpp b/src/strategy/actions/DropQuestAction.cpp index a1e2ffcb..910e5498 100644 --- a/src/strategy/actions/DropQuestAction.cpp +++ b/src/strategy/actions/DropQuestAction.cpp @@ -58,55 +58,92 @@ bool DropQuestAction::Execute(Event event) return true; } + bool CleanQuestLogAction::Execute(Event event) { Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); - std::string link = event.getParam(); - if (botAI->HasActivePlayerMaster() || !sRandomPlayerbotMgr->IsRandomBot(bot)) + if (!requester) + { + botAI->TellMaster("No event owner detected"); return false; - - uint8 totalQuests = 0; - // Count the total quests - DropQuestType(totalQuests); - if (MAX_QUEST_LOG_SIZE - totalQuests > 6) - { - // Drop failed quests - DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE, true, true); - return true; } - // Only drop gray quests when able to fight proper lvl quests. - if (AI_VALUE(bool, "can fight equal")) + // Only output this message if "debug rpg" strategy is enabled + if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) { - // Drop gray/red quests. - DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 6); - // Drop gray/red quests with progress. - DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 6, false, true); - // Drop gray/red completed quests. - DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 6, false, true, true); + botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests..."); } - if (MAX_QUEST_LOG_SIZE - totalQuests > 4) - return true; + uint8 botLevel = bot->GetLevel(); // Get bot's level + uint8 numQuest = 0; + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + if (bot->GetQuestSlotQuestId(slot)) + { + numQuest++; + } + } - DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 4, true); // Drop quests without progress. + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 questId = bot->GetQuestSlotQuestId(slot); + if (!questId) + continue; - if (MAX_QUEST_LOG_SIZE - totalQuests > 2) - return true; + const Quest* quest = sObjectMgr->GetQuestTemplate(questId); + if (!quest) + continue; - DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 2, true, true); // Drop quests with progress. + // Determine if quest is trivial by comparing levels + int32 questLevel = quest->GetQuestLevel(); + if (questLevel == -1) // For scaling quests, default to bot level + { + questLevel = botLevel; + } - if (MAX_QUEST_LOG_SIZE - totalQuests > 0) - return true; + // Check if the quest is trivial (grey) for the bot + if ((botLevel - questLevel) >= 5) + { + // Output only if "debug rpg" strategy is enabled + if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) + { + botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey)."); + } - DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 1, true, true, true); // Drop completed quests. + // Remove quest + bot->SetQuestSlot(slot, 0); + bot->TakeQuestSourceItem(questId, false); + bot->SetQuestStatus(questId, QUEST_STATUS_NONE); + bot->RemoveRewardedQuest(questId); - if (MAX_QUEST_LOG_SIZE - totalQuests > 0) - return true; + numQuest--; - return false; + if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) + { + const std::string text_quest = ChatHelper::FormatQuest(quest); + LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle()); + bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL); + } + + if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) + { + botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed."); + } + } + else + { + // Only output if "debug rpg" strategy is enabled + if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) + { + botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept."); + } + } + } + + return true; } + void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isGreen, bool hasProgress, bool isComplete) { std::vector slots; diff --git a/src/strategy/actions/LeaveGroupAction.cpp b/src/strategy/actions/LeaveGroupAction.cpp index 39d0451d..b2594e0f 100644 --- a/src/strategy/actions/LeaveGroupAction.cpp +++ b/src/strategy/actions/LeaveGroupAction.cpp @@ -30,6 +30,8 @@ bool PartyCommandAction::Execute(Event event) Player* master = GetMaster(); if (master && member == master->GetName()) return Leave(bot); + + botAI->Reset(); return false; } @@ -62,6 +64,8 @@ bool UninviteAction::Execute(Event event) if (bot->GetGUID() == guid) return Leave(bot); } + + botAI->Reset(); return false; } @@ -160,6 +164,8 @@ bool LeaveFarAwayAction::isUseful() { return true; } + + botAI->Reset(); return false; } diff --git a/src/strategy/actions/RpgAction.cpp b/src/strategy/actions/RpgAction.cpp index c300221c..f5ffa130 100644 --- a/src/strategy/actions/RpgAction.cpp +++ b/src/strategy/actions/RpgAction.cpp @@ -79,7 +79,7 @@ bool RpgAction::SetNextRpgAction() { NextAction* nextAction = nextActions[i]; - if (nextAction->getRelevance() > 2.0f) + if (nextAction->getRelevance() > 5.0f) continue; if (!isChecked && !trigger->IsActive()) @@ -92,7 +92,7 @@ bool RpgAction::SetNextRpgAction() continue; actions.push_back(action); - relevances.push_back((nextAction->getRelevance() - 1) * 1000); + relevances.push_back((nextAction->getRelevance() - 1) * 500); } NextAction::destroy(nextActions); }