mod-playerbots/src/Ai/World/Rpg/Action/NewRpgOutdoorPvP.cpp
Keleborn 53a607e147
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
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- 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**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->
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
<!-- Anything else that's helpful to review or test your pull request.
-->

---------

Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-04-10 22:16:58 -07:00

129 lines
4.5 KiB
C++

#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<NewRpgInfo::OutdoorPvP>(&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<OPvPCapturePoint*> 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));
}