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.
This commit is contained in:
dillyns
2026-02-23 14:00:55 -05:00
committed by GitHub
parent 2f7dfdbbfc
commit 629aa19dbd
9 changed files with 155 additions and 0 deletions

View File

@@ -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); }

View File

@@ -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<Unit*>("current target")->Get();

View File

@@ -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:

View File

@@ -0,0 +1,20 @@
/*
* 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 "AggressiveStrategy.h"
#include "Playerbots.h"
void AggressiveStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode(
"no target",
{
NextAction("aggressive target", 4.0f)
}
)
);
}

View File

@@ -0,0 +1,22 @@
/*
* 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.
*/
#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<TriggerNode*>& triggers) override;
};
#endif

View File

@@ -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); }

View File

@@ -0,0 +1,66 @@
/*
* 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 "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;
}

View File

@@ -0,0 +1,22 @@
/*
* 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.
*/
#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

View File

@@ -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); }