mod-playerbots/src/Mgr/Move/FleeManager.cpp
Alex Dcnh 17b8d7f68b
Stage1 refactor world position method names (#2126)
# Pull Request

This change replaces the non‑standard
WorldPosition::getX/getY/getZ/getO/getMapId wrappers with the core
getters (GetPositionX/Y/Z, GetOrientation, GetMapId) and removes the
redundant wrappers.
Goal: align the module with AzerothCore conventions, reduce local
adapters, and improve long‑term maintainability.

---

## Design Philosophy

This is a structural cleanup only (coordinate access) and does not alter
any AI behavior or decision logic.
It follows the stability/performance-first philosophy and does not add
branches or extra runtime work.

Before submitting: yes, this change aligns with the principles of
stability, performance, and predictability.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Minimum logic required: use core getters (GetPositionX/Y/Z, GetMapId,
GetOrientation) wherever coordinates are needed.
- Cheapest implementation: direct call replacement and removal of
redundant wrappers.
- Runtime cost: negligible (same data access, no additional logic).

---

## How to Test the Changes

- No functional testing required (behavior‑neutral refactor).
- Recommended: compile the module and run a normal server startup as
validation.

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [x] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [x] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [ ] No
- - [x] Yes (**explain below**)

If yes, please specify:

- AI tool or model used: Copilot
- Purpose of usage: Translate this PR text from french to English

---

## 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

---

## Notes for Reviewers

This is a core-friendly cleanup only, with no behavioral change.
No additional logic or CPU cost is introduced.
2026-02-13 09:24:42 -08:00

198 lines
6.3 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "FleeManager.h"
#include "Playerbots.h"
#include "ServerFacade.h"
FleeManager::FleeManager(Player* bot, float maxAllowedDistance, float followAngle, bool forceMaxDistance,
WorldPosition startPosition)
: bot(bot),
maxAllowedDistance(maxAllowedDistance),
followAngle(followAngle),
forceMaxDistance(forceMaxDistance),
startPosition(startPosition ? startPosition : WorldPosition(bot))
{
}
void FleeManager::calculateDistanceToCreatures(FleePoint* point)
{
point->minDistance = -1.0f;
point->sumDistance = 0.0f;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return;
}
GuidVector units = *botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los");
for (GuidVector::iterator i = units.begin(); i != units.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
float d = ServerFacade::instance().GetDistance2d(unit, point->x, point->y);
point->sumDistance += d;
if (point->minDistance < 0 || point->minDistance > d)
point->minDistance = d;
}
}
bool intersectsOri(float angle, std::vector<float>& angles, float angleIncrement)
{
for (std::vector<float>::iterator i = angles.begin(); i != angles.end(); ++i)
{
float ori = *i;
if (abs(angle - ori) < angleIncrement)
return true;
}
return false;
}
void FleeManager::calculatePossibleDestinations(std::vector<FleePoint*>& points)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return;
}
Unit* target = *botAI->GetAiObjectContext()->GetValue<Unit*>("current target");
float botPosX = startPosition.GetPositionX();
float botPosY = startPosition.GetPositionY();
float botPosZ = startPosition.GetPositionZ();
FleePoint start(botAI, botPosX, botPosY, botPosZ);
calculateDistanceToCreatures(&start);
std::vector<float> enemyOri;
GuidVector units = *botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los");
for (GuidVector::iterator i = units.begin(); i != units.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
float ori = bot->GetAngle(unit);
enemyOri.push_back(ori);
}
float distIncrement = std::max(sPlayerbotAIConfig.followDistance,
(maxAllowedDistance - sPlayerbotAIConfig.tooCloseDistance) / 10.0f);
for (float dist = maxAllowedDistance; dist >= sPlayerbotAIConfig.tooCloseDistance; dist -= distIncrement)
{
float angleIncrement = std::max(M_PI / 20, M_PI / 4 / (1.0 + dist - sPlayerbotAIConfig.tooCloseDistance));
for (float add = 0.0f; add < M_PI / 4 + angleIncrement; add += angleIncrement)
{
for (float angle = add; angle < add + 2 * static_cast<float>(M_PI) + angleIncrement;
angle += static_cast<float>(M_PI) / 4)
{
if (intersectsOri(angle, enemyOri, angleIncrement))
continue;
float x = botPosX + cos(angle) * maxAllowedDistance, y = botPosY + sin(angle) * maxAllowedDistance,
z = botPosZ + CONTACT_DISTANCE;
if (forceMaxDistance &&
ServerFacade::instance().IsDistanceLessThan(ServerFacade::instance().GetDistance2d(bot, x, y),
maxAllowedDistance - sPlayerbotAIConfig.tooCloseDistance))
continue;
bot->UpdateAllowedPositionZ(x, y, z);
Map* map = startPosition.getMap();
if (map && map->IsInWater(bot->GetPhaseMask(), x, y, z, bot->GetCollisionHeight()))
continue;
if (!bot->IsWithinLOS(x, y, z) || (target && !target->IsWithinLOS(x, y, z)))
continue;
FleePoint* point = new FleePoint(botAI, x, y, z);
calculateDistanceToCreatures(point);
if (ServerFacade::instance().IsDistanceGreaterOrEqualThan(point->minDistance - start.minDistance,
sPlayerbotAIConfig.followDistance))
points.push_back(point);
else
delete point;
}
}
}
}
void FleeManager::cleanup(std::vector<FleePoint*>& points)
{
for (std::vector<FleePoint*>::iterator i = points.begin(); i != points.end(); i++)
{
delete *i;
}
points.clear();
}
bool FleeManager::isBetterThan(FleePoint* point, FleePoint* other)
{
return point->sumDistance - other->sumDistance > 0;
}
FleePoint* FleeManager::selectOptimalDestination(std::vector<FleePoint*>& points)
{
FleePoint* best = nullptr;
for (std::vector<FleePoint*>::iterator i = points.begin(); i != points.end(); i++)
{
FleePoint* point = *i;
if (!best || isBetterThan(point, best))
best = point;
}
return best;
}
bool FleeManager::CalculateDestination(float* rx, float* ry, float* rz)
{
std::vector<FleePoint*> points;
calculatePossibleDestinations(points);
FleePoint* point = selectOptimalDestination(points);
if (!point)
{
cleanup(points);
return false;
}
*rx = point->x;
*ry = point->y;
*rz = point->z;
cleanup(points);
return true;
}
bool FleeManager::isUseful()
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return false;
}
GuidVector units = *botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los");
for (GuidVector::iterator i = units.begin(); i != units.end(); ++i)
{
Creature* creature = botAI->GetCreature(*i);
if (!creature)
continue;
if (startPosition.sqDistance(WorldPosition(creature)) <
creature->GetAttackDistance(bot) * creature->GetAttackDistance(bot))
return true;
// float d = ServerFacade::instance().GetDistance2d(unit, bot);
// if (ServerFacade::instance().IsDistanceLessThan(d, sPlayerbotAIConfig.aggroDistance)) return true;
}
return false;
}