From eea652f5d56e17557ce74ad67b29d2ca48e2cabb Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Tue, 24 Sep 2024 11:46:39 +0800 Subject: [PATCH] Tank face and dps behind --- src/AiFactory.cpp | 12 +- src/PlayerbotAI.cpp | 2 +- src/strategy/StrategyContext.h | 7 +- src/strategy/actions/ActionContext.h | 4 +- src/strategy/actions/MovementActions.cpp | 176 +++++++++++++++++--- src/strategy/actions/MovementActions.h | 25 ++- src/strategy/generic/CombatStrategy.cpp | 13 +- src/strategy/generic/CombatStrategy.h | 11 +- src/strategy/generic/ConserveManaStrategy.h | 40 +---- src/strategy/values/IsBehindValue.cpp | 9 +- 10 files changed, 216 insertions(+), 83 deletions(-) diff --git a/src/AiFactory.cpp b/src/AiFactory.cpp index d107b0dc..dadf327f 100644 --- a/src/AiFactory.cpp +++ b/src/AiFactory.cpp @@ -279,11 +279,11 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa } if (sPlayerbotAIConfig->autoSaveMana) { - engine->addStrategy("smana", false); + engine->addStrategy("save mana", false); } if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster()) { - engine->addStrategy("aaoe", false); + engine->addStrategy("avoid aoe", false); } engine->addStrategy("formation", false); switch (player->getClass()) @@ -388,6 +388,12 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa break; } + if (PlayerbotAI::IsTank(player, true)) { + engine->addStrategy("tank face", false); + } + if (PlayerbotAI::IsMelee(player, true) && PlayerbotAI::IsDps(player, true)) { + engine->addStrategy("behind", false); + } if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player)) { @@ -599,7 +605,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const if (sPlayerbotAIConfig->autoSaveMana) { - nonCombatEngine->addStrategy("smana", false); + nonCombatEngine->addStrategy("save mana", false); } if ((sRandomPlayerbotMgr->IsRandomBot(player)) && !player->InBattleground()) { diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 5443c9a6..07520d1a 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -2023,7 +2023,7 @@ bool PlayerbotAI::IsDps(Player* player, bool bySpec) { return true; } - if (tab == DRUID_TAB_FERAL && !IsTank(player)) + if (tab == DRUID_TAB_FERAL && !IsTank(player, bySpec)) { return true; } diff --git a/src/strategy/StrategyContext.h b/src/strategy/StrategyContext.h index 2679a59d..da3d8e42 100644 --- a/src/strategy/StrategyContext.h +++ b/src/strategy/StrategyContext.h @@ -60,8 +60,7 @@ public: creators["gather"] = &StrategyContext::gather; creators["emote"] = &StrategyContext::emote; creators["passive"] = &StrategyContext::passive; - // creators["conserve mana"] = &StrategyContext::conserve_mana; - creators["smana"] = &StrategyContext::auto_save_mana; + creators["save mana"] = &StrategyContext::auto_save_mana; creators["food"] = &StrategyContext::food; creators["chat"] = &StrategyContext::chat; creators["default"] = &StrategyContext::world_packet; @@ -113,7 +112,8 @@ public: creators["group"] = &StrategyContext::group; creators["guild"] = &StrategyContext::guild; creators["grind"] = &StrategyContext::grind; - creators["aaoe"] = &StrategyContext::avoid_aoe; + creators["avoid aoe"] = &StrategyContext::avoid_aoe; + creators["tank face"] = &StrategyContext::tank_face; creators["move random"] = &StrategyContext::move_random; creators["formation"] = &StrategyContext::combat_formation; creators["move from group"] = &StrategyContext::move_from_group; @@ -179,6 +179,7 @@ private: static Strategy* guild (PlayerbotAI* botAI) { return new GuildStrategy(botAI); } static Strategy* grind(PlayerbotAI* botAI) { return new GrindingStrategy(botAI); } static Strategy* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeStrategy(botAI); } + static Strategy* tank_face(PlayerbotAI* botAI) { return new TankFaceStrategy(botAI); } static Strategy* move_random(PlayerbotAI* ai) { return new MoveRandomStrategy(ai); } static Strategy* combat_formation(PlayerbotAI* ai) { return new CombatFormationStrategy(ai); } static Strategy* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupStrategy(botAI); } diff --git a/src/strategy/actions/ActionContext.h b/src/strategy/actions/ActionContext.h index 1b798f29..e195e2f9 100644 --- a/src/strategy/actions/ActionContext.h +++ b/src/strategy/actions/ActionContext.h @@ -91,8 +91,9 @@ public: creators["reach party member to resurrect"] = &ActionContext::reach_party_member_to_resurrect; creators["flee"] = &ActionContext::flee; creators["flee with pet"] = &ActionContext::flee_with_pet; - creators["aaoe"] = &ActionContext::avoid_aoe; + creators["avoid aoe"] = &ActionContext::avoid_aoe; creators["combat formation move"] = &ActionContext::combat_formation_move; + creators["tank face"] = &ActionContext::tank_face; creators["disperse set"] = &ActionContext::disperse_set; creators["gift of the naaru"] = &ActionContext::gift_of_the_naaru; creators["shoot"] = &ActionContext::shoot; @@ -276,6 +277,7 @@ private: static Action* flee_with_pet(PlayerbotAI* botAI) { return new FleeWithPetAction(botAI); } static Action* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeAction(botAI); } static Action* combat_formation_move(PlayerbotAI* botAI) { return new CombatFormationMoveAction(botAI); } + static Action* tank_face(PlayerbotAI* botAI) { return new TankFaceAction(botAI); } static Action* disperse_set(PlayerbotAI* botAI) { return new DisperseSetAction(botAI); } static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); } static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); } diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index dff079fb..f8600b63 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -24,6 +24,7 @@ #include "ObjectDefines.h" #include "ObjectGuid.h" #include "PathGenerator.h" +#include "PlayerbotAI.h" #include "PlayerbotAIConfig.h" #include "Playerbots.h" #include "Position.h" @@ -202,7 +203,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, return false; float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot - if (distance > sPlayerbotAIConfig->contactDistance) + if (distance > 0.01f) { MotionMaster& mm = *vehicleBase->GetMotionMaster(); // need to move vehicle, not bot mm.Clear(); @@ -217,7 +218,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, else if (exact_waypoint || disableMoveSplinePath || !generatePath) { float distance = bot->GetExactDist(x, y, z); - if (distance > sPlayerbotAIConfig->contactDistance) + if (distance > 0.01f) { if (bot->IsSitState()) bot->SetStandState(UNIT_STAND_STATE_STAND); @@ -247,7 +248,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, return false; } float distance = bot->GetExactDist(x, y, modifiedZ); - if (distance > sPlayerbotAIConfig->contactDistance) + if (distance > 0.01f) { if (bot->IsSitState()) bot->SetStandState(UNIT_STAND_STATE_STAND); @@ -2140,7 +2141,7 @@ bool MovementAction::FleePosition(Position pos, float radius) bool MovementAction::CheckLastFlee(float curAngle, std::list& infoList) { uint32 curTS = getMSTime(); - curAngle = fmod(curAngle, 2 * M_PI); + curAngle = Position::NormalizeOrientation(curAngle); while (!infoList.empty()) { if (infoList.size() > 10 || infoList.front().timestamp + 5000 < curTS) @@ -2159,7 +2160,7 @@ bool MovementAction::CheckLastFlee(float curAngle, std::list& infoList { continue; } - float revAngle = fmod(info.angle + M_PI, 2 * M_PI); + float revAngle = Position::NormalizeOrientation(info.angle + M_PI); // angle too close if (fabs(revAngle - curAngle) < M_PI / 4) { @@ -2179,13 +2180,14 @@ bool CombatFormationMoveAction::isUseful() { return false; } - float dis = AI_VALUE(float, "disperse distance"); - return dis > 0.0f; + return true; } bool CombatFormationMoveAction::Execute(Event event) { float dis = AI_VALUE(float, "disperse distance"); + if (dis <= 0.0f) + return false; Player* playerToLeave = NearestGroupMember(dis); if (playerToLeave && bot->GetExactDist(playerToLeave) < dis) { @@ -2197,7 +2199,7 @@ bool CombatFormationMoveAction::Execute(Event event) return false; } -Position CombatFormationMoveAction::AverageGroupPos(float dis) +Position CombatFormationMoveAction::AverageGroupPos(float dis, bool ranged, bool self) { float averageX = 0, averageY = 0, averageZ = 0; int cnt = 0; @@ -2210,10 +2212,19 @@ Position CombatFormationMoveAction::AverageGroupPos(float dis) for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) { Player* member = ObjectAccessor::FindPlayer(itr->guid); - if (!member || !member->IsAlive() || member->GetMapId() != bot->GetMapId() || member->IsCharmed() || + if (!member) + continue; + + if (!self && member == bot) + continue; + + if (ranged && !PlayerbotAI::IsRanged(member)) + continue; + + if (!member->IsAlive() || member->GetMapId() != bot->GetMapId() || member->IsCharmed() || sServerFacade->GetDistance2d(bot, member) > dis) continue; - cnt++; + averageX += member->GetPositionX(); averageY += member->GetPositionY(); averageZ += member->GetPositionZ(); @@ -2224,6 +2235,51 @@ Position CombatFormationMoveAction::AverageGroupPos(float dis) return Position(averageX, averageY, averageZ); } +float CombatFormationMoveAction::AverageGroupAngle(Unit* from, bool ranged, bool self) +{ + Group* group = bot->GetGroup(); + if (!from || !group) + { + return 0.0f; + } + float average = 0.0f; + int cnt = 0; + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player* member = ObjectAccessor::FindPlayer(itr->guid); + if (!member) + continue; + + if (!self && member == bot) + continue; + + if (ranged && !PlayerbotAI::IsRanged(member)) + continue; + + if (!member->IsAlive() || member->GetMapId() != bot->GetMapId() || member->IsCharmed() || + sServerFacade->GetDistance2d(bot, member) > sPlayerbotAIConfig->sightDistance) + continue; + + cnt++; + average += from->GetAngle(member); + } + if (cnt) + average /= cnt; + return average; +} + +Position CombatFormationMoveAction::GetNearestPosition(const std::vector& positions) +{ + Position result; + for (const Position& pos : positions) + { + if (bot->GetExactDist(pos) < bot->GetExactDist(result)) + result = pos; + } + return result; +} + Player* CombatFormationMoveAction::NearestGroupMember(float dis) { float nearestDis = 10000.0f; @@ -2249,6 +2305,60 @@ Player* CombatFormationMoveAction::NearestGroupMember(float dis) return result; } +bool TankFaceAction::Execute(Event event) +{ + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target) + return false; + + if (!bot->GetGroup()) + return false; + + if (!bot->IsWithinMeleeRange(target)) + return false; + + if (!AI_VALUE2(bool, "has aggro", "current target")) + return false; + + float averageAngle = AverageGroupAngle(target, true); + + if (averageAngle == 0.0f) + return false; + + float deltaAngle = Position::NormalizeOrientation(averageAngle - target->GetAngle(bot)); + if (deltaAngle > M_PI) + deltaAngle -= 2.0f * M_PI; // -PI..PI + + float tolerable = M_PI_2; + + if (fabs(deltaAngle) > tolerable) + return false; + + float goodAngle1 = Position::NormalizeOrientation(averageAngle + M_PI * 5 / 8); + float goodAngle2 = Position::NormalizeOrientation(averageAngle - M_PI * 5 / 8); + + // if dist < bot->GetMeleeRange(target) / 2, target will move backward + float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() - target->GetCombatReach(); + std::vector availablePos; + float x, y, z; + target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1); + if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), + x, y, z)) + { + availablePos.push_back(Position(x, y, z)); + } + target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2); + if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), + x, y, z)) + { + availablePos.push_back(Position(x, y, z)); + } + if (availablePos.empty()) + return false; + Position nearest = GetNearestPosition(availablePos); + return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); +} + bool DisperseSetAction::Execute(Event event) { std::string const text = event.getParam(); @@ -2388,25 +2498,43 @@ bool SetBehindTargetAction::Execute(Event event) if (!target) return false; - float angle = GetFollowAngle() / 3 + target->GetOrientation() + M_PI; + if (target->GetVictim() == bot) + return false; - // return ChaseTo(target, 0.f, angle); + if (!bot->IsWithinMeleeRange(target)) + return false; - float distance = sPlayerbotAIConfig->contactDistance; - float x = target->GetPositionX() + cos(angle) * distance; - float y = target->GetPositionY() + sin(angle) * distance; - float z = target->GetPositionZ(); - bot->UpdateGroundPositionZ(x, y, z); + float deltaAngle = Position::NormalizeOrientation(target->GetOrientation() - target->GetAngle(bot)); + if (deltaAngle > M_PI) + deltaAngle -= 2.0f * M_PI; // -PI..PI - return MoveTo(bot->GetMapId(), x, y, z); -} + float tolerable = M_PI_2; -bool SetBehindTargetAction::isUseful() { return !AI_VALUE2(bool, "behind", "current target"); } + if (fabs(deltaAngle) > tolerable) + return false; -bool SetBehindTargetAction::isPossible() -{ - Unit* target = AI_VALUE(Unit*, "current target"); - return target && !(target->GetVictim() && target->GetVictim()->GetGUID() == bot->GetGUID()); + float goodAngle1 = Position::NormalizeOrientation(target->GetOrientation() + M_PI * 5 / 8); + float goodAngle2 = Position::NormalizeOrientation(target->GetOrientation() - M_PI * 5 / 8); + + float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() - target->GetCombatReach(); + std::vector availablePos; + float x, y, z; + target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1); + if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), + x, y, z)) + { + availablePos.push_back(Position(x, y, z)); + } + target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2); + if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), + x, y, z)) + { + availablePos.push_back(Position(x, y, z)); + } + if (availablePos.empty()) + return false; + Position nearest = GetNearestPosition(availablePos); + return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } bool MoveOutOfCollisionAction::Execute(Event event) diff --git a/src/strategy/actions/MovementActions.h b/src/strategy/actions/MovementActions.h index 7edf119b..7b03f034 100644 --- a/src/strategy/actions/MovementActions.h +++ b/src/strategy/actions/MovementActions.h @@ -99,7 +99,7 @@ class AvoidAoeAction : public MovementAction { public: AvoidAoeAction(PlayerbotAI* botAI, int moveInterval = 1000) - : MovementAction(botAI, "aaoe"), moveInterval(moveInterval) + : MovementAction(botAI, "avoid aoe"), moveInterval(moveInterval) { } @@ -115,11 +115,12 @@ protected: int moveInterval; }; + class CombatFormationMoveAction : public MovementAction { public: - CombatFormationMoveAction(PlayerbotAI* botAI, int moveInterval = 1000) - : MovementAction(botAI, "combat formation move"), moveInterval(moveInterval) + CombatFormationMoveAction(PlayerbotAI* botAI, std::string name = "combat formation move", int moveInterval = 1000) + : MovementAction(botAI, name), moveInterval(moveInterval) { } @@ -127,12 +128,22 @@ public: bool Execute(Event event) override; protected: - Position AverageGroupPos(float dis = sPlayerbotAIConfig->sightDistance); + Position AverageGroupPos(float dis = sPlayerbotAIConfig->sightDistance, bool ranged = false, bool self = false); Player* NearestGroupMember(float dis = sPlayerbotAIConfig->sightDistance); + float AverageGroupAngle(Unit* from, bool ranged = false, bool self = false); + Position GetNearestPosition(const std::vector& positions); int lastMoveTimer = 0; int moveInterval; }; +class TankFaceAction : public CombatFormationMoveAction +{ +public: + TankFaceAction(PlayerbotAI* botAI) : CombatFormationMoveAction(botAI, "tank face") {} + + bool Execute(Event event) override; +}; + class DisperseSetAction : public Action { public: @@ -178,14 +189,12 @@ public: bool isPossible() override; }; -class SetBehindTargetAction : public MovementAction +class SetBehindTargetAction : public CombatFormationMoveAction { public: - SetBehindTargetAction(PlayerbotAI* botAI) : MovementAction(botAI, "set behind") {} + SetBehindTargetAction(PlayerbotAI* botAI) : CombatFormationMoveAction(botAI, "set behind") {} bool Execute(Event event) override; - bool isUseful() override; - bool isPossible() override; }; class MoveOutOfCollisionAction : public MovementAction diff --git a/src/strategy/generic/CombatStrategy.cpp b/src/strategy/generic/CombatStrategy.cpp index 9d2d9932..bd1e3838 100644 --- a/src/strategy/generic/CombatStrategy.cpp +++ b/src/strategy/generic/CombatStrategy.cpp @@ -70,7 +70,7 @@ AvoidAoeStrategy::AvoidAoeStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} NextAction** AvoidAoeStrategy::getDefaultActions() { - return NextAction::array(0, new NextAction("aaoe", ACTION_EMERGENCY), nullptr); + return NextAction::array(0, new NextAction("avoid aoe", ACTION_EMERGENCY), nullptr); } void AvoidAoeStrategy::InitTriggers(std::vector& triggers) @@ -85,6 +85,17 @@ void AvoidAoeStrategy::InitMultipliers(std::vector& multipliers) // multipliers.push_back(new AvoidAoeStrategyMultiplier(botAI)); } +TankFaceStrategy::TankFaceStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + +NextAction** TankFaceStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("tank face", ACTION_MOVE), nullptr); +} + +void TankFaceStrategy::InitTriggers(std::vector& triggers) +{ +} + NextAction** CombatFormationStrategy::getDefaultActions() { return NextAction::array(0, new NextAction("combat formation move", ACTION_NORMAL), nullptr); diff --git a/src/strategy/generic/CombatStrategy.h b/src/strategy/generic/CombatStrategy.h index e96a3eaf..9005f6f5 100644 --- a/src/strategy/generic/CombatStrategy.h +++ b/src/strategy/generic/CombatStrategy.h @@ -23,12 +23,21 @@ class AvoidAoeStrategy : public Strategy { public: explicit AvoidAoeStrategy(PlayerbotAI* ai); - const std::string getName() override { return "aaoe"; } + const std::string getName() override { return "avoid aoe"; } NextAction** getDefaultActions() override; void InitMultipliers(std::vector& multipliers) override; void InitTriggers(std::vector& triggers) override; }; +class TankFaceStrategy : public Strategy +{ +public: + explicit TankFaceStrategy(PlayerbotAI* ai); + const std::string getName() override { return "tank face"; } + NextAction** getDefaultActions() override; + void InitTriggers(std::vector& triggers) override; +}; + class CombatFormationStrategy : public Strategy { public: diff --git a/src/strategy/generic/ConserveManaStrategy.h b/src/strategy/generic/ConserveManaStrategy.h index d63daae6..0f389ca3 100644 --- a/src/strategy/generic/ConserveManaStrategy.h +++ b/src/strategy/generic/ConserveManaStrategy.h @@ -10,46 +10,10 @@ class PlayerbotAI; -// Yunfan: deprecate old save mana method. - -// class ConserveManaMultiplier : public Multiplier -// { -// public: -// ConserveManaMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "conserve mana") { } - -// float GetValue(Action* action) override; -// }; - -// class SaveManaMultiplier : public Multiplier -// { -// public: -// SaveManaMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "save mana") { } - -// float GetValue(Action* action) override; -// }; - -// class ConserveManaStrategy : public Strategy -// { -// public: -// ConserveManaStrategy(PlayerbotAI* botAI) : Strategy(botAI) { } - -// void InitMultipliers(std::vector& multipliers) override; -// std::string const getName() override { return "conserve mana"; } -// }; - -// class HealerSaveManaStrategy : public Strategy -// { -// public: -// HealerSaveManaStrategy(PlayerbotAI* botAI) : Strategy(botAI) { } - -// void InitMultipliers(std::vector& multipliers) override; -// std::string const getName() override { return "healer save mana"; } -// }; - class HealerAutoSaveManaMultiplier : public Multiplier { public: - HealerAutoSaveManaMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "smana") {} + HealerAutoSaveManaMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "save mana") {} float GetValue(Action* action) override; }; @@ -60,7 +24,7 @@ public: HealerAutoSaveManaStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} void InitMultipliers(std::vector& multipliers) override; - std::string const getName() override { return "smana"; } + std::string const getName() override { return "save mana"; } }; #endif diff --git a/src/strategy/values/IsBehindValue.cpp b/src/strategy/values/IsBehindValue.cpp index c803f681..80feef36 100644 --- a/src/strategy/values/IsBehindValue.cpp +++ b/src/strategy/values/IsBehindValue.cpp @@ -4,6 +4,7 @@ */ #include "IsBehindValue.h" +#include #include "Playerbots.h" @@ -14,8 +15,10 @@ bool IsBehindValue::Calculate() return false; float targetOrientation = target->GetOrientation(); - float orientation = bot->GetOrientation(); - float distance = bot->GetDistance(target); - return distance <= ATTACK_DISTANCE && abs(targetOrientation - orientation) < M_PI / 2; + float deltaAngle = Position::NormalizeOrientation(targetOrientation - target->GetAngle(bot)); + if (deltaAngle > M_PI) + deltaAngle -= 2.0f * M_PI; // -PI..PI + + return fabs(deltaAngle) > M_PI_2; }