From 4a00c954ed4d6c6f0dd937575f3f84659237941d Mon Sep 17 00:00:00 2001 From: Yunfan Li <56597220+liyunfan1223@users.noreply.github.com> Date: Wed, 23 Jul 2025 23:37:41 +0800 Subject: [PATCH] RPG update travel flight status (#1445) --- conf/playerbots.conf.dist | 88 +++-- src/PlayerbotAI.cpp | 172 +++++---- src/PlayerbotAI.h | 2 + src/PlayerbotAIConfig.cpp | 14 +- src/PlayerbotAIConfig.h | 22 ++ src/RandomPlayerbotMgr.cpp | 370 +++++++++---------- src/RandomPlayerbotMgr.h | 2 + src/strategy/actions/ActionContext.h | 14 +- src/strategy/rpg/NewRpgAction.cpp | 178 +++++----- src/strategy/rpg/NewRpgAction.h | 36 +- src/strategy/rpg/NewRpgBaseAction.cpp | 431 +++++++++++++++++++---- src/strategy/rpg/NewRpgBaseAction.h | 26 +- src/strategy/rpg/NewRpgInfo.cpp | 78 ++-- src/strategy/rpg/NewRpgInfo.h | 81 ++--- src/strategy/rpg/NewRpgStrategy.cpp | 9 +- src/strategy/triggers/TriggerContext.h | 14 +- src/strategy/values/GrindTargetValue.cpp | 19 +- 17 files changed, 977 insertions(+), 579 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 624cce51..b64454ce 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -32,6 +32,7 @@ # ACTIVITIES # SPELLS # STRATEGIES +# RPG STRATEGY # TELEPORTS # BATTLEGROUND & ARENA & PVP # INTERVALS @@ -756,11 +757,6 @@ AiPlayerbot.RandomBotGroupNearby = 0 # Default: 1 (enabled) AiPlayerbot.AutoDoQuests = 1 -# Randombots will behave more like real players (experimental) -# This option will override AiPlayerbot.AutoDoQuests, RandomBotTeleLowerLevel, and RandomBotTeleHigherLevel -# Default: 1 (enabled) -AiPlayerbot.EnableNewRpgStrategy = 1 - # Quest items to keep in bots' inventories (do not destroy) AiPlayerbot.RandomBotQuestItems = "5175,5176,5177,5178,6948,11000,12382,13704,16309" @@ -818,36 +814,34 @@ AiPlayerbot.NonCombatStrategies = "" #################################################################################################### #################################################################################################### -# TELEPORTS +# RPG STRATEGY # # -# Maps where bots can be teleported to -AiPlayerbot.RandomBotMaps = 0,1,530,571 - -# Probabilty bots teleport to banker (city) -# Default: 0.25 -AiPlayerbot.ProbTeleToBankers = 0.25 - -# How far randombots are teleported after death -AiPlayerbot.RandomBotTeleportDistance = 100 - -# How many levels below the lowest-level creature in a zone, can a bot be -# This will have no effect if AiPlayerbot.EnableNewRpgStrategy is enabled -# Default: 1 (randombot will leave if they are more than 1 level lower) -AiPlayerbot.RandomBotTeleLowerLevel = 1 - -# How many levels above the highest-level creature in a zone, can a bot be -# This will have no effect if AiPlayerbot.EnableNewRpgStrategy is enabled -# Default: 3 (randombot will leave if they are more than 3 levels higher) -AiPlayerbot.RandomBotTeleHigherLevel = 3 - -# Bots automatically teleport to another place for leveling on levelup +# Randombots will behave more like real players (experimental) +# This option will override AiPlayerbot.AutoDoQuests, RandomBotTeleLowerLevel, and RandomBotTeleHigherLevel # Default: 1 (enabled) -AiPlayerbot.AutoTeleportForLevel = 1 +AiPlayerbot.EnableNewRpgStrategy = 1 + +# Control probability weights for RPG status of bots. Takes effect only when the status meets its premise.​ +# Sum of weights need not be 100. Set to 0 to disable the status. +# +# WanderRandom (Default: 15 Move randomly nearby to find and kill mobs) +# WanderNpc (Default: 20 Randomly interact with nearby NPCs​) +# GoGrind (Default: 15 Go to nearby level-appropriate locations to grind for killing mobs) +# GoCamp (Default: 10 Return to a nearby camp depending on innkeeper/flightmaster)​ +# DoQuest (Default: 60 Select quest from the quest log and head to the location to attempt completion​) +# TravelFlight (Default: 15 Go to the nearest flightmaster and fly to a level-appropriate area) +# Rest (Default: 5 Take a break for a while and do nothing) +AiPlayerbot.RpgStatusProbWeight.WanderRandom = 15 +AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20 +AiPlayerbot.RpgStatusProbWeight.GoGrind = 15 +AiPlayerbot.RpgStatusProbWeight.GoCamp = 10 +AiPlayerbot.RpgStatusProbWeight.DoQuest = 60 +AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15 +AiPlayerbot.RpgStatusProbWeight.Rest = 5 # Bots' minimum and maximum level when teleporting in and out of a zone, according to the new RPG strategy -# Requires EnableNewRpgStrategy enabled # Format: AiPlayerbot.ZoneBracket.zoneID = minLevel,maxLevel # # Classic WoW - Low-level zones: @@ -987,6 +981,40 @@ AiPlayerbot.ZoneBracket.4197 = 79,80 # #################################################################################################### +#################################################################################################### +# TELEPORTS +# +# + +# Maps where bots can be teleported to +AiPlayerbot.RandomBotMaps = 0,1,530,571 + +# Probabilty bots teleport to banker (city) +# Default: 0.25 +AiPlayerbot.ProbTeleToBankers = 0.25 + +# How far randombots are teleported after death +AiPlayerbot.RandomBotTeleportDistance = 100 + +# How many levels below the lowest-level creature in a zone, can a bot be +# This will have no effect if AiPlayerbot.EnableNewRpgStrategy is enabled +# Default: 1 (randombot will leave if they are more than 1 level lower) +AiPlayerbot.RandomBotTeleLowerLevel = 1 + +# How many levels above the highest-level creature in a zone, can a bot be +# This will have no effect if AiPlayerbot.EnableNewRpgStrategy is enabled +# Default: 3 (randombot will leave if they are more than 3 levels higher) +AiPlayerbot.RandomBotTeleHigherLevel = 3 + +# Bots automatically teleport to another place for leveling on levelup +# Default: 1 (enabled) +AiPlayerbot.AutoTeleportForLevel = 1 + +# +# +# +#################################################################################################### + #################################################################################################### # BATTLEGROUNDS & ARENAS & PVP # @@ -1066,7 +1094,7 @@ AiPlayerbot.RandomBotArenaTeamMinRating = 1000 AiPlayerbot.DeleteRandomBotArenaTeams = 0 # PvP Restricted Zones (bots don't pvp) -AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,139" +AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,139,3951" # PvP Restricted Areas (bots don't pvp) AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080" diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 0d3f693b..5b2b707f 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -212,7 +212,8 @@ PlayerbotAI::PlayerbotAI(Player* bot) masterIncomingPacketHandlers.AddHandler(CMSG_PUSHQUESTTOPARTY, "quest share"); botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_COMPLETE, "quest update complete"); botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_KILL, "quest update add kill"); - // botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item"); // SMSG_QUESTUPDATE_ADD_ITEM no longer used + // SMSG_QUESTUPDATE_ADD_ITEM no longer used + // botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item"); botOutgoingPacketHandlers.AddHandler(SMSG_QUEST_CONFIRM_ACCEPT, "confirm quest"); } @@ -1291,11 +1292,6 @@ void PlayerbotAI::DoNextAction(bool min) return; } - if (bot->HasUnitState(UNIT_STATE_IN_FLIGHT)) - { - SetNextCheckDelay(sPlayerbotAIConfig->passiveDelay); - return; - } // Change engine if just died bool isBotAlive = bot->IsAlive(); @@ -1425,8 +1421,8 @@ void PlayerbotAI::DoNextAction(bool min) master = newMaster; botAI->SetMaster(newMaster); botAI->ResetStrategies(); - - if (!bot->InBattleground()) + + if (!bot->InBattleground()) { botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT); @@ -1437,7 +1433,7 @@ void PlayerbotAI::DoNextAction(bool min) } else { - // we're in a battleground, stay with the pack and focus on objective + // we're in a battleground, stay with the pack and focus on objective botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT); } } @@ -2358,7 +2354,6 @@ std::string PlayerbotAI::GetLocalizedCreatureName(uint32 entry) return name; } - std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry) { std::string name; @@ -3330,13 +3325,14 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget) std::ostringstream out; out << "Spell cast failed - "; out << "Spell ID: " << spellId << " (" << ChatHelper::FormatSpell(spellInfo) << "), "; - out << "Error Code: " << static_cast(result) << " (0x" << std::hex << static_cast(result) << std::dec << "), "; + out << "Error Code: " << static_cast(result) << " (0x" << std::hex << static_cast(result) + << std::dec << "), "; out << "Bot: " << bot->GetName() << ", "; - + // Check spell target type if (targets.GetUnitTarget()) { - out << "Target: Unit (" << targets.GetUnitTarget()->GetName() + out << "Target: Unit (" << targets.GetUnitTarget()->GetName() << ", Low GUID: " << targets.GetUnitTarget()->GetGUID().GetCounter() << ", High GUID: " << static_cast(targets.GetUnitTarget()->GetGUID().GetHigh()) << "), "; } @@ -3352,7 +3348,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget) out << "Target: Item (Low GUID: " << targets.GetItemTarget()->GetGUID().GetCounter() << ", High GUID: " << static_cast(targets.GetItemTarget()->GetGUID().GetHigh()) << "), "; } - + // Check if bot is in trade mode if (bot->GetTradeData()) { @@ -3360,7 +3356,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget) Item* tradeItem = bot->GetTradeData()->GetTraderData()->GetItem(TRADE_SLOT_NONTRADED); if (tradeItem) { - out << "Trade Item: " << tradeItem->GetEntry() + out << "Trade Item: " << tradeItem->GetEntry() << " (Low GUID: " << tradeItem->GetGUID().GetCounter() << ", High GUID: " << static_cast(tradeItem->GetGUID().GetHigh()) << "), "; } @@ -3373,7 +3369,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget) { out << "Trade Mode: Inactive, "; } - + TellMasterNoFacing(out); } @@ -4312,7 +4308,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) if (!player || !player->IsInWorld()) continue; - Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID()); + Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID()); if (!connectedPlayer) continue; @@ -4394,16 +4390,16 @@ uint32 PlayerbotAI::AutoScaleActivity(uint32 mod) // Perfrom binary decision if ceiling <= floor: Either all bots are active or none are return (maxDiff > diffLimitCeiling) ? 0 : mod; } - + if (maxDiff > diffLimitCeiling) return 0; - + if (maxDiff <= diffLimitFloor) return mod; - + // Calculate lag progress from floor to ceiling (0 to 1) double lagProgress = (maxDiff - diffLimitFloor) / (double)(diffLimitCeiling - diffLimitFloor); - + // Apply the percentage of active bots (the complement of lag progress) to the mod value return static_cast(mod * (1 - lagProgress)); } @@ -4434,47 +4430,47 @@ void PlayerbotAI::RemoveShapeshift() // https://wowpedia.fandom.com/wiki/API_GetAverageItemLevel uint32 PlayerbotAI::GetEquipGearScore(Player* player) { - constexpr uint8 TOTAL_SLOTS = 17; // every slot except Body & Tabard + constexpr uint8 TOTAL_SLOTS = 17; // every slot except Body & Tabard uint32 sumLevel = 0; /* ---------- 0. Detect “ignore off-hand” situations --------- */ Item* main = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); - Item* off = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + Item* off = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); - bool ignoreOffhand = false; // true → divisor = 16 + bool ignoreOffhand = false; // true → divisor = 16 if (main) { bool twoHand = (main->GetTemplate()->InventoryType == INVTYPE_2HWEAPON); if (twoHand && !player->HasAura(SPELL_TITAN_GRIP)) - ignoreOffhand = true; // classic 2-hander + ignoreOffhand = true; // classic 2-hander } - else if (!off) // both hands empty + else if (!off) // both hands empty ignoreOffhand = true; /* ---------- 1. Sum up item-levels -------------------------- */ for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) { if (slot == EQUIPMENT_SLOT_BODY || slot == EQUIPMENT_SLOT_TABARD) - continue; // Blizzard never counts these + continue; // Blizzard never counts these if (ignoreOffhand && slot == EQUIPMENT_SLOT_OFFHAND) - continue; // skip off-hand in 2-H case + continue; // skip off-hand in 2-H case if (Item* it = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) - sumLevel += it->GetTemplate()->ItemLevel; // missing items add 0 + sumLevel += it->GetTemplate()->ItemLevel; // missing items add 0 } /* ---------- 2. Divide by 17 or 16 -------------------------- */ - const uint8 divisor = ignoreOffhand ? TOTAL_SLOTS - 1 : TOTAL_SLOTS; // 16 or 17 + const uint8 divisor = ignoreOffhand ? TOTAL_SLOTS - 1 : TOTAL_SLOTS; // 16 or 17 return sumLevel / divisor; } // NOTE : function rewritten as flags "withBags" and "withBank" not used, and _fillGearScoreData sometimes attribute -// one-hand/2H Weapon in wrong slots +// one-hand/2H Weapon in wrong slots /*uint32 PlayerbotAI::GetEquipGearScore(Player* player) { // This function aims to calculate the equipped gear score - + uint32 sum = 0; uint8 count = EQUIPMENT_SLOT_END - 2; // ignore body and tabard slots uint8 mh_type = 0; @@ -4483,17 +4479,17 @@ uint32 PlayerbotAI::GetEquipGearScore(Player* player) { Item* item =player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); if (item && i != EQUIPMENT_SLOT_BODY && i != EQUIPMENT_SLOT_TABARD) - { + { ItemTemplate const* proto = item->GetTemplate(); sum += proto->ItemLevel; - + // If character is not warfury and have 2 hand weapon equipped, main hand will be counted twice if (i == SLOT_MAIN_HAND) mh_type = item->GetTemplate()->InventoryType; if (!player->HasAura(SPELL_TITAN_GRIP) && mh_type == INVTYPE_2HWEAPON && i == SLOT_MAIN_HAND) sum += item->GetTemplate()->ItemLevel; - } - } + } + } uint32 gs = uint32(sum / count); return gs; @@ -4730,7 +4726,7 @@ void PlayerbotAI::_fillGearScoreData(Player* player, Item* item, std::vector bool - { - return (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) && - (itemTemplate->LockID == 0 || !this->bot->GetItemByEntry(itemTemplate->ItemId)->IsLocked()); - }); + return FindItemInInventory( + [this](ItemTemplate const* itemTemplate) -> bool + { + return (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) && + (itemTemplate->LockID == 0 || !this->bot->GetItemByEntry(itemTemplate->ItemId)->IsLocked()); + }); } Item* PlayerbotAI::FindLockedItem() const { - return FindItemInInventory([this](ItemTemplate const* itemTemplate) -> bool - { - if (!this->bot->HasSkill(SKILL_LOCKPICKING)) // Ensure bot has Lockpicking skill - return false; - - if (itemTemplate->LockID == 0) // Ensure the item is actually locked - return false; - - Item* item = this->bot->GetItemByEntry(itemTemplate->ItemId); - if (!item || !item->IsLocked()) // Ensure item instance is locked - return false; - - // Check if bot has enough Lockpicking skill - LockEntry const* lockInfo = sLockStore.LookupEntry(itemTemplate->LockID); - if (!lockInfo) - return false; - - for (uint8 j = 0; j < 8; ++j) + return FindItemInInventory( + [this](ItemTemplate const* itemTemplate) -> bool { - if (lockInfo->Type[j] == LOCK_KEY_SKILL) + if (!this->bot->HasSkill(SKILL_LOCKPICKING)) // Ensure bot has Lockpicking skill + return false; + + if (itemTemplate->LockID == 0) // Ensure the item is actually locked + return false; + + Item* item = this->bot->GetItemByEntry(itemTemplate->ItemId); + if (!item || !item->IsLocked()) // Ensure item instance is locked + return false; + + // Check if bot has enough Lockpicking skill + LockEntry const* lockInfo = sLockStore.LookupEntry(itemTemplate->LockID); + if (!lockInfo) + return false; + + for (uint8 j = 0; j < 8; ++j) { - uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j])); - if (skillId == SKILL_LOCKPICKING) + if (lockInfo->Type[j] == LOCK_KEY_SKILL) { - uint32 requiredSkill = lockInfo->Skill[j]; - uint32 botSkill = this->bot->GetSkillValue(SKILL_LOCKPICKING); - return botSkill >= requiredSkill; + uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j])); + if (skillId == SKILL_LOCKPICKING) + { + uint32 requiredSkill = lockInfo->Skill[j]; + uint32 botSkill = this->bot->GetSkillValue(SKILL_LOCKPICKING); + return botSkill >= requiredSkill; + } } } - } - return false; - }); + return false; + }); } static const uint32 uPriorizedSharpStoneIds[8] = {ADAMANTITE_SHARPENING_DISPLAYID, FEL_SHARPENING_DISPLAYID, @@ -6072,6 +6070,35 @@ ChatChannelSource PlayerbotAI::GetChatChannelSource(Player* bot, uint32 type, st return ChatChannelSource::SRC_UNDEFINED; } +bool PlayerbotAI::CheckLocationDistanceByLevel(Player* player, const WorldLocation& loc, bool fromStartUp) +{ + if (player->GetLevel() > 16) + return true; + + float dis = 0.0f; + if (fromStartUp) + { + PlayerInfo const* pInfo = sObjectMgr->GetPlayerInfo(player->getRace(true), player->getClass()); + if (loc.GetMapId() != pInfo->mapId) + return false; + dis = loc.GetExactDist(pInfo->positionX, pInfo->positionY, pInfo->positionZ); + } + else + { + if (loc.GetMapId() != player->GetMapId()) + return false; + dis = loc.GetExactDist(player); + } + + float bound = 10000.0f; + if (player->GetLevel() <= 4) + bound = 500.0f; + else if (player->GetLevel() <= 10) + bound = 2500.0f; + + return dis <= bound; +} + std::vector PlayerbotAI::GetAllCurrentQuests() { std::vector result; @@ -6347,17 +6374,16 @@ void PlayerbotAI::AddTimedEvent(std::function callback, uint32 delayMs) class LambdaEvent final : public BasicEvent { std::function _cb; + public: explicit LambdaEvent(std::function cb) : _cb(std::move(cb)) {} bool Execute(uint64 /*execTime*/, uint32 /*diff*/) override { _cb(); - return true; // remove after execution + return true; // remove after execution } }; // Every Player already owns an EventMap called m_Events - bot->m_Events.AddEvent( - new LambdaEvent(std::move(callback)), - bot->m_Events.CalculateTime(delayMs)); + bot->m_Events.AddEvent(new LambdaEvent(std::move(callback)), bot->m_Events.CalculateTime(delayMs)); } diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 394f7ce2..89326e08 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -544,6 +544,8 @@ public: bool IsSafe(Player* player); bool IsSafe(WorldObject* obj); ChatChannelSource GetChatChannelSource(Player* bot, uint32 type, std::string channelName); + + bool CheckLocationDistanceByLevel(Player* player, const WorldLocation &loc, bool fromStartUp = false); bool HasCheat(BotCheatMask mask) { diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index b05e9b7e..cd03c7f1 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -6,6 +6,7 @@ #include "PlayerbotAIConfig.h" #include #include "Config.h" +#include "NewRpgInfo.h" #include "PlayerbotDungeonSuggestionMgr.h" #include "PlayerbotFactory.h" #include "Playerbots.h" @@ -148,7 +149,7 @@ bool PlayerbotAIConfig::Initialize() LoadList>( sConfigMgr->GetOption("AiPlayerbot.PvpProhibitedZoneIds", "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565," - "3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395"), + "3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,139,3951"), pvpProhibitedZoneIds); LoadList>(sConfigMgr->GetOption("AiPlayerbot.PvpProhibitedAreaIds", "976,35"), pvpProhibitedAreaIds); @@ -585,7 +586,16 @@ bool PlayerbotAIConfig::Initialize() autoLearnQuestSpells = sConfigMgr->GetOption("AiPlayerbot.AutoLearnQuestSpells", false); autoTeleportForLevel = sConfigMgr->GetOption("AiPlayerbot.AutoTeleportForLevel", false); autoDoQuests = sConfigMgr->GetOption("AiPlayerbot.AutoDoQuests", true); - enableNewRpgStrategy = sConfigMgr->GetOption("AiPlayerbot.EnableNewRpgStrategy", false); + enableNewRpgStrategy = sConfigMgr->GetOption("AiPlayerbot.EnableNewRpgStrategy", true); + + RpgStatusProbWeight[RPG_WANDER_RANDOM] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.WanderRandom", 15); + RpgStatusProbWeight[RPG_WANDER_NPC] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.WanderNpc", 20); + RpgStatusProbWeight[RPG_GO_GRIND] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.GoGrind", 15); + RpgStatusProbWeight[RPG_GO_CAMP] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.GoCamp", 10); + RpgStatusProbWeight[RPG_DO_QUEST] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.DoQuest", 60); + RpgStatusProbWeight[RPG_TRAVEL_FLIGHT] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.TravelFlight", 15); + RpgStatusProbWeight[RPG_REST] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.Rest", 5); + syncLevelWithPlayers = sConfigMgr->GetOption("AiPlayerbot.SyncLevelWithPlayers", false); freeFood = sConfigMgr->GetOption("AiPlayerbot.FreeFood", true); randomBotGroupNearby = sConfigMgr->GetOption("AiPlayerbot.RandomBotGroupNearby", true); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index a636ca21..65b99c5d 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -7,6 +7,7 @@ #define _PLAYERBOT_PLAYERbotAICONFIG_H #include +#include #include "Common.h" #include "DBCEnums.h" @@ -34,6 +35,26 @@ enum class HealingManaEfficiency : uint8 SUPERIOR = 32 }; +enum NewRpgStatus : int +{ + RPG_STATUS_START = 0, + // Going to far away place + RPG_GO_GRIND = 0, + RPG_GO_CAMP = 1, + // Exploring nearby + RPG_WANDER_RANDOM = 2, + RPG_WANDER_NPC = 3, + // Do Quest (based on quest status) + RPG_DO_QUEST = 4, + // Travel + RPG_TRAVEL_FLIGHT = 5, + // Taking a break + RPG_REST = 6, + // Initial status + RPG_IDLE = 7, + RPG_STATUS_END = 8 +}; + #define MAX_SPECNO 20 #define MAX_WORLDBUFF_SPECNO 3 @@ -315,6 +336,7 @@ public: bool autoLearnTrainerSpells; bool autoDoQuests; bool enableNewRpgStrategy; + std::unordered_map RpgStatusProbWeight; bool syncLevelWithPlayers; bool freeFood; bool autoLearnQuestSpells; diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 6e62df0c..fb9504a9 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -5,6 +5,8 @@ #include "RandomPlayerbotMgr.h" +#include + #include #include #include @@ -33,6 +35,7 @@ #include "MapMgr.h" #include "NewRpgInfo.h" #include "NewRpgStrategy.h" +#include "ObjectGuid.h" #include "PerformanceMonitor.h" #include "Player.h" #include "PlayerbotAI.h" @@ -42,16 +45,16 @@ #include "Playerbots.h" #include "Position.h" #include "Random.h" +#include "RandomPlayerbotFactory.h" #include "ServerFacade.h" #include "SharedDefines.h" #include "TravelMgr.h" #include "Unit.h" #include "UpdateTime.h" #include "World.h" -#include "RandomPlayerbotFactory.h" -#include -struct GuidClassRaceInfo { +struct GuidClassRaceInfo +{ ObjectGuid::LowType guid; uint32 rClass; uint32 rRace; @@ -179,7 +182,7 @@ RandomPlayerbotMgr::RandomPlayerbotMgr() : PlayerbotHolder(), processTicks(0) sPlayerbotCommandServer->Start(); } - BattlegroundData.clear(); // Clear here and here only. + BattlegroundData.clear(); // Clear here and here only. // Cleanup on server start: orphaned pet data that's often left behind by bot pets that no longer exist in the DB CharacterDatabase.Execute("DELETE FROM pet_aura WHERE guid NOT IN (SELECT id FROM character_pet)"); @@ -385,8 +388,7 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) time(nullptr) > RealPlayerLastTimeSeen + sPlayerbotAIConfig->disabledWithoutRealPlayerLogoutDelay) { LogoutAllBots(); - LOG_INFO("playerbots", - "Logout all bots due no real player session."); + LOG_INFO("playerbots", "Logout all bots due no real player session."); } } @@ -433,11 +435,12 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) } } uint32 updateBots = sPlayerbotAIConfig->randomBotsPerInterval * onlineBotFocus / 100; - uint32 maxNewBots = onlineBotCount < maxAllowedBotCount && + uint32 maxNewBots = + onlineBotCount < maxAllowedBotCount && (sPlayerbotAIConfig->disabledWithoutRealPlayer == false || (realPlayerIsLogged && DelayLoginBotsTimer != 0 && time(nullptr) >= DelayLoginBotsTimer)) - ? maxAllowedBotCount - onlineBotCount - : 0; + ? maxAllowedBotCount - onlineBotCount + : 0; uint32 loginBots = std::min(sPlayerbotAIConfig->randomBotsPerInterval - updateBots, maxNewBots); if (!availableBots.empty()) @@ -527,7 +530,8 @@ uint32 RandomPlayerbotMgr::AddRandomBots() uint32 remainder = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) % totalRatio; // Fix #1082: Randomly add one based on reminder - if (remainder && urand(1, totalRatio) <= remainder) { + if (remainder && urand(1, totalRatio) <= remainder) + { allowedAllianceCount++; } @@ -540,7 +544,8 @@ uint32 RandomPlayerbotMgr::AddRandomBots() if (sPlayerbotAIConfig->enablePeriodicOnlineOffline) { // minus addclass bots account - int32 baseAccount = RandomPlayerbotFactory::CalculateTotalAccountCount() - sPlayerbotAIConfig->addClassAccountPoolSize; + int32 baseAccount = + RandomPlayerbotFactory::CalculateTotalAccountCount() - sPlayerbotAIConfig->addClassAccountPoolSize; if (baseAccount <= 0 || baseAccount > sPlayerbotAIConfig->randomBotAccounts.size()) { @@ -559,7 +564,8 @@ uint32 RandomPlayerbotMgr::AddRandomBots() std::vector allGuidInfos; - do { + do + { Field* fields = result->Fetch(); GuidClassRaceInfo info; info.guid = fields[0].Get(); @@ -573,7 +579,8 @@ uint32 RandomPlayerbotMgr::AddRandomBots() std::shuffle(allGuidInfos.begin(), allGuidInfos.end(), rnd); std::vector guids; - for (const auto& info : allGuidInfos) { + for (const auto& info : allGuidInfos) + { ObjectGuid::LowType guid = info.guid; uint32 rClass = info.rClass; uint32 rRace = info.rRace; @@ -590,8 +597,10 @@ uint32 RandomPlayerbotMgr::AddRandomBots() if (std::find(currentBots.begin(), currentBots.end(), guid) != currentBots.end()) continue; - if (sPlayerbotAIConfig->disableDeathKnightLogin) { - if (rClass == CLASS_DEATH_KNIGHT) { + if (sPlayerbotAIConfig->disableDeathKnightLogin) + { + if (rClass == CLASS_DEATH_KNIGHT) + { continue; } } @@ -602,9 +611,12 @@ uint32 RandomPlayerbotMgr::AddRandomBots() if (factionNotAllowed) continue; - if (isAlliance) { + if (isAlliance) + { allowedAllianceCount--; - } else { + } + else + { allowedHordeCount--; } @@ -709,13 +721,15 @@ std::vector parseBrackets(const std::string& str) void RandomPlayerbotMgr::CheckBgQueue() { - if (!BgCheckTimer) { + if (!BgCheckTimer) + { BgCheckTimer = time(nullptr); - return; // Exit immediately after initializing the timer + return; // Exit immediately after initializing the timer } - if (time(nullptr) < BgCheckTimer) { - return; // No need to proceed if the current time is less than the timer + if (time(nullptr) < BgCheckTimer) + { + return; // No need to proceed if the current time is less than the timer } // Update the timer to the current time @@ -777,7 +791,8 @@ void RandomPlayerbotMgr::CheckBgQueue() isRated = ginfo.IsRated; } - if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) || (player->InArena() && player->GetBattleground()->isRated())) + if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) || + (player->InArena() && player->GetBattleground()->isRated())) isRated = true; if (isRated) @@ -800,7 +815,8 @@ void RandomPlayerbotMgr::CheckBgQueue() uint32 instanceId = player->GetBattleground()->GetInstanceID(); instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances; - if (instanceIds && std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end()) + if (instanceIds && + std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end()) instanceIds->push_back(instanceId); BattlegroundData[queueTypeId][bracketId].bgInstanceCount = instanceIds->size(); @@ -906,7 +922,8 @@ void RandomPlayerbotMgr::CheckBgQueue() instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances; } - if (instanceIds && std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end()) + if (instanceIds && + std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end()) instanceIds->push_back(instanceId); if (isArena) @@ -927,7 +944,7 @@ void RandomPlayerbotMgr::CheckBgQueue() // If enabled, wait for all bots to have logged in before queueing for Arena's / BG's if (sPlayerbotAIConfig->randomBotAutoJoinBG && playerBots.size() >= GetMaxAllowedBotCount()) { - uint32 randomBotAutoJoinArenaBracket = sPlayerbotAIConfig->randomBotAutoJoinArenaBracket; + uint32 randomBotAutoJoinArenaBracket = sPlayerbotAIConfig->randomBotAutoJoinArenaBracket; uint32 randomBotAutoJoinBGRatedArena2v2Count = sPlayerbotAIConfig->randomBotAutoJoinBGRatedArena2v2Count; uint32 randomBotAutoJoinBGRatedArena3v3Count = sPlayerbotAIConfig->randomBotAutoJoinBGRatedArena3v3Count; uint32 randomBotAutoJoinBGRatedArena5v5Count = sPlayerbotAIConfig->randomBotAutoJoinBGRatedArena5v5Count; @@ -946,14 +963,16 @@ void RandomPlayerbotMgr::CheckBgQueue() // Check both bgInstanceCount / bgInstances.size // to help counter against potentional inconsistencies - auto updateRatedArenaInstanceCount = [&](uint32 queueType, uint32 bracket, uint32 minCount) { + auto updateRatedArenaInstanceCount = [&](uint32 queueType, uint32 bracket, uint32 minCount) + { if (BattlegroundData[queueType][bracket].activeRatedArenaQueue == 0 && BattlegroundData[queueType][bracket].ratedArenaInstanceCount < minCount && BattlegroundData[queueType][bracket].ratedArenaInstances.size() < minCount) BattlegroundData[queueType][bracket].activeRatedArenaQueue = 1; }; - auto updateBGInstanceCount = [&](uint32 queueType, std::vector brackets, uint32 minCount) { + auto updateBGInstanceCount = [&](uint32 queueType, std::vector brackets, uint32 minCount) + { for (uint32 bracket : brackets) { if (BattlegroundData[queueType][bracket].activeBgQueue == 0 && @@ -964,9 +983,12 @@ void RandomPlayerbotMgr::CheckBgQueue() }; // Update rated arena instance counts - updateRatedArenaInstanceCount(BATTLEGROUND_QUEUE_2v2, randomBotAutoJoinArenaBracket, randomBotAutoJoinBGRatedArena2v2Count); - updateRatedArenaInstanceCount(BATTLEGROUND_QUEUE_3v3, randomBotAutoJoinArenaBracket, randomBotAutoJoinBGRatedArena3v3Count); - updateRatedArenaInstanceCount(BATTLEGROUND_QUEUE_5v5, randomBotAutoJoinArenaBracket, randomBotAutoJoinBGRatedArena5v5Count); + updateRatedArenaInstanceCount(BATTLEGROUND_QUEUE_2v2, randomBotAutoJoinArenaBracket, + randomBotAutoJoinBGRatedArena2v2Count); + updateRatedArenaInstanceCount(BATTLEGROUND_QUEUE_3v3, randomBotAutoJoinArenaBracket, + randomBotAutoJoinBGRatedArena3v3Count); + updateRatedArenaInstanceCount(BATTLEGROUND_QUEUE_5v5, randomBotAutoJoinArenaBracket, + randomBotAutoJoinBGRatedArena5v5Count); // Update battleground instance counts updateBGInstanceCount(BATTLEGROUND_QUEUE_IC, icBrackets, randomBotAutoJoinBGICCount); @@ -1045,7 +1067,8 @@ void RandomPlayerbotMgr::LogBattlegroundInfo() if (bgInfo.minLevel == 0) continue; - LOG_INFO("playerbots", "BG:{} {}: Player ({}:{}) Bot ({}:{}) Total (A:{} H:{}), Instances {}, Active Queue: {}", _bgType, + LOG_INFO("playerbots", + "BG:{} {}: Player ({}:{}) Bot ({}:{}) Total (A:{} H:{}), Instances {}, Active Queue: {}", _bgType, std::to_string(bgInfo.minLevel) + "-" + std::to_string(bgInfo.maxLevel), bgInfo.bgAlliancePlayerCount, bgInfo.bgHordePlayerCount, bgInfo.bgAllianceBotCount, bgInfo.bgHordeBotCount, bgInfo.bgAlliancePlayerCount + bgInfo.bgAllianceBotCount, @@ -1153,7 +1176,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) { if (player) LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: log out", bot, IsAlliance(player->getRace()) ? "A" : "H", - player->GetLevel(), player->GetName().c_str()); + player->GetLevel(), player->GetName().c_str()); else LOG_DEBUG("playerbots", "Bot #{}: log out", bot); @@ -1239,7 +1262,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) if (player && !logout && !isValid) { LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: log out", bot, IsAlliance(player->getRace()) ? "A" : "H", - player->GetLevel(), player->GetName().c_str()); + player->GetLevel(), player->GetName().c_str()); LogoutPlayerBot(botGUID); currentBots.remove(bot); SetEventValue(bot, "logout", 1, @@ -1481,22 +1504,9 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& continue; z = 0.05f + ground; - PlayerInfo const* pInfo = sObjectMgr->GetPlayerInfo(bot->getRace(true), bot->getClass()); - float dis = loc.GetExactDist(pInfo->positionX, pInfo->positionY, pInfo->positionZ); - // yunfan: distance check for low level - if (bot->GetLevel() <= 4 && (loc.GetMapId() != pInfo->mapId || dis > 500.0f)) - { + if (!botAI->CheckLocationDistanceByLevel(bot, loc, true)) continue; - } - if (bot->GetLevel() <= 10 && (loc.GetMapId() != pInfo->mapId || dis > 2500.0f)) - { - continue; - } - if (bot->GetLevel() <= 16 && (loc.GetMapId() != pInfo->mapId || dis > 10000.0f)) - { - continue; - } const LocaleConstant& locale = sWorld->GetDefaultDbcLocale(); LOG_DEBUG("playerbots", @@ -1513,7 +1523,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& } // Prevent blink to be detected by visible real players - if (dis < 150.0f && botAI->HasPlayerNearby(150.0f)) + if (botAI->HasPlayerNearby(150.0f)) { break; } @@ -1541,80 +1551,81 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& void RandomPlayerbotMgr::PrepareZone2LevelBracket() { // Classic WoW - Low - level zones - zone2LevelBracket[1] = {5, 12}; // Dun Morogh - zone2LevelBracket[12] = {5, 12}; // Elwynn Forest - zone2LevelBracket[14] = {5, 12}; // Durotar - zone2LevelBracket[85] = {5, 12}; // Tirisfal Glades - zone2LevelBracket[141] = {5, 12}; // Teldrassil - zone2LevelBracket[215] = {5, 12}; // Mulgore - zone2LevelBracket[3430] = {5, 12}; // Eversong Woods - zone2LevelBracket[3524] = {5, 12}; // Azuremyst Isle + zone2LevelBracket[1] = {5, 12}; // Dun Morogh + zone2LevelBracket[12] = {5, 12}; // Elwynn Forest + zone2LevelBracket[14] = {5, 12}; // Durotar + zone2LevelBracket[85] = {5, 12}; // Tirisfal Glades + zone2LevelBracket[141] = {5, 12}; // Teldrassil + zone2LevelBracket[215] = {5, 12}; // Mulgore + zone2LevelBracket[3430] = {5, 12}; // Eversong Woods + zone2LevelBracket[3524] = {5, 12}; // Azuremyst Isle // Classic WoW - Mid - level zones - zone2LevelBracket[17] = {10, 25}; // Barrens - zone2LevelBracket[38] = {10, 20}; // Loch Modan - zone2LevelBracket[40] = {10, 21}; // Westfall - zone2LevelBracket[130] = {10, 23}; // Silverpine Forest - zone2LevelBracket[148] = {10, 21}; // Darkshore - zone2LevelBracket[3433] = {10, 22}; // Ghostlands - zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle + zone2LevelBracket[17] = {10, 25}; // Barrens + zone2LevelBracket[38] = {10, 20}; // Loch Modan + zone2LevelBracket[40] = {10, 21}; // Westfall + zone2LevelBracket[130] = {10, 23}; // Silverpine Forest + zone2LevelBracket[148] = {10, 21}; // Darkshore + zone2LevelBracket[3433] = {10, 22}; // Ghostlands + zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle // Classic WoW - High - level zones - zone2LevelBracket[10] = {19, 33}; // Deadwind Pass - zone2LevelBracket[11] = {21, 30}; // Wetlands - zone2LevelBracket[44] = {16, 28}; // Redridge Mountains - zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills - zone2LevelBracket[331] = {18, 33}; // Ashenvale - zone2LevelBracket[400] = {24, 36}; // Thousand Needles - zone2LevelBracket[406] = {16, 29}; // Stonetalon Mountains + zone2LevelBracket[10] = {19, 33}; // Deadwind Pass + zone2LevelBracket[11] = {21, 30}; // Wetlands + zone2LevelBracket[44] = {16, 28}; // Redridge Mountains + zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills + zone2LevelBracket[331] = {18, 33}; // Ashenvale + zone2LevelBracket[400] = {24, 36}; // Thousand Needles + zone2LevelBracket[406] = {16, 29}; // Stonetalon Mountains // Classic WoW - Higher - level zones - zone2LevelBracket[3] = {36, 46}; // Badlands - zone2LevelBracket[8] = {36, 46}; // Swamp of Sorrows - zone2LevelBracket[15] = {35, 46}; // Dustwallow Marsh - zone2LevelBracket[16] = {45, 52}; // Azshara - zone2LevelBracket[33] = {32, 47}; // Stranglethorn Vale - zone2LevelBracket[45] = {30, 42}; // Arathi Highlands - zone2LevelBracket[47] = {42, 51}; // Hinterlands - zone2LevelBracket[51] = {45, 51}; // Searing Gorge - zone2LevelBracket[357] = {40, 52}; // Feralas - zone2LevelBracket[405] = {30, 41}; // Desolace - zone2LevelBracket[440] = {41, 52}; // Tanaris + zone2LevelBracket[3] = {36, 46}; // Badlands + zone2LevelBracket[8] = {36, 46}; // Swamp of Sorrows + zone2LevelBracket[15] = {35, 46}; // Dustwallow Marsh + zone2LevelBracket[16] = {45, 52}; // Azshara + zone2LevelBracket[33] = {32, 47}; // Stranglethorn Vale + zone2LevelBracket[45] = {30, 42}; // Arathi Highlands + zone2LevelBracket[47] = {42, 51}; // Hinterlands + zone2LevelBracket[51] = {45, 51}; // Searing Gorge + zone2LevelBracket[357] = {40, 52}; // Feralas + zone2LevelBracket[405] = {30, 41}; // Desolace + zone2LevelBracket[440] = {41, 52}; // Tanaris // Classic WoW - Top - level zones - zone2LevelBracket[4] = {52, 57}; // Blasted Lands - zone2LevelBracket[28] = {50, 60}; // Western Plaguelands - zone2LevelBracket[46] = {51, 60}; // Burning Steppes - zone2LevelBracket[139] = {54, 62}; // Eastern Plaguelands - zone2LevelBracket[361] = {47, 57}; // Felwood - zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater - zone2LevelBracket[618] = {54, 61}; // Winterspring - zone2LevelBracket[1377] = {54, 63}; // Silithus + zone2LevelBracket[4] = {52, 57}; // Blasted Lands + zone2LevelBracket[28] = {50, 60}; // Western Plaguelands + zone2LevelBracket[46] = {51, 60}; // Burning Steppes + zone2LevelBracket[139] = {54, 62}; // Eastern Plaguelands + zone2LevelBracket[361] = {47, 57}; // Felwood + zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater + zone2LevelBracket[618] = {54, 61}; // Winterspring + zone2LevelBracket[1377] = {54, 63}; // Silithus // The Burning Crusade - Zones - zone2LevelBracket[3483] = {58, 66}; // Hellfire Peninsula - zone2LevelBracket[3518] = {64, 70}; // Nagrand - zone2LevelBracket[3519] = {62, 73}; // Terokkar Forest - zone2LevelBracket[3520] = {66, 73}; // Shadowmoon Valley - zone2LevelBracket[3521] = {60, 67}; // Zangarmarsh - zone2LevelBracket[3522] = {64, 73}; // Blade's Edge Mountains - zone2LevelBracket[3523] = {67, 73}; // Netherstorm - zone2LevelBracket[4080] = {68, 73}; // Isle of Quel'Danas + zone2LevelBracket[3483] = {58, 66}; // Hellfire Peninsula + zone2LevelBracket[3518] = {64, 70}; // Nagrand + zone2LevelBracket[3519] = {62, 73}; // Terokkar Forest + zone2LevelBracket[3520] = {66, 73}; // Shadowmoon Valley + zone2LevelBracket[3521] = {60, 67}; // Zangarmarsh + zone2LevelBracket[3522] = {64, 73}; // Blade's Edge Mountains + zone2LevelBracket[3523] = {67, 73}; // Netherstorm + zone2LevelBracket[4080] = {68, 73}; // Isle of Quel'Danas // Wrath of the Lich King - Zones - zone2LevelBracket[65] = {71, 77}; // Dragonblight - zone2LevelBracket[66] = {74, 80}; // Zul'Drak - zone2LevelBracket[67] = {77, 80}; // Storm Peaks - zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier - zone2LevelBracket[394] = {72, 78}; // Grizzly Hills - zone2LevelBracket[495] = {68, 74}; // Howling Fjord - zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest - zone2LevelBracket[3537] = {68, 75}; // Borean Tundra - zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin - zone2LevelBracket[4197] = {79, 80}; // Wintergrasp + zone2LevelBracket[65] = {71, 77}; // Dragonblight + zone2LevelBracket[66] = {74, 80}; // Zul'Drak + zone2LevelBracket[67] = {77, 80}; // Storm Peaks + zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier + zone2LevelBracket[394] = {72, 78}; // Grizzly Hills + zone2LevelBracket[495] = {68, 74}; // Howling Fjord + zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest + zone2LevelBracket[3537] = {68, 75}; // Borean Tundra + zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin + zone2LevelBracket[4197] = {79, 80}; // Wintergrasp // Override with values from config - for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig->zoneBrackets) { + for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig->zoneBrackets) + { zone2LevelBracket[zoneId] = {bracketPair.first, bracketPair.second}; } } @@ -1696,25 +1707,27 @@ void RandomPlayerbotMgr::PrepareTeleportCache() if (sPlayerbotAIConfig->enableNewRpgStrategy) { PrepareZone2LevelBracket(); - LOG_INFO("playerbots", "Preparing innkeepers locations for level..."); + LOG_INFO("playerbots", "Preparing innkeepers / flightmasters locations for level..."); results = WorldDatabase.Query( - "SELECT " - "map, " - "position_x, " - "position_y, " - "position_z, " - "orientation, " - "t.faction, " - "t.entry " - "FROM " - "creature c " - "INNER JOIN creature_template t on c.id1 = t.entry " - "WHERE " - "t.npcflag & 73728 " - "AND map IN ({}) " - "ORDER BY " - "t.minlevel;", - sPlayerbotAIConfig->randomBotMapsAsString.c_str()); + "SELECT " + "map, " + "position_x, " + "position_y, " + "position_z, " + "orientation, " + "t.faction, " + "t.entry, " + "t.npcflag, " + "c.guid " + "FROM " + "creature c " + "INNER JOIN creature_template t on c.id1 = t.entry " + "WHERE " + "t.npcflag & 73728 " + "AND map IN ({}) " + "ORDER BY " + "t.minlevel;", + sPlayerbotAIConfig->randomBotMapsAsString.c_str()); collected_locs = 0; if (results) { @@ -1727,7 +1740,9 @@ void RandomPlayerbotMgr::PrepareTeleportCache() float z = fields[3].Get(); float orient = fields[4].Get(); uint32 faction = fields[5].Get(); - uint32 tEntry = fields[6].Get(); + uint32 tEntry = fields[6].Get(); + uint32 tNpcflag = fields[7].Get(); + uint32 guid = fields[8].Get(); if (tEntry == 3838 || tEntry == 29480) continue; @@ -1739,6 +1754,19 @@ void RandomPlayerbotMgr::PrepareTeleportCache() Map* map = sMapMgr->FindMap(loc.GetMapId(), 0); if (!map) continue; + bool forHorde = !(entry->hostileMask & 4); + bool forAlliance = !(entry->hostileMask & 2); + if (tNpcflag & UNIT_NPC_FLAG_FLIGHTMASTER) + { + if (forHorde) + { + hordeFlightMasterCache.push_back(guid); + } + if (forAlliance) + { + allianceFlightMasterCache.push_back(guid); + } + } const AreaTableEntry* area = sAreaTableStore.LookupEntry(map->GetAreaId(PHASEMASK_NORMAL, x, y, z)); uint32 zoneId = area->zone ? area->zone : area->ID; if (zone2LevelBracket.find(zoneId) == zone2LevelBracket.end()) @@ -1746,15 +1774,16 @@ void RandomPlayerbotMgr::PrepareTeleportCache() LevelBracket bracket = zone2LevelBracket[zoneId]; for (int i = bracket.low; i <= bracket.high; i++) { - if (!(entry->hostileMask & 4)) + if (forHorde) { hordeStarterPerLevelCache[i].push_back(loc); } - if (!(entry->hostileMask & 2)) + if (forAlliance) { allianceStarterPerLevelCache[i].push_back(loc); } } + } while (results->NextRow()); } @@ -1849,7 +1878,9 @@ void RandomPlayerbotMgr::PrepareAddclassCache() /// @FIXME: Modifying RandomBotAccountCount may cause the original addclass bots to be converted into rndbots, // which needs to be fixed by separating the two accounts in implementation size_t poolSize = sPlayerbotAIConfig->addClassAccountPoolSize; - size_t start = sPlayerbotAIConfig->randomBotAccounts.size() > poolSize ? sPlayerbotAIConfig->randomBotAccounts.size() - poolSize : 0; + size_t start = sPlayerbotAIConfig->randomBotAccounts.size() > poolSize + ? sPlayerbotAIConfig->randomBotAccounts.size() - poolSize + : 0; int32 collected = 0; for (size_t i = start; i < sPlayerbotAIConfig->randomBotAccounts.size(); i++) { @@ -2061,7 +2092,8 @@ void RandomPlayerbotMgr::RandomizeFirst(Player* bot) { level = maxLevel; } - else if (roll <= (100 * (sPlayerbotAIConfig->randomBotMaxLevelChance + sPlayerbotAIConfig->randomBotMinLevelChance))) + else if (roll <= + (100 * (sPlayerbotAIConfig->randomBotMaxLevelChance + sPlayerbotAIConfig->randomBotMinLevelChance))) { level = minLevel; } @@ -2281,7 +2313,8 @@ bool RandomPlayerbotMgr::IsAddclassBot(ObjectGuid::LowType bot) continue; for (uint8 isAlliance = 0; isAlliance <= 1; isAlliance++) { - if (addclassCache[GetTeamClassIdx(isAlliance, claz)].find(guid) != addclassCache[GetTeamClassIdx(isAlliance, claz)].end()) + if (addclassCache[GetTeamClassIdx(isAlliance, claz)].find(guid) != + addclassCache[GetTeamClassIdx(isAlliance, claz)].end()) return true; } } @@ -2626,8 +2659,8 @@ void RandomPlayerbotMgr::OnBotLoginInternal(Player* const bot) { if (_isBotLogging) { - LOG_INFO("playerbots", "{}/{} Bot {} logged in", playerBots.size(), sRandomPlayerbotMgr->GetMaxAllowedBotCount(), - bot->GetName().c_str()); + LOG_INFO("playerbots", "{}/{} Bot {} logged in", playerBots.size(), + sRandomPlayerbotMgr->GetMaxAllowedBotCount(), bot->GetName().c_str()); if (playerBots.size() == sRandomPlayerbotMgr->GetMaxAllowedBotCount()) { @@ -2725,7 +2758,8 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) if (IsRandomBot(player)) { - //ObjectGuid::LowType guid = player->GetGUID().GetCounter(); //not used, conditional could be rewritten for simplicity. line marked for removal. + // ObjectGuid::LowType guid = player->GetGUID().GetCounter(); //not used, conditional could be rewritten for + // simplicity. line marked for removal. } else { @@ -2788,8 +2822,8 @@ void RandomPlayerbotMgr::PrintStats() uint32 changeStrategy = 0; uint32 dead = 0; uint32 combat = 0; - //uint32 revive = 0; //not used, line marked for removal. - uint32 taxi = 0; + // uint32 revive = 0; //not used, line marked for removal. + uint32 inFlight = 0; uint32 moving = 0; uint32 mounted = 0; uint32 inBg = 0; @@ -2847,6 +2881,10 @@ void RandomPlayerbotMgr::PrintStats() { ++moving; } + if (bot->IsInFlight()) + { + ++inFlight; + } if (bot->IsMounted()) { ++mounted; @@ -2883,7 +2921,6 @@ void RandomPlayerbotMgr::PrintStats() } } - LOG_INFO("playerbots", "Bots level:"); // uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); uint32_t currentAlliance = 0, currentHorde = 0; @@ -2908,20 +2945,24 @@ void RandomPlayerbotMgr::PrintStats() LOG_INFO("playerbots", "Bots race:"); for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race) { - if (perRace[race]) { + if (perRace[race]) + { uint32 lvl = lvlPerRace[race] * 10 / perRace[race]; float flvl = lvl / 10.0f; - LOG_INFO("playerbots", " {}: {}, avg lvl: {}", ChatHelper::FormatRace(race).c_str(), perRace[race], flvl); + LOG_INFO("playerbots", " {}: {}, avg lvl: {}", ChatHelper::FormatRace(race).c_str(), perRace[race], + flvl); } } LOG_INFO("playerbots", "Bots class:"); for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls) { - if (perClass[cls]) { + if (perClass[cls]) + { uint32 lvl = lvlPerClass[cls] * 10 / perClass[cls]; float flvl = lvl / 10.0f; - LOG_INFO("playerbots", " {}: {}, avg lvl: {}", ChatHelper::FormatClass(cls).c_str(), perClass[cls], flvl); + LOG_INFO("playerbots", " {}: {}, avg lvl: {}", ChatHelper::FormatClass(cls).c_str(), perClass[cls], + flvl); } } @@ -2939,57 +2980,30 @@ void RandomPlayerbotMgr::PrintStats() // LOG_INFO("playerbots", " change_strategy: {}", changeStrategy); // LOG_INFO("playerbots", " revive: {}", revive); - LOG_INFO("playerbots", " On taxi: {}", taxi); + LOG_INFO("playerbots", " In flight: {}", inFlight); LOG_INFO("playerbots", " On mount: {}", mounted); LOG_INFO("playerbots", " In combat: {}", combat); LOG_INFO("playerbots", " In BG: {}", inBg); LOG_INFO("playerbots", " In Rest: {}", rest); LOG_INFO("playerbots", " Dead: {}", dead); - // LOG_INFO("playerbots", "Bots zone:"); - // for (auto &[zond_id, counter] : zoneCount) - // { - // const AreaTableEntry* entry = sAreaTableStore.LookupEntry(zond_id); - // std::string name = PlayerbotAI::GetLocalizedAreaName(entry); - // LOG_INFO("playerbots", " {}: {}", name, counter); - // } - if (sPlayerbotAIConfig->enableNewRpgStrategy) { LOG_INFO("playerbots", "Bots rpg status:"); - LOG_INFO("playerbots", " Idle: {}, Rest: {}, GoGrind: {}, GoInnkeeper: {}, MoveRandom: {}, MoveNpc: {}, DoQuest: {}", - rpgStatusCount[RPG_IDLE], rpgStatusCount[RPG_REST], rpgStatusCount[RPG_GO_GRIND], rpgStatusCount[RPG_GO_INNKEEPER], - rpgStatusCount[RPG_NEAR_RANDOM], rpgStatusCount[RPG_NEAR_NPC], rpgStatusCount[RPG_DO_QUEST]); + LOG_INFO("playerbots", + " Idle: {}, Rest: {}, GoGrind: {}, GoCamp: {}, MoveRandom: {}, MoveNpc: {}, DoQuest: {}, " + "TravelFlight: {}", + rpgStatusCount[RPG_IDLE], rpgStatusCount[RPG_REST], rpgStatusCount[RPG_GO_GRIND], + rpgStatusCount[RPG_GO_CAMP], rpgStatusCount[RPG_WANDER_RANDOM], rpgStatusCount[RPG_WANDER_NPC], + rpgStatusCount[RPG_DO_QUEST], rpgStatusCount[RPG_TRAVEL_FLIGHT]); LOG_INFO("playerbots", "Bots total quests:"); - LOG_INFO("playerbots", " Accepted: {}, Rewarded: {}, Dropped: {}", - rpgStasticTotal.questAccepted, rpgStasticTotal.questRewarded, rpgStasticTotal.questDropped); + LOG_INFO("playerbots", " Accepted: {}, Rewarded: {}, Dropped: {}", rpgStasticTotal.questAccepted, + rpgStasticTotal.questRewarded, rpgStasticTotal.questDropped); } LOG_INFO("playerbots", "Bots engine:", dead); LOG_INFO("playerbots", " Non-combat: {}, Combat: {}, Dead: {}", engine_noncombat, engine_combat, engine_dead); - - // LOG_INFO("playerbots", "Bots questing:"); - // LOG_INFO("playerbots", " Picking quests: {}", - // stateCount[TRAVEL_STATE_TRAVEL_PICK_UP_QUEST] + stateCount[TRAVEL_STATE_WORK_PICK_UP_QUEST]); - // LOG_INFO("playerbots", " Doing quests: {}", - // stateCount[TRAVEL_STATE_TRAVEL_DO_QUEST] + stateCount[TRAVEL_STATE_WORK_DO_QUEST]); - // LOG_INFO("playerbots", " Completing quests: {}", - // stateCount[TRAVEL_STATE_TRAVEL_HAND_IN_QUEST] + stateCount[TRAVEL_STATE_WORK_HAND_IN_QUEST]); - // LOG_INFO("playerbots", " Idling: {}", stateCount[TRAVEL_STATE_IDLE]); - - /*sort(questCount.begin(), questCount.end(), [](std::pair i, std::pair j) - {return i.second > j.second; }); - - LOG_INFO("playerbots", "Bots top quests:"); - - uint32 cnt = 0; - for (auto& quest : questCount) - { - LOG_INFO("playerbots", " [{}]: {} ({})", quest.second, quest.first->GetTitle().c_str(), - quest.first->GetQuestLevel()); cnt++; if (cnt > 25) break; - } - */ } double RandomPlayerbotMgr::GetBuyMultiplier(Player* bot) diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index f397276c..02d0ff9d 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -180,6 +180,8 @@ public: std::map> locsPerLevelCache; std::map> allianceStarterPerLevelCache; std::map> hordeStarterPerLevelCache; + std::vector allianceFlightMasterCache; + std::vector hordeFlightMasterCache; struct LevelBracket { uint32 low; uint32 high; diff --git a/src/strategy/actions/ActionContext.h b/src/strategy/actions/ActionContext.h index 0d66a4a9..250b9367 100644 --- a/src/strategy/actions/ActionContext.h +++ b/src/strategy/actions/ActionContext.h @@ -246,10 +246,11 @@ public: creators["new rpg status update"] = &ActionContext::new_rpg_status_update; creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind; - creators["new rpg go innkeeper"] = &ActionContext::new_rpg_go_innkeeper; - creators["new rpg move random"] = &ActionContext::new_rpg_move_random; - creators["new rpg move npc"] = &ActionContext::new_rpg_move_npc; + creators["new rpg go camp"] = &ActionContext::new_rpg_go_camp; + creators["new rpg wander random"] = &ActionContext::new_rpg_wander_random; + creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc; creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest; + creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight; } private: @@ -430,10 +431,11 @@ private: static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); } static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); } - static Action* new_rpg_go_innkeeper(PlayerbotAI* ai) { return new NewRpgGoInnKeeperAction(ai); } - static Action* new_rpg_move_random(PlayerbotAI* ai) { return new NewRpgMoveRandomAction(ai); } - static Action* new_rpg_move_npc(PlayerbotAI* ai) { return new NewRpgMoveNpcAction(ai); } + static Action* new_rpg_go_camp(PlayerbotAI* ai) { return new NewRpgGoCampAction(ai); } + static Action* new_rpg_wander_random(PlayerbotAI* ai) { return new NewRpgWanderRandomAction(ai); } + static Action* new_rpg_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(ai); } static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); } + static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); } }; #endif diff --git a/src/strategy/rpg/NewRpgAction.cpp b/src/strategy/rpg/NewRpgAction.cpp index c33f07b1..01595edf 100644 --- a/src/strategy/rpg/NewRpgAction.cpp +++ b/src/strategy/rpg/NewRpgAction.cpp @@ -4,7 +4,9 @@ #include #include +#include "BroadcastHelper.h" #include "ChatHelper.h" +#include "DBCStores.h" #include "G3D/Vector2.h" #include "GossipDef.h" #include "IVMapMgr.h" @@ -27,7 +29,6 @@ #include "StatsWeightCalculator.h" #include "Timer.h" #include "TravelMgr.h" -#include "BroadcastHelper.h" #include "World.h" bool TellRpgStatusAction::Execute(Event event) @@ -50,7 +51,7 @@ bool StartRpgDoQuestAction::Execute(Event event) PlayerbotChatHandler ch(owner); uint32 questId = ch.extractQuestId(text); const Quest* quest = sObjectMgr->GetQuestTemplate(questId); - if (quest) + if (quest) { botAI->rpgInfo.ChangeToDoQuest(questId, quest); bot->Whisper("Start to do quest " + std::to_string(questId), LANG_UNIVERSAL, owner); @@ -63,102 +64,50 @@ bool StartRpgDoQuestAction::Execute(Event event) bool NewRpgStatusUpdateAction::Execute(Event event) { NewRpgInfo& info = botAI->rpgInfo; - /// @TODO: Refactor by transition probability switch (info.status) { case RPG_IDLE: { - uint32 roll = urand(1, 100); - // IDLE -> NEAR_NPC - if (roll <= 30) - { - GuidVector possibleTargets = AI_VALUE(GuidVector, "possible new rpg targets"); - if (possibleTargets.size() >= 3) - { - info.ChangeToNearNpc(); - return true; - } - } - // IDLE -> GO_INNKEEPER - else if (roll <= 45) - { - WorldPosition pos = SelectRandomInnKeeperPos(bot); - if (pos != WorldPosition() && bot->GetExactDist(pos) > 50.0f) - { - info.ChangeToGoInnkeeper(pos); - return true; - } - } - // IDLE -> GO_GRIND - else if (roll <= 100) - { - if (roll >= 60) - { - std::vector availableQuests; - for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) - { - uint32 questId = bot->GetQuestSlotQuestId(slot); - if (botAI->lowPriorityQuest.find(questId) != botAI->lowPriorityQuest.end()) - continue; - - std::vector poiInfo; - if (GetQuestPOIPosAndObjectiveIdx(questId, poiInfo, true)) - { - availableQuests.push_back(questId); - } - } - if (availableQuests.size()) - { - uint32 questId = availableQuests[urand(0, availableQuests.size() - 1)]; - const Quest* quest = sObjectMgr->GetQuestTemplate(questId); - if (quest) - { - // IDLE -> DO_QUEST - info.ChangeToDoQuest(questId, quest); - return true; - } - } - } - WorldPosition pos = SelectRandomGrindPos(bot); - if (pos != WorldPosition()) - { - info.ChangeToGoGrind(pos); - return true; - } - } - // IDLE -> REST - info.ChangeToRest(); - bot->SetStandState(UNIT_STAND_STATE_SIT); - return true; + return RandomChangeStatus({RPG_GO_CAMP, RPG_GO_GRIND, RPG_WANDER_RANDOM, RPG_WANDER_NPC, RPG_DO_QUEST, + RPG_TRAVEL_FLIGHT, RPG_REST}); } case RPG_GO_GRIND: { WorldPosition& originalPos = info.go_grind.pos; assert(info.go_grind.pos != WorldPosition()); - // GO_GRIND -> NEAR_RANDOM + // GO_GRIND -> WANDER_RANDOM if (bot->GetExactDist(originalPos) < 10.0f) { - info.ChangeToNearRandom(); + info.ChangeToWanderRandom(); return true; } break; } - case RPG_GO_INNKEEPER: + case RPG_GO_CAMP: { - WorldPosition& originalPos = info.go_innkeeper.pos; - assert(info.go_innkeeper.pos != WorldPosition()); - // GO_INNKEEPER -> NEAR_NPC + WorldPosition& originalPos = info.go_camp.pos; + assert(info.go_camp.pos != WorldPosition()); + // GO_CAMP -> WANDER_NPC if (bot->GetExactDist(originalPos) < 10.0f) { - info.ChangeToNearNpc(); + info.ChangeToWanderNpc(); return true; } break; } - case RPG_NEAR_RANDOM: + case RPG_WANDER_RANDOM: { - // NEAR_RANDOM -> IDLE - if (info.HasStatusPersisted(statusNearRandomDuration)) + // WANDER_RANDOM -> IDLE + if (info.HasStatusPersisted(statusWanderRandomDuration)) + { + info.ChangeToIdle(); + return true; + } + break; + } + case RPG_WANDER_NPC: + { + if (info.HasStatusPersisted(statusWanderNpcDuration)) { info.ChangeToIdle(); return true; @@ -175,10 +124,11 @@ bool NewRpgStatusUpdateAction::Execute(Event event) } break; } - case RPG_NEAR_NPC: + case RPG_TRAVEL_FLIGHT: { - if (info.HasStatusPersisted(statusNearNpcDuration)) + if (info.flight.inFlight && !bot->IsInFlight()) { + // flight arrival info.ChangeToIdle(); return true; } @@ -208,26 +158,26 @@ bool NewRpgGoGrindAction::Execute(Event event) return MoveFarTo(botAI->rpgInfo.go_grind.pos); } -bool NewRpgGoInnKeeperAction::Execute(Event event) +bool NewRpgGoCampAction::Execute(Event event) { if (SearchQuestGiverAndAcceptOrReward()) return true; - return MoveFarTo(botAI->rpgInfo.go_innkeeper.pos); + return MoveFarTo(botAI->rpgInfo.go_camp.pos); } -bool NewRpgMoveRandomAction::Execute(Event event) +bool NewRpgWanderRandomAction::Execute(Event event) { if (SearchQuestGiverAndAcceptOrReward()) return true; - + return MoveRandomNear(); } -bool NewRpgMoveNpcAction::Execute(Event event) +bool NewRpgWanderNpcAction::Execute(Event event) { NewRpgInfo& info = botAI->rpgInfo; - if (!info.near_npc.npcOrGo) + if (!info.wander_npc.npcOrGo) { // No npc can be found, switch to IDLE ObjectGuid npcOrGo = ChooseNpcOrGameObjectToInteract(); @@ -236,32 +186,32 @@ bool NewRpgMoveNpcAction::Execute(Event event) info.ChangeToIdle(); return true; } - info.near_npc.npcOrGo = npcOrGo; - info.near_npc.lastReach = 0; + info.wander_npc.npcOrGo = npcOrGo; + info.wander_npc.lastReach = 0; return true; } - WorldObject* object = ObjectAccessor::GetWorldObject(*bot, info.near_npc.npcOrGo); + WorldObject* object = ObjectAccessor::GetWorldObject(*bot, info.wander_npc.npcOrGo); if (object && IsWithinInteractionDist(object)) { - if (!info.near_npc.lastReach) + if (!info.wander_npc.lastReach) { - info.near_npc.lastReach = getMSTime(); + info.wander_npc.lastReach = getMSTime(); if (bot->CanInteractWithQuestGiver(object)) - InteractWithNpcOrGameObjectForQuest(info.near_npc.npcOrGo); + InteractWithNpcOrGameObjectForQuest(info.wander_npc.npcOrGo); return true; } - if (info.near_npc.lastReach && GetMSTimeDiffToNow(info.near_npc.lastReach) < npcStayTime) + if (info.wander_npc.lastReach && GetMSTimeDiffToNow(info.wander_npc.lastReach) < npcStayTime) return false; // has reached the npc for more than `npcStayTime`, select the next target - info.near_npc.npcOrGo = ObjectGuid(); - info.near_npc.lastReach = 0; + info.wander_npc.npcOrGo = ObjectGuid(); + info.wander_npc.lastReach = 0; } else { - return MoveWorldObjectTo(info.near_npc.npcOrGo); + return MoveWorldObjectTo(info.wander_npc.npcOrGo); } return true; } @@ -384,7 +334,7 @@ bool NewRpgDoQuestAction::DoIncompleteQuest() /// @TODO: It may be better to make lowPriorityQuest a global set shared by all bots (or saved in db) botAI->lowPriorityQuest.insert(questId); botAI->rpgStatistic.questAbandoned++; - LOG_DEBUG("playerbots", "[New rpg] {} marked as abandoned quest {}", bot->GetName(), questId); + LOG_DEBUG("playerbots", "[New RPG] {} marked as abandoned quest {}", bot->GetName(), questId); botAI->rpgInfo.ChangeToIdle(); return true; } @@ -402,7 +352,7 @@ bool NewRpgDoQuestAction::DoCompletedQuest() { uint32 questId = RPG_INFO(quest, questId); const Quest* quest = RPG_INFO(quest, quest); - + if (RPG_INFO(quest, objectiveIdx) != -1) { // if quest is completed, back to poi with -1 idx to reward @@ -424,7 +374,7 @@ bool NewRpgDoQuestAction::DoCompletedQuest() // double check for GetQuestPOIPosAndObjectiveIdx if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE) return false; - + WorldPosition pos(bot->GetMapId(), dx, dy, dz); botAI->rpgInfo.do_quest.lastReachPOI = 0; botAI->rpgInfo.do_quest.pos = pos; @@ -451,9 +401,43 @@ bool NewRpgDoQuestAction::DoCompletedQuest() /// @TODO: It may be better to make lowPriorityQuest a global set shared by all bots (or saved in db) botAI->lowPriorityQuest.insert(questId); botAI->rpgStatistic.questAbandoned++; - LOG_DEBUG("playerbots", "[New rpg] {} marked as abandoned quest {}", bot->GetName(), questId); + LOG_DEBUG("playerbots", "[New RPG] {} marked as abandoned quest {}", bot->GetName(), questId); botAI->rpgInfo.ChangeToIdle(); return true; } return false; } + +bool NewRpgTravelFlightAction::Execute(Event event) +{ + if (bot->IsInFlight()) + { + botAI->rpgInfo.flight.inFlight = true; + return false; + } + Creature* flightMaster = ObjectAccessor::GetCreature(*bot, botAI->rpgInfo.flight.fromFlightMaster); + if (!flightMaster || !flightMaster->IsAlive()) + { + botAI->rpgInfo.ChangeToIdle(); + return true; + } + const TaxiNodesEntry* entry = sTaxiNodesStore.LookupEntry(botAI->rpgInfo.flight.toNode); + if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE) + { + return MoveFarTo(flightMaster); + } + std::vector nodes = {botAI->rpgInfo.flight.fromNode, botAI->rpgInfo.flight.toNode}; + + botAI->RemoveShapeshift(); + if (bot->IsMounted()) + { + bot->Dismount(); + } + if (!bot->ActivateTaxiPathTo(nodes, flightMaster, 0)) + { + LOG_DEBUG("playerbots", "[New RPG] {} active taxi path {} (from {} to {}) failed", bot->GetName(), + flightMaster->GetEntry(), nodes[0], nodes[1]); + botAI->rpgInfo.ChangeToIdle(); + } + return true; +} \ No newline at end of file diff --git a/src/strategy/rpg/NewRpgAction.h b/src/strategy/rpg/NewRpgAction.h index 422d5775..76692537 100644 --- a/src/strategy/rpg/NewRpgAction.h +++ b/src/strategy/rpg/NewRpgAction.h @@ -3,15 +3,15 @@ #include "Duration.h" #include "MovementActions.h" +#include "NewRpgBaseAction.h" #include "NewRpgInfo.h" #include "NewRpgStrategy.h" #include "Object.h" #include "ObjectDefines.h" #include "ObjectGuid.h" +#include "PlayerbotAI.h" #include "QuestDef.h" #include "TravelMgr.h" -#include "PlayerbotAI.h" -#include "NewRpgBaseAction.h" class TellRpgStatusAction : public Action { @@ -39,15 +39,16 @@ public: // transitionMat.resize(statusCount, std::vector(statusCount, 0)); // transitionMat[RPG_IDLE][RPG_GO_GRIND] = 20; - // transitionMat[RPG_IDLE][RPG_GO_INNKEEPER] = 15; - // transitionMat[RPG_IDLE][RPG_NEAR_NPC] = 30; + // transitionMat[RPG_IDLE][RPG_GO_CAMP] = 15; + // transitionMat[RPG_IDLE][RPG_WANDER_NPC] = 30; // transitionMat[RPG_IDLE][RPG_DO_QUEST] = 35; } bool Execute(Event event) override; + protected: // static NewRpgStatusTransitionProb transitionMat; - const int32 statusNearNpcDuration = 5 * 60 * 1000; - const int32 statusNearRandomDuration = 5 * 60 * 1000; + const int32 statusWanderNpcDuration = 5 * 60 * 1000; + const int32 statusWanderRandomDuration = 5 * 60 * 1000; const int32 statusRestDuration = 30 * 1000; const int32 statusDoQuestDuration = 30 * 60 * 1000; }; @@ -59,25 +60,24 @@ public: bool Execute(Event event) override; }; -class NewRpgGoInnKeeperAction : public NewRpgBaseAction +class NewRpgGoCampAction : public NewRpgBaseAction { public: - NewRpgGoInnKeeperAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg go innkeeper") {} + NewRpgGoCampAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg go camp") {} bool Execute(Event event) override; }; - -class NewRpgMoveRandomAction : public NewRpgBaseAction +class NewRpgWanderRandomAction : public NewRpgBaseAction { public: - NewRpgMoveRandomAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg move random") {} + NewRpgWanderRandomAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg wander random") {} bool Execute(Event event) override; }; -class NewRpgMoveNpcAction : public NewRpgBaseAction +class NewRpgWanderNpcAction : public NewRpgBaseAction { public: - NewRpgMoveNpcAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg move npcs") {} + NewRpgWanderNpcAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg move npcs") {} bool Execute(Event event) override; const uint32 npcStayTime = 8 * 1000; @@ -88,11 +88,19 @@ class NewRpgDoQuestAction : public NewRpgBaseAction public: NewRpgDoQuestAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg do quest") {} bool Execute(Event event) override; + protected: bool DoIncompleteQuest(); bool DoCompletedQuest(); - + const uint32 poiStayTime = 5 * 60 * 1000; }; +class NewRpgTravelFlightAction : public NewRpgBaseAction +{ +public: + NewRpgTravelFlightAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg travel flight") {} + bool Execute(Event event) override; +}; + #endif \ No newline at end of file diff --git a/src/strategy/rpg/NewRpgBaseAction.cpp b/src/strategy/rpg/NewRpgBaseAction.cpp index 7c50ed46..bd2a8221 100644 --- a/src/strategy/rpg/NewRpgBaseAction.cpp +++ b/src/strategy/rpg/NewRpgBaseAction.cpp @@ -1,5 +1,8 @@ #include "NewRpgBaseAction.h" + +#include "BroadcastHelper.h" #include "ChatHelper.h" +#include "Creature.h" #include "G3D/Vector2.h" #include "GameObject.h" #include "GossipDef.h" @@ -15,6 +18,7 @@ #include "PathGenerator.h" #include "Player.h" #include "PlayerbotAI.h" +#include "PlayerbotAIConfig.h" #include "Playerbots.h" #include "Position.h" #include "QuestDef.h" @@ -24,7 +28,6 @@ #include "StatsWeightCalculator.h" #include "Timer.h" #include "TravelMgr.h" -#include "BroadcastHelper.h" bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) { @@ -58,12 +61,15 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) botAI->rpgInfo.stuckAttempts = 0; const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId()); std::string zone_name = PlayerbotAI::GetLocalizedAreaName(entry); - LOG_DEBUG("playerbots", "[New Rpg] Teleport {} from ({},{},{},{}) to ({},{},{},{}) as it stuck when moving far - Zone: {} ({})", bot->GetName(), - bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(), - dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name); + LOG_DEBUG( + "playerbots", + "[New RPG] Teleport {} from ({},{},{},{}) to ({},{},{},{}) as it stuck when moving far - Zone: {} ({})", + bot->GetName(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(), + dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), + zone_name); return bot->TeleportTo(dest); } - + float dis = bot->GetExactDist(dest); if (dis < pathFinderDis) { @@ -131,7 +137,7 @@ bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance) else angle = object->GetOrientation() + (M_PI * irand(-25, 25) / 100.0); // 45 degrees infront of target (leading it's movement) - + float rnd = rand_norm(); x += cos(angle) * distance * rnd; y += sin(angle) * distance * rnd; @@ -151,7 +157,7 @@ bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority) { return false; } - + float distance = rand_norm() * moveStep; Map* map = bot->GetMap(); const float x = bot->GetPositionX(); @@ -190,8 +196,9 @@ bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority) bool NewRpgBaseAction::ForceToWait(uint32 duration, MovementPriority priority) { - AI_VALUE(LastMovement&, "last movement").Set(bot->GetMapId(), - bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(), duration, priority); + AI_VALUE(LastMovement&, "last movement") + .Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(), + duration, priority); return true; } @@ -212,27 +219,27 @@ bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid) // } bot->PrepareQuestMenu(guid); - const QuestMenu &menu = bot->PlayerTalkClass->GetQuestMenu(); + const QuestMenu& menu = bot->PlayerTalkClass->GetQuestMenu(); if (menu.Empty()) return true; for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++) { - const QuestMenuItem &item = menu.GetItem(idx); + const QuestMenuItem& item = menu.GetItem(idx); const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId); if (!quest) continue; - const QuestStatus &status = bot->GetQuestStatus(item.QuestId); - if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) && - bot->CanAddQuest(quest, false) && IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest)) + const QuestStatus& status = bot->GetQuestStatus(item.QuestId); + if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) && bot->CanAddQuest(quest, false) && + IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest)) { AcceptQuest(quest, guid); if (botAI->GetMaster()) botAI->TellMasterNoFacing("Quest accepted " + ChatHelper::FormatQuest(quest)); BroadcastHelper::BroadcastQuestAccepted(botAI, bot, quest); botAI->rpgStatistic.questAccepted++; - LOG_DEBUG("playerbots", "[New rpg] {} accept quest {}", bot->GetName(), quest->GetQuestId()); + LOG_DEBUG("playerbots", "[New RPG] {} accept quest {}", bot->GetName(), quest->GetQuestId()); } if (status == QUEST_STATUS_COMPLETE && bot->CanRewardQuest(quest, 0, false)) { @@ -241,7 +248,7 @@ bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid) botAI->TellMasterNoFacing("Quest rewarded " + ChatHelper::FormatQuest(quest)); BroadcastHelper::BroadcastQuestTurnedIn(botAI, bot, quest); botAI->rpgStatistic.questRewarded++; - LOG_DEBUG("playerbots", "[New rpg] {} turned in quest {}", bot->GetName(), quest->GetQuestId()); + LOG_DEBUG("playerbots", "[New RPG] {} turned in quest {}", bot->GetName(), quest->GetQuestId()); } } return true; @@ -273,11 +280,13 @@ bool NewRpgBaseAction::CanInteractWithQuestGiver(Object* questGiver) return false; // Deathstate checks - if (!bot->IsAlive() && !(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_VISIBLE_TO_GHOSTS)) + if (!bot->IsAlive() && + !(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_VISIBLE_TO_GHOSTS)) return false; // alive or spirit healer - if (!creature->IsAlive() && !(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_INTERACT_WHILE_DEAD)) + if (!creature->IsAlive() && + !(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_INTERACT_WHILE_DEAD)) return false; // appropriate npc type @@ -292,11 +301,14 @@ bool NewRpgBaseAction::CanInteractWithQuestGiver(Object* questGiver) if (creature->GetReactionTo(bot) <= REP_UNFRIENDLY) return false; - // pussywizard: many npcs have missing conditions for class training and rogue trainer can for eg. train dual wield to a shaman :/ too many to change in sql and watch in the future - // pussywizard: this function is not used when talking, but when already taking action (buy spell, reset talents, show spell list) - if (npcflagmask & (UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_TRAINER_CLASS) && creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS && !bot->IsClass((Classes)creature->GetCreatureTemplate()->trainer_class, CLASS_CONTEXT_CLASS_TRAINER)) + // pussywizard: many npcs have missing conditions for class training and rogue trainer can for eg. train + // dual wield to a shaman :/ too many to change in sql and watch in the future pussywizard: this function is + // not used when talking, but when already taking action (buy spell, reset talents, show spell list) + if (npcflagmask & (UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_TRAINER_CLASS) && + creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS && + !bot->IsClass((Classes)creature->GetCreatureTemplate()->trainer_class, CLASS_CONTEXT_CLASS_TRAINER)) return false; - + return true; } case TYPEID_GAMEOBJECT: @@ -349,7 +361,7 @@ bool NewRpgBaseAction::IsWithinInteractionDist(Object* questGiver) if (!creature->IsWithinDistInMap(bot, INTERACTION_DISTANCE)) return false; - + return true; } case TYPEID_GAMEOBJECT: @@ -459,7 +471,8 @@ uint32 NewRpgBaseAction::BestRewardIndex(Quest const* quest) bool NewRpgBaseAction::IsQuestWorthDoing(Quest const* quest) { - bool isLowLevelQuest = bot->GetLevel() > (bot->GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF)); + bool isLowLevelQuest = + bot->GetLevel() > (bot->GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF)); if (isLowLevelQuest) return false; @@ -500,7 +513,7 @@ bool NewRpgBaseAction::OrganizeQuestLog() if (!questId) freeSlotNum++; } - + // it's ok if we have two more free slots if (freeSlotNum >= 2) return false; @@ -514,11 +527,10 @@ bool NewRpgBaseAction::OrganizeQuestLog() continue; const Quest* quest = sObjectMgr->GetQuestTemplate(questId); - if (!IsQuestWorthDoing(quest) || - !IsQuestCapableDoing(quest) || + if (!IsQuestWorthDoing(quest) || !IsQuestCapableDoing(quest) || bot->GetQuestStatus(questId) == QUEST_STATUS_FAILED) { - LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId); + LOG_DEBUG("playerbots", "[New RPG] {} drop quest {}", bot->GetName(), questId); WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST); packet << (uint8)i; bot->GetSession()->HandleQuestLogRemoveQuest(packet); @@ -528,7 +540,7 @@ bool NewRpgBaseAction::OrganizeQuestLog() dropped++; } } - + // drop more than 8 quests at once to avoid repeated accept and drop if (dropped >= 8) return true; @@ -541,10 +553,9 @@ bool NewRpgBaseAction::OrganizeQuestLog() continue; const Quest* quest = sObjectMgr->GetQuestTemplate(questId); - if (quest->GetZoneOrSort() < 0 || - (quest->GetZoneOrSort() > 0 && quest->GetZoneOrSort() != bot->GetZoneId())) + if (quest->GetZoneOrSort() < 0 || (quest->GetZoneOrSort() > 0 && quest->GetZoneOrSort() != bot->GetZoneId())) { - LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId); + LOG_DEBUG("playerbots", "[New RPG] {} drop quest {}", bot->GetName(), questId); WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST); packet << (uint8)i; bot->GetSession()->HandleQuestLogRemoveQuest(packet); @@ -566,7 +577,7 @@ bool NewRpgBaseAction::OrganizeQuestLog() continue; const Quest* quest = sObjectMgr->GetQuestTemplate(questId); - LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId); + LOG_DEBUG("playerbots", "[New RPG] {} drop quest {}", bot->GetName(), questId); WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST); packet << (uint8)i; bot->GetSession()->HandleQuestLogRemoveQuest(packet); @@ -604,7 +615,7 @@ ObjectGuid NewRpgBaseAction::ChooseNpcOrGameObjectToInteract(bool questgiverOnly return ObjectGuid(); WorldObject* nearestObject = nullptr; - for (ObjectGuid& guid: possibleTargets) + for (ObjectGuid& guid : possibleTargets) { WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid); @@ -613,7 +624,7 @@ ObjectGuid NewRpgBaseAction::ChooseNpcOrGameObjectToInteract(bool questgiverOnly if (distanceLimit && bot->GetDistance(object) > distanceLimit) continue; - + if (CanInteractWithQuestGiver(object) && HasQuestToAcceptOrReward(object)) { if (!nearestObject || bot->GetExactDist(nearestObject) > bot->GetExactDist(object)) @@ -622,7 +633,7 @@ ObjectGuid NewRpgBaseAction::ChooseNpcOrGameObjectToInteract(bool questgiverOnly } } - for (ObjectGuid& guid: possibleGameObjects) + for (ObjectGuid& guid : possibleGameObjects) { WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid); @@ -631,7 +642,7 @@ ObjectGuid NewRpgBaseAction::ChooseNpcOrGameObjectToInteract(bool questgiverOnly if (distanceLimit && bot->GetDistance(object) > distanceLimit) continue; - + if (CanInteractWithQuestGiver(object) && HasQuestToAcceptOrReward(object)) { if (!nearestObject || bot->GetExactDist(nearestObject) > bot->GetExactDist(object)) @@ -649,7 +660,7 @@ ObjectGuid NewRpgBaseAction::ChooseNpcOrGameObjectToInteract(bool questgiverOnly if (possibleTargets.empty()) return ObjectGuid(); - + int idx = urand(0, possibleTargets.size() - 1); ObjectGuid guid = possibleTargets[idx]; WorldObject* object = ObjectAccessor::GetCreatureOrPetOrVehicle(*bot, guid); @@ -667,17 +678,17 @@ bool NewRpgBaseAction::HasQuestToAcceptOrReward(WorldObject* object) { ObjectGuid guid = object->GetGUID(); bot->PrepareQuestMenu(guid); - const QuestMenu &menu = bot->PlayerTalkClass->GetQuestMenu(); + const QuestMenu& menu = bot->PlayerTalkClass->GetQuestMenu(); if (menu.Empty()) return false; - + for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++) { - const QuestMenuItem &item = menu.GetItem(idx); + const QuestMenuItem& item = menu.GetItem(idx); const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId); if (!quest) continue; - const QuestStatus &status = bot->GetQuestStatus(item.QuestId); + const QuestStatus& status = bot->GetQuestStatus(item.QuestId); if (status == QUEST_STATUS_COMPLETE && bot->CanRewardQuest(quest, 0, false)) { return true; @@ -685,14 +696,14 @@ bool NewRpgBaseAction::HasQuestToAcceptOrReward(WorldObject* object) } for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++) { - const QuestMenuItem &item = menu.GetItem(idx); + const QuestMenuItem& item = menu.GetItem(idx); const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId); if (!quest) continue; - const QuestStatus &status = bot->GetQuestStatus(item.QuestId); - if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) && - bot->CanAddQuest(quest, false) && IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest)) + const QuestStatus& status = bot->GetQuestStatus(item.QuestId); + if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) && bot->CanAddQuest(quest, false) && + IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest)) { return true; } @@ -700,26 +711,29 @@ bool NewRpgBaseAction::HasQuestToAcceptOrReward(WorldObject* object) return false; } -static std::vector GenerateRandomWeights(int n) { +static std::vector GenerateRandomWeights(int n) +{ std::vector weights(n); float sum = 0.0; - for (int i = 0; i < n; ++i) { + for (int i = 0; i < n; ++i) + { weights[i] = rand_norm(); sum += weights[i]; } - for (int i = 0; i < n; ++i) { + for (int i = 0; i < n; ++i) + { weights[i] /= sum; } return weights; } -bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector &poiInfo, bool toComplete) +bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector& poiInfo, bool toComplete) { Quest const* quest = sObjectMgr->GetQuestTemplate(questId); if (!quest) return false; - + const QuestPOIVector* poiVector = sObjectMgr->GetQuestPOIVector(questId); if (!poiVector) { @@ -730,15 +744,15 @@ bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector if (toComplete && q_status.Status == QUEST_STATUS_COMPLETE) { - for (const QuestPOI &qPoi : *poiVector) + for (const QuestPOI& qPoi : *poiVector) { if (qPoi.MapId != bot->GetMapId()) continue; - + // not the poi pos to reward quest if (qPoi.ObjectiveIndex != -1) continue; - + if (qPoi.points.size() == 0) continue; @@ -746,22 +760,22 @@ bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector std::vector weights = GenerateRandomWeights(qPoi.points.size()); for (size_t i = 0; i < qPoi.points.size(); i++) { - const QuestPOIPoint &point = qPoi.points[i]; + const QuestPOIPoint& point = qPoi.points[i]; dx += point.x * weights[i]; dy += point.y * weights[i]; } - + if (bot->GetDistance2d(dx, dy) >= 1500.0f) continue; - + float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy)); - + if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE) continue; - + if (bot->GetZoneId() != bot->GetMap()->GetZoneId(bot->GetPhaseMask(), dx, dy, dz)) continue; - + poiInfo.push_back({{dx, dy}, qPoi.ObjectiveIndex}); } @@ -781,7 +795,7 @@ bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector int32 npcOrGo = quest->RequiredNpcOrGo[i]; if (!npcOrGo) continue; - + if (q_status.CreatureOrGOCount[i] < quest->RequiredNpcOrGoCount[i]) incompleteObjectiveIdx.push_back(i); } @@ -796,7 +810,7 @@ bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector } // Get POIs to go - for (const QuestPOI &qPoi : *poiVector) + for (const QuestPOI& qPoi : *poiVector) { if (qPoi.MapId != bot->GetMapId()) continue; @@ -818,16 +832,16 @@ bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector std::vector weights = GenerateRandomWeights(qPoi.points.size()); for (size_t i = 0; i < qPoi.points.size(); i++) { - const QuestPOIPoint &point = qPoi.points[i]; + const QuestPOIPoint& point = qPoi.points[i]; dx += point.x * weights[i]; dy += point.y * weights[i]; } if (bot->GetDistance2d(dx, dy) >= 1500.0f) continue; - + float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy)); - + if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE) continue; @@ -837,7 +851,8 @@ bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector poiInfo.push_back({{dx, dy}, qPoi.ObjectiveIndex}); } - if (poiInfo.size() == 0) { + if (poiInfo.size() == 0) + { // LOG_DEBUG("playerbots", "[New rpg] {}: No available poi can be found for quest {}", bot->GetName(), questId); return false; } @@ -872,8 +887,8 @@ WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot) if (bot->GetExactDist(loc) > 2500.0f) continue; - if (!inCity && bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) != - bot->GetZoneId()) + if (!inCity && bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), + loc.GetPositionZ()) != bot->GetZoneId()) continue; if (bot->GetExactDist(loc) < hiRange) @@ -897,40 +912,43 @@ WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot) uint32 idx = urand(0, lo_prepared_locs.size() - 1); dest = lo_prepared_locs[idx]; } - LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})", + LOG_DEBUG("playerbots", "[New RPG] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})", bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), hi_prepared_locs.size(), lo_prepared_locs.size() - hi_prepared_locs.size(), locs.size()); return dest; } -WorldPosition NewRpgBaseAction::SelectRandomInnKeeperPos(Player* bot) +WorldPosition NewRpgBaseAction::SelectRandomCampPos(Player* bot) { const std::vector& locs = IsAlliance(bot->getRace()) ? sRandomPlayerbotMgr->allianceStarterPerLevelCache[bot->GetLevel()] : sRandomPlayerbotMgr->hordeStarterPerLevelCache[bot->GetLevel()]; - + bool inCity = false; - + if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(bot->GetZoneId())) { if (zone->flags & AREA_FLAG_CAPITAL) inCity = true; } - + std::vector prepared_locs; for (auto& loc : locs) { if (bot->GetMapId() != loc.GetMapId()) continue; - + float range = bot->GetLevel() <= 5 ? 500.0f : 2500.0f; if (bot->GetExactDist(loc) > range) continue; - if (!inCity && bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) != - bot->GetZoneId()) + if (bot->GetExactDist(loc) < 50.0f) continue; - + + if (!inCity && bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), + loc.GetPositionZ()) != bot->GetZoneId()) + continue; + prepared_locs.push_back(loc); } WorldPosition dest{}; @@ -939,8 +957,265 @@ WorldPosition NewRpgBaseAction::SelectRandomInnKeeperPos(Player* bot) uint32 idx = urand(0, prepared_locs.size() - 1); dest = prepared_locs[idx]; } - LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})", + LOG_DEBUG("playerbots", "[New RPG] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})", bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), prepared_locs.size(), locs.size()); return dest; +} + +bool NewRpgBaseAction::SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, uint32& fromNode, uint32& toNode) +{ + const std::vector& flightMasters = IsAlliance(bot->getRace()) + ? sRandomPlayerbotMgr->allianceFlightMasterCache + : sRandomPlayerbotMgr->hordeFlightMasterCache; + Creature* nearestFlightMaster = nullptr; + for (const uint32& guid : flightMasters) + { + Creature* flightMaster = ObjectAccessor::GetSpawnedCreatureByDBGUID(bot->GetMapId(), guid); + if (!flightMaster) + continue; + + if (bot->GetMapId() != flightMaster->GetMapId()) + continue; + + if (!nearestFlightMaster || bot->GetDistance(nearestFlightMaster) > bot->GetDistance(flightMaster)) + nearestFlightMaster = flightMaster; + } + if (!nearestFlightMaster || bot->GetDistance(nearestFlightMaster) > 500.0f) + return false; + + fromNode = sObjectMgr->GetNearestTaxiNode(nearestFlightMaster->GetPositionX(), nearestFlightMaster->GetPositionY(), + nearestFlightMaster->GetPositionZ(), nearestFlightMaster->GetMapId(), + bot->GetTeamId()); + + if (!fromNode) + return false; + + std::vector availableToNodes; + for (uint32 i = 1; i < sTaxiNodesStore.GetNumRows(); ++i) + { + if (fromNode == i) + continue; + + TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(i); + + // check map + if (!node || node->map_id != bot->GetMapId() || + (!node->MountCreatureID[bot->GetTeamId() == TEAM_ALLIANCE ? 1 : 0])) // dk flight + continue; + + // check taxi node known + if (!bot->isTaxiCheater() && !bot->m_taxi.IsTaximaskNodeKnown(i)) + continue; + + // check distance by level + if (!botAI->CheckLocationDistanceByLevel(bot, WorldLocation(node->map_id, node->x, node->y, node->z), false)) + continue; + + // check path + uint32 path, cost; + sObjectMgr->GetTaxiPath(fromNode, i, path, cost); + if (!path) + continue; + + // check area level + uint32 nodeZoneId = bot->GetMap()->GetZoneId(bot->GetPhaseMask(), node->x, node->y, node->z); + bool capital = false; + if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(nodeZoneId)) + { + capital = zone->flags & AREA_FLAG_CAPITAL; + } + + auto itr = sRandomPlayerbotMgr->zone2LevelBracket.find(nodeZoneId); + if (!capital && itr == sRandomPlayerbotMgr->zone2LevelBracket.end()) + continue; + + if (!capital && (bot->GetLevel() < itr->second.low || bot->GetLevel() > itr->second.high)) + continue; + + availableToNodes.push_back(i); + } + if (availableToNodes.empty()) + return false; + + flightMaster = nearestFlightMaster->GetGUID(); + toNode = availableToNodes[urand(0, availableToNodes.size() - 1)]; + LOG_DEBUG("playerbots", "[New RPG] Bot {} select random flight taxi node from:{} (node {}) to:{} ({} available)", + bot->GetName(), flightMaster.GetEntry(), fromNode, toNode, availableToNodes.size()); + return true; +} + +bool NewRpgBaseAction::RandomChangeStatus(std::vector candidateStatus) +{ + std::vector availableStatus; + uint32 probSum = 0; + for (NewRpgStatus status : candidateStatus) + { + if (sPlayerbotAIConfig->RpgStatusProbWeight[status] == 0) + continue; + + if (CheckRpgStatusAvailable(status)) + { + availableStatus.push_back(status); + probSum += sPlayerbotAIConfig->RpgStatusProbWeight[status]; + } + } + uint32 rand = urand(1, probSum); + uint32 accumulate = 0; + NewRpgStatus chosenStatus = RPG_STATUS_END; + for (NewRpgStatus status : availableStatus) + { + accumulate += sPlayerbotAIConfig->RpgStatusProbWeight[status]; + if (accumulate >= rand) + { + chosenStatus = status; + break; + } + } + + switch (chosenStatus) + { + case RPG_WANDER_RANDOM: + { + botAI->rpgInfo.ChangeToWanderRandom(); + return true; + } + case RPG_WANDER_NPC: + { + botAI->rpgInfo.ChangeToWanderNpc(); + return true; + } + case RPG_GO_GRIND: + { + WorldPosition pos = SelectRandomGrindPos(bot); + if (pos != WorldPosition()) + { + botAI->rpgInfo.ChangeToGoGrind(pos); + return true; + } + return false; + } + case RPG_GO_CAMP: + { + WorldPosition pos = SelectRandomCampPos(bot); + if (pos != WorldPosition()) + { + botAI->rpgInfo.ChangeToGoCamp(pos); + return true; + } + return false; + } + case RPG_DO_QUEST: + { + std::vector availableQuests; + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 questId = bot->GetQuestSlotQuestId(slot); + if (botAI->lowPriorityQuest.find(questId) != botAI->lowPriorityQuest.end()) + continue; + + std::vector poiInfo; + if (GetQuestPOIPosAndObjectiveIdx(questId, poiInfo, true)) + { + availableQuests.push_back(questId); + } + } + if (availableQuests.size()) + { + uint32 questId = availableQuests[urand(0, availableQuests.size() - 1)]; + const Quest* quest = sObjectMgr->GetQuestTemplate(questId); + if (quest) + { + botAI->rpgInfo.ChangeToDoQuest(questId, quest); + return true; + } + } + return false; + } + case RPG_TRAVEL_FLIGHT: + { + ObjectGuid flightMaster; + uint32 fromNode, toNode; + if (SelectRandomFlightTaxiNode(flightMaster, fromNode, toNode)) + { + botAI->rpgInfo.ChangeToTravelFlight(flightMaster, fromNode, toNode); + return true; + } + return false; + } + case RPG_IDLE: + { + botAI->rpgInfo.ChangeToIdle(); + return true; + } + case RPG_REST: + { + botAI->rpgInfo.ChangeToRest(); + bot->SetStandState(UNIT_STAND_STATE_SIT); + return true; + } + default: + { + botAI->rpgInfo.ChangeToRest(); + bot->SetStandState(UNIT_STAND_STATE_SIT); + return true; + } + } + return false; +} + +bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status) +{ + switch (status) + { + case RPG_IDLE: + case RPG_REST: + return true; + case RPG_WANDER_RANDOM: + { + Unit* target = AI_VALUE(Unit*, "grind target"); + return target != nullptr; + } + case RPG_GO_GRIND: + { + WorldPosition pos = SelectRandomGrindPos(bot); + return pos != WorldPosition(); + } + case RPG_GO_CAMP: + { + WorldPosition pos = SelectRandomCampPos(bot); + return pos != WorldPosition(); + } + case RPG_WANDER_NPC: + { + GuidVector possibleTargets = AI_VALUE(GuidVector, "possible new rpg targets"); + return possibleTargets.size() >= 3; + } + case RPG_DO_QUEST: + { + std::vector availableQuests; + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 questId = bot->GetQuestSlotQuestId(slot); + if (botAI->lowPriorityQuest.find(questId) != botAI->lowPriorityQuest.end()) + continue; + + std::vector poiInfo; + if (GetQuestPOIPosAndObjectiveIdx(questId, poiInfo, true)) + { + return true; + } + } + return false; + } + case RPG_TRAVEL_FLIGHT: + { + ObjectGuid flightMaster; + uint32 fromNode, toNode; + return SelectRandomFlightTaxiNode(flightMaster, fromNode, toNode); + } + default: + return false; + } + return false; } \ No newline at end of file diff --git a/src/strategy/rpg/NewRpgBaseAction.h b/src/strategy/rpg/NewRpgBaseAction.h index 5fc2e1cd..0d2a96fb 100644 --- a/src/strategy/rpg/NewRpgBaseAction.h +++ b/src/strategy/rpg/NewRpgBaseAction.h @@ -4,15 +4,17 @@ #include "Duration.h" #include "LastMovementValue.h" #include "MovementActions.h" +#include "NewRpgInfo.h" #include "NewRpgStrategy.h" #include "Object.h" #include "ObjectDefines.h" #include "ObjectGuid.h" +#include "PlayerbotAI.h" #include "QuestDef.h" #include "TravelMgr.h" -#include "PlayerbotAI.h" -struct POIInfo { +struct POIInfo +{ G3D::Vector2 pos; int32 objectiveIdx; }; @@ -24,15 +26,15 @@ class NewRpgBaseAction : public MovementAction { public: NewRpgBaseAction(PlayerbotAI* botAI, std::string name) : MovementAction(botAI, name) {} - + protected: - // MOVEMENT RELATED + /* MOVEMENT RELATED */ bool MoveFarTo(WorldPosition dest); bool MoveWorldObjectTo(ObjectGuid guid, float distance = INTERACTION_DISTANCE); bool MoveRandomNear(float moveStep = 50.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool ForceToWait(uint32 duration, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); - // QUEST RELATED CHECK + /* QUEST RELATED CHECK */ ObjectGuid ChooseNpcOrGameObjectToInteract(bool questgiverOnly = false, float distanceLimit = 0.0f); bool HasQuestToAcceptOrReward(WorldObject* object); bool InteractWithNpcOrGameObjectForQuest(ObjectGuid guid); @@ -41,20 +43,24 @@ protected: uint32 BestRewardIndex(Quest const* quest); bool IsQuestWorthDoing(Quest const* quest); bool IsQuestCapableDoing(Quest const* quest); - // QUEST RELATED ACTION + + /* QUEST RELATED ACTION */ bool SearchQuestGiverAndAcceptOrReward(); bool AcceptQuest(Quest const* quest, ObjectGuid guid); bool TurnInQuest(Quest const* quest, ObjectGuid guid); bool OrganizeQuestLog(); protected: - bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector &poiInfo, bool toComplete = false); + bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector& poiInfo, bool toComplete = false); static WorldPosition SelectRandomGrindPos(Player* bot); - static WorldPosition SelectRandomInnKeeperPos(Player* bot); + static WorldPosition SelectRandomCampPos(Player* bot); + bool SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, uint32& fromNode, uint32& toNode); + bool RandomChangeStatus(std::vector candidateStatus); + bool CheckRpgStatusAvailable(NewRpgStatus status); protected: - // WorldPosition dest; - const float pathFinderDis = 70.0f; // path finder + /* FOR MOVE FAR */ + const float pathFinderDis = 70.0f; const uint32 stuckTime = 5 * 60 * 1000; }; diff --git a/src/strategy/rpg/NewRpgInfo.cpp b/src/strategy/rpg/NewRpgInfo.cpp index a9c82646..ff830464 100644 --- a/src/strategy/rpg/NewRpgInfo.cpp +++ b/src/strategy/rpg/NewRpgInfo.cpp @@ -1,4 +1,7 @@ #include "NewRpgInfo.h" + +#include + #include "Timer.h" void NewRpgInfo::ChangeToGoGrind(WorldPosition pos) @@ -9,26 +12,26 @@ void NewRpgInfo::ChangeToGoGrind(WorldPosition pos) go_grind.pos = pos; } -void NewRpgInfo::ChangeToGoInnkeeper(WorldPosition pos) +void NewRpgInfo::ChangeToGoCamp(WorldPosition pos) { Reset(); - status = RPG_GO_INNKEEPER; - go_innkeeper = GoInnkeeper(); - go_innkeeper.pos = pos; + status = RPG_GO_CAMP; + go_camp = GoCamp(); + go_camp.pos = pos; } -void NewRpgInfo::ChangeToNearNpc() +void NewRpgInfo::ChangeToWanderNpc() { Reset(); - status = RPG_NEAR_NPC; - near_npc = NearNpc(); + status = RPG_WANDER_NPC; + wander_npc = WanderNpc(); } -void NewRpgInfo::ChangeToNearRandom() +void NewRpgInfo::ChangeToWanderRandom() { Reset(); - status = RPG_NEAR_RANDOM; - near_random = NearRandom(); + status = RPG_WANDER_RANDOM; + WANDER_RANDOM = WanderRandom(); } void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest) @@ -40,6 +43,16 @@ void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest) do_quest.quest = quest; } +void NewRpgInfo::ChangeToTravelFlight(ObjectGuid fromFlightMaster, uint32 fromNode, uint32 toNode) +{ + Reset(); + status = RPG_TRAVEL_FLIGHT; + flight = TravelFlight(); + flight.fromFlightMaster = fromFlightMaster; + flight.fromNode = fromNode; + flight.toNode = toNode; +} + void NewRpgInfo::ChangeToRest() { Reset(); @@ -53,10 +66,7 @@ void NewRpgInfo::ChangeToIdle() status = RPG_IDLE; } -bool NewRpgInfo::CanChangeTo(NewRpgStatus status) -{ - return true; -} +bool NewRpgInfo::CanChangeTo(NewRpgStatus status) { return true; } void NewRpgInfo::Reset() { @@ -80,23 +90,25 @@ std::string NewRpgInfo::ToString() { case RPG_GO_GRIND: out << "GO_GRIND"; - out << "\nGrindPos: " << go_grind.pos.GetMapId() << " " << go_grind.pos.GetPositionX() << " " << go_grind.pos.GetPositionY() << " " << go_grind.pos.GetPositionZ(); + out << "\nGrindPos: " << go_grind.pos.GetMapId() << " " << go_grind.pos.GetPositionX() << " " + << go_grind.pos.GetPositionY() << " " << go_grind.pos.GetPositionZ(); out << "\nlastGoGrind: " << startT; break; - case RPG_GO_INNKEEPER: - out << "GO_INNKEEPER"; - out << "\nInnKeeperPos: " << go_innkeeper.pos.GetMapId() << " " << go_innkeeper.pos.GetPositionX() << " " << go_innkeeper.pos.GetPositionY() << " " << go_innkeeper.pos.GetPositionZ(); - out << "\nlastGoInnKeeper: " << startT; + case RPG_GO_CAMP: + out << "GO_CAMP"; + out << "\nCampPos: " << go_camp.pos.GetMapId() << " " << go_camp.pos.GetPositionX() << " " + << go_camp.pos.GetPositionY() << " " << go_camp.pos.GetPositionZ(); + out << "\nlastGoCamp: " << startT; break; - case RPG_NEAR_NPC: - out << "NEAR_NPC"; - out << "\nnpcOrGoEntry: " << near_npc.npcOrGo.GetCounter(); - out << "\nlastNearNpc: " << startT; - out << "\nlastReachNpcOrGo: " << near_npc.lastReach; + case RPG_WANDER_NPC: + out << "WANDER_NPC"; + out << "\nnpcOrGoEntry: " << wander_npc.npcOrGo.GetCounter(); + out << "\nlastWanderNpc: " << startT; + out << "\nlastReachNpcOrGo: " << wander_npc.lastReach; break; - case RPG_NEAR_RANDOM: - out << "NEAR_RANDOM"; - out << "\nlastNearRandom: " << startT; + case RPG_WANDER_RANDOM: + out << "WANDER_RANDOM"; + out << "\nlastWanderRandom: " << startT; break; case RPG_IDLE: out << "IDLE"; @@ -109,8 +121,16 @@ std::string NewRpgInfo::ToString() out << "DO_QUEST"; out << "\nquestId: " << do_quest.questId; out << "\nobjectiveIdx: " << do_quest.objectiveIdx; - out << "\npoiPos: " << do_quest.pos.GetMapId() << " " << do_quest.pos.GetPositionX() << " " << do_quest.pos.GetPositionY() << " " << do_quest.pos.GetPositionZ(); - out << "\nlastReachPOI: " << do_quest.lastReachPOI; + out << "\npoiPos: " << do_quest.pos.GetMapId() << " " << do_quest.pos.GetPositionX() << " " + << do_quest.pos.GetPositionY() << " " << do_quest.pos.GetPositionZ(); + out << "\nlastReachPOI: " << do_quest.lastReachPOI ? GetMSTimeDiffToNow(do_quest.lastReachPOI) : 0; + break; + case RPG_TRAVEL_FLIGHT: + out << "TRAVEL_FLIGHT"; + out << "\nfromFlightMaster: " << flight.fromFlightMaster.GetEntry(); + out << "\nfromNode: " << flight.fromNode; + out << "\ntoNode: " << flight.toNode; + out << "\ninFlight: " << flight.inFlight; break; default: out << "UNKNOWN"; diff --git a/src/strategy/rpg/NewRpgInfo.h b/src/strategy/rpg/NewRpgInfo.h index d4b39b25..5c14f70d 100644 --- a/src/strategy/rpg/NewRpgInfo.h +++ b/src/strategy/rpg/NewRpgInfo.h @@ -9,67 +9,61 @@ #include "Timer.h" #include "TravelMgr.h" -enum NewRpgStatus: int -{ - RPG_STATUS_START = 0, - // Going to far away place - RPG_GO_GRIND = 0, - RPG_GO_INNKEEPER = 1, - // Exploring nearby - RPG_NEAR_RANDOM = 2, - RPG_NEAR_NPC = 3, - // Do Quest (based on quest status) - RPG_DO_QUEST = 4, - // Taking a break - RPG_REST = 5, - // Initial status - RPG_IDLE = 6, - RPG_STATUS_END = 7 -}; - using NewRpgStatusTransitionProb = std::vector>; struct NewRpgInfo { NewRpgInfo() {} - + // RPG_GO_GRIND - struct GoGrind { - GoGrind() = default; + struct GoGrind + { WorldPosition pos{}; }; - // RPG_GO_INNKEEPER - struct GoInnkeeper { - GoInnkeeper() = default; + // RPG_GO_CAMP + struct GoCamp + { WorldPosition pos{}; }; - // RPG_NEAR_NPC - struct NearNpc { - NearNpc() = default; + // RPG_WANDER_NPC + struct WanderNpc + { ObjectGuid npcOrGo{}; uint32 lastReach{0}; }; - // RPG_NEAR_RANDOM - struct NearRandom { - NearRandom() = default; + // RPG_WANDER_RANDOM + struct WanderRandom + { + WanderRandom() = default; }; - // NewRpgStatus::QUESTING - struct DoQuest { + // RPG_DO_QUEST + struct DoQuest + { const Quest* quest{nullptr}; uint32 questId{0}; int32 objectiveIdx{0}; WorldPosition pos{}; uint32 lastReachPOI{0}; }; + // RPG_TRAVEL_FLIGHT + struct TravelFlight + { + ObjectGuid fromFlightMaster{}; + uint32 fromNode{0}; + uint32 toNode{0}; + bool inFlight{false}; + }; // RPG_REST - struct Rest { + struct Rest + { Rest() = default; }; - struct Idle { + struct Idle + { }; NewRpgStatus status{RPG_IDLE}; - uint32 startT{0}; // start timestamp of the current status + uint32 startT{0}; // start timestamp of the current status // MOVE_FAR float nearestMoveFarDis{FLT_MAX}; @@ -78,22 +72,25 @@ struct NewRpgInfo WorldPosition moveFarPos; // END MOVE_FAR - union { + union + { GoGrind go_grind; - GoInnkeeper go_innkeeper; - NearNpc near_npc; - NearRandom near_random; + GoCamp go_camp; + WanderNpc wander_npc; + WanderRandom WANDER_RANDOM; DoQuest do_quest; Rest rest; DoQuest quest; + TravelFlight flight; }; bool HasStatusPersisted(uint32 maxDuration) { return GetMSTimeDiffToNow(startT) > maxDuration; } void ChangeToGoGrind(WorldPosition pos); - void ChangeToGoInnkeeper(WorldPosition pos); - void ChangeToNearNpc(); - void ChangeToNearRandom(); + void ChangeToGoCamp(WorldPosition pos); + void ChangeToWanderNpc(); + void ChangeToWanderRandom(); void ChangeToDoQuest(uint32 questId, const Quest* quest); + void ChangeToTravelFlight(ObjectGuid fromFlightMaster, uint32 fromNode, uint32 toNode); void ChangeToRest(); void ChangeToIdle(); bool CanChangeTo(NewRpgStatus status); diff --git a/src/strategy/rpg/NewRpgStrategy.cpp b/src/strategy/rpg/NewRpgStrategy.cpp index 9a078d4a..c4616059 100644 --- a/src/strategy/rpg/NewRpgStrategy.cpp +++ b/src/strategy/rpg/NewRpgStrategy.cpp @@ -23,16 +23,19 @@ void NewRpgStrategy::InitTriggers(std::vector& triggers) new TriggerNode("go grind status", NextAction::array(0, new NextAction("new rpg go grind", 3.0f), nullptr))); triggers.push_back( - new TriggerNode("go innkeeper status", NextAction::array(0, new NextAction("new rpg go innkeeper", 3.0f), nullptr))); + new TriggerNode("go camp status", NextAction::array(0, new NextAction("new rpg go camp", 3.0f), nullptr))); triggers.push_back( - new TriggerNode("near random status", NextAction::array(0, new NextAction("new rpg move random", 3.0f), nullptr))); + new TriggerNode("wander random status", NextAction::array(0, new NextAction("new rpg wander random", 3.0f), nullptr))); triggers.push_back( - new TriggerNode("near npc status", NextAction::array(0, new NextAction("new rpg move npc", 3.0f), nullptr))); + new TriggerNode("wander npc status", NextAction::array(0, new NextAction("new rpg wander npc", 3.0f), nullptr))); triggers.push_back( new TriggerNode("do quest status", NextAction::array(0, new NextAction("new rpg do quest", 3.0f), nullptr))); + + triggers.push_back( + new TriggerNode("travel flight status", NextAction::array(0, new NextAction("new rpg travel flight", 3.0f), nullptr))); } void NewRpgStrategy::InitMultipliers(std::vector& multipliers) diff --git a/src/strategy/triggers/TriggerContext.h b/src/strategy/triggers/TriggerContext.h index 340c5808..8fcf60bc 100644 --- a/src/strategy/triggers/TriggerContext.h +++ b/src/strategy/triggers/TriggerContext.h @@ -221,10 +221,11 @@ public: creators["rpg trade useful"] = &TriggerContext::rpg_trade_useful; creators["rpg duel"] = &TriggerContext::rpg_duel; creators["go grind status"] = &TriggerContext::go_grind_status; - creators["go innkeeper status"] = &TriggerContext::go_innkeeper_status; - creators["near random status"] = &TriggerContext::near_random_status; - creators["near npc status"] = &TriggerContext::near_npc_status; + creators["go camp status"] = &TriggerContext::go_camp_status; + creators["wander random status"] = &TriggerContext::wander_random_status; + creators["wander npc status"] = &TriggerContext::wander_npc_status; creators["do quest status"] = &TriggerContext::do_quest_status; + creators["travel flight status"] = &TriggerContext::travel_flight_status; creators["can self resurrect"] = &TriggerContext::can_self_resurrect; } @@ -418,10 +419,11 @@ private: static Trigger* rpg_trade_useful(PlayerbotAI* botAI) { return new RpgTradeUsefulTrigger(botAI); } static Trigger* rpg_duel(PlayerbotAI* botAI) { return new RpgDuelTrigger(botAI); } static Trigger* go_grind_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_GO_GRIND); } - static Trigger* go_innkeeper_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_GO_INNKEEPER); } - static Trigger* near_random_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_NEAR_RANDOM); } - static Trigger* near_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_NEAR_NPC); } + static Trigger* go_camp_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_GO_CAMP); } + static Trigger* wander_random_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_WANDER_RANDOM); } + static Trigger* wander_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_WANDER_NPC); } static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); } + static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); } static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); } }; diff --git a/src/strategy/values/GrindTargetValue.cpp b/src/strategy/values/GrindTargetValue.cpp index 71b5dbf6..1737b7a7 100644 --- a/src/strategy/values/GrindTargetValue.cpp +++ b/src/strategy/values/GrindTargetValue.cpp @@ -8,8 +8,8 @@ #include "NewRpgInfo.h" #include "Playerbots.h" #include "ReputationMgr.h" -#include "SharedDefines.h" #include "ServerFacade.h" +#include "SharedDefines.h" Unit* GrindTargetValue::Calculate() { @@ -34,7 +34,8 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount) Group* group = bot->GetGroup(); Player* master = GetMaster(); - if (master && (master == bot || master->GetMapId() != bot->GetMapId() || master->IsBeingTeleported() || !GET_PLAYERBOT_AI(master))) + if (master && (master == bot || master->GetMapId() != bot->GetMapId() || master->IsBeingTeleported() || + !GET_PLAYERBOT_AI(master))) master = nullptr; GuidVector attackers = context->GetValue("attackers")->Get(); @@ -89,16 +90,16 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount) // !sRandomPlayerbotMgr->IsRandomBot(bot)) continue; // Bots in bot-groups no have a more limited range to look for grind target - if (!bot->InBattleground() && master && botAI->HasStrategy("follow", BotState::BOT_STATE_NON_COMBAT) - && sServerFacade->GetDistance2d(master, unit) > sPlayerbotAIConfig->lootDistance) + if (!bot->InBattleground() && master && botAI->HasStrategy("follow", BotState::BOT_STATE_NON_COMBAT) && + sServerFacade->GetDistance2d(master, unit) > sPlayerbotAIConfig->lootDistance) { if (botAI->HasStrategy("debug grind", BotState::BOT_STATE_NON_COMBAT)) botAI->TellMaster(chat->FormatWorldobject(unit) + " ignored (far from master)."); continue; } - if (!bot->InBattleground() && (int)unit->GetLevel() - (int)bot->GetLevel() > 4 && !unit->GetGUID().IsPlayer()) - continue; + if (!bot->InBattleground() && (int)unit->GetLevel() - (int)bot->GetLevel() > 4 && !unit->GetGUID().IsPlayer()) + continue; if (Creature* creature = unit->ToCreature()) if (CreatureTemplate const* CreatureTemplate = creature->GetCreatureTemplate()) @@ -110,11 +111,7 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount) continue; } - bool inactiveGrindStatus = botAI->rpgInfo.status == RPG_GO_GRIND || - botAI->rpgInfo.status == RPG_NEAR_NPC || - botAI->rpgInfo.status == RPG_REST || - botAI->rpgInfo.status == RPG_GO_INNKEEPER || - botAI->rpgInfo.status == RPG_DO_QUEST; + bool inactiveGrindStatus = botAI->rpgInfo.status != RPG_WANDER_RANDOM && botAI->rpgInfo.status != RPG_IDLE; float aggroRange = 30.0f; if (unit->ToCreature())