Fix Warrior Battle Shout Spam (#2259)

<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
There is a known problem that Warriors will repeatedly spam Battle Shout
(BS) if there is a stronger Blessing of Might (BoM) present that they
cannot override. It has been long known that the issue arises from the
lack of a BS trigger. It was not a problem in the past because BS and
BoM used to (erroneously) stack, and that was eliminated when AC ported
TC's stacking rules.

This PR adds a trigger for BS that prevents the bot from attempting to
cast it if a stronger BoM (or Greater BoM) is present. To do this, it
compares the AP bonus of any BoM/GBoM applied to the Warrior bot, and
then will attempt BS only if the Warrior has a stronger one.

I also did some minor reformatting of the other Warrior triggers, mostly
just deleting comments and extra braces.

## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing cost** when this logic executes across many
bots.

The trigger is fairly complicated because it uses an AP comparison and
takes into account spell ranks and the talent Commanding Presence (which
increases the strength of BS). The bug could be solved by a very simple
aura check for BoM/GBoM. That's suboptimal though because BS will be
stronger if the Paladin has a lower rank of BoM or the Warrior has
Commanding Presence and the Paladin does not have Improved BoM.

At the end of the day, this is a new trigger that runs every tick, but
it's not really more expensive than generic BuffTriggers, which all do
this. And it is needed to solve a very annoying bug that essentially
renders Warriors useless in any optimized raid unless BS is disabled.

I don't believe this more robust trigger (as opposed to just calling
BuffTrigger and additionally checking for any BoM/GBoM aura) has any
further impact on performance--any added logic is practically free AFAIK
(e.g., checking if the bot has a particular talent is just an integer
lookup).


## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->

Form a group with a Warrior and Paladin bot, with the Warrior bot having
a weaker BS than the Paladin's BoM (this is naturally the case at max
level, unless the Warrior has Commanding Presence and the Paladin does
not have Improved BoM). Have the Paladin cast BoM on the Warrior if it
has not done so automatically. Enter combat and see if the Warrior uses
BS. Try again with the Warrior having a stronger BS.



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [ ] No, not at all
    - - [x] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)

As noted above, this is a new BuffTrigger that runs every tick.

- Does this change modify default bot behavior?
    - - [ ] No
    - - [x] Yes (**explain why**)

Default behavior was broken.

- Does this change add new decision branches or increase maintenance
complexity?
    - - [x] No
    - - [ ] Yes (**explain below**)

This is, with respect to the code, a pretty complex trigger. But that
should not impact other logic, and if need be, there is a very simple
version that could be implemented that would at least fix the bug, as
noted above.

## Messages to Translate
<!--
Bot messages have to be translatable, but you don't need to do the
translations here. You only need to make sure
the message is in a translatable format, and list in the table the
message_key and the default English message.
Search for GetBotTextOrDefault in the codebase for examples.
-->
- Does this change add bot messages to translate?
    - - [x] No
    - - [ ] Yes (**list messages in the table**)

| Message key  | Default message |
| --------------- | ------------------ |
|			 |			      |
|			 |			      |

## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
- 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.
-->

I used Claude Sonnet 4.6. This is essentially the first PR that I
entirely vibe coded. I knew what the problem was and how to fix it,
including the conditions I wanted in the trigger, and the LLM did the
rest. I can tell you what each block does and why that's needed, but I
can't tell you how they all work technically.

I tested this ingame in scenarios where the available BS was stronger
and not as strong, and in each case, the correct behavior was observed.
I've since run several raids without BS disabled on my Warriors, and
they are correctly not using it (as my Ret Paladin is applying a
stronger BoM). The trigger does not show up as an expensive one with
pmon (unlike, say, VIgilance, which is probably the most expensive class
trigger in the mod (excluding any DK triggers just because I've never
used one so I have no idea)).

## 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.
-->

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
This commit is contained in:
Crow 2026-04-04 05:59:45 -05:00 committed by GitHub
parent a87999bef5
commit f5363c9465
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 40 deletions

View File

