From bd1e3420c0b4adba9d8bbee7ea8230a787591971 Mon Sep 17 00:00:00 2001 From: Nicolas Lebacq Date: Sun, 10 May 2026 19:42:29 +0200 Subject: [PATCH] feat: Improved taunt logic to avoid taunt wars between tanks. --- .../Actions/combat/UniversalTauntAction.h | 51 +++++- src/Ai/Class/Dk/Action/DKActions.h | 133 ++++++++++++++- src/Ai/Class/Druid/Action/DruidBearActions.h | 127 +++++++++++++++ src/Ai/Class/Paladin/Action/PaladinActions.h | 127 +++++++++++++++ .../Paladin/Strategy/PaladinPullStrategy.cpp | 1 - src/Ai/Class/Warrior/Action/WarriorActions.h | 151 +++++++++++++++++- 6 files changed, 581 insertions(+), 9 deletions(-) diff --git a/src/Ai/Base/Actions/combat/UniversalTauntAction.h b/src/Ai/Base/Actions/combat/UniversalTauntAction.h index 21c35e565fa..b25cdc29407 100644 --- a/src/Ai/Base/Actions/combat/UniversalTauntAction.h +++ b/src/Ai/Base/Actions/combat/UniversalTauntAction.h @@ -5,6 +5,7 @@ #include "AiFactory.h" #include "DKActions.h" #include "PaladinActions.h" +#include "PlayerbotAI.h" #include "WarriorActions.h" #include "DruidBearActions.h" @@ -34,20 +35,64 @@ class UniversalTauntAction : public Action bool isUseful() override { - const Unit* const target = this->GetTarget(); + Unit* const target = this->GetTarget(); if (target == nullptr) { return false; } - const ObjectGuid targetTargetGUID = target->GetTarget(); + const ObjectGuid targetTarget = target->GetTarget(); - if (targetTargetGUID == this->bot->GetGUID()) + if (targetTarget.IsEmpty()) { return false; } + if (targetTarget == this->bot->GetGUID()) + { + return false; + } + + Player* const playerTargetTarget = ObjectAccessor::FindPlayer(targetTarget); + + if (playerTargetTarget == nullptr) + { + return true; + } + + + Value* const rtiTargetValue = this->context->GetValue("rti target"); + + // This is a normally impossible situation where the Value is not correctly instantiated. + // It does not mean the value itself is empty. + if (rtiTargetValue == nullptr) + { + return false; + } + + const Unit* const rtiTarget = rtiTargetValue->Get(); + + if (PlayerbotAI::IsMainTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + if (PlayerbotAI::IsAssistTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + return true; } diff --git a/src/Ai/Class/Dk/Action/DKActions.h b/src/Ai/Class/Dk/Action/DKActions.h index 5e8bf968fe1..60eeb7d7b59 100644 --- a/src/Ai/Class/Dk/Action/DKActions.h +++ b/src/Ai/Class/Dk/Action/DKActions.h @@ -6,6 +6,7 @@ #ifndef _PLAYERBOT_DKACTIONS_H #define _PLAYERBOT_DKACTIONS_H +#include "AiObjectContext.h" #include "Event.h" #include "GenericSpellActions.h" @@ -41,10 +42,138 @@ class CastDarkCommandAction : public CastSpellAction { public: CastDarkCommandAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "dark command") {} + + bool isUseful() override + { + Unit* const target = this->GetTarget(); + + if (target == nullptr) + { + return false; + } + + const ObjectGuid targetTarget = target->GetTarget(); + + if (targetTarget.IsEmpty()) + { + return false; + } + + if (targetTarget == this->bot->GetGUID()) + { + return false; + } + + Player* const playerTargetTarget = ObjectAccessor::FindPlayer(targetTarget); + + if (playerTargetTarget == nullptr) + { + return true; + } + + + Value* const rtiTargetValue = this->context->GetValue("rti target"); + + // This is a normally impossible situation where the Value is not correctly instantiated. + // It does not mean the value itself is empty. + if (rtiTargetValue == nullptr) + { + return false; + } + + const Unit* const rtiTarget = rtiTargetValue->Get(); + + if (PlayerbotAI::IsMainTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + if (PlayerbotAI::IsAssistTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + return true; + } }; +class CastDeathGripAction : public CastSpellAction +{ +public: + CastDeathGripAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "death grip") {} -BEGIN_RANGED_SPELL_ACTION(CastDeathGripAction, "death grip") -END_SPELL_ACTION() + bool isUseful() override + { + Unit* const target = this->GetTarget(); + + if (target == nullptr) + { + return false; + } + + const ObjectGuid targetTarget = target->GetTarget(); + + if (targetTarget.IsEmpty()) + { + return false; + } + + if (targetTarget == this->bot->GetGUID()) + { + return false; + } + + Player* const playerTargetTarget = ObjectAccessor::FindPlayer(targetTarget); + + if (playerTargetTarget == nullptr) + { + return true; + } + + + Value* const rtiTargetValue = this->context->GetValue("rti target"); + + // This is a normally impossible situation where the Value is not correctly instantiated. + // It does not mean the value itself is empty. + if (rtiTargetValue == nullptr) + { + return false; + } + + const Unit* const rtiTarget = rtiTargetValue->Get(); + + if (PlayerbotAI::IsMainTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + if (PlayerbotAI::IsAssistTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + return true; + } +}; // Unholy presence class CastUnholyMeleeSpellAction : public CastMeleeSpellAction diff --git a/src/Ai/Class/Druid/Action/DruidBearActions.h b/src/Ai/Class/Druid/Action/DruidBearActions.h index d5354b7e6c5..9320deb4013 100644 --- a/src/Ai/Class/Druid/Action/DruidBearActions.h +++ b/src/Ai/Class/Druid/Action/DruidBearActions.h @@ -6,6 +6,7 @@ #ifndef _PLAYERBOT_DRUIDBEARACTIONS_H #define _PLAYERBOT_DRUIDBEARACTIONS_H +#include "AiObjectContext.h" #include "GenericSpellActions.h" #include "ReachTargetActions.h" @@ -21,12 +22,138 @@ class CastGrowlAction : public CastSpellAction { public: CastGrowlAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "growl") {} + + bool isUseful() override + { + Unit* const target = this->GetTarget(); + + if (target == nullptr) + { + return false; + } + + const ObjectGuid targetTarget = target->GetTarget(); + + if (targetTarget.IsEmpty()) + { + return false; + } + + if (targetTarget == this->bot->GetGUID()) + { + return false; + } + + Player* const playerTargetTarget = ObjectAccessor::FindPlayer(targetTarget); + + if (playerTargetTarget == nullptr) + { + return true; + } + + + Value* const rtiTargetValue = this->context->GetValue("rti target"); + + // This is a normally impossible situation where the Value is not correctly instantiated. + // It does not mean the value itself is empty. + if (rtiTargetValue == nullptr) + { + return false; + } + + const Unit* const rtiTarget = rtiTargetValue->Get(); + + if (PlayerbotAI::IsMainTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + if (PlayerbotAI::IsAssistTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + return true; + } }; class CastChallengingRoarAction : public CastMeleeDebuffSpellAction { public: CastChallengingRoarAction(PlayerbotAI* botAI) : CastMeleeDebuffSpellAction(botAI, "challenging roar") {} + + bool isUseful() override + { + Unit* const target = this->GetTarget(); + + if (target == nullptr) + { + return false; + } + + const ObjectGuid targetTarget = target->GetTarget(); + + if (targetTarget.IsEmpty()) + { + return false; + } + + if (targetTarget == this->bot->GetGUID()) + { + return false; + } + + Player* const playerTargetTarget = ObjectAccessor::FindPlayer(targetTarget); + + if (playerTargetTarget == nullptr) + { + return true; + } + + + Value* const rtiTargetValue = this->context->GetValue("rti target"); + + // This is a normally impossible situation where the Value is not correctly instantiated. + // It does not mean the value itself is empty. + if (rtiTargetValue == nullptr) + { + return false; + } + + const Unit* const rtiTarget = rtiTargetValue->Get(); + + if (PlayerbotAI::IsMainTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + if (PlayerbotAI::IsAssistTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + return true; + } }; class CastMaulAction : public CastMeleeSpellAction diff --git a/src/Ai/Class/Paladin/Action/PaladinActions.h b/src/Ai/Class/Paladin/Action/PaladinActions.h index 819d4184648..a8af1f98f8a 100644 --- a/src/Ai/Class/Paladin/Action/PaladinActions.h +++ b/src/Ai/Class/Paladin/Action/PaladinActions.h @@ -7,6 +7,7 @@ #define _PLAYERBOT_PALADINACTIONS_H #include "AiObject.h" +#include "AiObjectContext.h" #include "GenericSpellActions.h" #include "SharedDefines.h" @@ -275,6 +276,69 @@ class CastHandOfReckoningAction : public CastSpellAction { public: CastHandOfReckoningAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hand of reckoning") {} + + bool isUseful() override + { + Unit* const target = this->GetTarget(); + + if (target == nullptr) + { + return false; + } + + const ObjectGuid targetTarget = target->GetTarget(); + + if (targetTarget.IsEmpty()) + { + return false; + } + + if (targetTarget == this->bot->GetGUID()) + { + return false; + } + + Player* const playerTargetTarget = ObjectAccessor::FindPlayer(targetTarget); + + if (playerTargetTarget == nullptr) + { + return true; + } + + + Value* const rtiTargetValue = this->context->GetValue("rti target"); + + // This is a normally impossible situation where the Value is not correctly instantiated. + // It does not mean the value itself is empty. + if (rtiTargetValue == nullptr) + { + return false; + } + + const Unit* const rtiTarget = rtiTargetValue->Get(); + + if (PlayerbotAI::IsMainTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + if (PlayerbotAI::IsAssistTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + return true; + } }; class CastHandOfSalvationAction : public CastSpellAction @@ -288,6 +352,69 @@ class CastRighteousDefenseAction : public CastSpellAction public: CastRighteousDefenseAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "righteous defense") {} virtual Unit* GetTarget() override; + + bool isUseful() override + { + Unit* const target = this->GetTarget(); + + if (target == nullptr) + { + return false; + } + + const ObjectGuid targetTarget = target->GetTarget(); + + if (targetTarget.IsEmpty()) + { + return false; + } + + if (targetTarget == this->bot->GetGUID()) + { + return false; + } + + Player* const playerTargetTarget = ObjectAccessor::FindPlayer(targetTarget); + + if (playerTargetTarget == nullptr) + { + return true; + } + + + Value* const rtiTargetValue = this->context->GetValue("rti target"); + + // This is a normally impossible situation where the Value is not correctly instantiated. + // It does not mean the value itself is empty. + if (rtiTargetValue == nullptr) + { + return false; + } + + const Unit* const rtiTarget = rtiTargetValue->Get(); + + if (PlayerbotAI::IsMainTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + if (PlayerbotAI::IsAssistTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + return true; + } }; class CastCleansePoisonAction : public CastCureSpellAction diff --git a/src/Ai/Class/Paladin/Strategy/PaladinPullStrategy.cpp b/src/Ai/Class/Paladin/Strategy/PaladinPullStrategy.cpp index ba0381b5ab3..c02d3f9b7d2 100644 --- a/src/Ai/Class/Paladin/Strategy/PaladinPullStrategy.cpp +++ b/src/Ai/Class/Paladin/Strategy/PaladinPullStrategy.cpp @@ -8,7 +8,6 @@ #include "AiObjectContext.h" #include "Player.h" #include "PlayerbotAI.h" -#include "Playerbots.h" std::string PaladinPullStrategy::GetPullActionName() const { diff --git a/src/Ai/Class/Warrior/Action/WarriorActions.h b/src/Ai/Class/Warrior/Action/WarriorActions.h index fbeba717fa6..24ef7658799 100644 --- a/src/Ai/Class/Warrior/Action/WarriorActions.h +++ b/src/Ai/Class/Warrior/Action/WarriorActions.h @@ -7,7 +7,10 @@ #define _PLAYERBOT_WARRIORACTIONS_H #include "AiObject.h" +#include "AiObjectContext.h" #include "GenericSpellActions.h" +#include "ObjectAccessor.h" +#include "ObjectGuid.h" #include "Player.h" #include "PlayerbotAI.h" #include "ReachTargetActions.h" @@ -38,7 +41,75 @@ class CastDemoralizingShoutWithoutLifeTimeCheckAction : public CastMeleeDebuffSp } }; -MELEE_ACTION(CastChallengingShoutAction, "challenging shout"); +class CastChallengingShoutAction : public CastMeleeSpellAction +{ +public: + CastChallengingShoutAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "challenging shout") {} + + bool isUseful() override + { + Unit* const target = this->GetTarget(); + + if (target == nullptr) + { + return false; + } + + const ObjectGuid targetTarget = target->GetTarget(); + + if (targetTarget.IsEmpty()) + { + return false; + } + + if (targetTarget == this->bot->GetGUID()) + { + return false; + } + + Player* const playerTargetTarget = ObjectAccessor::FindPlayer(targetTarget); + + if (playerTargetTarget == nullptr) + { + return true; + } + + + Value* const rtiTargetValue = this->context->GetValue("rti target"); + + // This is a normally impossible situation where the Value is not correctly instantiated. + // It does not mean the value itself is empty. + if (rtiTargetValue == nullptr) + { + return false; + } + + const Unit* const rtiTarget = rtiTargetValue->Get(); + + if (PlayerbotAI::IsMainTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + if (PlayerbotAI::IsAssistTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + return true; + } +}; + DEBUFF_ACTION_R(CastIntimidatingShoutAction, "intimidating shout", 8.0f); // shouts 2.4.3 BUFF_ACTION(CastCommandingShoutAction, "commanding shout"); @@ -59,7 +130,13 @@ MELEE_ACTION(CastThunderClapAction, "thunder clap"); SNARE_ACTION(CastThunderClapSnareAction, "thunder clap"); SNARE_ACTION(CastHamstringAction, "hamstring"); MELEE_ACTION(CastOverpowerAction, "overpower"); -MELEE_ACTION(CastMockingBlowAction, "mocking blow"); + +class CastMockingBlowAction : public CastMeleeSpellAction +{ +public: + CastMockingBlowAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "mocking blow") {} +}; + // BUFF_ACTION(CastRetaliationAction, "retaliation"); // arms 3.3.5 SPELL_ACTION(CastHeroicThrowAction, "heroic throw"); @@ -106,7 +183,75 @@ DEBUFF_ACTION_R(CastPiercingHowlAction, "piercing howl", 8.0f); BUFF_ACTION(CastRampageAction, "rampage"); // protection -MELEE_ACTION_U(CastTauntAction, "taunt", GetTarget() && GetTarget()->GetTarget() != bot->GetGUID()); +class CastTauntAction : public CastMeleeSpellAction +{ +public: + CastTauntAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "taunt") {} + + bool isUseful() override + { + Unit* const target = this->GetTarget(); + + if (target == nullptr) + { + return false; + } + + const ObjectGuid targetTarget = target->GetTarget(); + + if (targetTarget.IsEmpty()) + { + return false; + } + + if (targetTarget == this->bot->GetGUID()) + { + return false; + } + + Player* const playerTargetTarget = ObjectAccessor::FindPlayer(targetTarget); + + if (playerTargetTarget == nullptr) + { + return true; + } + + + Value* const rtiTargetValue = this->context->GetValue("rti target"); + + // This is a normally impossible situation where the Value is not correctly instantiated. + // It does not mean the value itself is empty. + if (rtiTargetValue == nullptr) + { + return false; + } + + const Unit* const rtiTarget = rtiTargetValue->Get(); + + if (PlayerbotAI::IsMainTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + if (PlayerbotAI::IsAssistTank(playerTargetTarget)) + { + if (rtiTarget != nullptr && rtiTarget->GetGUID() == target->GetGUID()) + { + return true; + } + + return false; + } + + return true; + } +}; + SNARE_ACTION(CastTauntOnSnareTargetAction, "taunt"); BUFF_ACTION(CastBloodrageAction, "bloodrage"); MELEE_ACTION(CastShieldBashAction, "shield bash");