mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
## Pull Request Description Migration of "wait for attack" strategy from cmangos playerbots. Resolves: https://github.com/mod-playerbots/mod-playerbots/issues/990 ## Feature Evaluation Optional strategy for bots which are in party with real player. ## How to Test the Changes - add strategy to bot "nc +wait for attack" and "co +wait for attack" - set time via command "wait for attack time x" where x is time which they wait in seconds (you should get response from bot) - attack any target (for example dummy in main city)(bot should wait with attack) ## Impact Assessment - [ ] No, not at all - [x] Minimal impact (**explain below**) - [ ] Moderate impact (**explain below**) Performance wise only bots having this optinal strategy have additional cost in multiplier which check every attack action that should be execute. - Does this change modify default bot behavior? - [x] No - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - [x] No - [ ] Yes (**explain below**) ## Messages to Translate Does this change add bot messages to translate? - [ ] No - [x] Yes (**list messages in the table**) | Message key | Default message | | --------------- | ------------------ | 1740 | Please provide a time to set (in seconds) 1741 | Please provide valid time to set (in seconds) between 0 and 99 1742 | Wait for attack time set to %new_time seconds ## 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. --> Copilot CLI - help with migration ## 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. -->
167 lines
5.6 KiB
C++
167 lines
5.6 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 "WaitForAttackAction.h"
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
|
|
#include "ObjectAccessor.h"
|
|
#include "PlayerbotAI.h"
|
|
#include "PlayerbotTextMgr.h"
|
|
#include "Playerbots.h"
|
|
#include "ServerFacade.h"
|
|
#include "TravelMgr.h"
|
|
#include "WaitForAttackStrategy.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
WorldPosition GetBestPoint(AiObjectContext* context, Player* bot, Unit* target,
|
|
float minDistance, float maxDistance)
|
|
{
|
|
WorldPosition botPosition(bot);
|
|
WorldPosition targetPosition(target);
|
|
|
|
int8 startDir = urand(0, 1) * 2 - 1;
|
|
float const radiansIncrement = (5.0f / 180.0f) * static_cast<float>(M_PI);
|
|
float startAngle = targetPosition.getAngleTo(botPosition) +
|
|
frand(0.0f, radiansIncrement) * startDir;
|
|
float distance = frand(minDistance, maxDistance);
|
|
|
|
GuidVector enemies = AI_VALUE(GuidVector, "possible targets no los");
|
|
|
|
for (float tryAngle = 0.0f; tryAngle < static_cast<float>(M_PI); tryAngle += radiansIncrement)
|
|
{
|
|
for (int8 tryDir = -1; tryAngle && tryDir < 1; tryDir += 2)
|
|
{
|
|
float pointAngle = startAngle + tryAngle * startDir * tryDir;
|
|
|
|
float x = targetPosition.GetPositionX() + distance * cos(pointAngle);
|
|
float y = targetPosition.GetPositionY() + distance * sin(pointAngle);
|
|
float z = targetPosition.GetPositionZ() + 1.0f;
|
|
|
|
WorldPosition point(targetPosition.GetMapId(), x, y, z);
|
|
point.setZ(point.getHeight());
|
|
|
|
// Check line of sight to target
|
|
if (!target->IsWithinLOS(point.GetPositionX(), point.GetPositionY(),
|
|
point.GetPositionZ() + bot->GetCollisionHeight()))
|
|
continue;
|
|
|
|
// Check if enemies are close to this point
|
|
bool enemyClose = false;
|
|
for (ObjectGuid const& enemyGUID : enemies)
|
|
{
|
|
Unit* enemy = ObjectAccessor::GetUnit(*bot, enemyGUID);
|
|
if (enemy && enemy->IsWithinLOSInMap(bot) && enemy->IsHostileTo(bot))
|
|
{
|
|
float enemyAttackRange = enemy->GetCombatReach() + ATTACK_DISTANCE;
|
|
WorldPosition enemyPos(enemy);
|
|
if (enemyPos.sqDistance(point) <= (enemyAttackRange * enemyAttackRange))
|
|
{
|
|
enemyClose = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (enemyClose)
|
|
continue;
|
|
|
|
// Check if bot can path to this point
|
|
if (!botPosition.canPathTo(point, bot))
|
|
continue;
|
|
|
|
return point;
|
|
}
|
|
}
|
|
|
|
return botPosition;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool WaitForAttackKeepSafeDistanceAction::Execute(Event /*event*/)
|
|
{
|
|
Unit* target = AI_VALUE(Unit*, "current target");
|
|
|
|
// If our target is moving towards a stationary unit, use that unit as anchor
|
|
if (target && !target->IsStopped())
|
|
{
|
|
ObjectGuid targetGuid = target->GetTarget();
|
|
if (targetGuid)
|
|
{
|
|
Unit* targetsTarget = ObjectAccessor::GetUnit(*target, targetGuid);
|
|
if (targetsTarget && targetsTarget->IsStopped())
|
|
target = targetsTarget;
|
|
}
|
|
}
|
|
|
|
if (target && target->IsAlive())
|
|
{
|
|
float safeDistance = std::max(
|
|
target->GetCombatReach() + ATTACK_DISTANCE,
|
|
WaitForAttackStrategy::GetSafeDistance());
|
|
float safeDistanceThreshold = WaitForAttackStrategy::GetSafeDistanceThreshold();
|
|
|
|
WorldPosition bestPoint = GetBestPoint(context, bot, target,
|
|
safeDistance - safeDistanceThreshold, safeDistance);
|
|
|
|
if (bestPoint)
|
|
return MoveTo(bestPoint.GetMapId(), bestPoint.GetPositionX(),
|
|
bestPoint.GetPositionY(), bestPoint.GetPositionZ());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SetWaitForAttackTimeAction::Execute(Event event)
|
|
{
|
|
std::string newTimeStr = event.getParam();
|
|
|
|
if (newTimeStr.empty())
|
|
{
|
|
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
|
"wait_for_attack_provide_time",
|
|
"Please provide a time to set (in seconds)",
|
|
std::map<std::string, std::string>());
|
|
botAI->TellMaster(text);
|
|
return false;
|
|
}
|
|
|
|
if (!std::all_of(newTimeStr.begin(), newTimeStr.end(), ::isdigit))
|
|
{
|
|
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
|
"wait_for_attack_invalid_time",
|
|
"Please provide valid time to set (in seconds) between 0 and 99",
|
|
std::map<std::string, std::string>());
|
|
botAI->TellMaster(text);
|
|
return false;
|
|
}
|
|
|
|
int newTime = std::stoi(newTimeStr);
|
|
if (newTime < 0 || newTime > 99)
|
|
{
|
|
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
|
"wait_for_attack_invalid_time",
|
|
"Please provide valid time to set (in seconds) between 0 and 99",
|
|
std::map<std::string, std::string>());
|
|
botAI->TellMaster(text);
|
|
return false;
|
|
}
|
|
|
|
context->GetValue<uint8>("wait for attack time")->Set(static_cast<uint8>(newTime));
|
|
|
|
std::map<std::string, std::string> placeholders;
|
|
placeholders["%new_time"] = std::to_string(newTime);
|
|
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
|
"wait_for_attack_time_set",
|
|
"Wait for attack time set to %new_time seconds",
|
|
placeholders);
|
|
botAI->TellMaster(text);
|
|
return true;
|
|
}
|