mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Paladin buff logic: Sanctuary+Kings synergy, role-aware targeting, safer Greater buffs (#1603)
* Paladin buff logic: Sanctuary+Kings synergy, role-aware targeting, safer Greater buffs * Update PaladinActions.cpp * Update PaladinActions.cpp * All configs should be implement into PlayerbotAIConfig and sPlayerbotAIConfig used in code * added: prayer of fortitude * Magic number removed * Update PaladinActions.cpp * Update PaladinActions.cpp * Update PaladinActions.cpp * Update PaladinActions.cpp * Update PaladinActions.cpp * Add patch for solo paladin in group * Correction review * Update PaladinActions.cpp * Add harcoded text to DB
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
# THRESHOLDS
|
||||
# QUESTS
|
||||
# COMBAT
|
||||
# PALADIN BUFFS STRATEGIES
|
||||
# CHEATS
|
||||
# SPELLS
|
||||
# FLIGHTPATH
|
||||
@@ -457,6 +458,24 @@ AiPlayerbot.FleeingEnabled = 1
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# PALADIN BUFFS STRATEGIES
|
||||
#
|
||||
#
|
||||
|
||||
# Min group size to use Greater buffs (Paladin, Mage, Druid)
|
||||
# Default: 3
|
||||
AiPlayerbot.MinBotsForGreaterBuff = 3
|
||||
|
||||
# Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff
|
||||
# Default: 30
|
||||
AiPlayerbot.RPWarningCooldown = 30
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# CHEATS
|
||||
#
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
DELETE FROM ai_playerbot_texts
|
||||
WHERE name IN (
|
||||
'rp_missing_reagent_greater_blessing',
|
||||
'rp_missing_reagent_gift_of_the_wild',
|
||||
'rp_missing_reagent_arcane_brilliance',
|
||||
'rp_missing_reagent_generic'
|
||||
);
|
||||
|
||||
DELETE FROM ai_playerbot_texts_chance
|
||||
WHERE name IN (
|
||||
'rp_missing_reagent_greater_blessing',
|
||||
'rp_missing_reagent_gift_of_the_wild',
|
||||
'rp_missing_reagent_arcane_brilliance',
|
||||
'rp_missing_reagent_generic'
|
||||
);
|
||||
|
||||
INSERT INTO ai_playerbot_texts (name, text, say_type, reply_type, text_loc2) VALUES
|
||||
('rp_missing_reagent_greater_blessing',
|
||||
'By the Light... I forgot my Symbols of Kings. We’ll make do with %base_spell!', 0, 0,
|
||||
'Par la Lumière... J''ai oublié mes Symboles du roi. On se contentera de %base_spell !'),
|
||||
('rp_missing_reagent_gift_of_the_wild',
|
||||
'Nature is generous, my bags are not... out of herbs for %group_spell. Take %base_spell for now!', 0, 0,
|
||||
'La nature est généreuse, pas mes sacs... plus d''herbes pour %group_spell. Prenez %base_spell pour l''instant !'),
|
||||
('rp_missing_reagent_arcane_brilliance',
|
||||
'Out of Arcane Powder... %group_spell will have to wait. Casting %base_spell!', 0, 0,
|
||||
'Plus de poudre des arcanes... %group_spell attendra. Je lance %base_spell !'),
|
||||
('rp_missing_reagent_generic',
|
||||
'Oops, I’m out of components for %group_spell. We’ll go with %base_spell!', 0, 0,
|
||||
'Oups, je n''ai plus de composants pour %group_spell. On fera avec %base_spell !');
|
||||
|
||||
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||
('rp_missing_reagent_greater_blessing', 100),
|
||||
('rp_missing_reagent_gift_of_the_wild', 100),
|
||||
('rp_missing_reagent_arcane_brilliance', 100),
|
||||
('rp_missing_reagent_generic', 100);
|
||||
@@ -81,6 +81,9 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sitDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.SitDelay", 30000);
|
||||
returnDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReturnDelay", 7000);
|
||||
lootDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.LootDelay", 1000);
|
||||
// Buff system
|
||||
minBotsForGreaterBuff = sConfigMgr->GetOption<int32>("AiPlayerbot.MinBotsForGreaterBuff", 3);
|
||||
rpWarningCooldown = sConfigMgr->GetOption<int32>("AiPlayerbot.RPWarningCooldown", 30);
|
||||
disabledWithoutRealPlayerLoginDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.DisabledWithoutRealPlayerLoginDelay", 30);
|
||||
disabledWithoutRealPlayerLogoutDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay", 300);
|
||||
|
||||
|
||||
@@ -139,6 +139,12 @@ public:
|
||||
uint32 disabledWithoutRealPlayerLoginDelay, disabledWithoutRealPlayerLogoutDelay;
|
||||
bool randomBotJoinLfg;
|
||||
|
||||
// Buff system
|
||||
// Min group size to use Greater buffs (Paladin, Mage, Druid). Default: 3
|
||||
int32 minBotsForGreaterBuff;
|
||||
// Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff. Default: 30
|
||||
int32 rpWarningCooldown;
|
||||
|
||||
// chat
|
||||
bool randomBotTalk;
|
||||
bool randomBotEmote;
|
||||
|
||||
153
src/strategy/actions/GenericBuffUtils.cpp
Normal file
153
src/strategy/actions/GenericBuffUtils.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GenericBuffUtils.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "Player.h"
|
||||
#include "Group.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Value.h"
|
||||
#include "Config.h"
|
||||
#include "PlayerbotTextMgr.h"
|
||||
|
||||
namespace ai::buff
|
||||
{
|
||||
std::string MakeAuraQualifierForBuff(std::string const& name)
|
||||
{
|
||||
// Paladin
|
||||
if (name == "blessing of kings") return "blessing of kings,greater blessing of kings";
|
||||
if (name == "blessing of might") return "blessing of might,greater blessing of might";
|
||||
if (name == "blessing of wisdom") return "blessing of wisdom,greater blessing of wisdom";
|
||||
if (name == "blessing of sanctuary") return "blessing of sanctuary,greater blessing of sanctuary";
|
||||
// Druid
|
||||
if (name == "mark of the wild") return "mark of the wild,gift of the wild";
|
||||
// Mage
|
||||
if (name == "arcane intellect") return "arcane intellect,arcane brilliance";
|
||||
// Priest
|
||||
if (name == "power word: fortitude") return "power word: fortitude,prayer of fortitude";
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string GroupVariantFor(std::string const& name)
|
||||
{
|
||||
// Paladin
|
||||
if (name == "blessing of kings") return "greater blessing of kings";
|
||||
if (name == "blessing of might") return "greater blessing of might";
|
||||
if (name == "blessing of wisdom") return "greater blessing of wisdom";
|
||||
if (name == "blessing of sanctuary") return "greater blessing of sanctuary";
|
||||
// Druid
|
||||
if (name == "mark of the wild") return "gift of the wild";
|
||||
// Mage
|
||||
if (name == "arcane intellect") return "arcane brilliance";
|
||||
// Priest
|
||||
if (name == "power word: fortitude") return "prayer of fortitude";
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool HasRequiredReagents(Player* bot, uint32 spellId)
|
||||
{
|
||||
if (!spellId)
|
||||
return false;
|
||||
|
||||
if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId))
|
||||
{ for (int i = 0; i < MAX_SPELL_REAGENTS; ++i)
|
||||
{
|
||||
if (info->Reagent[i] > 0)
|
||||
{
|
||||
uint32 const itemId = info->Reagent[i];
|
||||
int32 const need = info->ReagentCount[i];
|
||||
if ((int32)bot->GetItemCount(itemId, false) < need)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// No reagent required
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string UpgradeToGroupIfAppropriate(
|
||||
Player* bot,
|
||||
PlayerbotAI* botAI,
|
||||
std::string const& baseName,
|
||||
bool announceOnMissing,
|
||||
std::function<void(std::string const&)> announce)
|
||||
{
|
||||
std::string castName = baseName; Group* g = bot->GetGroup();
|
||||
if (!g || g->GetMembersCount() < static_cast<uint32>(sPlayerbotAIConfig->minBotsForGreaterBuff))
|
||||
return castName; // Group too small: stay in solo mode
|
||||
|
||||
if (std::string const groupName = GroupVariantFor(baseName); !groupName.empty())
|
||||
{
|
||||
uint32 const groupVariantSpellId = botAI->GetAiObjectContext()
|
||||
->GetValue<uint32>("spell id", groupName)->Get();
|
||||
|
||||
// We check usefulness on the **basic** buff (not the greater version),
|
||||
// because "spell cast useful" may return false for the greater variant.
|
||||
bool const usefulBase = botAI->GetAiObjectContext()
|
||||
->GetValue<bool>("spell cast useful", baseName)->Get();
|
||||
|
||||
if (groupVariantSpellId && HasRequiredReagents(bot, groupVariantSpellId))
|
||||
{
|
||||
// Learned + reagents OK -> switch to greater
|
||||
return groupName;
|
||||
}
|
||||
|
||||
// Missing reagents -> announce if (a) greater is known, (b) base buff is useful,
|
||||
// (c) announce was requested, (d) a callback is provided.
|
||||
if (announceOnMissing && groupVariantSpellId && usefulBase && announce)
|
||||
{
|
||||
static std::map<std::pair<uint32, std::string>, time_t> s_lastWarn; // par bot & par buff
|
||||
time_t now = std::time(nullptr);
|
||||
uint32 botLow = static_cast<uint32>(bot->GetGUID().GetCounter());
|
||||
time_t& last = s_lastWarn[ std::make_pair(botLow, groupName) ];
|
||||
if (!last || now - last >= sPlayerbotAIConfig->rpWarningCooldown) // Configurable anti-spam
|
||||
{
|
||||
// DB Key choice in regard of the buff
|
||||
std::string key;
|
||||
if (groupName.find("greater blessing") != std::string::npos)
|
||||
key = "rp_missing_reagent_greater_blessing";
|
||||
else if (groupName == "gift of the wild")
|
||||
key = "rp_missing_reagent_gift_of_the_wild";
|
||||
else if (groupName == "arcane brilliance")
|
||||
key = "rp_missing_reagent_arcane_brilliance";
|
||||
else
|
||||
key = "rp_missing_reagent_generic";
|
||||
|
||||
// Placeholders
|
||||
std::map<std::string, std::string> ph;
|
||||
ph["%group_spell"] = groupName;
|
||||
ph["%base_spell"] = baseName;
|
||||
|
||||
// Respecte ai_playerbot_texts_chance if present
|
||||
std::string rp;
|
||||
bool got = sPlayerbotTextMgr->GetBotText(key, rp, ph);
|
||||
if (got && !rp.empty())
|
||||
{
|
||||
announce(rp);
|
||||
last = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Minimal Fallback
|
||||
std::ostringstream oss;
|
||||
oss << "Out of components for " << groupName << ". Using " << baseName << "!";
|
||||
announce(oss.str());
|
||||
last = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return castName;
|
||||
}
|
||||
}
|
||||
64
src/strategy/actions/GenericBuffUtils.h
Normal file
64
src/strategy/actions/GenericBuffUtils.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#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<void(std::string const&)> announce = {}
|
||||
);
|
||||
}
|
||||
|
||||
namespace ai::chat {
|
||||
inline std::function<void(std::string const&)> 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "GenericSpellActions.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "ObjectDefines.h"
|
||||
@@ -13,6 +15,14 @@
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "Group.h"
|
||||
#include "Chat.h"
|
||||
#include "Language.h"
|
||||
#include "GenericBuffUtils.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
using ai::buff::MakeAuraQualifierForBuff;
|
||||
using ai::buff::UpgradeToGroupIfAppropriate;
|
||||
|
||||
CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell)
|
||||
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell)
|
||||
@@ -216,11 +226,24 @@ Value<Unit*>* CurePartyMemberAction::GetTargetValue()
|
||||
return context->GetValue<Unit*>("party member to dispel", dispelType);
|
||||
}
|
||||
|
||||
// Make Bots Paladin, druid, mage use the greater buff rank spell
|
||||
// TODO Priest doen't verify il he have components
|
||||
Value<Unit*>* BuffOnPartyAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("party member without aura", spell);
|
||||
return context->GetValue<Unit*>("party member without aura", MakeAuraQualifierForBuff(spell));
|
||||
}
|
||||
|
||||
bool BuffOnPartyAction::Execute(Event event)
|
||||
{
|
||||
std::string castName = spell; // default = mono
|
||||
|
||||
auto SendGroupRP = ai::chat::MakeGroupAnnouncer(bot);
|
||||
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP);
|
||||
|
||||
return botAI->CastSpell(castName, GetTarget());
|
||||
}
|
||||
// End greater buff fix
|
||||
|
||||
CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot")
|
||||
{
|
||||
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
|
||||
|
||||
@@ -215,17 +215,18 @@ protected:
|
||||
uint32 dispelType;
|
||||
};
|
||||
|
||||
// Make Bots Paladin, druid, mage use the greater buff rank spell
|
||||
class BuffOnPartyAction : public CastBuffSpellAction, public PartyMemberActionNameSupport
|
||||
{
|
||||
public:
|
||||
BuffOnPartyAction(PlayerbotAI* botAI, std::string const spell)
|
||||
: CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell)
|
||||
{
|
||||
}
|
||||
: CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) { }
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
bool Execute(Event event) override;
|
||||
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
||||
};
|
||||
// End Fix
|
||||
|
||||
class CastShootAction : public CastSpellAction
|
||||
{
|
||||
|
||||
@@ -13,6 +13,53 @@
|
||||
#include "Playerbots.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "../../../../../src/server/scripts/Spells/spell_generic.cpp"
|
||||
#include "GenericBuffUtils.h"
|
||||
#include "Config.h"
|
||||
#include "Group.h"
|
||||
#include "ObjectAccessor.h"
|
||||
|
||||
using ai::buff::MakeAuraQualifierForBuff;
|
||||
using ai::buff::UpgradeToGroupIfAppropriate;
|
||||
|
||||
// Helper : detect tank role on the target (player bot or not) return true if spec is tank or if the bot have tank strategies (bear/tank/tank face).
|
||||
static inline bool IsTankRole(Player* p)
|
||||
{
|
||||
if (!p) return false;
|
||||
if (p->HasTankSpec())
|
||||
return true;
|
||||
if (PlayerbotAI* otherAI = GET_PLAYERBOT_AI(p))
|
||||
{
|
||||
if (otherAI->HasStrategy("tank", BOT_STATE_NON_COMBAT) ||
|
||||
otherAI->HasStrategy("tank", BOT_STATE_COMBAT) ||
|
||||
otherAI->HasStrategy("tank face", BOT_STATE_NON_COMBAT) ||
|
||||
otherAI->HasStrategy("tank face", BOT_STATE_COMBAT) ||
|
||||
otherAI->HasStrategy("bear", BOT_STATE_NON_COMBAT) ||
|
||||
otherAI->HasStrategy("bear", BOT_STATE_COMBAT))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Added for solo paladin patch : determine if he's the only paladin on party
|
||||
static inline bool IsOnlyPaladinInGroup(Player* bot)
|
||||
{
|
||||
if (!bot) return false;
|
||||
Group* g = bot->GetGroup();
|
||||
if (!g) return true; // solo
|
||||
uint32 pals = 0u;
|
||||
for (GroupReference* r = g->GetFirstMember(); r; r = r->next())
|
||||
{
|
||||
Player* p = r->GetSource();
|
||||
if (!p || !p->IsInWorld()) continue;
|
||||
if (p->getClass() == CLASS_PALADIN) ++pals;
|
||||
}
|
||||
return pals == 1u;
|
||||
}
|
||||
|
||||
static inline bool GroupHasTankOfClass(Group* g, uint8 classId)
|
||||
{
|
||||
return GroupHasTankOfClass(g, static_cast<Classes>(classId));
|
||||
}
|
||||
|
||||
inline std::string const GetActualBlessingOfMight(Unit* target)
|
||||
{
|
||||
@@ -92,24 +139,32 @@ inline std::string const GetActualBlessingOfWisdom(Unit* target)
|
||||
|
||||
inline std::string const GetActualBlessingOfSanctuary(Unit* target, Player* bot)
|
||||
{
|
||||
Player* targetPlayer = target->ToPlayer();
|
||||
if (!bot->HasSpell(SPELL_BLESSING_OF_SANCTUARY))
|
||||
return "";
|
||||
|
||||
if (!targetPlayer)
|
||||
Player* tp = target->ToPlayer();
|
||||
if (!tp)
|
||||
return "";
|
||||
|
||||
if (auto* ai = GET_PLAYERBOT_AI(bot))
|
||||
{
|
||||
return "blessing of sanctuary";
|
||||
if (Unit* mt = ai->GetAiObjectContext()->GetValue<Unit*>("main tank")->Get())
|
||||
{
|
||||
if (mt == target)
|
||||
return "blessing of sanctuary";
|
||||
}
|
||||
}
|
||||
|
||||
if (targetPlayer->HasTankSpec() && bot->HasSpell(SPELL_BLESSING_OF_SANCTUARY))
|
||||
{
|
||||
if (tp->HasTankSpec())
|
||||
return "blessing of sanctuary";
|
||||
}
|
||||
|
||||
return "blessing of kings";
|
||||
return "";
|
||||
}
|
||||
|
||||
Value<Unit*>* CastBlessingOnPartyAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("party member without aura", name);
|
||||
|
||||
return context->GetValue<Unit*>("party member without aura", MakeAuraQualifierForBuff(spell));
|
||||
}
|
||||
|
||||
bool CastBlessingOfMightAction::Execute(Event event)
|
||||
@@ -118,12 +173,19 @@ bool CastBlessingOfMightAction::Execute(Event event)
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
return botAI->CastSpell(GetActualBlessingOfMight(target), target);
|
||||
std::string castName = GetActualBlessingOfMight(target);
|
||||
auto RP = ai::chat::MakeGroupAnnouncer(bot);
|
||||
|
||||
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP);
|
||||
return botAI->CastSpell(castName, target);
|
||||
}
|
||||
|
||||
Value<Unit*>* CastBlessingOfMightOnPartyAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("party member without aura", "blessing of might,blessing of wisdom");
|
||||
return context->GetValue<Unit*>(
|
||||
"party member without aura",
|
||||
"blessing of might,greater blessing of might,blessing of wisdom,greater blessing of wisdom,blessing of sanctuary,greater blessing of sanctuary"
|
||||
);
|
||||
}
|
||||
|
||||
bool CastBlessingOfMightOnPartyAction::Execute(Event event)
|
||||
@@ -132,7 +194,11 @@ bool CastBlessingOfMightOnPartyAction::Execute(Event event)
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
return botAI->CastSpell(GetActualBlessingOfMight(target), target);
|
||||
std::string castName = GetActualBlessingOfMight(target);
|
||||
auto RP = ai::chat::MakeGroupAnnouncer(bot);
|
||||
|
||||
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP);
|
||||
return botAI->CastSpell(castName, target);
|
||||
}
|
||||
|
||||
bool CastBlessingOfWisdomAction::Execute(Event event)
|
||||
@@ -141,12 +207,19 @@ bool CastBlessingOfWisdomAction::Execute(Event event)
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
return botAI->CastSpell(GetActualBlessingOfWisdom(target), target);
|
||||
std::string castName = GetActualBlessingOfWisdom(target);
|
||||
auto RP = ai::chat::MakeGroupAnnouncer(bot);
|
||||
|
||||
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP);
|
||||
return botAI->CastSpell(castName, target);
|
||||
}
|
||||
|
||||
Value<Unit*>* CastBlessingOfWisdomOnPartyAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("party member without aura", "blessing of might,blessing of wisdom");
|
||||
return context->GetValue<Unit*>(
|
||||
"party member without aura",
|
||||
"blessing of wisdom,greater blessing of wisdom,blessing of might,greater blessing of might,blessing of sanctuary,greater blessing of sanctuary"
|
||||
);
|
||||
}
|
||||
|
||||
bool CastBlessingOfWisdomOnPartyAction::Execute(Event event)
|
||||
@@ -155,21 +228,250 @@ bool CastBlessingOfWisdomOnPartyAction::Execute(Event event)
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
return botAI->CastSpell(GetActualBlessingOfWisdom(target), target);
|
||||
Player* targetPlayer = target->ToPlayer();
|
||||
|
||||
if (Group* g = bot->GetGroup())
|
||||
if (targetPlayer && !g->IsMember(targetPlayer->GetGUID()))
|
||||
return false;
|
||||
|
||||
if (botAI->HasStrategy("bmana", BOT_STATE_NON_COMBAT) &&
|
||||
targetPlayer && IsTankRole(targetPlayer))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[Wisdom/bmana] Skip tank {} (Kings only)", target->GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string castName = GetActualBlessingOfWisdom(target);
|
||||
if (castName.empty())
|
||||
return false;
|
||||
|
||||
auto RP = ai::chat::MakeGroupAnnouncer(bot);
|
||||
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP);
|
||||
return botAI->CastSpell(castName, target);
|
||||
}
|
||||
|
||||
|
||||
Value<Unit*>* CastBlessingOfSanctuaryOnPartyAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("party member without aura", "blessing of sanctuary,blessing of kings");
|
||||
return context->GetValue<Unit*>(
|
||||
"party member without aura",
|
||||
"blessing of sanctuary,greater blessing of sanctuary"
|
||||
);
|
||||
}
|
||||
|
||||
bool CastBlessingOfSanctuaryOnPartyAction::Execute(Event event)
|
||||
{
|
||||
if (!bot->HasSpell(SPELL_BLESSING_OF_SANCTUARY))
|
||||
return false;
|
||||
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
{
|
||||
// Fallback: GetTarget() can be null if no one needs a buff.
|
||||
// Keep a valid pointer for the checks/logs that follow.
|
||||
target = bot;
|
||||
}
|
||||
|
||||
Player* targetPlayer = target ? target->ToPlayer() : nullptr;
|
||||
|
||||
// Small helpers to check relevant auras
|
||||
const auto HasKingsAura = [&](Unit* u) -> bool {
|
||||
return botAI->HasAura("blessing of kings", u) || botAI->HasAura("greater blessing of kings", u);
|
||||
};
|
||||
const auto HasSanctAura = [&](Unit* u) -> bool {
|
||||
return botAI->HasAura("blessing of sanctuary", u) || botAI->HasAura("greater blessing of sanctuary", u);
|
||||
};
|
||||
|
||||
if (Group* g = bot->GetGroup())
|
||||
{
|
||||
if (targetPlayer && !g->IsMember(targetPlayer->GetGUID()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[Sanct] Initial target not in group, ignoring");
|
||||
target = bot;
|
||||
targetPlayer = bot->ToPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
if (Player* self = bot->ToPlayer())
|
||||
{
|
||||
bool selfHasSanct = HasSanctAura(self);
|
||||
bool needSelf = IsTankRole(self) && !selfHasSanct;
|
||||
|
||||
LOG_DEBUG("playerbots", "[Sanct] {} isTank={} selfHasSanct={} needSelf={}",
|
||||
bot->GetName(), IsTankRole(self), selfHasSanct, needSelf);
|
||||
|
||||
if (needSelf)
|
||||
{
|
||||
target = self;
|
||||
targetPlayer = self;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to re-target a valid tank in group if needed
|
||||
bool targetOk = false;
|
||||
if (targetPlayer)
|
||||
{
|
||||
bool hasSanct = HasSanctAura(targetPlayer);
|
||||
targetOk = IsTankRole(targetPlayer) && !hasSanct;
|
||||
}
|
||||
|
||||
if (!targetOk)
|
||||
{
|
||||
if (Group* g = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* gref = g->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* p = gref->GetSource();
|
||||
if (!p) continue;
|
||||
if (!p->IsInWorld() || !p->IsAlive()) continue;
|
||||
if (!IsTankRole(p)) continue;
|
||||
|
||||
bool hasSanct = HasSanctAura(p);
|
||||
if (!hasSanct)
|
||||
{
|
||||
target = p; // prioritize this tank
|
||||
targetPlayer = p;
|
||||
targetOk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
bool hasKings = HasKingsAura(target);
|
||||
bool hasSanct = HasSanctAura(target);
|
||||
bool knowSanct = bot->HasSpell(SPELL_BLESSING_OF_SANCTUARY);
|
||||
LOG_DEBUG("playerbots", "[Sanct] Final target={} hasKings={} hasSanct={} knowSanct={}",
|
||||
target->GetName(), hasKings, hasSanct, knowSanct);
|
||||
}
|
||||
|
||||
std::string castName = GetActualBlessingOfSanctuary(target, bot);
|
||||
// If internal logic didn't recognize the tank (e.g., bear druid), force single-target Sanctuary
|
||||
if (castName.empty())
|
||||
{
|
||||
if (targetPlayer)
|
||||
{
|
||||
if (IsTankRole(targetPlayer))
|
||||
castName = "blessing of sanctuary"; // force single-target
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
if (targetPlayer && !IsTankRole(targetPlayer))
|
||||
{
|
||||
auto RP = ai::chat::MakeGroupAnnouncer(bot);
|
||||
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP);
|
||||
}
|
||||
else
|
||||
{
|
||||
castName = "blessing of sanctuary";
|
||||
}
|
||||
|
||||
bool ok = botAI->CastSpell(castName, target);
|
||||
LOG_DEBUG("playerbots", "[Sanct] Cast {} on {} result={}", castName, target->GetName(), ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
Value<Unit*>* CastBlessingOfKingsOnPartyAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>(
|
||||
"party member without aura",
|
||||
"blessing of kings,greater blessing of kings,blessing of sanctuary,greater blessing of sanctuary"
|
||||
);
|
||||
}
|
||||
|
||||
bool CastBlessingOfKingsOnPartyAction::Execute(Event event)
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
return botAI->CastSpell(GetActualBlessingOfSanctuary(target, bot), target);
|
||||
Group* g = bot->GetGroup();
|
||||
if (!g)
|
||||
return false;
|
||||
|
||||
// Added for patch solo paladin, never buff itself to not remove his sanctuary buff
|
||||
if (botAI->HasStrategy("bstats", BOT_STATE_NON_COMBAT) && IsOnlyPaladinInGroup(bot))
|
||||
{
|
||||
if (target->GetGUID() == bot->GetGUID())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[Kings/bstats-solo] Skip self to keep Sanctuary on {}", bot->GetName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// End solo paladin patch
|
||||
|
||||
Player* targetPlayer = target->ToPlayer();
|
||||
if (targetPlayer && !g->IsMember(targetPlayer->GetGUID()))
|
||||
return false;
|
||||
|
||||
const bool hasBmana = botAI->HasStrategy("bmana", BOT_STATE_NON_COMBAT);
|
||||
const bool hasBstats = botAI->HasStrategy("bstats", BOT_STATE_NON_COMBAT);
|
||||
|
||||
if (hasBmana)
|
||||
{
|
||||
if (!targetPlayer || !IsTankRole(targetPlayer))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[Kings/bmana] Skip non-tank {}", target->GetName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetPlayer)
|
||||
{
|
||||
const bool isTank = IsTankRole(targetPlayer);
|
||||
const bool hasSanctFromMe =
|
||||
target->HasAura(SPELL_BLESSING_OF_SANCTUARY, bot->GetGUID()) ||
|
||||
target->HasAura(SPELL_GREATER_BLESSING_OF_SANCTUARY, bot->GetGUID());
|
||||
const bool hasSanctAny =
|
||||
botAI->HasAura("blessing of sanctuary", target) ||
|
||||
botAI->HasAura("greater blessing of sanctuary", target);
|
||||
|
||||
if (isTank && hasSanctFromMe)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[Kings] Skip: {} has my Sanctuary and is a tank", target->GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasBstats && isTank && hasSanctAny)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[Kings] Skip (bstats): {} already has Sanctuary and is a tank", target->GetName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string castName = "blessing of kings";
|
||||
|
||||
bool allowGreater = true;
|
||||
|
||||
if (hasBmana)
|
||||
allowGreater = false;
|
||||
|
||||
if (allowGreater && hasBstats && targetPlayer)
|
||||
{
|
||||
switch (targetPlayer->getClass())
|
||||
{
|
||||
case CLASS_WARRIOR:
|
||||
case CLASS_PALADIN:
|
||||
case CLASS_DRUID:
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
allowGreater = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowGreater)
|
||||
{
|
||||
auto RP = ai::chat::MakeGroupAnnouncer(bot);
|
||||
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, RP);
|
||||
}
|
||||
|
||||
return botAI->CastSpell(castName, target);
|
||||
}
|
||||
|
||||
bool CastSealSpellAction::isUseful() { return AI_VALUE2(bool, "combat", "self target"); }
|
||||
|
||||
@@ -141,6 +141,8 @@ public:
|
||||
CastBlessingOfKingsOnPartyAction(PlayerbotAI* botAI) : CastBlessingOnPartyAction(botAI, "blessing of kings") {}
|
||||
|
||||
std::string const getName() override { return "blessing of kings on party"; }
|
||||
Value<Unit*>* GetTargetValue() override; // added for Sanctuary priority
|
||||
bool Execute(Event event) override; // added for 2 paladins logic
|
||||
};
|
||||
|
||||
class CastBlessingOfSanctuaryAction : public CastBuffSpellAction
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
void PaladinBuffManaStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(
|
||||
new TriggerNode("blessing of wisdom on party",
|
||||
NextAction::array(0, new NextAction("blessing of wisdom on party", 11.0f), nullptr)));
|
||||
// triggers.push_back(new TriggerNode("blessing", NextAction::array(0, new NextAction("blessing of wisdom",
|
||||
// ACTION_HIGH + 8), nullptr)));
|
||||
triggers.push_back(new TriggerNode("blessing of wisdom on party",
|
||||
NextAction::array(0, new NextAction("blessing of wisdom on party", 11.0f), NULL)));
|
||||
|
||||
triggers.push_back(new TriggerNode("blessing of kings on party",
|
||||
NextAction::array(0, new NextAction("blessing of kings on party", 10.5f), NULL)));
|
||||
}
|
||||
|
||||
void PaladinBuffHealthStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
@@ -86,9 +86,13 @@ void PaladinBuffThreatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers
|
||||
|
||||
void PaladinBuffStatsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
// First Sanctuary (prio > Kings)
|
||||
triggers.push_back(
|
||||
new TriggerNode("blessing of sanctuary on party",
|
||||
NextAction::array(0, new NextAction("blessing of sanctuary on party", 12.0f), nullptr)));
|
||||
|
||||
// After Kings
|
||||
triggers.push_back(
|
||||
new TriggerNode("blessing of kings on party",
|
||||
NextAction::array(0, new NextAction("blessing of kings on party", 11.0f), nullptr)));
|
||||
// triggers.push_back(new TriggerNode("blessing", NextAction::array(0, new NextAction("blessing of kings",
|
||||
// ACTION_HIGH + 8), nullptr)));
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ class BlessingOfSanctuaryOnPartyTrigger : public BuffOnPartyTrigger
|
||||
{
|
||||
public:
|
||||
BlessingOfSanctuaryOnPartyTrigger(PlayerbotAI* botAI)
|
||||
: BuffOnPartyTrigger(botAI, "blessing of sanctuary,blessing of kings", 2 * 2000)
|
||||
: BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user