diff --git a/src/Ai/Base/ActionContext.h b/src/Ai/Base/ActionContext.h index 256c0de38..41763be90 100644 --- a/src/Ai/Base/ActionContext.h +++ b/src/Ai/Base/ActionContext.h @@ -165,6 +165,7 @@ public: creators["blood fury"] = &ActionContext::blood_fury; creators["berserking"] = &ActionContext::berserking; creators["every man for himself"] = &ActionContext::every_man_for_himself; + creators["will of the forsaken"] = &ActionContext::will_of_the_forsaken; creators["use trinket"] = &ActionContext::use_trinket; creators["auto talents"] = &ActionContext::auto_talents; creators["auto share quest"] = &ActionContext::auto_share_quest; @@ -361,6 +362,7 @@ private: static Action* blood_fury(PlayerbotAI* botAI) { return new CastBloodFuryAction(botAI); } static Action* berserking(PlayerbotAI* botAI) { return new CastBerserkingAction(botAI); } static Action* every_man_for_himself(PlayerbotAI* botAI) { return new CastEveryManForHimselfAction(botAI); } + static Action* will_of_the_forsaken(PlayerbotAI* botAI) { return new CastWillOfTheForsakenAction(botAI); } static Action* use_trinket(PlayerbotAI* botAI) { return new UseTrinketAction(botAI); } static Action* auto_talents(PlayerbotAI* botAI) { return new AutoSetTalentsAction(botAI); } static Action* auto_share_quest(PlayerbotAI* ai) { return new AutoShareQuestAction(ai); } diff --git a/src/Ai/Base/Actions/GenericBuffUtils.h b/src/Ai/Base/Actions/GenericBuffUtils.h deleted file mode 100644 index c893de597..000000000 --- a/src/Ai/Base/Actions/GenericBuffUtils.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include "Common.h" -#include "Group.h" -#include "Chat.h" -#include "Language.h" - -class Player; -class PlayerbotAI; - -namespace ai::buff -{ - - // Build an aura qualifier "single + greater" to avoid double-buffing - std::string MakeAuraQualifierForBuff(std::string const& name); - - // Returns the group spell name for a given single-target buff. - // If no group equivalent exists, returns "". - std::string GroupVariantFor(std::string const& name); - - // Checks if the bot has the required reagents to cast a spell (by its spellId). - // Returns false if the spellId is invalid. - bool HasRequiredReagents(Player* bot, uint32 spellId); - - // Applies the "switch to group buff" policy if: the bot is in a group of size x+, - // the group variant is known/useful, and reagents are available. Otherwise, returns baseName. - // If announceOnMissing == true and reagents are missing, calls the 'announce' callback - // (if provided) to notify the party/raid. - std::string UpgradeToGroupIfAppropriate( - Player* bot, - PlayerbotAI* botAI, - std::string const& baseName, - bool announceOnMissing = false, - std::function announce = {} - ); -} - -namespace ai::chat { - inline std::function MakeGroupAnnouncer(Player* me) - { - return [me](std::string const& msg) - { - if (Group* g = me->GetGroup()) - { - WorldPacket data; - ChatMsg type = g->isRaidGroup() ? CHAT_MSG_RAID : CHAT_MSG_PARTY; - ChatHandler::BuildChatPacket(data, type, LANG_UNIVERSAL, me, /*receiver=*/nullptr, msg.c_str()); - g->BroadcastPacket(&data, true, -1, me->GetGUID()); - } - else - { - me->Say(msg, LANG_UNIVERSAL); - } - }; - } -} diff --git a/src/Ai/Base/Actions/GenericSpellActions.cpp b/src/Ai/Base/Actions/GenericSpellActions.cpp index 392d18500..82bdb74d2 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.cpp +++ b/src/Ai/Base/Actions/GenericSpellActions.cpp @@ -17,10 +17,11 @@ #include "WorldPacket.h" #include "Group.h" #include "Chat.h" -#include "GenericBuffUtils.h" +#include "Ai/Base/Util/GenericBuffUtils.h" #include "PlayerbotAI.h" using ai::buff::MakeAuraQualifierForBuff; +using ai::spell::HasSpellOrCategoryCooldown; CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell) : Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell) @@ -320,7 +321,7 @@ bool CastEveryManForHimselfAction::isPossible() if (!bot->HasSpell(spellId)) return false; - if (bot->HasSpellCooldown(spellId)) + if (HasSpellOrCategoryCooldown(bot, spellId)) return false; return true; @@ -328,11 +329,36 @@ bool CastEveryManForHimselfAction::isPossible() bool CastEveryManForHimselfAction::isUseful() { - return bot->HasAuraType(SPELL_AURA_MOD_STUN) || + return (bot->HasAuraType(SPELL_AURA_MOD_STUN) || bot->HasAuraType(SPELL_AURA_MOD_FEAR) || bot->HasAuraType(SPELL_AURA_MOD_ROOT) || bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || - bot->HasAuraType(SPELL_AURA_MOD_CHARM); + bot->HasAuraType(SPELL_AURA_MOD_CHARM)) + && CastSpellAction::isUseful(); +} + +bool CastWillOfTheForsakenAction::isPossible() +{ + uint32 spellId = AI_VALUE2(uint32, "spell id", spell); + if (!spellId) + return false; + + if (!bot->HasSpell(spellId)) + return false; + + if (HasSpellOrCategoryCooldown(bot, spellId)) + return false; + + return true; +} + +bool CastWillOfTheForsakenAction::isUseful() +{ + return (bot->HasAuraType(SPELL_AURA_MOD_FEAR) || + bot->HasAuraType(SPELL_AURA_MOD_CHARM) || + bot->HasAuraType(SPELL_AURA_AOE_CHARM) || + bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP)) + && CastSpellAction::isUseful(); } bool UseTrinketAction::Execute(Event /*event*/) diff --git a/src/Ai/Base/Actions/GenericSpellActions.h b/src/Ai/Base/Actions/GenericSpellActions.h index fdc0dcdcf..b15c4894a 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.h +++ b/src/Ai/Base/Actions/GenericSpellActions.h @@ -294,6 +294,16 @@ public: bool isUseful() override; }; +class CastWillOfTheForsakenAction : public CastSpellAction +{ +public: + CastWillOfTheForsakenAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "will of the forsaken") {} + + std::string const GetTargetName() override { return "self target"; } + bool isPossible() override; + bool isUseful() override; +}; + class UseTrinketAction : public Action { public: diff --git a/src/Ai/Base/Strategy/RacialsStrategy.cpp b/src/Ai/Base/Strategy/RacialsStrategy.cpp index 753302c35..ae45cdaaf 100644 --- a/src/Ai/Base/Strategy/RacialsStrategy.cpp +++ b/src/Ai/Base/Strategy/RacialsStrategy.cpp @@ -37,6 +37,9 @@ void RacialsStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode( "loss of control", { NextAction("every man for himself", ACTION_EMERGENCY + 1) })); + triggers.push_back(new TriggerNode( + "fear charm sleep", { NextAction("will of the forsaken", ACTION_EMERGENCY + 1) })); + } RacialsStrategy::RacialsStrategy(PlayerbotAI* botAI) : Strategy(botAI) diff --git a/src/Ai/Base/Trigger/GenericTriggers.cpp b/src/Ai/Base/Trigger/GenericTriggers.cpp index 735e1df7e..68f5a5239 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.cpp +++ b/src/Ai/Base/Trigger/GenericTriggers.cpp @@ -473,6 +473,14 @@ bool LossOfControlTrigger::IsActive() bot->HasAuraType(SPELL_AURA_MOD_CHARM); } +bool FearCharmSleepTrigger::IsActive() +{ + return bot->HasAuraType(SPELL_AURA_MOD_FEAR) || + bot->HasAuraType(SPELL_AURA_MOD_CHARM) || + bot->HasAuraType(SPELL_AURA_AOE_CHARM) || + bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP); +} + bool HasAuraStackTrigger::IsActive() { Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack); diff --git a/src/Ai/Base/Trigger/GenericTriggers.h b/src/Ai/Base/Trigger/GenericTriggers.h index 68fc4b61f..f78728cd5 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.h +++ b/src/Ai/Base/Trigger/GenericTriggers.h @@ -754,6 +754,14 @@ public: bool IsActive() override; }; +class FearCharmSleepTrigger : public Trigger +{ +public: + FearCharmSleepTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fear charm sleep", 1) {} + + bool IsActive() override; +}; + class IsSwimmingTrigger : public Trigger { public: diff --git a/src/Ai/Base/TriggerContext.h b/src/Ai/Base/TriggerContext.h index 6536c0d9c..63f9be404 100644 --- a/src/Ai/Base/TriggerContext.h +++ b/src/Ai/Base/TriggerContext.h @@ -61,6 +61,7 @@ public: creators["generic boost"] = &TriggerContext::generic_boost; creators["loss of control"] = &TriggerContext::loss_of_control; + creators["fear charm sleep"] = &TriggerContext::fear_charm_sleep; creators["protect party member"] = &TriggerContext::protect_party_member; @@ -367,6 +368,7 @@ private: } static Trigger* generic_boost(PlayerbotAI* botAI) { return new GenericBoostTrigger(botAI); } static Trigger* loss_of_control(PlayerbotAI* botAI) { return new LossOfControlTrigger(botAI); } + static Trigger* fear_charm_sleep(PlayerbotAI* botAI) { return new FearCharmSleepTrigger(botAI); } static Trigger* PartyMemberCriticalHealth(PlayerbotAI* botAI) { return new PartyMemberCriticalHealthTrigger(botAI); diff --git a/src/Ai/Base/Actions/GenericBuffUtils.cpp b/src/Ai/Base/Util/GenericBuffUtils.cpp similarity index 89% rename from src/Ai/Base/Actions/GenericBuffUtils.cpp rename to src/Ai/Base/Util/GenericBuffUtils.cpp index e33c12f18..22d56c0ad 100644 --- a/src/Ai/Base/Actions/GenericBuffUtils.cpp +++ b/src/Ai/Base/Util/GenericBuffUtils.cpp @@ -67,7 +67,7 @@ namespace ai::buff if (info->Reagent[i] > 0) { uint32 const itemId = info->Reagent[i]; - int32 const need = info->ReagentCount[i]; + int32 const need = info->ReagentCount[i]; if ((int32)bot->GetItemCount(itemId, false) < need) return false; } @@ -143,3 +143,28 @@ namespace ai::buff return castName; } } + +namespace ai::spell +{ + bool HasSpellOrCategoryCooldown(Player* bot, uint32 spellId) + { + if (bot->HasSpellCooldown(spellId)) + return true; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + return false; + + uint32 category = spellInfo->GetCategory(); + if (!category) + return false; + + for (auto const& [cooldownSpellId, cooldown] : bot->GetSpellCooldownMap()) + { + if (cooldown.category == category && bot->GetSpellCooldownDelay(cooldownSpellId) > 0) + return true; + } + + return false; + } +} diff --git a/src/Ai/Base/Util/GenericBuffUtils.h b/src/Ai/Base/Util/GenericBuffUtils.h new file mode 100644 index 000000000..37bad58a6 --- /dev/null +++ b/src/Ai/Base/Util/GenericBuffUtils.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include "Common.h" +#include "Group.h" +#include "Chat.h" +#include "Language.h" + +class Player; +class PlayerbotAI; + +namespace ai::buff +{ + +// Build an aura qualifier "single + greater" to avoid double-buffing +std::string MakeAuraQualifierForBuff(std::string const& name); + +// Returns the group spell name for a given single-target buff. +// If no group equivalent exists, returns "". +std::string GroupVariantFor(std::string const& name); + +// Checks if the bot has the required reagents to cast a spell (by its spellId). +// Returns false if the spellId is invalid. +bool HasRequiredReagents(Player* bot, uint32 spellId); + +// Applies the "switch to group buff" policy if: the bot is in a group of size x+, +// the group variant is known/useful, and reagents are available. Otherwise, returns baseName. +// If announceOnMissing == true and reagents are missing, calls the 'announce' callback +// (if provided) to notify the party/raid. +std::string UpgradeToGroupIfAppropriate( + Player* bot, + PlayerbotAI* botAI, + std::string const& baseName, + bool announceOnMissing = false, + std::function announce = {} + ); +} + +namespace ai::spell +{ + bool HasSpellOrCategoryCooldown(Player* bot, uint32 spellId); +} + +namespace ai::chat { + inline std::function MakeGroupAnnouncer(Player* me) + { + return [me](std::string const& msg) + { + if (Group* g = me->GetGroup()) + { + WorldPacket data; + ChatMsg type = g->isRaidGroup() ? CHAT_MSG_RAID : CHAT_MSG_PARTY; + ChatHandler::BuildChatPacket(data, type, LANG_UNIVERSAL, me, /*receiver=*/nullptr, msg.c_str()); + g->BroadcastPacket(&data, true, -1, me->GetGUID()); + } + else + { + me->Say(msg, LANG_UNIVERSAL); + } + }; + } +} diff --git a/src/Ai/Class/Paladin/Action/PaladinActions.cpp b/src/Ai/Class/Paladin/Action/PaladinActions.cpp index c1521bb1e..944b6e686 100644 --- a/src/Ai/Class/Paladin/Action/PaladinActions.cpp +++ b/src/Ai/Class/Paladin/Action/PaladinActions.cpp @@ -11,7 +11,7 @@ #include "Playerbots.h" #include "SharedDefines.h" #include "../../../../../src/server/scripts/Spells/spell_generic.cpp" -#include "GenericBuffUtils.h" +#include "Ai/Base/Util/GenericBuffUtils.h" #include "Group.h" #include "ObjectAccessor.h"