From 53a607e1478b0bbea2c73a6676231621bdb3e9b5 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:16:58 -0700 Subject: [PATCH] Enable bots to do Outdoor pvp (#2217) ## Pull Request Description Bots will now engage with outdoor pvp targets when in an area with them. I carved this out of the guildrpg system Im working on since it should work just fine as a standalone. Note this requires a core update https://github.com/azerothcore/azerothcore-wotlk/pull/25103 ## Feature Evaluation Its not expensive. the status checks are fairly light and simple. Should be on par with current rpg system actions ## How to Test the Changes You can try to use selfbot to enable this while in EPL, or set the probability of all other rpg actions to 0. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - [ ] No, not at all - [x] Minimal impact (**explain below**) - [ ] Moderate impact (**explain below**) There is some impact, but should be minimal overall. - Does this change modify default bot behavior? - [ ] No - [x] Yes (**explain why**) It will activate automatically based on default config. - Does this change add new decision branches or increase maintenance complexity? - [ ] No - [x] Yes (**explain below**) ## AI Assistance Was AI assistance used while working on this change? - [ ] No - [x] Yes (**explain below**) Nothing beyond search functionality and autocomplete. ## Final Checklist - [x] Stability is not compromised. - [x] Performance impact is understood, tested, and acceptable. - [x] Added logic complexity is justified and explained. - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- conf/playerbots.conf.dist | 2 + src/Ai/Base/ActionContext.h | 3 + src/Ai/Base/TriggerContext.h | 2 + src/Ai/World/Rpg/Action/NewRpgAction.cpp | 11 +- src/Ai/World/Rpg/Action/NewRpgAction.h | 9 +- src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp | 21 ++- src/Ai/World/Rpg/Action/NewRpgBaseAction.h | 2 +- src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.cpp | 128 +++++++++++++++++++ src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.h | 19 +++ src/Ai/World/Rpg/NewRpgInfo.cpp | 17 +++ src/Ai/World/Rpg/NewRpgInfo.h | 9 +- src/Ai/World/Rpg/Strategy/NewRpgStrategy.cpp | 8 ++ src/Bot/RandomPlayerbotMgr.cpp | 4 +- src/PlayerbotAIConfig.cpp | 1 + src/PlayerbotAIConfig.h | 3 +- 15 files changed, 226 insertions(+), 13 deletions(-) create mode 100644 src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.cpp create mode 100644 src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.h diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index ccb392e1a..88920a050 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1038,6 +1038,7 @@ AiPlayerbot.EnableNewRpgStrategy = 1 # 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) +# OutdoorPvp (Default: 10 Participate in outdoor PvP capture points if already in an outdoor PvP zone) AiPlayerbot.RpgStatusProbWeight.WanderRandom = 15 AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20 AiPlayerbot.RpgStatusProbWeight.GoGrind = 15 @@ -1045,6 +1046,7 @@ AiPlayerbot.RpgStatusProbWeight.GoCamp = 10 AiPlayerbot.RpgStatusProbWeight.DoQuest = 60 AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15 AiPlayerbot.RpgStatusProbWeight.Rest = 5 +AiPlayerbot.RpgStatusProbWeight.OutdoorPvp = 10 # Bots' minimum and maximum level when teleporting in and out of a zone, according to the new RPG strategy # Format: AiPlayerbot.ZoneBracket.zoneID = minLevel,maxLevel diff --git a/src/Ai/Base/ActionContext.h b/src/Ai/Base/ActionContext.h index 41763be90..79b5b4985 100644 --- a/src/Ai/Base/ActionContext.h +++ b/src/Ai/Base/ActionContext.h @@ -63,6 +63,7 @@ #include "WorldBuffAction.h" #include "XpGainAction.h" #include "NewRpgAction.h" +#include "NewRpgOutdoorPvP.h" #include "FishingAction.h" #include "CancelChannelAction.h" #include "WaitForAttackAction.h" @@ -265,6 +266,7 @@ public: 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; + creators["new rpg outdoor pvp"] = &ActionContext::new_rpg_outdoor_pvp; creators["wait for attack keep safe distance"] = &ActionContext::wait_for_attack_keep_safe_distance; } @@ -462,6 +464,7 @@ private: 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); } + static Action* new_rpg_outdoor_pvp(PlayerbotAI* ai) { return new NewRpgOutdoorPvpAction(ai); } static Action* wait_for_attack_keep_safe_distance(PlayerbotAI* ai) { return new WaitForAttackKeepSafeDistanceAction(ai); } }; diff --git a/src/Ai/Base/TriggerContext.h b/src/Ai/Base/TriggerContext.h index c77df3a31..bfdddecf7 100644 --- a/src/Ai/Base/TriggerContext.h +++ b/src/Ai/Base/TriggerContext.h @@ -230,6 +230,7 @@ public: 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["outdoor pvp status"] = &TriggerContext::outdoor_pvp_status; creators["can self resurrect"] = &TriggerContext::can_self_resurrect; creators["can fish"] = &TriggerContext::can_fish; creators["can use fishing bobber"] = &TriggerContext::can_use_fishing_bobber; @@ -436,6 +437,7 @@ private: 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* outdoor_pvp_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_OUTDOOR_PVP); } static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); } static Trigger* can_fish(PlayerbotAI* ai) { return new CanFishTrigger(ai); } static Trigger* can_use_fishing_bobber(PlayerbotAI* ai) { return new CanUseFishingBobberTrigger(ai); } diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.cpp b/src/Ai/World/Rpg/Action/NewRpgAction.cpp index ddd1240da..ca0ca2433 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgAction.cpp @@ -62,7 +62,7 @@ bool NewRpgStatusUpdateAction::Execute(Event /*event*/) { case RPG_IDLE: return RandomChangeStatus({RPG_GO_CAMP, RPG_GO_GRIND, RPG_WANDER_RANDOM, RPG_WANDER_NPC, RPG_DO_QUEST, - RPG_TRAVEL_FLIGHT, RPG_REST}); + RPG_TRAVEL_FLIGHT, RPG_REST, RPG_OUTDOOR_PVP}); case RPG_GO_GRIND: { @@ -140,6 +140,15 @@ bool NewRpgStatusUpdateAction::Execute(Event /*event*/) } break; } + case RPG_OUTDOOR_PVP: + { + if (info.HasStatusPersisted(statusOutDoorPvPDuration)) + { + info.ChangeToIdle(); + return true; + } + break; + } default: break; } diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.h b/src/Ai/World/Rpg/Action/NewRpgAction.h index a8cb7a2bc..83594204b 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.h +++ b/src/Ai/World/Rpg/Action/NewRpgAction.h @@ -47,10 +47,11 @@ public: protected: // static NewRpgStatusTransitionProb transitionMat; - const int32 statusWanderNpcDuration = 5 * 60 * 1000; - const int32 statusWanderRandomDuration = 5 * 60 * 1000; - const int32 statusRestDuration = 30 * 1000; - const int32 statusDoQuestDuration = 30 * 60 * 1000; + const int32 statusWanderNpcDuration = 5 * MINUTE * IN_MILLISECONDS ; + const int32 statusWanderRandomDuration = 5 * MINUTE * IN_MILLISECONDS ; + const int32 statusRestDuration = 30 * IN_MILLISECONDS ; + const int32 statusDoQuestDuration = 30 * MINUTE * IN_MILLISECONDS ; + const int32 statusOutDoorPvPDuration = HOUR * IN_MILLISECONDS ; }; class NewRpgGoGrindAction : public NewRpgBaseAction diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp index 986b2f0f5..092b11538 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp @@ -12,6 +12,7 @@ #include "NewRpgStrategy.h" #include "Object.h" #include "ObjectAccessor.h" +#include "OutdoorPvPMgr.h" #include "ObjectDefines.h" #include "ObjectGuid.h" #include "ObjectMgr.h" @@ -222,12 +223,10 @@ bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance) return MoveTo(mapId, x, y, z, false, false, false, true); } -bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority) +bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority, WorldObject* center) { if (IsWaitingForLastMove(priority)) - { return false; - } Map* map = bot->GetMap(); const float x = bot->GetPositionX(); @@ -1160,6 +1159,11 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector candidateSta bot->SetStandState(UNIT_STAND_STATE_SIT); return true; } + case RPG_OUTDOOR_PVP: + { + botAI->rpgInfo.ChangeToOutdoorPvp(); + return true; + } default: { botAI->rpgInfo.ChangeToRest(); @@ -1220,6 +1224,17 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status) std::vector path; return SelectRandomFlightTaxiNode(flightMaster, path); } + case RPG_OUTDOOR_PVP: + { + if (!bot->IsPvP()) + return false; + uint32 zoneId = bot->GetZoneId(); + if (zoneId == AREA_NAGRAND) + return false; + + OutdoorPvP* outdoorPvP = sOutdoorPvPMgr->GetOutdoorPvPToZoneId(zoneId); + return outdoorPvP != nullptr; + } default: return false; } diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h index f17891ffc..eaba72446 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h @@ -31,7 +31,7 @@ protected: /* 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 MoveRandomNear(float moveStep = 50.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, WorldObject* center = nullptr); bool ForceToWait(uint32 duration, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); /* QUEST RELATED CHECK */ diff --git a/src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.cpp b/src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.cpp new file mode 100644 index 000000000..042246cfb --- /dev/null +++ b/src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.cpp @@ -0,0 +1,128 @@ +#include "NewRpgOutdoorPvP.h" +#include "OutdoorPvP.h" +#include "OutdoorPvPMgr.h" + +bool NewRpgOutdoorPvpAction::Execute(Event event) +{ + if (!bot->IsPvP()) + { + botAI->rpgInfo.ChangeToIdle(); + return false; + } + if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL) || !bot->IsOutdoorPvPActive()) + return false; + + uint32 zoneId = bot->GetZoneId(); + OutdoorPvP* outdoorPvP = sOutdoorPvPMgr->GetOutdoorPvPToZoneId(zoneId); + if (!outdoorPvP || zoneId == AREA_NAGRAND) + { + botAI->rpgInfo.ChangeToIdle(); + return false; + } + + OutdoorPvP::OPvPCapturePointMap const& capturePointMap = outdoorPvP->GetCapturePoints(); + + NewRpgInfo& info = botAI->rpgInfo; + auto* dataPtr = std::get_if(&info.data); + if (!dataPtr) + return false; + auto& data = *dataPtr; + // Re-resolve stored spawn ID from the capture point map each tick (avoids dangling pointers) + OPvPCapturePoint* objective = nullptr; + if (data.capturePointSpawnId && !capturePointMap.empty()) + { + auto it = capturePointMap.find(data.capturePointSpawnId); + if (it != capturePointMap.end()) + { + OPvPCapturePoint* capturePoint = it->second; + if (capturePoint && capturePoint->_capturePoint) + { + float threshold = capturePoint->GetMinValue(); + float slider = capturePoint->GetSlider(); + uint8 faction = bot->GetTeamId(); + LOG_DEBUG("playerbots", "[NEW RPG] Bot {} with faction {} is evaluating existing RPG objective {} with threshold {} and slider value {}", bot->GetName(), faction, capturePoint->_capturePoint->GetName(), threshold, slider); + if ((faction == TEAM_HORDE && slider >= -threshold) || + (faction == TEAM_ALLIANCE && slider <= threshold)) + objective = capturePoint; + } + } + if (!objective) + data.capturePointSpawnId = 0; + } + + if (!objective) + { + objective = SelectNewObjective(capturePointMap); + if (!objective) + { + botAI->rpgInfo.ChangeToIdle(); + return true; + } + data.capturePointSpawnId = objective->m_capturePointSpawnId; + LOG_DEBUG("playerbots","[NEW RPG] Bot {} selected OutDoorPvP target capturePointSpawnId {}", bot->GetName(), data.capturePointSpawnId); + } + + GameObject* objectiveGO = objective->_capturePoint; + if (!objectiveGO) + return false; + + if (objectiveGO->GetGoType() != GAMEOBJECT_TYPE_CAPTURE_POINT) + return false; + + float radius = objectiveGO->GetGOInfo()->capturePoint.radius / 2.0f; + if (!objectiveGO->IsWithinDistInMap(bot, radius)) + return MoveFarTo(WorldPosition(objectiveGO)); + + return PatrolCapturePoint(objectiveGO, radius); +} + +OPvPCapturePoint* NewRpgOutdoorPvpAction::SelectNewObjective(OutdoorPvP::OPvPCapturePointMap const& capturePointMap) +{ + OPvPCapturePoint* objective = nullptr; + uint8 faction = bot->GetTeamId(); + std::vector candidateObjectives; + + if (capturePointMap.empty()) + { + botAI->rpgInfo.ChangeToIdle(); + return objective; + } + for (auto const& [guid, point] : capturePointMap) + { + GameObject* capturePointObject = point->_capturePoint; + if (!capturePointObject) + continue; + + float threshold = point->GetMinValue(); + float slider = point->GetSlider(); + if (faction == TEAM_HORDE && slider > -threshold) + candidateObjectives.push_back(point); + else if (faction == TEAM_ALLIANCE && slider < threshold) + candidateObjectives.push_back(point); + } + if (candidateObjectives.empty()) + { + LOG_DEBUG("playerbots", "[New RPG] Bot {} found no valid outdoor PVP objectives to capture", bot->GetName()); + botAI->rpgInfo.ChangeToIdle(); + return objective; + } + int randomIndex = urand(0, candidateObjectives.size() - 1); + objective = candidateObjectives[randomIndex]; + return objective; +} + +bool NewRpgOutdoorPvpAction::PatrolCapturePoint(GameObject* objectiveGO, float radius) +{ + if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL)) + return false; + + // Randomly pause at the current spot before picking a new patrol point + if (urand(0, 2) == 0) + return ForceToWait(urand(3000, 6000)); + + float patrolRadius = radius * 0.8f; + if (MoveRandomNear(patrolRadius, MovementPriority::MOVEMENT_NORMAL, objectiveGO)) + return true; + + return ForceToWait(urand(3000, 6000)); +} diff --git a/src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.h b/src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.h new file mode 100644 index 000000000..0487b9891 --- /dev/null +++ b/src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.h @@ -0,0 +1,19 @@ +#ifndef PLAYERBOT_NEWRPGOUTDOORPVP_H +#define PLAYERBOT_NEWRPGOUTDOORPVP_H + +#include "NewRpgBaseAction.h" +#include "OutdoorPvP.h" + +class NewRpgOutdoorPvpAction : public NewRpgBaseAction +{ +public: + NewRpgOutdoorPvpAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg outdoor pvp") {} + + virtual bool Execute(Event event) override; + OPvPCapturePoint* SelectNewObjective(OutdoorPvP::OPvPCapturePointMap const& capturePointMap); + +private: + bool PatrolCapturePoint(GameObject* objectiveGO, float radius); +}; + +#endif diff --git a/src/Ai/World/Rpg/NewRpgInfo.cpp b/src/Ai/World/Rpg/NewRpgInfo.cpp index 4e04ab086..4935503fc 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.cpp +++ b/src/Ai/World/Rpg/NewRpgInfo.cpp @@ -47,6 +47,14 @@ void NewRpgInfo::ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector) return RPG_REST; if constexpr (std::is_same_v) return RPG_DO_QUEST; if constexpr (std::is_same_v) return RPG_TRAVEL_FLIGHT; + if constexpr (std::is_same_v) return RPG_OUTDOOR_PVP; return RPG_IDLE; }, data); } @@ -153,6 +162,14 @@ std::string NewRpgInfo::ToString() out << "\ntoNode: " << arg.path[arg.path.size() - 1]; out << "\ninFlight: " << arg.inFlight; } + else if constexpr (std::is_same_v) + { + out << "OUTDOOR_PVP"; + if (!arg.capturePointSpawnId) + out << "\nNo capture point assigned."; + else + out << "\ncapturePointSpawnId: " << arg.capturePointSpawnId; + } else out << "UNKNOWN"; }, data); diff --git a/src/Ai/World/Rpg/NewRpgInfo.h b/src/Ai/World/Rpg/NewRpgInfo.h index c2349c14b..9e6abdda4 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.h +++ b/src/Ai/World/Rpg/NewRpgInfo.h @@ -58,6 +58,11 @@ struct NewRpgInfo { Rest() = default; }; + // RPG_OUTDOOR_PVP + struct OutdoorPvP + { + ObjectGuid::LowType capturePointSpawnId{0}; + }; struct Idle { }; @@ -79,7 +84,8 @@ struct NewRpgInfo WanderRandom, DoQuest, Rest, - TravelFlight + TravelFlight, + OutdoorPvP >; RpgData data; @@ -91,6 +97,7 @@ struct NewRpgInfo void ChangeToWanderRandom(); void ChangeToDoQuest(uint32 questId, const Quest* quest); void ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector path); + void ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId = 0); void ChangeToRest(); void ChangeToIdle(); bool CanChangeTo(NewRpgStatus status); diff --git a/src/Ai/World/Rpg/Strategy/NewRpgStrategy.cpp b/src/Ai/World/Rpg/Strategy/NewRpgStrategy.cpp index f0d0ce5a9..521b15c34 100644 --- a/src/Ai/World/Rpg/Strategy/NewRpgStrategy.cpp +++ b/src/Ai/World/Rpg/Strategy/NewRpgStrategy.cpp @@ -65,6 +65,14 @@ void NewRpgStrategy::InitTriggers(std::vector& triggers) } ) ); + triggers.push_back( + new TriggerNode( + "outdoor pvp status", + { + NextAction("new rpg outdoor pvp", 3.0f) + } + ) + ); } void NewRpgStrategy::InitMultipliers(std::vector&) diff --git a/src/Bot/RandomPlayerbotMgr.cpp b/src/Bot/RandomPlayerbotMgr.cpp index a772136e7..ee06c58f8 100644 --- a/src/Bot/RandomPlayerbotMgr.cpp +++ b/src/Bot/RandomPlayerbotMgr.cpp @@ -2861,10 +2861,10 @@ void RandomPlayerbotMgr::PrintStats() LOG_INFO("playerbots", "Bots rpg status:"); LOG_INFO("playerbots", " Idle: {}, Rest: {}, GoGrind: {}, GoCamp: {}, MoveRandom: {}, MoveNpc: {}, DoQuest: {}, " - "TravelFlight: {}", + "TravelFlight: {}, OutdoorPvP: {}", 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]); + rpgStatusCount[RPG_DO_QUEST], rpgStatusCount[RPG_TRAVEL_FLIGHT], rpgStatusCount[RPG_OUTDOOR_PVP]); LOG_INFO("playerbots", "Bots total quests:"); LOG_INFO("playerbots", " Accepted: {}, Rewarded: {}, Dropped: {}", rpgStasticTotal.questAccepted, diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index f150f7af1..8c8343db2 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -652,6 +652,7 @@ bool PlayerbotAIConfig::Initialize() 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); + RpgStatusProbWeight[RPG_OUTDOOR_PVP] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.OutdoorPvp", 10); syncLevelWithPlayers = sConfigMgr->GetOption("AiPlayerbot.SyncLevelWithPlayers", false); randomBotGroupNearby = sConfigMgr->GetOption("AiPlayerbot.RandomBotGroupNearby", false); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 7b6c1eb6f..4e758e79e 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -56,7 +56,8 @@ enum NewRpgStatus : int RPG_TRAVEL_FLIGHT = 6, // Taking a break RPG_REST = 7, - RPG_STATUS_END = 8 + RPG_OUTDOOR_PVP = 8, + RPG_STATUS_END = 9 }; #define MAX_SPECNO 20