@ -4,7 +4,6 @@
*/
#include "WarriorTriggers.h"
#include "Playerbots.h"
bool BloodrageBuffTrigger::IsActive()
@ -16,15 +15,11 @@ bool BloodrageBuffTrigger::IsActive()
bool VigilanceTrigger::IsActive()
{
if (!bot->HasSpell(50720))
{
return false;
}
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
Player* currentVigilanceTarget = nullptr;
Player* mainTank = nullptr;
@ -33,37 +28,23 @@ bool VigilanceTrigger::IsActive()
Player* highestGearScorePlayer = nullptr;
uint32 highestGearScore = 0;
// Iterate once through the group to gather all necessary information
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || member == bot || !member->IsAlive())
continue;
// Check if member has Vigilance applied by the bot
if (!currentVigilanceTarget && botAI->HasAura("vigilance", member, false, true))
{
currentVigilanceTarget = member;
}
// Identify Main Tank
if (!mainTank && botAI->IsMainTank(member))
{
mainTank = member;
}
// Identify Assist Tanks
if (assistTank1 == nullptr && botAI->IsAssistTankOfIndex(member, 0))
{
else if (!assistTank1 && botAI->IsAssistTankOfIndex(member, 0))
assistTank1 = member;
}
else if (assistTank2 == nullptr && botAI->IsAssistTankOfIndex(member, 1))
{
else if (!assistTank2 && botAI->IsAssistTankOfIndex(member, 1))
assistTank2 = member;
}
// Determine Highest Gear Score
uint32 gearScore = botAI->GetEquipGearScore(member/*, false, false*/);
uint32 gearScore = botAI->GetEquipGearScore(member);
if (gearScore > highestGearScore)
{
highestGearScore = gearScore;
@ -71,33 +52,20 @@ bool VigilanceTrigger::IsActive()
}
}
// Determine the highest-priority target
Player* highestPriorityTarget = mainTank ? mainTank :
(assistTank1 ? assistTank1 :
(assistTank2 ? assistTank2 : highestGearScorePlayer));
// Trigger if no Vigilance is active or the current target is not the highest-priority target
if (!currentVigilanceTarget || currentVigilanceTarget != highestPriorityTarget)
{
return true;
}
return false; // No need to reassign Vigilance
return false;
}
bool ShatteringThrowTrigger::IsActive()
{
// Spell cooldown check
if (!bot->HasSpell(64382))
{
if (!bot->HasSpell(64382) || bot->HasSpellCooldown(64382))
return false;
}
// Spell cooldown check
if (bot->HasSpellCooldown(64382))
{
return false;
}
GuidVector enemies = AI_VALUE(GuidVector, "possible targets");
@ -107,7 +75,6 @@ bool ShatteringThrowTrigger::IsActive()
if (!enemy || !enemy->IsAlive() || enemy->IsFriendlyTo(bot))
continue;
// Check if the enemy is within 25 yards and has the specific auras
if (bot->IsWithinDistInMap(enemy, 25.0f) &&
(enemy->HasAura(642) || // Divine Shield
enemy->HasAura(45438) || // Ice Block
@ -117,5 +84,74 @@ bool ShatteringThrowTrigger::IsActive()
}
}
return false; // No valid targets within range
return false;
}
bool BattleShoutTrigger::IsActive()
{
if (!BuffTrigger::IsActive())
return false;
uint32 battleShoutSpellId = AI_VALUE2(uint32, "spell id", "battle shout");
if (!battleShoutSpellId)
return false;
SpellInfo const* bsInfo = sSpellMgr->GetSpellInfo(battleShoutSpellId);
if (!bsInfo)
return false;
int32 bsApValue = 0;
for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff)
{
if (bsInfo->Effects[eff].ApplyAuraName == SPELL_AURA_MOD_ATTACK_POWER)
{
bsApValue = bsInfo->Effects[eff].BasePoints + 1;
break;
}
}
if (!bsApValue)
return false;
static const uint32 commandingPresenceSpells[] = {
12318, 12857, 12858, 12860, 12861 };
static const float commandingPresenceBonus[] = {
0.05f, 0.10f, 0.15f, 0.20f, 0.25f };
float cpBonus = 0.0f;
for (int rank = 4; rank >= 0; --rank)
{
if (bot->HasAura(commandingPresenceSpells[rank]))
{
cpBonus = commandingPresenceBonus[rank];
break;
}
}
int32 effectiveBsAp = int32(bsApValue * (1.0f + cpBonus));
static const char* blessingNames[] = {
"blessing of might", "greater blessing of might", nullptr
};
for (int i = 0; blessingNames[i] != nullptr; ++i)
{
Aura* bom = botAI->GetAura(blessingNames[i], bot);
if (!bom)
continue;
SpellInfo const* bomInfo = bom->GetSpellInfo();
if (!bomInfo)
continue;
for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff)
{
if (bomInfo->Effects[eff].ApplyAuraName == SPELL_AURA_MOD_ATTACK_POWER)
{
int32 bomApValue = bomInfo->Effects[eff].BasePoints + 1;
if (bomApValue >= effectiveBsAp)
return false;
break;
}
}
}
return true;
}

View File

@ -9,7 +9,13 @@
#include "GenericTriggers.h"
#include "PlayerbotAI.h"
BUFF_TRIGGER(BattleShoutTrigger, "battle shout");
class BattleShoutTrigger : public BuffTrigger
{
public:
BattleShoutTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "battle shout") {}
bool IsActive() override;
};
BUFF_TRIGGER(BattleStanceTrigger, "battle stance");
BUFF_TRIGGER(DefensiveStanceTrigger, "defensive stance");
BUFF_TRIGGER(BerserkerStanceTrigger, "berserker stance");
@ -85,4 +91,5 @@ public:
// public:
// SlamTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "slam!") {}
// };
#endif