diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 47501b29..83b89268 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1014,6 +1014,20 @@ AiPlayerbot.RandomBotMaps = 0,1,530,571 # Default: 0.25 AiPlayerbot.ProbTeleToBankers = 0.25 +# Control probability weights for bots teleporting to Capital city bankers +# Sum of weights need not be 100. Set to 0 to disable teleporting to the city. +AiPlayerbot.EnableWeightTeleToCityBankers = 1 +AiPlayerbot.TeleToStormwindWeight = 2 +AiPlayerbot.TeleToIronforgeWeight = 1 +AiPlayerbot.TeleToDarnassusWeight = 1 +AiPlayerbot.TeleToExodarWeight = 1 +AiPlayerbot.TeleToOrgrimmarWeight = 2 +AiPlayerbot.TeleToUndercityWeight = 1 +AiPlayerbot.TeleToThunderBluffWeight = 1 +AiPlayerbot.TeleToSilvermoonCityWeight = 1 +AiPlayerbot.TeleToShattrathCityWeight = 1 +AiPlayerbot.TeleToDalaranWeight = 1 + # How far randombots are teleported after death AiPlayerbot.RandomBotTeleportDistance = 100 diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 7a6fa429..abdd38c5 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -139,6 +139,17 @@ bool PlayerbotAIConfig::Initialize() randomBotMapsAsString = sConfigMgr->GetOption("AiPlayerbot.RandomBotMaps", "0,1,530,571"); LoadList>(randomBotMapsAsString, randomBotMaps); probTeleToBankers = sConfigMgr->GetOption("AiPlayerbot.ProbTeleToBankers", 0.25f); + enableWeightTeleToCityBankers = sConfigMgr->GetOption("AiPlayerbot.EnableWeightTeleToCityBankers", false); + weightTeleToStormwind = sConfigMgr->GetOption("AiPlayerbot.TeleToStormwindWeight", 1); + weightTeleToIronforge = sConfigMgr->GetOption("AiPlayerbot.TeleToIronforgeWeight", 1); + weightTeleToDarnassus = sConfigMgr->GetOption("AiPlayerbot.TeleToDarnassusWeight", 1); + weightTeleToExodar = sConfigMgr->GetOption("AiPlayerbot.TeleToExodarWeight", 1); + weightTeleToOrgrimmar = sConfigMgr->GetOption("AiPlayerbot.TeleToOrgrimmarWeight", 1); + weightTeleToUndercity = sConfigMgr->GetOption("AiPlayerbot.TeleToUndercityWeight", 1); + weightTeleToThunderBluff = sConfigMgr->GetOption("AiPlayerbot.TeleToThunderBluffWeight", 1); + weightTeleToSilvermoonCity = sConfigMgr->GetOption("AiPlayerbot.TeleToSilvermoonCityWeight", 1); + weightTeleToShattrathCity = sConfigMgr->GetOption("AiPlayerbot.TeleToShattrathCityWeight", 1); + weightTeleToDalaran = sConfigMgr->GetOption("AiPlayerbot.TeleToDalaranWeight", 1); LoadList>( sConfigMgr->GetOption("AiPlayerbot.RandomBotQuestItems", "6948,5175,5176,5177,5178,16309,12382,13704,11000"), diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 36faa98b..db85e538 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -102,6 +102,17 @@ public: bool botAutologin; std::string randomBotMapsAsString; float probTeleToBankers; + bool enableWeightTeleToCityBankers; + int weightTeleToStormwind; + int weightTeleToIronforge; + int weightTeleToDarnassus; + int weightTeleToExodar; + int weightTeleToOrgrimmar; + int weightTeleToUndercity; + int weightTeleToThunderBluff; + int weightTeleToSilvermoonCity; + int weightTeleToShattrathCity; + int weightTeleToDalaran; std::vector randomBotMaps; std::vector randomBotQuestItems; std::vector randomBotAccounts; diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 1b69d7f3..81263823 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -60,6 +60,48 @@ struct GuidClassRaceInfo uint32 rRace; }; +enum class CityId : uint8 { + STORMWIND, IRONFORGE, DARNASSUS, EXODAR, + ORGRIMMAR, UNDERCITY, THUNDER_BLUFF, SILVERMOON_CITY, + SHATTRATH_CITY, DALARAN +}; + +enum class FactionId : uint8 { ALLIANCE, HORDE, NEUTRAL }; + +// Map of banker entry → city + faction +static const std::unordered_map> bankerToCity = { + {2455, {CityId::STORMWIND, FactionId::ALLIANCE}}, {2456, {CityId::STORMWIND, FactionId::ALLIANCE}}, {2457, {CityId::STORMWIND, FactionId::ALLIANCE}}, + {2460, {CityId::IRONFORGE, FactionId::ALLIANCE}}, {2461, {CityId::IRONFORGE, FactionId::ALLIANCE}}, {5099, {CityId::IRONFORGE, FactionId::ALLIANCE}}, + {4155, {CityId::DARNASSUS, FactionId::ALLIANCE}}, {4208, {CityId::DARNASSUS, FactionId::ALLIANCE}}, {4209, {CityId::DARNASSUS, FactionId::ALLIANCE}}, + {17773, {CityId::EXODAR, FactionId::ALLIANCE}}, {18350, {CityId::EXODAR, FactionId::ALLIANCE}}, {16710, {CityId::EXODAR, FactionId::ALLIANCE}}, + {3320, {CityId::ORGRIMMAR, FactionId::HORDE}}, {3309, {CityId::ORGRIMMAR, FactionId::HORDE}}, {3318, {CityId::ORGRIMMAR, FactionId::HORDE}}, + {4549, {CityId::UNDERCITY, FactionId::HORDE}}, {2459, {CityId::UNDERCITY, FactionId::HORDE}}, {2458, {CityId::UNDERCITY, FactionId::HORDE}}, {4550, {CityId::UNDERCITY, FactionId::HORDE}}, + {2996, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8356, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8357, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, + {17631, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17632, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17633, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, + {16615, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16616, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16617, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, + {19246, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, + {19034, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, + {30604, {CityId::DALARAN, FactionId::NEUTRAL}}, {30605, {CityId::DALARAN, FactionId::NEUTRAL}}, {30607, {CityId::DALARAN, FactionId::NEUTRAL}}, + {28675, {CityId::DALARAN, FactionId::NEUTRAL}}, {28676, {CityId::DALARAN, FactionId::NEUTRAL}}, {28677, {CityId::DALARAN, FactionId::NEUTRAL}} +}; + +// Map of city → available banker entries +static const std::unordered_map> cityToBankers = { + {CityId::STORMWIND, {2455, 2456, 2457}}, + {CityId::IRONFORGE, {2460, 2461, 5099}}, + {CityId::DARNASSUS, {4155, 4208, 4209}}, + {CityId::EXODAR, {17773, 18350, 16710}}, + {CityId::ORGRIMMAR, {3320, 3309, 3318}}, + {CityId::UNDERCITY, {4549, 2459, 2458, 4550}}, + {CityId::THUNDER_BLUFF, {2996, 8356, 8357}}, + {CityId::SILVERMOON_CITY, {17631, 17632, 17633, 16615, 16616, 16617}}, + {CityId::SHATTRATH_CITY, {19246, 19338, 19034, 19318}}, + {CityId::DALARAN, {30604, 30605, 30607, 28675, 28676, 28677, 29530}} +}; + +// Quick lookup map: banker entry → location +static std::unordered_map bankerEntryToLocation; + void PrintStatsThread() { sRandomPlayerbotMgr->PrintStats(); } void activatePrintStatsThread() @@ -1660,11 +1702,6 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& return; } - if (tlocs.empty()) - { - LOG_DEBUG("playerbots", "Cannot teleport bot {} - no locations available", bot->GetName().c_str()); - return; - } PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "RandomTeleportByLocations"); @@ -2021,7 +2058,8 @@ void RandomPlayerbotMgr::PrepareTeleportCache() "position_y, " "position_z, " "orientation, " - "t.minlevel " + "t.minlevel, " + "t.entry " "FROM " "creature c " "INNER JOIN creature_template t on c.id1 = t.entry " @@ -2048,27 +2086,30 @@ void RandomPlayerbotMgr::PrepareTeleportCache() float z = fields[3].Get(); float orient = fields[4].Get(); uint32 level = fields[5].Get(); - WorldLocation loc(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI); + uint32 entry = fields[6].Get(); + BankerLocation bLoc; + bLoc.loc = WorldLocation(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI); + bLoc.entry = entry; collected_locs++; for (int32 l = 1; l <= maxLevel; l++) { - if (l <= 60 && level >= 60) + // Bots 1-60 go to base game bankers (all have minlevel 30 or 45) + if (l <=60 && level > 45) { continue; } - if (l <= 70 && level >= 70) + // Bots 61-70 go to Shattrath bankers (all have minlevel 60 or 70) + if ((l >=61 && l <=70) && (level < 60 || level > 70)) { continue; } - if (l >= 70 && level >= 60 && level <= 70) + // Bots 71+ go to Dalaran bankers (all have minlevel 75) + if ((l >=71) && level != 75) { continue; } - if (l >= 30 && level <= 30) - { - continue; - } - bankerLocsPerLevelCache[(uint8)l].push_back(loc); + bankerLocsPerLevelCache[(uint8)l].push_back(bLoc); + bankerEntryToLocation[bLoc.entry] = bLoc.loc; } } while (results->NextRow()); } @@ -2138,11 +2179,92 @@ void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot) locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level]; else locs = &locsPerLevelCache[level]; - LOG_DEBUG("playerbots", "Random teleporting bot {} for level {} ({} locations available)", bot->GetName().c_str(), - bot->GetLevel(), locs->size()); if (level >= 10 && urand(0, 100) < sPlayerbotAIConfig->probTeleToBankers * 100) { - RandomTeleport(bot, bankerLocsPerLevelCache[level], true); + std::vector fallbackLocs; + for (auto& bLoc : bankerLocsPerLevelCache[level]) + fallbackLocs.push_back(bLoc.loc); + + if (!sPlayerbotAIConfig->enableWeightTeleToCityBankers) + { + RandomTeleport(bot, fallbackLocs, true); + return; + } + + // Collect valid cities based on bot faction. + std::unordered_set validBankerCities; + for (auto& loc : bankerLocsPerLevelCache[level]) + { + auto cityIt = bankerToCity.find(loc.entry); + if (cityIt == bankerToCity.end()) continue; + + CityId cityId = cityIt->second.first; + FactionId cityFactionId = cityIt->second.second; + + if ((IsAlliance(bot->getRace()) && cityFactionId == FactionId::ALLIANCE) || + (!IsAlliance(bot->getRace()) && cityFactionId == FactionId::HORDE) || + (cityFactionId == FactionId::NEUTRAL)) + { + validBankerCities.insert(cityId); + } + } + + // Fallback if no valid cities + if (validBankerCities.empty()) + { + RandomTeleport(bot, fallbackLocs, true); + return; + } + + // Apply weights to valid cities + std::vector weightedCities; + for (CityId city : validBankerCities) + { + int weight = 0; + switch (city) + { + case CityId::STORMWIND: weight = sPlayerbotAIConfig->weightTeleToStormwind; break; + case CityId::IRONFORGE: weight = sPlayerbotAIConfig->weightTeleToIronforge; break; + case CityId::DARNASSUS: weight = sPlayerbotAIConfig->weightTeleToDarnassus; break; + case CityId::EXODAR: weight = sPlayerbotAIConfig->weightTeleToExodar; break; + case CityId::ORGRIMMAR: weight = sPlayerbotAIConfig->weightTeleToOrgrimmar; break; + case CityId::UNDERCITY: weight = sPlayerbotAIConfig->weightTeleToUndercity; break; + case CityId::THUNDER_BLUFF: weight = sPlayerbotAIConfig->weightTeleToThunderBluff; break; + case CityId::SILVERMOON_CITY: weight = sPlayerbotAIConfig->weightTeleToSilvermoonCity; break; + case CityId::SHATTRATH_CITY: weight = sPlayerbotAIConfig->weightTeleToShattrathCity; break; + case CityId::DALARAN: weight = sPlayerbotAIConfig->weightTeleToDalaran; break; + default: weight = 0; break; + } + if (weight <= 0) continue; + + for (int i = 0; i < weight; ++i) + { + weightedCities.push_back(city); + } + } + + // Fallback if no valid cities + if (weightedCities.empty()) + { + RandomTeleport(bot, fallbackLocs, true); + return; + } + + // Pick a weighted city randomly, then a random banker in that city + // then teleport to that banker + CityId selectedCity = weightedCities[urand(0, weightedCities.size() - 1)]; + const auto& bankers = cityToBankers.at(selectedCity); + uint32 selectedBankerEntry = bankers[urand(0, bankers.size() - 1)]; + auto locIt = bankerEntryToLocation.find(selectedBankerEntry); + if (locIt != bankerEntryToLocation.end()) + { + std::vector teleportTarget = { locIt->second }; + RandomTeleport(bot, teleportTarget, true); + return; + } + + // Fallback if something went wrong + RandomTeleport(bot, *locs); } else { diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index 6a62a68b..ed4c1cd2 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -183,7 +183,11 @@ public: bool InsideBracket(uint32 val) { return val >= low && val <= high; } }; std::map zone2LevelBracket; - std::map> bankerLocsPerLevelCache; + struct BankerLocation { + WorldLocation loc; + uint32 entry; + }; + std::map> bankerLocsPerLevelCache; // Account type management void AssignAccountTypes();