From 629aa19dbd23d650058f63a670b1ed08cbcb331c Mon Sep 17 00:00:00 2001 From: dillyns <49765217+dillyns@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:00:55 -0500 Subject: [PATCH] Add aggressive non combat targeting strategy (#2117) # Pull Request Tired of failing that escort quest because your bots stood and watched while the escort npc got swarmed and killed? Tired of your bots standing around doing nothing while the npc you are supposed to be guarding for 5 minutes is getting attacked? Don't want to use the grind strategy because it is too heavy-handed and has too many restrictions? Look no further! Just do "nc +aggressive" and your bots will pick a fight with anything they can in a 30 yard radius. The aggressive targetting is a stripped down version of the grind target. ## Feature Evaluation Please answer the following: - Describe the **minimum logic** required to achieve the intended behavior? Add a strategy, action, and targetting that will cause bots to attack nearby enemies when out of combat. - Describe the **cheapest implementation** that produces an acceptable result? Hopefully this is the cheapest. - Describe the **runtime cost** when this logic executes across many bots? Minimal runtime cost as this strategy needs to be added specifically to bots. --- ## How to Test the Changes - Add a bot to party, or use selfbot - Give them the aggressive strategy via "nc +aggressive" - They should attack anything within 30 yards. - If it is a bot with a master, the 30 yards should be centered around the master not the bot (prevent chaining from enemy to enemy) ## Complexity & Impact Does this change add new decision branches? ``` [] No [x] Yes (**explain below**) Only for bots that have the added strategy, adds decision to attack nearby targets when out of combat. ``` Does this change increase per-bot or per-tick processing? ``` [] No [x] Yes (**describe and justify impact**) Minimal increase to only bots that have this strategy added. ``` 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 [ ] 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**) ``` Claude is used to explore the codebase to find similar implementations to be used for examples. --- ## 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 Anything that significantly improves realism at the cost of stability or performance should be carefully discussed before merging. --- src/Ai/Base/ActionContext.h | 2 + src/Ai/Base/Actions/ChooseTargetActions.cpp | 8 +++ src/Ai/Base/Actions/ChooseTargetActions.h | 9 +++ src/Ai/Base/Strategy/AggressiveStrategy.cpp | 20 +++++++ src/Ai/Base/Strategy/AggressiveStrategy.h | 22 +++++++ src/Ai/Base/StrategyContext.h | 3 + src/Ai/Base/Value/AggressiveTargetValue.cpp | 66 +++++++++++++++++++++ src/Ai/Base/Value/AggressiveTargetValue.h | 22 +++++++ src/Ai/Base/ValueContext.h | 3 + 9 files changed, 155 insertions(+) create mode 100644 src/Ai/Base/Strategy/AggressiveStrategy.cpp create mode 100644 src/Ai/Base/Strategy/AggressiveStrategy.h create mode 100644 src/Ai/Base/Value/AggressiveTargetValue.cpp create mode 100644 src/Ai/Base/Value/AggressiveTargetValue.h diff --git a/src/Ai/Base/ActionContext.h b/src/Ai/Base/ActionContext.h index 93bba4af..6431ee87 100644 --- a/src/Ai/Base/ActionContext.h +++ b/src/Ai/Base/ActionContext.h @@ -125,6 +125,7 @@ public: creators["runaway"] = &ActionContext::runaway; creators["stay"] = &ActionContext::stay; creators["sit"] = &ActionContext::sit; + creators["aggressive target"] = &ActionContext::aggressive_target; creators["attack anything"] = &ActionContext::attack_anything; creators["attack least hp target"] = &ActionContext::attack_least_hp_target; creators["attack enemy player"] = &ActionContext::attack_enemy_player; @@ -315,6 +316,7 @@ private: static Action* suggest_what_to_do(PlayerbotAI* botAI) { return new SuggestWhatToDoAction(botAI); } static Action* suggest_trade(PlayerbotAI* botAI) { return new SuggestTradeAction(botAI); } static Action* suggest_dungeon(PlayerbotAI* botAI) { return new SuggestDungeonAction(botAI); } + static Action* aggressive_target(PlayerbotAI* botAI) { return new AggressiveTargetAction(botAI); } static Action* attack_anything(PlayerbotAI* botAI) { return new AttackAnythingAction(botAI); } static Action* attack_least_hp_target(PlayerbotAI* botAI) { return new AttackLeastHpTargetAction(botAI); } static Action* attack_enemy_player(PlayerbotAI* botAI) { return new AttackEnemyPlayerAction(botAI); } diff --git a/src/Ai/Base/Actions/ChooseTargetActions.cpp b/src/Ai/Base/Actions/ChooseTargetActions.cpp index f7538c03..debcfac0 100644 --- a/src/Ai/Base/Actions/ChooseTargetActions.cpp +++ b/src/Ai/Base/Actions/ChooseTargetActions.cpp @@ -30,6 +30,14 @@ bool AttackEnemyFlagCarrierAction::isUseful() PlayerHasFlag::IsCapturingFlag(bot); } +bool AggressiveTargetAction::isUseful() +{ + if (bot->IsInCombat()) + return false; + + return true; +} + bool DropTargetAction::Execute(Event /*event*/) { Unit* target = context->GetValue("current target")->Get(); diff --git a/src/Ai/Base/Actions/ChooseTargetActions.h b/src/Ai/Base/Actions/ChooseTargetActions.h index 5f905019..5822543e 100644 --- a/src/Ai/Base/Actions/ChooseTargetActions.h +++ b/src/Ai/Base/Actions/ChooseTargetActions.h @@ -35,6 +35,15 @@ public: std::string const GetTargetName() override { return "tank target"; } }; +class AggressiveTargetAction : public AttackAction +{ +public: + AggressiveTargetAction(PlayerbotAI* botAI) : AttackAction(botAI, "aggressive target") {} + + std::string const GetTargetName() override { return "aggressive target"; } + bool isUseful() override; +}; + class AttackAnythingAction : public AttackAction { public: diff --git a/src/Ai/Base/Strategy/AggressiveStrategy.cpp b/src/Ai/Base/Strategy/AggressiveStrategy.cpp new file mode 100644 index 00000000..385e9408 --- /dev/null +++ b/src/Ai/Base/Strategy/AggressiveStrategy.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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 "AggressiveStrategy.h" + +#include "Playerbots.h" + +void AggressiveStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back( + new TriggerNode( + "no target", + { + NextAction("aggressive target", 4.0f) + } + ) + ); +} diff --git a/src/Ai/Base/Strategy/AggressiveStrategy.h b/src/Ai/Base/Strategy/AggressiveStrategy.h new file mode 100644 index 00000000..8a81192e --- /dev/null +++ b/src/Ai/Base/Strategy/AggressiveStrategy.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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. + */ + +#ifndef _PLAYERBOT_AGGRESSIVESTRATEGY_H +#define _PLAYERBOT_AGGRESSIVESTRATEGY_H + +#include "NonCombatStrategy.h" + +class PlayerbotAI; + +class AggressiveStrategy : public NonCombatStrategy +{ +public: + AggressiveStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) {} + + std::string const getName() override { return "aggressive"; } + void InitTriggers(std::vector& triggers) override; +}; + +#endif diff --git a/src/Ai/Base/StrategyContext.h b/src/Ai/Base/StrategyContext.h index 0cc6855f..60e6808c 100644 --- a/src/Ai/Base/StrategyContext.h +++ b/src/Ai/Base/StrategyContext.h @@ -6,6 +6,7 @@ #ifndef _PLAYERBOT_STRATEGYCONTEXT_H #define _PLAYERBOT_STRATEGYCONTEXT_H +#include "AggressiveStrategy.h" #include "AttackEnemyPlayersStrategy.h" #include "BattlegroundStrategy.h" #include "CastTimeStrategy.h" @@ -61,6 +62,7 @@ public: creators["gather"] = &StrategyContext::gather; creators["emote"] = &StrategyContext::emote; creators["passive"] = &StrategyContext::passive; + creators["aggressive"] = &StrategyContext::aggressive; creators["save mana"] = &StrategyContext::auto_save_mana; creators["food"] = &StrategyContext::food; creators["chat"] = &StrategyContext::chat; @@ -144,6 +146,7 @@ private: static Strategy* gather(PlayerbotAI* botAI) { return new GatherStrategy(botAI); } static Strategy* emote(PlayerbotAI* botAI) { return new EmoteStrategy(botAI); } static Strategy* passive(PlayerbotAI* botAI) { return new PassiveStrategy(botAI); } + static Strategy* aggressive(PlayerbotAI* botAI) { return new AggressiveStrategy(botAI); } // static Strategy* conserve_mana(PlayerbotAI* botAI) { return new ConserveManaStrategy(botAI); } static Strategy* auto_save_mana(PlayerbotAI* botAI) { return new HealerAutoSaveManaStrategy(botAI); } static Strategy* food(PlayerbotAI* botAI) { return new UseFoodStrategy(botAI); } diff --git a/src/Ai/Base/Value/AggressiveTargetValue.cpp b/src/Ai/Base/Value/AggressiveTargetValue.cpp new file mode 100644 index 00000000..bbf39348 --- /dev/null +++ b/src/Ai/Base/Value/AggressiveTargetValue.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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 "AggressiveTargetValue.h" + +#include "Playerbots.h" +#include "ServerFacade.h" +#include "SharedDefines.h" + +Unit* AggressiveTargetValue::Calculate() +{ + Player* master = GetMaster(); + + if (master && (master == bot || master->GetMapId() != bot->GetMapId() || master->IsBeingTeleported() || + !GET_PLAYERBOT_AI(master))) + master = nullptr; + + GuidVector targets = AI_VALUE(GuidVector, "possible targets"); + if (targets.empty()) + return nullptr; + + float aggroRange = sPlayerbotAIConfig.aggroDistance; + float distance = 0; + Unit* result = nullptr; + + for (ObjectGuid const guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (!unit->IsInWorld() || unit->IsDuringRemoveFromWorld()) + continue; + + if (unit->ToCreature() && !unit->ToCreature()->GetCreatureTemplate()->lootid && + bot->GetReactionTo(unit) >= REP_NEUTRAL) + continue; + + if (!bot->IsHostileTo(unit) && unit->GetNpcFlags() != UNIT_NPC_FLAG_NONE) + continue; + + if (abs(bot->GetPositionZ() - unit->GetPositionZ()) > INTERACTION_DISTANCE) + continue; + + if (!bot->InBattleground() && master && botAI->HasStrategy("follow", BotState::BOT_STATE_NON_COMBAT) && + ServerFacade::instance().GetDistance2d(master, unit) > aggroRange) + continue; + + if (!bot->IsWithinLOSInMap(unit)) + continue; + + if (bot->GetDistance(unit) > aggroRange) + continue; + + float newdistance = bot->GetDistance(unit); + if (!result || (newdistance < distance)) + { + distance = newdistance; + result = unit; + } + } + + return result; +} diff --git a/src/Ai/Base/Value/AggressiveTargetValue.h b/src/Ai/Base/Value/AggressiveTargetValue.h new file mode 100644 index 00000000..61b4d695 --- /dev/null +++ b/src/Ai/Base/Value/AggressiveTargetValue.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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. + */ + +#ifndef _PLAYERBOT_AGGRESSIVETARGETVALUE_H +#define _PLAYERBOT_AGGRESSIVETARGETVALUE_H + +#include "TargetValue.h" + +class PlayerbotAI; +class Unit; + +class AggressiveTargetValue : public TargetValue +{ +public: + AggressiveTargetValue(PlayerbotAI* botAI, std::string const name = "aggressive target") : TargetValue(botAI, name) {} + + Unit* Calculate() override; +}; + +#endif diff --git a/src/Ai/Base/ValueContext.h b/src/Ai/Base/ValueContext.h index 115294bd..6038db3b 100644 --- a/src/Ai/Base/ValueContext.h +++ b/src/Ai/Base/ValueContext.h @@ -7,6 +7,7 @@ #define _PLAYERBOT_VALUECONTEXT_H #include "ActiveSpellValue.h" +#include "AggressiveTargetValue.h" #include "AlwaysLootListValue.h" #include "AoeHealValues.h" #include "AoeValues.h" @@ -144,6 +145,7 @@ public: creators["pet target"] = &ValueContext::pet_target; creators["old target"] = &ValueContext::old_target; creators["grind target"] = &ValueContext::grind_target; + creators["aggressive target"] = &ValueContext::aggressive_target; creators["rti target"] = &ValueContext::rti_target; creators["rti cc target"] = &ValueContext::rti_cc_target; creators["duel target"] = &ValueContext::duel_target; @@ -459,6 +461,7 @@ private: static UntypedValue* current_cc_target(PlayerbotAI* botAI) { return new CurrentCcTargetValue(botAI); } static UntypedValue* pet_target(PlayerbotAI* botAI) { return new PetTargetValue(botAI); } static UntypedValue* grind_target(PlayerbotAI* botAI) { return new GrindTargetValue(botAI); } + static UntypedValue* aggressive_target(PlayerbotAI* botAI) { return new AggressiveTargetValue(botAI); } static UntypedValue* rti_target(PlayerbotAI* botAI) { return new RtiTargetValue(botAI); } static UntypedValue* rti_cc_target(PlayerbotAI* botAI) { return new RtiCcTargetValue(botAI); } static UntypedValue* duel_target(PlayerbotAI* botAI) { return new DuelTargetValue(botAI); }