From dc55bdd8dc3764826ac89db5fa3c58dbe6dd800f Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Fri, 1 Nov 2024 22:03:04 +0100 Subject: [PATCH 1/7] disabled smartscale by default for now (#663) * disabled smartscale by default for now * disabled smartscale by default for now --- conf/playerbots.conf.dist | 2 +- src/PlayerbotAIConfig.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 8d7bf12e..884ab9f1 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1460,7 +1460,7 @@ 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 = 1 +AiPlayerbot.botActiveAloneSmartScale = 0 AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1 AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80 diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 7bf16079..6c721024 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -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", 1); + botActiveAloneSmartScale = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScale", 0); botActiveAloneSmartScaleWhenMinLevel = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1); botActiveAloneSmartScaleWhenMaxLevel = From 5c16e14d576e5f85a82f1d2eb82768e0456c14cb Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Sun, 3 Nov 2024 12:15:03 +0000 Subject: [PATCH 2/7] Fix Crash SayToChannel (#666) Additional checks for null pointers to ensure that the Channel and ChannelMgr objects are correctly initialized before accessing them. Validation of empty strings to avoid problems when checking the channel name (channel->GetName()). Implemented mutex (std::lock_guard) around access to SocialMgr to avoid race conditions, improving security in multi-threaded operations. --- src/PlayerbotAI.cpp | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 3c515ebb..b9c7ff60 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "AiFactory.h" #include "BudgetValues.h" @@ -2452,8 +2453,12 @@ bool PlayerbotAI::SayToWorld(const std::string& msg) bool PlayerbotAI::SayToChannel(const std::string& msg, const ChatChannelId& chanId) { + // Checks whether the message or ChannelMgr is valid + if (msg.empty()) + return false; + ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId()); - if (!cMgr || msg.empty()) + if (!cMgr) return false; AreaTableEntry const* current_zone = GetCurrentZone(); @@ -2461,11 +2466,24 @@ bool PlayerbotAI::SayToChannel(const std::string& msg, const ChatChannelId& chan return false; const auto current_str_zone = GetLocalizedAreaName(current_zone); + + std::mutex socialMutex; + std::lock_guard lock(socialMutex); // Blocking for thread safety when accessing SocialMgr + for (auto const& [key, channel] : cMgr->GetChannels()) { - // check for current zone - if (channel && channel->GetChannelId() == chanId) + // Checks if the channel pointer is valid + if (!channel) + continue; + + // Checks if the channel matches the specified ChatChannelId + if (channel->GetChannelId() == chanId) { + // If the channel name is empty, skip it to avoid access problems + if (channel->GetName().empty()) + continue; + + // Checks if the channel name contains the current zone const auto does_contains = channel->GetName().find(current_str_zone) != std::string::npos; if (chanId != ChatChannelId::LOOKING_FOR_GROUP && chanId != ChatChannelId::WORLD_DEFENSE && !does_contains) { @@ -2473,11 +2491,15 @@ bool PlayerbotAI::SayToChannel(const std::string& msg, const ChatChannelId& chan } else if (chanId == ChatChannelId::LOOKING_FOR_GROUP || chanId == ChatChannelId::WORLD_DEFENSE) { - // check if capitals then return false if not + // Here you can add the capital check if necessary } - channel->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL); - return true; + // Final check to ensure the channel is correct before trying to say something + if (channel) + { + channel->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL); + return true; + } } } From 7e74fbe1811340c2679eea2577ef17a90c927042 Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Sun, 3 Nov 2024 12:15:16 +0000 Subject: [PATCH 3/7] Fix Crash QuestAction (#665) Added checks to ensure bot and botAI are valid at function start. Added extra pointer checks when accessing unit and gameobj in loops that iterate over nearest NPCs and game objects. This adjustment ensures that objects are initialized correctly before being used in the ProcessQuests function. --- src/strategy/actions/QuestAction.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/strategy/actions/QuestAction.cpp b/src/strategy/actions/QuestAction.cpp index 139033f1..13ed7bba 100644 --- a/src/strategy/actions/QuestAction.cpp +++ b/src/strategy/actions/QuestAction.cpp @@ -15,9 +15,13 @@ bool QuestAction::Execute(Event event) { ObjectGuid guid = event.getObject(); - Player* master = GetMaster(); + // Checks if the bot and botAI are valid + if (!bot || !botAI) + return false; + + // Sets guid based on bot or master target if (!guid) { if (!master) @@ -36,19 +40,27 @@ bool QuestAction::Execute(Event event) } bool result = false; + + // Check the nearest NPCs GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs"); - for (const auto npc : npcs) + for (const auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); if (unit && bot->GetDistance(unit) <= INTERACTION_DISTANCE) + { result |= ProcessQuests(unit); + } } + + // Checks the nearest game objects std::list gos = AI_VALUE(std::list, "nearest game objects"); - for (const auto go : gos) + for (const auto& go : gos) { GameObject* gameobj = botAI->GetGameObject(go); if (gameobj && bot->GetDistance(gameobj) <= INTERACTION_DISTANCE) + { result |= ProcessQuests(gameobj); + } } return result; From 2f0f8ad496fc522c1575d8f89f527f415326780d Mon Sep 17 00:00:00 2001 From: Yunfan Li <56597220+liyunfan1223@users.noreply.github.com> Date: Sun, 3 Nov 2024 23:21:37 +0800 Subject: [PATCH 4/7] Set _isBotInitializing if randomBotAutologin=0 (#670) --- src/RandomPlayerbotMgr.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index ca9049ec..a0446707 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -164,7 +164,10 @@ RandomPlayerbotMgr::RandomPlayerbotMgr() : PlayerbotHolder(), processTicks(0) sPlayerbotCommandServer->Start(); PrepareTeleportCache(); } - + if (!sPlayerbotAIConfig->randomBotAutologin) + { + setBotInitializing(false); + } BattlegroundData.clear(); BgCheckTimer = 0; From a7bcdad65c9b7d5bb0ca1d24963886f60aac5fd2 Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:05:05 +0100 Subject: [PATCH 5/7] [performance] bots scale idle fix (#678) * [performance] bots scale idle fix * [performance] featureset --- conf/playerbots.conf.dist | 2 +- src/PlayerbotAI.cpp | 345 ++++++++++++++++--------------------- src/PlayerbotAI.h | 33 +--- src/PlayerbotAIConfig.cpp | 2 +- src/Playerbots.cpp | 11 ++ src/RandomPlayerbotMgr.cpp | 71 +++++--- src/RandomPlayerbotMgr.h | 10 +- 7 files changed, 215 insertions(+), 259 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 884ab9f1..4a42ed8e 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -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 diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index b9c7ff60..f804e975 100644 --- a/src/PlayerbotAI.cpp +++ b/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,73 +4098,83 @@ inline bool HasRealPlayers(Map* map) return false; } -ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) +bool PlayerbotAI::AllowActive(ActivityType activityType) { - // First priority - priorities disabled or has player master. Always active. - if (HasRealPlayerMaster()) - return ActivePiorityType::HAS_REAL_PLAYER_MASTER; + // only keep updating till initializing time has completed, + // which prevents unneeded expensive GameTime calls. + if (_isBotInitializing) + { + _isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.12; - // Self bot in a group with a bot master. - if (IsRealPlayer()) - return ActivePiorityType::IS_REAL_PLAYER; + // no activity allowed during bot initialization + if (_isBotInitializing) + { + return false; + } + } + // General exceptions + if (activityType == PACKET_ACTIVITY) + { + 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 + // bg, raid, dungeon if (!WorldPosition(bot).isOverworld()) - return ActivePiorityType::IN_INSTANCE; - - //IN_COMBAT - if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY) { - // 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; - + // In bg queue. Speed up bg queue/join. if (bot->InBattlegroundQueue()) - return ActivePiorityType::IN_BG_QUEUE; + { + return true; + } bool isLFG = false; if (group) @@ -4183,122 +4189,82 @@ 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; + return true; + } - if (player->GetSocial() && - bot->GetGUID() && - player->GetSocial()->HasFriend(bot->GetGUID())) - return ActivePiorityType::PLAYER_FRIEND; + // Is in combat. Defend yourself. + if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY) + { + if (bot->IsInCombat()) + { + return true; + } + } + + // Player is near. Always active. + if (HasPlayerNearby(300.f)) + { + return true; + } + + // friends always active + + // HasFriend sometimes cause crash, disable + // for (auto& player : sRandomPlayerbotMgr->GetPlayers()) + // { + // if (!player || !player->IsInWorld() || !player->GetSocial() || !bot->GetGUID()) + // continue; + + // if (player->GetSocial()->HasFriend(bot->GetGUID())) + // return true; + // } + + /* if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY) + { + if (HasManyPlayersNearby()) + { + return false; + } }*/ - //PLAYER_GUILD (contains real player) - if (IsInRealGuild()) - return ActivePiorityType::PLAYER_GUILD; - - - //IN_NOT_ACTIVE_MAP - if (Map* map = bot->GetMap()) + // Bots don't need to move using PathGenerator. + if (activityType == DETAILED_MOVE_ACTIVITY) { - if (map->GetEntry()->IsWorldMap()) - { - if (!HasRealPlayers(map)) - return ActivePiorityType::IN_NOT_ACTIVE_MAP; - - if (!map->IsGridLoaded(bot->GetPositionX(), bot->GetPositionY())) - return ActivePiorityType::IN_NOT_ACTIVE_MAP; - } - } - - //IN_ACTIVE_MAP - if (Map* map = bot->GetMap()) - { - if (map->GetEntry()->IsWorldMap()) - { - if (HasRealPlayers(map)) - return ActivePiorityType::IN_ACTIVE_MAP; - } - } - - // IN_VERY_ACTIVE_AREA - if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY) - { - if (HasManyPlayersNearby(20, sPlayerbotAIConfig->sightDistance)) - return ActivePiorityType::IN_VERY_ACTIVE_AREA; - } - - 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()) 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) - { - 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; } - // Bots do not need to move using PathGenerator. - //if (activityType == DETAILED_MOVE_ACTIVITY) return false; + if (sPlayerbotAIConfig->botActiveAlone <= 0) + { + return false; + } + if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale) + { + return true; + } - // 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; + // ####################################################################################### + // All mandatory conditations are checked to be active or not, from here the remaining + // situations are usable for scaling when enabled. + // ####################################################################################### + + // 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 +4281,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) - { - 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; - } + uint32 maxDiff = sWorldUpdateTime.GetAverageUpdateTime(); - return botActiveAlonePerc; + if (maxDiff > 500) return 0; + if (maxDiff > 250) + { + 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 mod; } bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); } @@ -5460,29 +5421,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/src/PlayerbotAI.h b/src/PlayerbotAI.h index f7cf77ca..5477685e 100644 --- a/src/PlayerbotAI.h +++ b/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 @@ 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); 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 @@ public: 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 @@ private: 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/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 6c721024..a56408d5 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/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 = diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index eeacb85a..94b6b806 100644 --- a/src/Playerbots.cpp +++ b/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 @@ public: { 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/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index a0446707..e2fa72f9 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/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; + + // only keep updating till initializing time has completed, + // which prevents unneeded expensive GameTime calls. + if (_isBotInitializing) + { + _isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.13; } - setBotInitializing( - //onlineBotCount < maxAllowedBotCount && <-- these fields are incorrect when using bot amount min/max are not equal. - GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.12); - - // 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; } diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index 10405413..f7ebf9aa 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -103,7 +103,8 @@ public: void LogPlayerLocation(); void UpdateAIInternal(uint32 elapsed, bool minimal = false) override; -//private: +private: + //void ScaleBotActivity(); public: uint32 activeBots = 0; @@ -163,11 +164,11 @@ public: 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 @@ protected: 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); From 7fa1ab36a3af189cb439d318fdf0fa67d849fe6e Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Tue, 5 Nov 2024 15:15:09 +0000 Subject: [PATCH 6/7] Update LfgAccept (#681) Fixed issue with Bots refusing to join DungeonFinder. --- src/strategy/actions/LfgActions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategy/actions/LfgActions.cpp b/src/strategy/actions/LfgActions.cpp index 9a07f527..3e36086d 100644 --- a/src/strategy/actions/LfgActions.cpp +++ b/src/strategy/actions/LfgActions.cpp @@ -188,7 +188,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; } From 2d13373a8d4db33835dd30a2fbf129abb4bcd8fe Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Fri, 8 Nov 2024 03:18:59 +1100 Subject: [PATCH 7/7] Handle bot gear upgrades for multi-slot items (#695) * Handle bot gear upgrades for multi-slot items Switch to using opcode "CMSG_AUTOEQUIP_ITEM_SLOT" to equip items to specific slots, rather than "right clicking" item upgrades. Fixes an issue with rings, trinkets and offhand weapons where the bot would only ever upgrade their first slot. Also evaluate the above item types for equipping in both slots rather than just comparing to the first item. * Update EquipAction.cpp --- src/strategy/actions/EquipAction.cpp | 66 +++++++++-- src/strategy/values/ItemUsageValue.cpp | 154 ++++++++++++++----------- 2 files changed, 140 insertions(+), 80 deletions(-) diff --git a/src/strategy/actions/EquipAction.cpp b/src/strategy/actions/EquipAction.cpp index 85591cf8..f4437e2c 100644 --- a/src/strategy/actions/EquipAction.cpp +++ b/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,64 @@ 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); + if (dstSlot == EQUIPMENT_SLOT_FINGER1 || + dstSlot == EQUIPMENT_SLOT_TRINKET1 || + dstSlot == EQUIPMENT_SLOT_MAINHAND) + { + 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/src/strategy/values/ItemUsageValue.cpp b/src/strategy/values/ItemUsageValue.cpp index 9c6adbf9..0855f707 100644 --- a/src/strategy/values/ItemUsageValue.cpp +++ b/src/strategy/values/ItemUsageValue.cpp @@ -205,102 +205,116 @@ 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); + if (dstSlot == EQUIPMENT_SLOT_FINGER1 || + dstSlot == EQUIPMENT_SLOT_TRINKET1 || + dstSlot == EQUIPMENT_SLOT_MAINHAND) { - if (shouldEquip) - return ITEM_USAGE_EQUIP; - else - { - return ITEM_USAGE_BAD_EQUIP; - } + possibleSlots = 2; } - ItemTemplate const* oldItemProto = oldItem->GetTemplate(); - float oldScore = calculator.CalculateItem(oldItemProto->ItemId); - if (oldItem) + for (uint8 i = 0; i < possibleSlots; i++) { - // uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); - if (itemScore || oldScore) + bool shouldEquipInSlot = shouldEquip; + Item* oldItem = bot->GetItemByPos(dest + i); + + // No item equipped + if (!oldItem) { - shouldEquip = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold; + if (shouldEquipInSlot) + return ITEM_USAGE_EQUIP; + else + { + return ITEM_USAGE_BAD_EQUIP; + } } - } - // Bigger quiver - if (itemProto->Class == ITEM_CLASS_QUIVER) - { - if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots) + ItemTemplate const* oldItemProto = oldItem->GetTemplate(); + float oldScore = calculator.CalculateItem(oldItemProto->ItemId); + if (oldItem) { - return ITEM_USAGE_EQUIP; + // uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); + if (itemScore || oldScore) + { + shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold; + } } - else + + // Bigger quiver + if (itemProto->Class == ITEM_CLASS_QUIVER) { - return ITEM_USAGE_NONE; + 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; + 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; + 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); + // 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; + // 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) + 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; }