mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-02-24 12:34:36 +00:00
# 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.
205 lines
7.2 KiB
C++
205 lines
7.2 KiB
C++
#include "RaidGruulsLairHelpers.h"
|
|
#include "AiFactory.h"
|
|
#include "GroupReference.h"
|
|
#include "Playerbots.h"
|
|
#include "Unit.h"
|
|
|
|
namespace GruulsLairHelpers
|
|
{
|
|
// Olm does not chase properly due to the Core's caster movement issues
|
|
// Thus, the below "OlmTankPosition" is beyond the actual desired tanking location
|
|
// It is the spot to which the OlmTank runs to to pull Olm to a decent tanking location
|
|
// "MaulgarRoomCenter" is to keep healers in a centralized location
|
|
const Position MAULGAR_TANK_POSITION = { 90.686f, 167.047f, -13.234f };
|
|
const Position OLM_TANK_POSITION = { 87.485f, 234.942f, -3.635f };
|
|
const Position BLINDEYE_TANK_POSITION = { 99.681f, 213.989f, -10.345f };
|
|
const Position KROSH_TANK_POSITION = { 116.880f, 166.208f, -14.231f };
|
|
const Position MAULGAR_ROOM_CENTER = { 88.754f, 150.759f, -11.569f };
|
|
const Position GRUUL_TANK_POSITION = { 241.238f, 365.025f, -4.220f };
|
|
|
|
bool IsAnyOgreBossAlive(PlayerbotAI* botAI)
|
|
{
|
|
const char* ogreBossNames[] =
|
|
{
|
|
"high king maulgar",
|
|
"kiggler the crazed",
|
|
"krosh firehand",
|
|
"olm the summoner",
|
|
"blindeye the seer"
|
|
};
|
|
|
|
for (const char* name : ogreBossNames)
|
|
{
|
|
Unit* boss = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", name)->Get();
|
|
if (!boss || !boss->IsAlive())
|
|
continue;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot)
|
|
{
|
|
Group* group = bot->GetGroup();
|
|
if (!group)
|
|
return false;
|
|
|
|
// (1) First loop: Return the first assistant Mage (real player or bot)
|
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
{
|
|
Player* member = ref->GetSource();
|
|
if (!member || !member->IsAlive() || member->getClass() != CLASS_MAGE)
|
|
continue;
|
|
|
|
if (group->IsAssistant(member->GetGUID()))
|
|
return member == bot;
|
|
}
|
|
|
|
// (2) Fall back to bot Mage with highest HP
|
|
Player* highestHpMage = nullptr;
|
|
uint32 highestHp = 0;
|
|
|
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
{
|
|
Player* member = ref->GetSource();
|
|
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
|
|
member->getClass() != CLASS_MAGE)
|
|
continue;
|
|
|
|
uint32 hp = member->GetMaxHealth();
|
|
if (!highestHpMage || hp > highestHp)
|
|
{
|
|
highestHpMage = member;
|
|
highestHp = hp;
|
|
}
|
|
}
|
|
|
|
// (3) Return the found Mage tank, or nullptr if none found
|
|
return highestHpMage == bot;
|
|
}
|
|
|
|
bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot)
|
|
{
|
|
Group* group = bot->GetGroup();
|
|
if (!group)
|
|
return false;
|
|
|
|
// (1) First loop: Return the first assistant Moonkin (real player or bot)
|
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
{
|
|
Player* member = ref->GetSource();
|
|
if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID)
|
|
continue;
|
|
|
|
if (group->IsAssistant(member->GetGUID()) &&
|
|
AiFactory::GetPlayerSpecTab(member) == DRUID_TAB_BALANCE)
|
|
return member == bot;
|
|
}
|
|
|
|
// (2) Fall back to bot Moonkin with highest HP
|
|
Player* highestHpMoonkin = nullptr;
|
|
uint32 highestHp = 0;
|
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
{
|
|
Player* member = ref->GetSource();
|
|
if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID ||
|
|
!GET_PLAYERBOT_AI(member) || AiFactory::GetPlayerSpecTab(member) != DRUID_TAB_BALANCE)
|
|
continue;
|
|
|
|
uint32 hp = member->GetMaxHealth();
|
|
if (!highestHpMoonkin || hp > highestHp)
|
|
{
|
|
highestHpMoonkin = member;
|
|
highestHp = hp;
|
|
}
|
|
}
|
|
|
|
// (3) Return the found Moonkin tank, or nullptr if none found
|
|
return highestHpMoonkin == bot;
|
|
}
|
|
|
|
bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos)
|
|
{
|
|
const float KROSH_SAFE_DISTANCE = 20.0f;
|
|
const float MAULGAR_SAFE_DISTANCE = 10.0f;
|
|
bool isSafe = true;
|
|
|
|
Unit* krosh = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "krosh firehand")->Get();
|
|
if (krosh && krosh->IsAlive())
|
|
{
|
|
float dist = sqrt(pow(pos.GetPositionX() - krosh->GetPositionX(), 2) + pow(pos.GetPositionY() - krosh->GetPositionY(), 2));
|
|
if (dist < KROSH_SAFE_DISTANCE)
|
|
isSafe = false;
|
|
}
|
|
|
|
Unit* maulgar = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "high king maulgar")->Get();
|
|
if (botAI->IsRanged(bot) && maulgar && maulgar->IsAlive())
|
|
{
|
|
float dist = sqrt(pow(pos.GetPositionX() - maulgar->GetPositionX(), 2) + pow(pos.GetPositionY() - maulgar->GetPositionY(), 2));
|
|
if (dist < MAULGAR_SAFE_DISTANCE)
|
|
isSafe = false;
|
|
}
|
|
|
|
return isSafe;
|
|
}
|
|
|
|
bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos)
|
|
{
|
|
const float SEARCH_RADIUS = 30.0f;
|
|
const uint8 NUM_POSITIONS = 32;
|
|
|
|
outPos = { bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ() };
|
|
if (IsPositionSafe(botAI, bot, outPos))
|
|
{
|
|
outPos = Position();
|
|
return false;
|
|
}
|
|
|
|
float bestScore = std::numeric_limits<float>::max();
|
|
bool foundSafeSpot = false;
|
|
Position bestPos;
|
|
|
|
for (int i = 0; i < NUM_POSITIONS; ++i)
|
|
{
|
|
float angle = 2 * M_PI * i / NUM_POSITIONS;
|
|
Position candidatePos;
|
|
candidatePos.Relocate(bot->GetPositionX() + SEARCH_RADIUS * cos(angle),
|
|
bot->GetPositionY() + SEARCH_RADIUS * sin(angle),
|
|
bot->GetPositionZ());
|
|
|
|
float destX = candidatePos.GetPositionX();
|
|
float destY = candidatePos.GetPositionY();
|
|
float destZ = candidatePos.GetPositionZ();
|
|
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
|
bot->GetPositionZ(), destX, destY, destZ, true))
|
|
continue;
|
|
|
|
if (destX != candidatePos.GetPositionX() || destY != candidatePos.GetPositionY())
|
|
continue;
|
|
|
|
candidatePos.Relocate(destX, destY, destZ);
|
|
|
|
if (IsPositionSafe(botAI, bot, candidatePos))
|
|
{
|
|
float movementDistance = sqrt(pow(destX - bot->GetPositionX(), 2) + pow(destY - bot->GetPositionY(), 2));
|
|
if (movementDistance < bestScore)
|
|
{
|
|
bestScore = movementDistance;
|
|
bestPos = candidatePos;
|
|
foundSafeSpot = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundSafeSpot)
|
|
{
|
|
outPos = bestPos;
|
|
return true;
|
|
}
|
|
|
|
outPos = Position();
|
|
return false;
|
|
}
|
|
}
|