Merge pull request #2432 from mod-playerbots/test-staging

Test staging
This commit is contained in:
Keleborn 2026-06-05 08:42:15 -07:00 committed by GitHub
commit 62ef4b63b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
450 changed files with 3800 additions and 2050 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
DELETE FROM ai_playerbot_texts WHERE name IN (
'send_mail_disabled'
);
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
'send_mail_disabled'
);
INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES
(1899, 'send_mail_disabled', 'I cannot send mail', 0, 0, '우편을 보낼 수 없습니다', 'Je ne peux pas envoyer de courrier', 'Ich kann keine Post senden', '我不能寄送邮件', '我不能寄送郵件', 'No puedo enviar correo', 'No puedo enviar correo', 'Я не могу отправить почту');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
('send_mail_disabled', 100);

View File

@ -12,8 +12,8 @@ bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/)
{
AutoPickTalents();
AutoLearnSpell();
AutoUpgradeEquip();
AutoTeleportForLevel();
AutoUpgradeEquip();
return true;
}
@ -21,13 +21,11 @@ bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/)
void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel()
{
if (!sPlayerbotAIConfig.autoTeleportForLevel || !sRandomPlayerbotMgr.IsRandomBot(bot))
{
return;
}
if (botAI->HasRealPlayerMaster())
{
return;
}
sRandomPlayerbotMgr.RandomTeleportForLevel(bot);
return;
}
@ -89,21 +87,17 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
{
Quest const* quest = i->second;
// only process class-specific quests to learn class-related spells, cuz
// we don't want all these bunch of entries to be handled!
if (!quest->GetRequiredClasses())
if (!quest->GetRequiredClasses() || quest->IsRepeatable() || quest->GetMinLevel() < 10 ||
quest->GetMinLevel() > bot->GetLevel())
{
continue;
}
// skip quests that are repeatable, too low level, or above bots' level
if (quest->IsRepeatable() || quest->GetMinLevel() < 10 || quest->GetMinLevel() > bot->GetLevel())
continue;
// skip if bot doesnt satisfy class, race, or skill requirements
if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) ||
!bot->SatisfyQuestSkill(quest, false))
{
continue;
// use the same logic and impl from Player::learnQuestRewardedSpells
}
int32 spellId = quest->GetRewSpellCast();
if (!spellId)
@ -113,31 +107,26 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
if (!spellInfo)
continue;
// xinef: find effect with learn spell and check if we have this spell
bool found = false;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell &&
!bot->HasSpell(spellInfo->Effects[i].TriggerSpell))
{
// pusywizard: don't re-add profession specialties!
if (SpellInfo const* triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell))
if (triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
break; // pussywizard: break and not cast the spell (found is false)
break;
found = true;
break;
}
}
// xinef: we know the spell, continue
if (!found)
continue;
bot->CastSpell(bot, spellId, true);
// Check if RewardDisplaySpell is set to output the proper spell learned
// after processing quests. Output the original RewardSpell otherwise.
uint32 rewSpellId = quest->GetRewSpell();
if (rewSpellId)
{
@ -167,12 +156,11 @@ std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* s
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
{
if (!sPlayerbotAIConfig.autoUpgradeEquip || !sRandomPlayerbotMgr.IsRandomBot(bot))
if (!sRandomPlayerbotMgr.IsRandomBot(bot))
return;
PlayerbotFactory factory(bot, bot->GetLevel());
// Clean up old consumables before adding new ones
factory.CleanupConsumables();
factory.InitAmmo();
@ -181,9 +169,6 @@ void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
factory.InitConsumables();
factory.InitPotions();
if (!sPlayerbotAIConfig.equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel)
{
if (sPlayerbotAIConfig.incrementalGearInit)
factory.InitEquipment(true);
}
if (sPlayerbotAIConfig.autoUpgradeEquip)
factory.InitEquipment(true);
}

View File

@ -154,9 +154,11 @@ void EquipAction::EquipItem(Item* item)
calculator.SetOverflowPenalty(false);
// Calculate item scores once and store them
float newItemScore = calculator.CalculateItem(itemId);
float mainHandScore = mainHandItem ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId) : 0.0f;
float offHandScore = offHandItem ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId) : 0.0f;
float newItemScore = calculator.CalculateItem(itemId, item->GetItemRandomPropertyId());
float mainHandScore = mainHandItem
? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId, mainHandItem->GetItemRandomPropertyId()) : 0.0f;
float offHandScore = offHandItem
? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId, offHandItem->GetItemRandomPropertyId()) : 0.0f;
// Determine where this weapon can go
bool canGoMain = (invType == INVTYPE_WEAPON ||

View File

@ -86,7 +86,7 @@ bool TogglePetSpellAutoCastAction::Execute(Event /*event*/)
uint32 spellId = itr->first;
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo->IsAutocastable())
if (!spellInfo || !spellInfo->IsAutocastable())
continue;
bool shouldApply = true;

View File

@ -6,6 +6,7 @@
#include "GenericSpellActions.h"
#include <ctime>
#include <unordered_set>
#include "Event.h"
#include "ItemTemplate.h"
@ -23,11 +24,119 @@
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)
namespace
{
std::unordered_set<uint32> const& GetMixedTriggerTrinketSpellIds()
{
static std::unordered_set<uint32> const mixedTriggerSpellIds = []()
{
std::unordered_set<uint32> onUseSpellIds;
std::unordered_set<uint32> onEquipSpellIds;
std::unordered_set<uint32> mixedSpellIds;
auto const* itemTemplates = sObjectMgr->GetItemTemplateStore();
if (!itemTemplates)
return mixedSpellIds;
auto const markSpellId = [&](int32 spellId, uint8 spellTrigger)
{
if (spellId <= 0)
return;
if (spellTrigger == ITEM_SPELLTRIGGER_ON_USE)
{
if (onEquipSpellIds.find(spellId) != onEquipSpellIds.end())
mixedSpellIds.insert(spellId);
onUseSpellIds.insert(spellId);
}
else if (spellTrigger == ITEM_SPELLTRIGGER_ON_EQUIP)
{
if (onUseSpellIds.find(spellId) != onUseSpellIds.end())
mixedSpellIds.insert(spellId);
onEquipSpellIds.insert(spellId);
}
};
for (auto const& itr : *itemTemplates)
{
ItemTemplate const& proto = itr.second;
if (proto.InventoryType != INVTYPE_TRINKET)
continue;
for (uint8 spellIndex = 0; spellIndex < MAX_ITEM_PROTO_SPELLS; ++spellIndex)
{
auto const& spellData = proto.Spells[spellIndex];
markSpellId(spellData.SpellId, spellData.SpellTrigger);
}
}
return mixedSpellIds;
}();
return mixedTriggerSpellIds;
}
bool IsManaRestoreEffect(SpellEffectInfo const& effectInfo)
{
return (effectInfo.Effect == SPELL_EFFECT_ENERGIZE &&
effectInfo.MiscValue == POWER_MANA) ||
(effectInfo.Effect == SPELL_EFFECT_APPLY_AURA &&
effectInfo.ApplyAuraName == SPELL_AURA_PERIODIC_ENERGIZE &&
effectInfo.MiscValue == POWER_MANA);
}
bool IsManaEfficiencyEffect(SpellEffectInfo const& effectInfo)
{
return effectInfo.Effect == SPELL_EFFECT_APPLY_AURA &&
(((effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN ||
effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN_PERCENT) &&
effectInfo.MiscValue == POWER_MANA) ||
effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_COST_SCHOOL ||
effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT ||
effectInfo.ApplyAuraName == SPELL_AURA_MOD_MANA_REGEN_INTERRUPT);
}
bool IsDefensiveTankEffect(SpellEffectInfo const& effectInfo)
{
if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA)
return false;
uint32 const tankRatingsMask =
(1u << CR_DEFENSE_SKILL) |
(1u << CR_DODGE) |
(1u << CR_PARRY) |
(1u << CR_BLOCK) |
(1u << CR_HIT_TAKEN_MELEE) |
(1u << CR_HIT_TAKEN_RANGED) |
(1u << CR_HIT_TAKEN_SPELL) |
(1u << CR_CRIT_TAKEN_MELEE) |
(1u << CR_CRIT_TAKEN_RANGED) |
(1u << CR_CRIT_TAKEN_SPELL);
switch (effectInfo.ApplyAuraName)
{
case SPELL_AURA_MOD_RESISTANCE:
return (effectInfo.MiscValue & SPELL_SCHOOL_MASK_NORMAL) != 0;
case SPELL_AURA_MOD_RATING:
return (effectInfo.MiscValue & tankRatingsMask) != 0;
case SPELL_AURA_MOD_INCREASE_HEALTH:
case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT:
case SPELL_AURA_MOD_PARRY_PERCENT:
case SPELL_AURA_MOD_DODGE_PERCENT:
case SPELL_AURA_MOD_BLOCK_PERCENT:
case SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN:
return true;
default:
return false;
}
}
}
CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell)
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell) {}
bool CastSpellAction::Execute(Event /*event*/)
{
if (spell == "conjure food" || spell == "conjure water")
@ -53,18 +162,12 @@ bool CastSpellAction::Execute(Event /*event*/)
wstrToLower(wnamepart);
if (!Utf8FitTo(spell, wnamepart))
continue;
if (spellInfo->Effects[0].Effect != SPELL_EFFECT_CREATE_ITEM)
if (!Utf8FitTo(spell, wnamepart) || spellInfo->Effects[0].Effect != SPELL_EFFECT_CREATE_ITEM)
continue;
uint32 itemId = spellInfo->Effects[0].ItemType;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
if (bot->CanUseItem(proto) != EQUIP_ERR_OK)
if (!proto || bot->CanUseItem(proto) != EQUIP_ERR_OK)
continue;
if (spellInfo->Id > castId)
@ -92,10 +195,7 @@ bool CastSpellAction::isUseful()
}
Unit* spellTarget = GetTarget();
if (!spellTarget)
return false;
if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId())
if (!spellTarget || !spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId())
return false;
// float combatReach = bot->GetCombatReach() + target->GetCombatReach();
@ -143,10 +243,7 @@ CastMeleeSpellAction::CastMeleeSpellAction(
bool CastMeleeSpellAction::isUseful()
{
Unit* target = GetTarget();
if (!target)
return false;
if (!bot->IsWithinMeleeRange(target))
if (!target || !bot->IsWithinMeleeRange(target))
return false;
return CastSpellAction::isUseful();
@ -162,10 +259,7 @@ CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(
bool CastMeleeDebuffSpellAction::isUseful()
{
Unit* target = GetTarget();
if (!target)
return false;
if (!bot->IsWithinMeleeRange(target))
if (!target || !bot->IsWithinMeleeRange(target))
return false;
return CastDebuffSpellAction::isUseful();
@ -175,14 +269,55 @@ bool CastAuraSpellAction::isUseful()
{
if (!GetTarget() || !CastSpellAction::isUseful())
return false;
Aura* aura = botAI->GetAura(spell, GetTarget(), isOwner, checkDuration);
if (!aura)
return true;
if (beforeDuration && aura->GetDuration() < beforeDuration)
if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration))
return true;
return false;
}
bool CastBuffSpellAction::isUseful()
{
Unit* target = GetTarget();
if (!target || !CastSpellAction::isUseful())
return false;
Aura* aura = botAI->GetAura(spell, target, isOwner, checkDuration);
return !aura || (beforeDuration && aura->GetDuration() < beforeDuration);
}
bool CastBuffSpellAction::Execute(Event /*event*/)
{
return botAI->CastSpell(spell, GetTarget());
}
bool GroupBuffSpellAction::isUseful()
{
Unit* target = GetTarget();
if (!target || !CastSpellAction::isUseful())
return false;
if (ai::buff::IsGroupVariantEnabled(bot, spell))
{
std::string const groupVariant = ai::buff::GroupVariantFor(spell);
if (!groupVariant.empty() && botAI->HasAura(groupVariant, target, false, isOwner, -1, checkDuration))
return false;
}
Aura* aura = botAI->GetAura(spell, target, isOwner, checkDuration);
if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration))
return true;
return false;
}
bool GroupBuffSpellAction::Execute(Event /*event*/)
{
std::string const castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, spell);
return botAI->CastSpell(castName, GetTarget());
}
CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
@ -248,25 +383,16 @@ 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);
}
Value<Unit*>* GroupBuffOnPartyAction::GetTargetValue()
{
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"), shootSpellId(0)
{
@ -365,50 +491,32 @@ bool CastVehicleSpellAction::Execute(Event /*event*/)
bool CastEveryManForHimselfAction::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;
return spellId && bot->HasSpell(spellId) && !HasSpellOrCategoryCooldown(bot, spellId);
}
bool CastEveryManForHimselfAction::isUseful()
{
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))
&& CastSpellAction::isUseful();
bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
bot->HasAuraType(SPELL_AURA_MOD_ROOT) ||
bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) ||
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;
return spellId && bot->HasSpell(spellId) && !HasSpellOrCategoryCooldown(bot, spellId);
}
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();
bot->HasAuraType(SPELL_AURA_MOD_CHARM) ||
bot->HasAuraType(SPELL_AURA_AOE_CHARM) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP))
&& CastSpellAction::isUseful();
}
bool UseTrinketAction::Execute(Event /*event*/)
@ -427,70 +535,141 @@ bool UseTrinketAction::Execute(Event /*event*/)
bool UseTrinketAction::UseTrinket(Item* item)
{
if (bot->CanUseItem(item) != EQUIP_ERR_OK)
return false;
if (bot->IsNonMeleeSpellCast(true))
if (bot->CanUseItem(item) != EQUIP_ERR_OK || bot->IsNonMeleeSpellCast(true))
return false;
uint8 bagIndex = item->GetBagSlot();
uint8 slot = item->GetSlot();
// uint8 spell_index = 0; //not used, line marked for removal.
uint8 cast_count = 1;
ObjectGuid item_guid = item->GetGUID();
uint32 glyphIndex = 0;
uint8 castFlags = 0;
uint32 targetFlag = TARGET_FLAG_NONE;
uint32 spellId = 0;
int32 itemSpellCooldown = 0;
uint32 itemSpellCategory = 0;
int32 itemSpellCategoryCooldown = 0;
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
{
if (item->GetTemplate()->Spells[i].SpellId > 0 &&
item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
{
spellId = item->GetTemplate()->Spells[i].SpellId;
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
itemSpellCooldown = item->GetTemplate()->Spells[i].SpellCooldown;
itemSpellCategory = item->GetTemplate()->Spells[i].SpellCategory;
itemSpellCategoryCooldown = item->GetTemplate()->Spells[i].SpellCategoryCooldown;
uint64 const itemCooldownKey = (static_cast<uint64>(item->GetEntry()) << 32) | spellId;
uint32 const now = getMSTime();
if (itemSpellCooldown > 0)
{
auto const itemCooldownItr = trinketItemCooldownExpiries.find(itemCooldownKey);
if (itemCooldownItr != trinketItemCooldownExpiries.end())
{
if (itemCooldownItr->second > now)
return false;
trinketItemCooldownExpiries.erase(itemCooldownItr);
}
}
if (itemSpellCategory && itemSpellCategoryCooldown > 0)
{
auto const categoryCooldownItr = trinketCategoryCooldownExpiries.find(itemSpellCategory);
if (categoryCooldownItr != trinketCategoryCooldownExpiries.end())
{
if (categoryCooldownItr->second > now)
return false;
trinketCategoryCooldownExpiries.erase(categoryCooldownItr);
}
}
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo || !spellInfo->IsPositive())
return false;
bool applyAura = false;
bool restoresMana = false;
bool improvesManaEfficiency = false;
bool defensiveTankEffect = false;
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
{
const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
if (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA)
{
applyAura = true;
break;
restoresMana = restoresMana || IsManaRestoreEffect(effectInfo);
improvesManaEfficiency = improvesManaEfficiency || IsManaEfficiencyEffect(effectInfo);
defensiveTankEffect = defensiveTankEffect || IsDefensiveTankEffect(effectInfo);
}
if (!applyAura && !restoresMana)
return false;
if (restoresMana || improvesManaEfficiency)
{
if (!AI_VALUE2(bool, "has mana", "self target"))
return false;
uint8 const manaPct = AI_VALUE2(uint8, "mana", "self target");
if ((restoresMana && manaPct >= sPlayerbotAIConfig.mediumMana) ||
manaPct >= sPlayerbotAIConfig.highMana)
{
return false;
}
}
if (!applyAura)
return false;
uint32 spellProcFlag = spellInfo->ProcFlags;
// Handle items with procflag "if you kill a target that grants honor or experience"
// Bots will "learn" the trinket proc, so CanCastSpell() will be true
// e.g. on Item https://www.wowhead.com/wotlk/item=44074/oracle-talisman-of-ablution leading to
// constant casting of the proc spell onto themselfes https://www.wowhead.com/wotlk/spell=59787/oracle-ablutions
// This will lead to multiple hundreds of entries in m_appliedAuras -> Once killing an enemy -> Big diff time spikes
if (spellProcFlag != 0) return false;
if (!botAI->CanCastSpell(spellId, bot, false))
if (defensiveTankEffect)
{
return false;
uint8 const healthPct = AI_VALUE2(uint8, "health", "self target");
if (healthPct > sPlayerbotAIConfig.lowHealth)
return false;
}
auto const& mixedTriggerTrinketSpellIds = GetMixedTriggerTrinketSpellIds();
// Exclude trinkets that expose the same spell as both ON_EQUIP and ON_USE across
// item templates. Those are equip/proc effects leaking into the active-use path,
// as seen with the error versions of Oracle Talisman of Ablution (44870) and
// Frenzyheart Insignia of Fury (44869).
if (mixedTriggerTrinketSpellIds.find(spellId) != mixedTriggerTrinketSpellIds.end())
return false;
if (!botAI->CanCastSpell(spellId, bot, false, nullptr, item))
return false;
break;
}
}
if (!spellId)
return false;
WorldPacket packet(CMSG_USE_ITEM);
packet << bagIndex << slot << cast_count << spellId << item_guid << glyphIndex << castFlags;
targetFlag = TARGET_FLAG_NONE;
packet << targetFlag << bot->GetPackGUID();
bot->GetSession()->HandleUseItemOpcode(packet);
uint32 const now = getMSTime();
uint32 const cooldownDelay = bot->GetSpellCooldownDelay(spellId);
if (cooldownDelay > 0)
{
if (itemSpellCooldown > 0)
{
uint64 const itemCooldownKey = (static_cast<uint64>(item->GetEntry()) << 32) | spellId;
trinketItemCooldownExpiries[itemCooldownKey] = now + static_cast<uint32>(itemSpellCooldown);
}
if (itemSpellCategory && itemSpellCategoryCooldown > 0)
{
trinketCategoryCooldownExpiries[itemSpellCategory] = now + static_cast<uint32>(itemSpellCategoryCooldown);
}
}
return true;
}
@ -500,9 +679,8 @@ bool CastDebuffSpellAction::isUseful()
{
Unit* target = GetTarget();
if (!target || !target->IsAlive() || !target->IsInWorld())
{
return false;
}
return CastAuraSpellAction::isUseful() &&
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
}

View File

@ -69,9 +69,7 @@ class CastDebuffSpellAction : public CastAuraSpellAction
{
public:
CastDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false, float needLifeTime = 8.0f)
: CastAuraSpellAction(botAI, spell, isOwner), needLifeTime(needLifeTime)
{
}
: CastAuraSpellAction(botAI, spell, isOwner), needLifeTime(needLifeTime) {}
bool isUseful() override;
private:
@ -90,9 +88,7 @@ class CastDebuffSpellOnAttackerAction : public CastDebuffSpellAction
public:
CastDebuffSpellOnAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true,
float needLifeTime = 8.0f)
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
{
}
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime) {}
Value<Unit*>* GetTargetValue() override;
std::string const getName() override { return spell + " on attacker"; }
@ -104,9 +100,7 @@ class CastDebuffSpellOnMeleeAttackerAction : public CastDebuffSpellAction
public:
CastDebuffSpellOnMeleeAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true,
float needLifeTime = 8.0f)
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
{
}
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime) {}
Value<Unit*>* GetTargetValue() override;
std::string const getName() override { return spell + " on attacker"; }
@ -119,6 +113,19 @@ public:
CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false, uint32 beforeDuration = 0);
std::string const GetTargetName() override { return "self target"; }
bool isUseful() override;
bool Execute(Event event) override;
};
class GroupBuffSpellAction : public CastBuffSpellAction
{
public:
GroupBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false,
uint32 beforeDuration = 0)
: CastBuffSpellAction(botAI, spell, checkIsOwner, beforeDuration) {}
bool isUseful() override;
bool Execute(Event event) override;
};
class CastEnchantItemMainHandAction : public CastSpellAction
@ -151,8 +158,6 @@ public:
// Yunfan: Mana efficiency tell the bot how to save mana. The higher the better.
HealingManaEfficiency manaEfficiency;
uint8 estAmount;
// protected:
};
class CastAoeHealSpellAction : public CastHealingSpellAction
@ -192,9 +197,7 @@ class HealPartyMemberAction : public CastHealingSpellAction, public PartyMemberA
public:
HealPartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f,
HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM, bool isOwner = true)
: CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency, isOwner), PartyMemberActionNameSupport(spell)
{
}
: CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency, isOwner), PartyMemberActionNameSupport(spell) {}
std::string const GetTargetName() override { return "party member to heal"; }
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
@ -219,9 +222,7 @@ class CurePartyMemberAction : public CastSpellAction, public PartyMemberActionNa
{
public:
CurePartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint32 dispelType)
: CastSpellAction(botAI, spell), PartyMemberActionNameSupport(spell), dispelType(dispelType)
{
}
: CastSpellAction(botAI, spell), PartyMemberActionNameSupport(spell), dispelType(dispelType) {}
Value<Unit*>* GetTargetValue() override;
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
@ -230,18 +231,25 @@ 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;
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
};
class GroupBuffOnPartyAction : public GroupBuffSpellAction, public PartyMemberActionNameSupport
{
public:
GroupBuffOnPartyAction(PlayerbotAI* botAI, std::string const spell)
: GroupBuffSpellAction(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
{
@ -323,8 +331,13 @@ class UseTrinketAction : public Action
public:
UseTrinketAction(PlayerbotAI* botAI) : Action(botAI, "use trinket") {}
bool Execute(Event event) override;
protected:
bool UseTrinket(Item* trinket);
private:
std::unordered_map<uint64, uint32> trinketItemCooldownExpiries;
std::unordered_map<uint32, uint32> trinketCategoryCooldownExpiries;
};
class CastSpellOnEnemyHealerAction : public CastSpellAction
@ -461,12 +474,11 @@ class BuffOnMainTankAction : public CastBuffSpellAction, public MainTankActionNa
{
public:
BuffOnMainTankAction(PlayerbotAI* ai, std::string spell, bool checkIsOwner = false)
: CastBuffSpellAction(ai, spell, checkIsOwner), MainTankActionNameSupport(spell)
{
}
: CastBuffSpellAction(ai, spell, checkIsOwner), MainTankActionNameSupport(spell) {}
public:
virtual Value<Unit*>* GetTargetValue();
virtual std::string const getName() { return MainTankActionNameSupport::getName(); }
};
#endif

View File

@ -44,6 +44,21 @@ bool ResetAiAction::Execute(Event event)
}
}
}
if (Player* master = botAI->GetMaster())
{
Group* botGroup = bot->GetGroup();
Group* masterGroup = master->GetGroup();
if (botGroup && (!masterGroup || masterGroup != botGroup))
botAI->SetMaster(nullptr);
}
if (sRandomPlayerbotMgr.IsRandomBot(bot) && !bot->InBattleground())
{
if (bot->GetGroup() && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster())))
{
if (Player* newMaster = botAI->FindNewMaster())
botAI->SetMaster(newMaster);
}
}
PlayerbotRepository::instance().Reset(botAI);
botAI->ResetStrategies(false);
botAI->TellMaster("AI was reset to defaults");

View File

@ -34,24 +34,23 @@ bool SendMailAction::Execute(Event event)
Player* receiver = GetMaster();
Player* tellTo = receiver;
std::vector<std::string> ss = split(text, ' ');
if (ss.size() > 1)
{
if (Player* p = ObjectAccessor::FindPlayer(ObjectGuid(uint64(ss[ss.size() - 1].c_str()))))
receiver = p;
}
if (!receiver)
receiver = event.getOwner();
if (!receiver || receiver == bot)
{
return false;
}
if (!tellTo)
tellTo = receiver;
if (!sPlayerbotAIConfig.botSendMailEnabled)
{
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
"send_mail_disabled", "I cannot send mail", {}),
LANG_UNIVERSAL, tellTo);
return false;
}
if (!mailboxFound && !randomBot)
{
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(

View File

@ -16,7 +16,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(
new TriggerNode("uninvite guid", { NextAction("uninvite", relevance) }));
triggers.push_back(
new TriggerNode("group set leader", { /*NextAction("leader", relevance),*/ }));
new TriggerNode("group set leader", { NextAction("reset botAI", relevance) }));
triggers.push_back(new TriggerNode(
"not enough money", { NextAction("tell not enough money", relevance) }));
triggers.push_back(

View File

@ -7,6 +7,7 @@
#include <string>
#include "GenericBuffUtils.h"
#include "CreatureAI.h"
#include "ItemVisitors.h"
#include "LastSpellCastValue.h"
@ -41,52 +42,50 @@ bool LowEnergyTrigger::IsActive()
bool NoPetTrigger::IsActive()
{
return (bot->GetMinionGUID().IsEmpty()) && (!AI_VALUE(Unit*, "pet target")) && (!bot->GetGuardianPet()) &&
(!bot->GetFirstControlled()) && (!AI_VALUE2(bool, "mounted", "self target"));
return bot->GetMinionGUID().IsEmpty() && !AI_VALUE(Unit*, "pet target") && !bot->GetGuardianPet() &&
!bot->GetFirstControlled() && !AI_VALUE2(bool, "mounted", "self target");
}
bool HasPetTrigger::IsActive()
{
return (AI_VALUE(Unit*, "pet target")) && !AI_VALUE2(bool, "mounted", "self target");
;
return AI_VALUE(Unit*, "pet target") && !AI_VALUE2(bool, "mounted", "self target");
}
bool PetAttackTrigger::IsActive()
{
Guardian* pet = bot->GetGuardianPet();
if (!pet)
{
return false;
}
Unit* target = AI_VALUE(Unit*, "current target");
if (!target)
{
return false;
}
if (pet->GetVictim() == target && pet->GetCharmInfo()->IsCommandAttack())
{
return false;
}
if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat())
{
return false;
}
return true;
}
bool HighManaTrigger::IsActive()
{
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.highMana;
return AI_VALUE2(bool, "has mana", "self target") &&
AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.highMana;
}
bool AlmostFullManaTrigger::IsActive()
{
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > 85;
return AI_VALUE2(bool, "has mana", "self target") &&
AI_VALUE2(uint8, "mana", "self target") > 85;
}
bool EnoughManaTrigger::IsActive()
{
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.highMana;
return AI_VALUE2(bool, "has mana", "self target") &&
AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.highMana;
}
bool RageAvailable::IsActive() { return AI_VALUE2(uint8, "rage", "self target") >= amount; }
@ -101,9 +100,8 @@ bool TargetWithComboPointsLowerHealTrigger::IsActive()
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target || !target->IsAlive() || !target->IsInWorld())
{
return false;
}
return ComboPointsAvailableTrigger::IsActive() &&
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) <= lifeTime;
}
@ -164,19 +162,27 @@ bool BuffTrigger::IsActive()
Unit* target = GetTarget();
if (!target)
return false;
if (!SpellTrigger::IsActive())
return false;
Aura* aura = botAI->GetAura(spell, target, checkIsOwner, checkDuration);
if (!aura)
return true;
if (beforeDuration && aura->GetDuration() < beforeDuration)
if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration))
return true;
return false;
}
Value<Unit*>* BuffOnPartyTrigger::GetTargetValue()
{
return context->GetValue<Unit*>("party member without aura", spell);
return context->GetValue<Unit*>(
"party member without aura", ai::buff::MakeAuraQualifierForBuff(spell));
}
bool BuffOnPartyTrigger::IsActive()
{
Unit* target = GetTarget();
if (ai::buff::ShouldDeferPartyBuffEvaluationForRecentLogin(bot, target, spell))
return false;
return BuffTrigger::IsActive();
}
bool ProtectPartyMemberTrigger::IsActive() { return AI_VALUE(Unit*, "party member to protect"); }
@ -209,13 +215,14 @@ bool MediumThreatTrigger::IsActive()
{
if (!AI_VALUE(Unit*, "main tank"))
return false;
return MyAttackerCountTrigger::IsActive();
}
bool LowTankThreatTrigger::IsActive()
{
Unit* mt = AI_VALUE(Unit*, "main tank");
if (!mt)
Unit* mainTank = AI_VALUE(Unit*, "main tank");
if (!mainTank)
return false;
Unit* current_target = AI_VALUE(Unit*, "current target");
@ -224,7 +231,7 @@ bool LowTankThreatTrigger::IsActive()
ThreatManager& mgr = current_target->GetThreatMgr();
float threat = mgr.GetThreat(bot);
float tankThreat = mgr.GetThreat(mt);
float tankThreat = mgr.GetThreat(mainTank);
return tankThreat == 0.0f || threat > tankThreat * 0.5f;
}
@ -232,9 +239,8 @@ bool AoeTrigger::IsActive()
{
Unit* current_target = AI_VALUE(Unit*, "current target");
if (!current_target)
{
return false;
}
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
int attackers_count = 0;
for (ObjectGuid const guid : attackers)
@ -242,10 +248,9 @@ bool AoeTrigger::IsActive()
Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive())
continue;
if (unit->GetDistance(current_target->GetPosition()) <= range)
{
attackers_count++;
}
}
return attackers_count >= amount;
}
@ -274,20 +279,19 @@ bool DebuffTrigger::IsActive()
{
Unit* target = GetTarget();
if (!target || !target->IsAlive() || !target->IsInWorld())
{
return false;
}
return BuffTrigger::IsActive() && (target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
return BuffTrigger::IsActive() &&
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
}
bool DebuffOnBossTrigger::IsActive()
{
if (!DebuffTrigger::IsActive())
{
return false;
}
Creature* c = GetTarget()->ToCreature();
return c && ((c->IsDungeonBoss()) || (c->isWorldBoss()));
Creature* creature = GetTarget()->ToCreature();
return creature && (creature->IsDungeonBoss() || creature->isWorldBoss());
}
bool SpellTrigger::IsActive() { return GetTarget(); }
@ -317,9 +321,7 @@ bool SpellCooldownTrigger::IsActive()
}
RandomTrigger::RandomTrigger(PlayerbotAI* botAI, std::string const name, int32 probability)
: Trigger(botAI, name), probability(probability), lastCheck(getMSTime())
{
}
: Trigger(botAI, name), probability(probability), lastCheck(getMSTime()) {}
bool RandomTrigger::IsActive()
{
@ -330,6 +332,7 @@ bool RandomTrigger::IsActive()
int32 k = (int32)(probability / sPlayerbotAIConfig.randomChangeMultiplier);
if (k < 1)
k = 1;
return (rand() % k) == 0;
}
@ -368,9 +371,11 @@ bool BoostTrigger::IsActive()
{
if (!BuffTrigger::IsActive())
return false;
Unit* target = AI_VALUE(Unit*, "current target");
if (target && target->ToPlayer())
return true;
return AI_VALUE(uint8, "balance") <= balance;
}
@ -379,20 +384,19 @@ bool GenericBoostTrigger::IsActive()
Unit* target = AI_VALUE(Unit*, "current target");
if (target && target->ToPlayer())
return true;
return AI_VALUE(uint8, "balance") <= balance;
}
bool HealerShouldAttackTrigger::IsActive()
{
// nobody can help me
if (botAI->GetNearGroupMemberCount(sPlayerbotAIConfig.sightDistance) <= 1)
return true;
if (AI_VALUE2(uint8, "health", "party member to heal") < sPlayerbotAIConfig.almostFullHealth)
return false;
// special check for resto druid (dont remove tree of life frequently)
if (bot->GetAura(33891))
if (bot->GetAura(33891)) // Tree of Life
{
LastSpellCast& lastSpell = botAI->GetAiObjectContext()->GetValue<LastSpellCast&>("last spell cast")->Get();
if (lastSpell.timer + 5 > time(nullptr))
@ -401,7 +405,6 @@ bool HealerShouldAttackTrigger::IsActive()
int manaThreshold;
int balance = AI_VALUE(uint8, "balance");
// higher threshold in higher pressure
if (balance <= 50)
manaThreshold = 85;
else if (balance <= 100)
@ -425,13 +428,7 @@ bool InterruptSpellTrigger::IsActive()
bool DeflectSpellTrigger::IsActive()
{
Unit* target = GetTarget();
if (!target)
return false;
if (!target->IsNonMeleeSpellCast(true))
return false;
if (target->GetTarget() != bot->GetGUID())
if (!target || !target->IsNonMeleeSpellCast(true) || target->GetTarget() != bot->GetGUID())
return false;
uint32 spellid = context->GetValue<uint32>("spell id", spell)->Get();
@ -462,6 +459,7 @@ bool DeflectSpellTrigger::IsActive()
return true;
}
}
return false;
}
@ -495,17 +493,16 @@ bool FearSleepSapTrigger::IsActive()
bool HasAuraStackTrigger::IsActive()
{
Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack);
// sLog->outMessage("playerbot", LOG_LEVEL_DEBUG, "HasAuraStackTrigger::IsActive %s %d", getName(), aura ?
// aura->GetStackAmount() : -1);
return aura;
return botAI->GetAura(getName(), GetTarget(), false, true, stack);
}
bool TimerTrigger::IsActive()
{
if (time(nullptr) != lastCheck)
time_t now = time(nullptr);
if (now != lastCheck)
{
lastCheck = time(nullptr);
lastCheck = now;
return true;
}
@ -552,9 +549,8 @@ bool IsBehindTargetTrigger::IsActive()
bool IsNotBehindTargetTrigger::IsActive()
{
if (botAI->HasStrategy("stay", botAI->GetState()))
{
return false;
}
Unit* target = AI_VALUE(Unit*, "current target");
return target && !AI_VALUE2(bool, "behind", "current target");
}
@ -562,9 +558,8 @@ bool IsNotBehindTargetTrigger::IsActive()
bool IsNotFacingTargetTrigger::IsActive()
{
if (botAI->HasStrategy("stay", botAI->GetState()))
{
return false;
}
return !AI_VALUE2(bool, "facing", "current target");
}
@ -581,12 +576,14 @@ bool NoPossibleTargetsTrigger::IsActive()
return !targets.size();
}
bool PossibleAddsTrigger::IsActive() { return AI_VALUE(bool, "possible adds") && !AI_VALUE(ObjectGuid, "pull target"); }
bool PossibleAddsTrigger::IsActive()
{
return AI_VALUE(bool, "possible adds") && !AI_VALUE(ObjectGuid, "pull target");
}
bool NotDpsTargetActiveTrigger::IsActive()
{
Unit* target = AI_VALUE(Unit*, "current target");
// do not switch if enemy target
if (target && target->IsAlive())
{
Unit* enemy = AI_VALUE(Unit*, "enemy player target");
@ -604,7 +601,6 @@ bool NotDpsAoeTargetActiveTrigger::IsActive()
Unit* target = AI_VALUE(Unit*, "current target");
Unit* enemy = AI_VALUE(Unit*, "enemy player target");
// do not switch if enemy target
if (target && target == enemy && target->IsAlive())
return false;
@ -638,7 +634,10 @@ Value<Unit*>* InterruptEnemyHealerTrigger::GetTargetValue()
return context->GetValue<Unit*>("enemy healer target", spell);
}
bool RandomBotUpdateTrigger::IsActive() { return RandomTrigger::IsActive() && AI_VALUE(bool, "random bot update"); }
bool RandomBotUpdateTrigger::IsActive()
{
return RandomTrigger::IsActive() && AI_VALUE(bool, "random bot update");
}
bool NoNonBotPlayersAroundTrigger::IsActive()
{
@ -718,43 +717,24 @@ bool AmmoCountTrigger::IsActive()
bool NewPetTrigger::IsActive()
{
// Get the bot player object from the AI
Player* bot = botAI->GetBot();
if (!bot)
return false;
// Try to get the current pet; initialize guardian and GUID to null/empty
Pet* pet = bot->GetPet();
Guardian* guardian = nullptr;
ObjectGuid currentPetGuid = ObjectGuid::Empty;
// If bot has a pet, get its GUID
if (pet)
{
if (Pet* pet = bot->GetPet())
currentPetGuid = pet->GetGUID();
}
else
{
// If no pet, try to get a guardian pet and its GUID
guardian = bot->GetGuardianPet();
if (guardian)
currentPetGuid = guardian->GetGUID();
}
else if (Guardian* guardian = bot->GetGuardianPet())
currentPetGuid = guardian->GetGUID();
// If the current pet or guardian GUID has changed (including becoming empty), reset the trigger state
if (currentPetGuid != lastPetGuid)
{
triggered = false;
lastPetGuid = currentPetGuid;
}
// If there's a valid current pet/guardian (non-empty GUID) and we haven't triggered yet, activate trigger
if (currentPetGuid != ObjectGuid::Empty && !triggered)
{
triggered = true;
return true;
}
// Otherwise, do not activate
return false;
}

View File

@ -20,9 +20,7 @@ class StatAvailable : public Trigger
{
public:
StatAvailable(PlayerbotAI* botAI, int32 amount, std::string const name = "stat available")
: Trigger(botAI, name), amount(amount)
{
}
: Trigger(botAI, name), amount(amount) {}
protected:
int32 amount;
@ -118,8 +116,8 @@ public:
class TargetWithComboPointsLowerHealTrigger : public ComboPointsAvailableTrigger
{
public:
TargetWithComboPointsLowerHealTrigger(PlayerbotAI* ai, int32 combo_point = 5, float lifeTime = 8.0f)
: ComboPointsAvailableTrigger(ai, combo_point), lifeTime(lifeTime)
TargetWithComboPointsLowerHealTrigger(PlayerbotAI* botAI, int32 combo_point = 5, float lifeTime = 8.0f)
: ComboPointsAvailableTrigger(botAI, combo_point), lifeTime(lifeTime)
{
}
bool IsActive() override;
@ -196,7 +194,6 @@ public:
bool IsActive() override;
};
// TODO: check other targets
class InterruptSpellTrigger : public SpellTrigger
{
public:
@ -217,9 +214,7 @@ class AttackerCountTrigger : public Trigger
{
public:
AttackerCountTrigger(PlayerbotAI* botAI, int32 amount, float distance = sPlayerbotAIConfig.sightDistance)
: Trigger(botAI), amount(amount), distance(distance)
{
}
: Trigger(botAI), amount(amount), distance(distance) {}
bool IsActive() override;
std::string const getName() override { return "attacker count"; }
@ -269,9 +264,7 @@ class AoeTrigger : public AttackerCountTrigger
{
public:
AoeTrigger(PlayerbotAI* botAI, int32 amount = 3, float range = 15.0f)
: AttackerCountTrigger(botAI, amount), range(range)
{
}
: AttackerCountTrigger(botAI, amount), range(range) {}
bool IsActive() override;
std::string const getName() override { return "aoe"; }
@ -317,7 +310,8 @@ public:
class BuffTrigger : public SpellTrigger
{
public:
BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false, bool checkDuration = false, uint32 beforeDuration = 0)
BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1,
bool checkIsOwner = false, bool checkDuration = false, uint32 beforeDuration = 0)
: SpellTrigger(botAI, spell, checkInterval)
{
this->checkIsOwner = checkIsOwner;
@ -339,11 +333,10 @@ class BuffOnPartyTrigger : public BuffTrigger
{
public:
BuffOnPartyTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1)
: BuffTrigger(botAI, spell, checkInterval)
{
}
: BuffTrigger(botAI, spell, checkInterval) {}
Value<Unit*>* GetTargetValue() override;
bool IsActive() override;
std::string const getName() override { return spell + " on party"; }
};
@ -393,9 +386,7 @@ class DebuffTrigger : public BuffTrigger
public:
DebuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false,
float needLifeTime = 8.0f, uint32 beforeDuration = 0)
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner, false, beforeDuration), needLifeTime(needLifeTime)
{
}
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner, false, beforeDuration), needLifeTime(needLifeTime) {}
std::string const GetTargetName() override { return "current target"; }
bool IsActive() override;
@ -408,9 +399,7 @@ class DebuffOnBossTrigger : public DebuffTrigger
{
public:
DebuffOnBossTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false)
: DebuffTrigger(botAI, spell, checkInterval, checkIsOwner)
{
}
: DebuffTrigger(botAI, spell, checkInterval, checkIsOwner) {}
bool IsActive() override;
};
@ -419,9 +408,7 @@ class DebuffOnAttackerTrigger : public DebuffTrigger
public:
DebuffOnAttackerTrigger(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = true,
float needLifeTime = 8.0f)
: DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime)
{
}
: DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime) {}
Value<Unit*>* GetTargetValue() override;
std::string const getName() override { return spell + " on attacker"; }
@ -432,9 +419,7 @@ class DebuffOnMeleeAttackerTrigger : public DebuffTrigger
public:
DebuffOnMeleeAttackerTrigger(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = true,
float needLifeTime = 8.0f)
: DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime)
{
}
: DebuffTrigger(botAI, spell, 1, checkIsOwner, needLifeTime) {}
Value<Unit*>* GetTargetValue() override;
std::string const getName() override { return spell + " on attacker"; }
@ -444,9 +429,7 @@ class BoostTrigger : public BuffTrigger
{
public:
BoostTrigger(PlayerbotAI* botAI, std::string const spell, float balance = 50.f)
: BuffTrigger(botAI, spell, 1), balance(balance)
{
}
: BuffTrigger(botAI, spell, 1), balance(balance) {}
bool IsActive() override;
@ -458,9 +441,7 @@ class GenericBoostTrigger : public Trigger
{
public:
GenericBoostTrigger(PlayerbotAI* botAI, float balance = 50.f)
: Trigger(botAI, "generic boost", 1), balance(balance)
{
}
: Trigger(botAI, "generic boost", 1), balance(balance) {}
bool IsActive() override;
@ -472,9 +453,7 @@ class HealerShouldAttackTrigger : public Trigger
{
public:
HealerShouldAttackTrigger(PlayerbotAI* botAI)
: Trigger(botAI, "healer should attack", 1)
{
}
: Trigger(botAI, "healer should attack", 1) {}
bool IsActive() override;
};
@ -580,7 +559,7 @@ public:
class HasPetTrigger : public Trigger
{
public:
HasPetTrigger(PlayerbotAI* ai) : Trigger(ai, "has pet", 5 * 1000) {}
HasPetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "has pet", 5 * 1000) {}
virtual bool IsActive() override;
};
@ -588,7 +567,7 @@ public:
class PetAttackTrigger : public Trigger
{
public:
PetAttackTrigger(PlayerbotAI* ai) : Trigger(ai, "pet attack") {}
PetAttackTrigger(PlayerbotAI* botAI) : Trigger(botAI, "pet attack") {}
virtual bool IsActive() override;
};
@ -597,9 +576,7 @@ class ItemCountTrigger : public Trigger
{
public:
ItemCountTrigger(PlayerbotAI* botAI, std::string const item, int32 count, int32 interval = 30 * 1000)
: Trigger(botAI, item, interval), item(item), count(count)
{
}
: Trigger(botAI, item, interval), item(item), count(count) {}
bool IsActive() override;
std::string const getName() override { return "item count"; }
@ -613,9 +590,7 @@ class AmmoCountTrigger : public ItemCountTrigger
{
public:
AmmoCountTrigger(PlayerbotAI* botAI, std::string const item, uint32 count = 1, int32 interval = 30 * 1000)
: ItemCountTrigger(botAI, item, count, interval)
{
}
: ItemCountTrigger(botAI, item, count, interval) {}
bool IsActive() override;
};
@ -623,9 +598,7 @@ class HasAuraTrigger : public Trigger
{
public:
HasAuraTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1)
: Trigger(botAI, spell, checkInterval)
{
}
: Trigger(botAI, spell, checkInterval) {}
std::string const GetTargetName() override { return "self target"; }
bool IsActive() override;
@ -634,10 +607,8 @@ public:
class HasAuraStackTrigger : public Trigger
{
public:
HasAuraStackTrigger(PlayerbotAI* ai, std::string spell, int stack, int checkInterval = 1)
: Trigger(ai, spell, checkInterval), stack(stack)
{
}
HasAuraStackTrigger(PlayerbotAI* botAI, std::string spell, int stack, int checkInterval = 1)
: Trigger(botAI, spell, checkInterval), stack(stack) {}
std::string const GetTargetName() override { return "self target"; }
bool IsActive() override;
@ -858,9 +829,7 @@ class StayTimeTrigger : public Trigger
{
public:
StayTimeTrigger(PlayerbotAI* botAI, uint32 delay, std::string const name)
: Trigger(botAI, name, 5 * 1000), delay(delay)
{
}
: Trigger(botAI, name, 5 * 1000), delay(delay) {}
bool IsActive() override;
@ -877,7 +846,7 @@ public:
class ReturnToStayPositionTrigger : public Trigger
{
public:
ReturnToStayPositionTrigger(PlayerbotAI* ai) : Trigger(ai, "return to stay position", 2) {}
ReturnToStayPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "return to stay position", 2) {}
virtual bool IsActive() override;
};
@ -892,9 +861,7 @@ class GiveItemTrigger : public Trigger
{
public:
GiveItemTrigger(PlayerbotAI* botAI, std::string const name, std::string const item)
: Trigger(botAI, name, 2 * 1000), item(item)
{
}
: Trigger(botAI, name, 2 * 1000), item(item) {}
bool IsActive() override;
@ -962,9 +929,7 @@ class BuffOnMainTankTrigger : public BuffTrigger
{
public:
BuffOnMainTankTrigger(PlayerbotAI* botAI, std::string spell, bool checkIsOwner = false, int checkInterval = 1)
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner)
{
}
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner) {}
public:
virtual Value<Unit*>* GetTargetValue();
@ -973,7 +938,7 @@ public:
class SelfResurrectTrigger : public Trigger
{
public:
SelfResurrectTrigger(PlayerbotAI* ai) : Trigger(ai, "can self resurrect") {}
SelfResurrectTrigger(PlayerbotAI* botAI) : Trigger(botAI, "can self resurrect") {}
bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); }
};
@ -981,7 +946,7 @@ public:
class NewPetTrigger : public Trigger
{
public:
NewPetTrigger(PlayerbotAI* ai) : Trigger(ai, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {}
NewPetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {}
bool IsActive() override;

View File

@ -4,23 +4,89 @@
*/
#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 "GameTime.h"
#include "Group.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "SpellMgr.h"
#include "Unit.h"
#include "Value.h"
#include "Config.h"
#include "PlayerbotTextMgr.h"
namespace ai::buff
{
namespace
{
// Prevents bots from immediately casting already-present buffs upon logging in
constexpr uint32 POST_LOGIN_BUFF_GRACE_MS = 5 * IN_MILLISECONDS;
bool IsWithinPostLoginBuffGrace(Player* player)
{
if (!player)
return false;
return getMSTimeDiff(
player->GetInGameTime(), GameTime::GetGameTimeMS().count()) < POST_LOGIN_BUFF_GRACE_MS;
}
}
static bool HasEnoughSameMapMissingPlayersForGroupVariant(
Player* bot, PlayerbotAI* botAI, std::string const& baseName,
std::string const& groupName, uint32 requiredCount = 3)
{
Group* group = bot->GetGroup();
if (!group)
return false;
uint32 missingCount = 0;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || !member->IsInWorld() || !member->IsAlive() ||
member->GetMap() != bot->GetMap())
{
continue;
}
if (botAI->HasAura(baseName, member) || botAI->HasAura(groupName, member))
continue;
if (++missingCount >= requiredCount)
return true;
}
return false;
}
static bool IsEligibleGroupForPartyBuffs(Group const* group)
{
if (!group)
return false;
switch (sPlayerbotAIConfig.autoPartyBuffs)
{
case AutoPartyBuffMode::RAID_ONLY:
return group->isRaidGroup();
case AutoPartyBuffMode::GROUP_OR_RAID:
return true;
case AutoPartyBuffMode::DISABLED:
return false;
}
return false;
}
bool IsGroupVariantEnabled(Player* bot, std::string const& name)
{
if (!IsEligibleGroupForPartyBuffs(bot->GetGroup()))
return false;
return !GroupVariantFor(name).empty();
}
std::string MakeAuraQualifierForBuff(std::string const& name)
{
// Paladin
@ -34,27 +100,89 @@ namespace ai::buff
if (name == "arcane intellect") return "arcane intellect,arcane brilliance";
// Priest
if (name == "power word: fortitude") return "power word: fortitude,prayer of fortitude";
if (name == "divine spirit") return "divine spirit,prayer of spirit";
if (name == "shadow protection") return "shadow protection,prayer of shadow protection";
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";
if (name == "divine spirit") return "prayer of spirit";
if (name == "shadow protection") return "prayer of shadow protection";
// Paladin blessings are intentionally not included here because they are
// coordinated by the auto greater blessing system instead.
return std::string();
}
bool NeedsPostLoginBuffGrace(std::string const& name)
{
static char const* const trackedBuffs[] = {
"mark of the wild",
"arcane intellect",
"power word: fortitude",
"prayer of fortitude",
"divine spirit",
"prayer of spirit",
"shadow protection",
"prayer of shadow protection",
"blessing of kings",
"blessing of might",
"blessing of wisdom",
"blessing of sanctuary"
};
for (char const* trackedBuff : trackedBuffs)
{
if (name.find(trackedBuff) != std::string::npos)
return true;
}
return false;
}
bool ShouldDeferPartyBuffEvaluationForRecentLogin(
Player* bot, Unit* target, std::string const& spell)
{
if (!NeedsPostLoginBuffGrace(spell))
return false;
if (IsWithinPostLoginBuffGrace(bot))
return true;
Player* playerTarget = target ? target->ToPlayer() : nullptr;
return IsWithinPostLoginBuffGrace(playerTarget);
}
bool ShouldDeferGreaterBlessingAssignmentForRecentLogin(Player* bot)
{
if (IsWithinPostLoginBuffGrace(bot))
return true;
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || !member->IsInWorld())
continue;
if (IsWithinPostLoginBuffGrace(member))
return true;
}
return false;
}
bool HasRequiredReagents(Player* bot, uint32 spellId)
{
if (!spellId)
@ -72,75 +200,33 @@ namespace ai::buff
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)
Player* bot, PlayerbotAI* botAI, std::string const& baseName)
{
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 (!IsGroupVariantEnabled(bot, baseName))
return baseName;
if (std::string const groupName = GroupVariantFor(baseName); !groupName.empty())
{
uint32 const groupVariantSpellId = botAI->GetAiObjectContext()
std::string const groupName = GroupVariantFor(baseName);
if (groupName.empty())
return baseName;
// Prefer singles until at least three living, in-world group members on the bot's map
// are missing both the single-target buff and its group variant.
if (!HasEnoughSameMapMissingPlayersForGroupVariant(bot, botAI, baseName, groupName))
return baseName;
uint32 const groupSpellId = 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 (groupSpellId && HasRequiredReagents(bot, groupSpellId))
return groupName;
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> placeholders;
placeholders["%group_spell"] = groupName;
placeholders["%base_spell"] = baseName;
std::string announceText = PlayerbotTextMgr::instance().GetBotTextOrDefault(key,
"Out of components for %group_spell. Using %base_spell!", placeholders);
announce(announceText);
last = now;
}
}
}
return castName;
return baseName;
}
}

View File

@ -6,63 +6,40 @@
#pragma once
#include <string>
#include <functional>
#include "Common.h"
#include "Group.h"
#include "Chat.h"
#include "Language.h"
class Player;
class PlayerbotAI;
class Unit;
namespace ai::buff
{
// Build an aura qualifier "single + greater" to avoid double-buffing
bool IsGroupVariantEnabled(Player* bot, std::string const& name);
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 NeedsPostLoginBuffGrace(std::string const& name);
bool ShouldDeferPartyBuffEvaluationForRecentLogin(
Player* bot,
Unit* target,
std::string const& spell);
bool ShouldDeferGreaterBlessingAssignmentForRecentLogin(Player* bot);
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 = {}
);
std::string const& baseName);
}
namespace ai::spell
{
bool HasSpellOrCategoryCooldown(Player* bot, uint32 spellId);
}
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);
}
};
}
}

View File

@ -87,16 +87,16 @@ public:
bool isUseful() override;
};
class CastMarkOfTheWildAction : public CastBuffSpellAction
class CastMarkOfTheWildAction : public GroupBuffSpellAction
{
public:
CastMarkOfTheWildAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mark of the wild") {}
CastMarkOfTheWildAction(PlayerbotAI* botAI) : GroupBuffSpellAction(botAI, "mark of the wild") {}
};
class CastMarkOfTheWildOnPartyAction : public BuffOnPartyAction
class CastMarkOfTheWildOnPartyAction : public GroupBuffOnPartyAction
{
public:
CastMarkOfTheWildOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "mark of the wild") {}
CastMarkOfTheWildOnPartyAction(PlayerbotAI* botAI) : GroupBuffOnPartyAction(botAI, "mark of the wild") {}
};
class CastSurvivalInstinctsAction : public CastBuffSpellAction

View File

@ -9,11 +9,6 @@
#include "Playerbots.h"
#include "ServerFacade.h"
bool MarkOfTheWildOnPartyTrigger::IsActive()
{
return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("gift of the wild", GetTarget());
}
bool MarkOfTheWildTrigger::IsActive()
{
return BuffTrigger::IsActive() && !botAI->HasAura("gift of the wild", GetTarget());

View File

@ -23,15 +23,13 @@ class PlayerbotAI;
class MarkOfTheWildOnPartyTrigger : public BuffOnPartyTrigger
{
public:
MarkOfTheWildOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "mark of the wild", 2 * 2000) {}
bool IsActive() override;
MarkOfTheWildOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "mark of the wild", 4 * 2000) {}
};
class MarkOfTheWildTrigger : public BuffTrigger
{
public:
MarkOfTheWildTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "mark of the wild", 2 * 2000) {}
MarkOfTheWildTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "mark of the wild", 4 * 2000) {}
bool IsActive() override;
};

View File

@ -40,16 +40,16 @@ public:
CastFrostArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "frost armor") {}
};
class CastArcaneIntellectAction : public CastBuffSpellAction
class CastArcaneIntellectAction : public GroupBuffSpellAction
{
public:
CastArcaneIntellectAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "arcane intellect") {}
CastArcaneIntellectAction(PlayerbotAI* botAI) : GroupBuffSpellAction(botAI, "arcane intellect") {}
};
class CastArcaneIntellectOnPartyAction : public BuffOnPartyAction
class CastArcaneIntellectOnPartyAction : public GroupBuffOnPartyAction
{
public:
CastArcaneIntellectOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "arcane intellect") {}
CastArcaneIntellectOnPartyAction(PlayerbotAI* botAI) : GroupBuffOnPartyAction(botAI, "arcane intellect") {}
};
class CastFocusMagicOnPartyAction : public CastSpellAction

View File

@ -31,11 +31,6 @@ bool NoManaGemTrigger::IsActive()
return true;
}
bool ArcaneIntellectOnPartyTrigger::IsActive()
{
return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("arcane brilliance", GetTarget());
}
bool ArcaneIntellectTrigger::IsActive()
{
return BuffTrigger::IsActive() && !botAI->HasAura("arcane brilliance", GetTarget());

View File

@ -19,14 +19,13 @@ class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger
{
public:
ArcaneIntellectOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "arcane intellect", 2 * 2000) {}
bool IsActive() override;
: BuffOnPartyTrigger(botAI, "arcane intellect", 4 * 2000) {}
};
class ArcaneIntellectTrigger : public BuffTrigger
{
public:
ArcaneIntellectTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane intellect", 2 * 2000) {}
ArcaneIntellectTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane intellect", 4 * 2000) {}
bool IsActive() override;
};

View File

@ -1,529 +0,0 @@
/*
* 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 "PaladinActions.h"
#include "AiFactory.h"
#include "Event.h"
#include "PaladinHelper.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "SharedDefines.h"
#include "../../../../../src/server/scripts/Spells/spell_generic.cpp"
#include "Ai/Base/Util/GenericBuffUtils.h"
#include "Group.h"
#include "ObjectAccessor.h"
using ai::buff::MakeAuraQualifierForBuff;
// 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;
}
inline std::string const GetActualBlessingOfMight(Unit* target)
{
if (!target->ToPlayer())
{
return "blessing of might";
}
int tab = AiFactory::GetPlayerSpecTab(target->ToPlayer());
switch (target->getClass())
{
case CLASS_MAGE:
case CLASS_PRIEST:
case CLASS_WARLOCK:
return "blessing of wisdom";
break;
case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_ELEMENTAL || tab == SHAMAN_TAB_RESTORATION)
{
return "blessing of wisdom";
}
break;
case CLASS_DRUID:
if (tab == DRUID_TAB_RESTORATION || tab == DRUID_TAB_BALANCE)
{
return "blessing of wisdom";
}
break;
case CLASS_PALADIN:
if (tab == PALADIN_TAB_HOLY)
{
return "blessing of wisdom";
}
break;
}
return "blessing of might";
}
inline std::string const GetActualBlessingOfWisdom(Unit* target)
{
if (!target->ToPlayer())
{
return "blessing of might";
}
int tab = AiFactory::GetPlayerSpecTab(target->ToPlayer());
switch (target->getClass())
{
case CLASS_WARRIOR:
case CLASS_ROGUE:
case CLASS_DEATH_KNIGHT:
case CLASS_HUNTER:
return "blessing of might";
break;
case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_ENHANCEMENT)
{
return "blessing of might";
}
break;
case CLASS_DRUID:
if (tab == DRUID_TAB_FERAL)
{
return "blessing of might";
}
break;
case CLASS_PALADIN:
if (tab == PALADIN_TAB_PROTECTION || tab == PALADIN_TAB_RETRIBUTION)
{
return "blessing of might";
}
break;
}
return "blessing of wisdom";
}
inline std::string const GetActualBlessingOfSanctuary(Unit* target, Player* bot)
{
if (!bot->HasSpell(SPELL_BLESSING_OF_SANCTUARY))
return "";
Player* tp = target->ToPlayer();
if (!tp)
return "";
if (auto* ai = GET_PLAYERBOT_AI(bot))
{
if (Unit* mt = ai->GetAiObjectContext()->GetValue<Unit*>("main tank")->Get())
{
if (mt == target)
return "blessing of sanctuary";
}
}
if (tp->HasTankSpec())
return "blessing of sanctuary";
return "";
}
Value<Unit*>* CastBlessingOnPartyAction::GetTargetValue()
{
return context->GetValue<Unit*>("party member without aura", MakeAuraQualifierForBuff(spell));
}
bool CastBlessingOfMightAction::Execute(Event /*event*/)
{
Unit* target = GetTarget();
if (!target)
return false;
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,greater blessing of might,blessing of wisdom,greater blessing of wisdom,blessing of sanctuary,greater blessing of sanctuary"
);
}
bool CastBlessingOfMightOnPartyAction::Execute(Event /*event*/)
{
Unit* target = GetTarget();
if (!target)
return false;
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*/)
{
Unit* target = GetTarget();
if (!target)
return false;
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 wisdom,greater blessing of wisdom,blessing of might,greater blessing of might,blessing of sanctuary,greater blessing of sanctuary"
);
}
bool CastBlessingOfWisdomOnPartyAction::Execute(Event /*event*/)
{
Unit* target = GetTarget();
if (!target)
return false;
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,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;
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"); }
Value<Unit*>* CastTurnUndeadAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", getName()); }
Unit* CastHandOfFreedomOnPartyAction::GetTarget()
{
bool const selfImpaired = botAI->IsMovementImpaired(bot);
bool const hasSelfHand = selfImpaired && ai::paladin::HasAnyPaladinHandFromCaster(bot, bot);
if (!bot->GetGroup())
{
if (selfImpaired && !hasSelfHand)
return bot;
return nullptr;
}
if (selfImpaired && !hasSelfHand)
return bot;
return CastBuffSpellAction::GetTarget();
}
Value<Unit*>* CastHandOfFreedomOnPartyAction::GetTargetValue()
{
return context->GetValue<Unit*>("party member snared target");
}
bool CastHandOfFreedomOnPartyAction::isUseful()
{
Unit* target = GetTarget();
if (!target)
return false;
return CastBuffSpellAction::isUseful() && !ai::paladin::HasAnyPaladinHandFromCaster(target, bot);
}
Unit* CastRighteousDefenseAction::GetTarget()
{
Unit* current_target = AI_VALUE(Unit*, "current target");
if (!current_target)
return nullptr;
return current_target->GetVictim();
}
bool CastDivineSacrificeAction::isUseful()
{
return GetTarget() && (GetTarget() != nullptr) && CastSpellAction::isUseful() &&
!botAI->HasAura("divine guardian", GetTarget(), false, false, -1, true);
}
bool CastCancelDivineSacrificeAction::Execute(Event /*event*/)
{
botAI->RemoveAura("divine sacrifice");
return true;
}
bool CastCancelDivineSacrificeAction::isUseful()
{
return botAI->HasAura("divine sacrifice", GetTarget(), false, true, -1, true);
}

View File

@ -0,0 +1,609 @@
/*
* 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 "PaladinActions.h"
#include "AiFactory.h"
#include "Event.h"
#include "GenericBuffUtils.h"
#include "PaladinGreaterBlessingAction.h"
#include "PaladinHelper.h"
#include "Playerbots.h"
#include "SharedDefines.h"
static bool IsBlessingTargetCandidate(Player* bot, Player* player)
{
if (!player || !player->IsAlive() || player->GetMapId() != bot->GetMapId())
return false;
if (player->IsGameMaster())
return false;
return bot->GetDistance(player) < sPlayerbotAIConfig.spellDistance * 2 &&
bot->IsWithinLOS(player->GetPositionX(), player->GetPositionY(),
player->GetPositionZ());
}
static bool HasBlessingAura(
PlayerbotAI* botAI, Unit* target, std::initializer_list<char const*> auraNames)
{
for (char const* auraName : auraNames)
{
if (botAI->HasAura(auraName, target))
return true;
}
return false;
}
static bool IsGreaterBlessingMode(Player* bot)
{
return ai::gbless::IsEligibleGroupForAutoBlessings(bot->GetGroup());
}
template <typename Predicate>
static Unit* FindBlessingTarget(
Player* bot, PlayerbotAI* botAI, Predicate&& predicate)
{
std::vector<Player*> masters;
std::vector<Player*> healers;
std::vector<Player*> tanks;
std::vector<Player*> others;
Player* master = botAI->GetMaster();
auto addPlayer = [&](Player* player)
{
if (!IsBlessingTargetCandidate(bot, player))
return;
if (player == master)
masters.push_back(player);
else if (botAI->IsHeal(player))
healers.push_back(player);
else if (botAI->IsTank(player))
tanks.push_back(player);
else
others.push_back(player);
};
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
addPlayer(ref->GetSource());
}
else
{
addPlayer(bot);
}
std::vector<std::vector<Player*>*> orderedLists = {
&masters, &healers, &tanks, &others };
for (std::vector<Player*>* players : orderedLists)
{
for (Player* player : *players)
{
if (predicate(player))
return player;
}
}
return nullptr;
}
static inline bool IsTankRole(Player* player)
{
if (!player)
return false;
if (player->HasTankSpec())
return true;
if (PlayerbotAI* otherAI = GET_PLAYERBOT_AI(player))
{
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;
}
static inline bool IsOnlyPaladinInGroup(Player* bot)
{
if (!bot)
return false;
Group* group = bot->GetGroup();
if (!group)
return true;
uint32 paladins = 0u;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* player = ref->GetSource();
if (!player || !player->IsInWorld()) continue;
if (player->getClass() == CLASS_PALADIN) ++paladins;
}
return paladins == 1u;
}
inline std::string const GetActualBlessingOfMight(Unit* target)
{
if (!target->ToPlayer())
return "blessing of might";
uint8 tab = AiFactory::GetPlayerSpecTab(target->ToPlayer());
switch (target->getClass())
{
case CLASS_MAGE:
case CLASS_PRIEST:
case CLASS_WARLOCK:
return "blessing of wisdom";
break;
case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_ELEMENTAL || tab == SHAMAN_TAB_RESTORATION)
return "blessing of wisdom";
break;
case CLASS_DRUID:
if (tab == DRUID_TAB_RESTORATION || tab == DRUID_TAB_BALANCE)
return "blessing of wisdom";
break;
case CLASS_PALADIN:
if (tab == PALADIN_TAB_HOLY)
return "blessing of wisdom";
break;
}
return "blessing of might";
}
inline std::string const GetActualBlessingOfWisdom(Unit* target)
{
if (!target->ToPlayer())
return "blessing of might";
uint8 tab = AiFactory::GetPlayerSpecTab(target->ToPlayer());
switch (target->getClass())
{
case CLASS_WARRIOR:
case CLASS_ROGUE:
case CLASS_DEATH_KNIGHT:
case CLASS_HUNTER:
return "blessing of might";
break;
case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_ENHANCEMENT)
return "blessing of might";
break;
case CLASS_DRUID:
if (tab == DRUID_TAB_FERAL)
return "blessing of might";
break;
case CLASS_PALADIN:
if (tab == PALADIN_TAB_PROTECTION || tab == PALADIN_TAB_RETRIBUTION)
return "blessing of might";
break;
}
return "blessing of wisdom";
}
inline std::string const GetActualBlessingOfSanctuary(Unit* target, Player* bot)
{
if (!bot->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY))
return "";
Player* targetPlayer = target->ToPlayer();
if (!targetPlayer)
return "";
if (auto* botAI = GET_PLAYERBOT_AI(bot))
{
if (Unit* mainTank =
botAI->GetAiObjectContext()->GetValue<Unit*>("main tank")->Get())
{
if (mainTank == target)
return "blessing of sanctuary";
}
}
if (targetPlayer->HasTankSpec())
return "blessing of sanctuary";
return "";
}
Unit* CastBlessingOfMightOnPartyAction::GetTarget()
{
if (IsGreaterBlessingMode(bot))
return nullptr;
return FindBlessingTarget(bot, botAI, [&](Player* player)
{
return !HasBlessingAura(botAI, player,
{ "blessing of might", "greater blessing of might",
"blessing of wisdom", "greater blessing of wisdom",
"blessing of sanctuary", "greater blessing of sanctuary" });
});
}
bool CastBlessingOfMightAction::Execute(Event /*event*/)
{
Unit* target = GetTarget();
if (!target)
return false;
std::string castName = GetActualBlessingOfMight(target);
return botAI->CastSpell(castName, target);
}
Value<Unit*>* CastBlessingOfMightOnPartyAction::GetTargetValue()
{
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*/)
{
if (IsGreaterBlessingMode(bot))
return false;
Unit* target = GetTarget();
if (!target)
return false;
std::string castName = GetActualBlessingOfMight(target);
return botAI->CastSpell(castName, target);
}
bool CastBlessingOfWisdomAction::Execute(Event /*event*/)
{
Unit* target = GetTarget();
if (!target)
return false;
std::string castName = GetActualBlessingOfWisdom(target);
return botAI->CastSpell(castName, target);
}
Unit* CastBlessingOfWisdomOnPartyAction::GetTarget()
{
if (IsGreaterBlessingMode(bot))
return nullptr;
return FindBlessingTarget(bot, botAI, [&](Player* player)
{
if (botAI->HasStrategy("bwisdom", BOT_STATE_NON_COMBAT) && IsTankRole(player))
return false;
return !HasBlessingAura(botAI, player,
{ "blessing of might", "greater blessing of might",
"blessing of wisdom", "greater blessing of wisdom",
"blessing of sanctuary", "greater blessing of sanctuary" });
});
}
Value<Unit*>* CastBlessingOfWisdomOnPartyAction::GetTargetValue()
{
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*/)
{
if (IsGreaterBlessingMode(bot))
return false;
Unit* target = GetTarget();
if (!target)
return false;
Player* targetPlayer = target->ToPlayer();
if (Group* group = bot->GetGroup())
if (targetPlayer && !group->IsMember(targetPlayer->GetGUID()))
return false;
if (botAI->HasStrategy("bwisdom", BOT_STATE_NON_COMBAT) &&
targetPlayer && IsTankRole(targetPlayer))
return false;
std::string castName = GetActualBlessingOfWisdom(target);
if (castName.empty())
return false;
return botAI->CastSpell(castName, target);
}
Value<Unit*>* CastBlessingOfSanctuaryOnPartyAction::GetTargetValue()
{
return context->GetValue<Unit*>(
"party member without aura",
"blessing of sanctuary,greater blessing of sanctuary"
);
}
bool CastBlessingOfSanctuaryOnPartyAction::Execute(Event /*event*/)
{
if (IsGreaterBlessingMode(bot))
return false;
if (!bot->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY))
return false;
Unit* target = GetTarget();
if (!target)
target = bot;
Player* targetPlayer = target ? target->ToPlayer() : nullptr;
const auto HasKingsAura = [&](Unit* unit) -> bool {
return botAI->HasAura("blessing of kings", unit) ||
botAI->HasAura("greater blessing of kings", unit);
};
const auto HasSanctAura = [&](Unit* unit) -> bool {
return botAI->HasAura("blessing of sanctuary", unit) ||
botAI->HasAura("greater blessing of sanctuary", unit);
};
if (Group* group = bot->GetGroup())
{
if (targetPlayer && !group->IsMember(targetPlayer->GetGUID()))
{
target = bot;
targetPlayer = bot->ToPlayer();
}
}
if (Player* self = bot->ToPlayer())
{
bool selfHasSanct = HasSanctAura(self);
bool needSelf = IsTankRole(self) && !selfHasSanct;
if (needSelf)
{
target = self;
targetPlayer = self;
}
}
bool targetOk = false;
if (targetPlayer)
{
bool hasSanct = HasSanctAura(targetPlayer);
targetOk = IsTankRole(targetPlayer) && !hasSanct;
}
if (!targetOk)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* player = ref->GetSource();
if (!player) continue;
if (!player->IsInWorld() || !player->IsAlive()) continue;
if (!IsTankRole(player)) continue;
bool hasSanct = HasSanctAura(player);
if (!hasSanct)
{
target = player;
targetPlayer = player;
targetOk = true;
break;
}
}
}
}
if (GetActualBlessingOfSanctuary(target, bot).empty())
{
if (targetPlayer)
{
if (IsTankRole(targetPlayer))
return botAI->CastSpell("blessing of sanctuary", target);
else
return false;
}
else
return false;
}
return botAI->CastSpell("blessing of sanctuary", target);
}
Unit* CastBlessingOfSanctuaryOnPartyAction::GetTarget()
{
if (IsGreaterBlessingMode(bot))
return nullptr;
if (!bot->HasSpell(ai::paladin::SPELL_BLESSING_OF_SANCTUARY))
return nullptr;
return FindBlessingTarget(bot, botAI, [&](Player* player)
{
return IsTankRole(player) &&
!HasBlessingAura(botAI, player,
{ "blessing of sanctuary", "greater blessing of sanctuary" });
});
}
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"
);
}
Unit* CastBlessingOfKingsOnPartyAction::GetTarget()
{
if (IsGreaterBlessingMode(bot))
return nullptr;
const bool hasBwisdom = botAI->HasStrategy("bwisdom", BOT_STATE_NON_COMBAT);
const bool hasBkings = botAI->HasStrategy("bkings", BOT_STATE_NON_COMBAT);
const bool onlyPaladinInGroup = IsOnlyPaladinInGroup(bot);
return FindBlessingTarget(bot, botAI, [&](Player* player)
{
const bool isTank = IsTankRole(player);
const bool hasKingsOrSanct = HasBlessingAura(botAI, player,
{ "blessing of kings", "greater blessing of kings",
"blessing of sanctuary", "greater blessing of sanctuary" });
if (hasKingsOrSanct)
return false;
if (hasBwisdom)
return isTank;
if (hasBkings)
{
if (isTank)
return false;
if (onlyPaladinInGroup && player == bot)
return false;
}
return true;
});
}
bool CastBlessingOfKingsOnPartyAction::Execute(Event /*event*/)
{
if (IsGreaterBlessingMode(bot))
return false;
Unit* target = GetTarget();
if (!target)
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
if (botAI->HasStrategy("bkings", BOT_STATE_NON_COMBAT) &&
IsOnlyPaladinInGroup(bot))
{
if (target->GetGUID() == bot->GetGUID())
return false;
}
Player* targetPlayer = target->ToPlayer();
if (targetPlayer && !group->IsMember(targetPlayer->GetGUID()))
return false;
const bool hasBwisdom = botAI->HasStrategy("bwisdom", BOT_STATE_NON_COMBAT);
const bool hasBkings = botAI->HasStrategy("bkings", BOT_STATE_NON_COMBAT);
if (hasBwisdom && (!targetPlayer || !IsTankRole(targetPlayer)))
return false;
if (targetPlayer)
{
const bool isTank = IsTankRole(targetPlayer);
const bool hasSanctFromMe =
target->HasAura(ai::paladin::SPELL_BLESSING_OF_SANCTUARY, bot->GetGUID()) ||
target->HasAura(ai::paladin::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)
return false;
if (hasBkings && isTank && hasSanctAny)
return false;
}
return botAI->CastSpell("blessing of kings", target);
}
bool CastSealSpellAction::isUseful()
{
return AI_VALUE2(bool, "combat", "self target");
}
Value<Unit*>* CastTurnUndeadAction::GetTargetValue()
{
return context->GetValue<Unit*>("cc target", getName());
}
Unit* CastHandOfFreedomOnPartyAction::GetTarget()
{
bool const selfImpaired = botAI->IsMovementImpaired(bot);
bool const hasSelfHand =
selfImpaired && ai::paladin::HasAnyPaladinHandFromCaster(bot, bot);
if (!bot->GetGroup())
{
if (selfImpaired && !hasSelfHand)
return bot;
return nullptr;
}
if (selfImpaired && !hasSelfHand)
return bot;
return CastBuffSpellAction::GetTarget();
}
Value<Unit*>* CastHandOfFreedomOnPartyAction::GetTargetValue()
{
return context->GetValue<Unit*>("party member snared target");
}
bool CastHandOfFreedomOnPartyAction::isUseful()
{
Unit* target = GetTarget();
if (!target)
return false;
return CastBuffSpellAction::isUseful() &&
!ai::paladin::HasAnyPaladinHandFromCaster(target, bot);
}
Unit* CastRighteousDefenseAction::GetTarget()
{
Unit* current_target = AI_VALUE(Unit*, "current target");
if (!current_target)
return nullptr;
return current_target->GetVictim();
}
bool CastDivineSacrificeAction::isUseful()
{
return GetTarget() && (GetTarget() != nullptr) && CastSpellAction::isUseful() &&
!botAI->HasAura("divine guardian", GetTarget(), false, false, -1, true);
}
bool CastCancelDivineSacrificeAction::Execute(Event /*event*/)
{
botAI->RemoveAura("divine sacrifice");
return true;
}
bool CastCancelDivineSacrificeAction::isUseful()
{
return botAI->HasAura("divine sacrifice", GetTarget(), false, true, -1, true);
}

View File

@ -8,10 +8,6 @@
#include "AiObject.h"
#include "GenericSpellActions.h"
#include "SharedDefines.h"
class PlayerbotAI;
class Unit;
// seals
BUFF_ACTION(CastSealOfRighteousnessAction, "seal of righteousness");
@ -88,24 +84,13 @@ public:
bool Execute(Event event) override;
};
class CastBlessingOnPartyAction : public BuffOnPartyAction
{
public:
CastBlessingOnPartyAction(PlayerbotAI* botAI, std::string const name)
: BuffOnPartyAction(botAI, name), name(name) {}
Value<Unit*>* GetTargetValue() override;
private:
std::string name;
};
class CastBlessingOfMightOnPartyAction : public BuffOnPartyAction
{
public:
CastBlessingOfMightOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of might") {}
std::string const getName() override { return "blessing of might on party"; }
Unit* GetTarget() override;
Value<Unit*>* GetTargetValue() override;
bool Execute(Event event) override;
};
@ -124,6 +109,7 @@ public:
CastBlessingOfWisdomOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of wisdom") {}
std::string const getName() override { return "blessing of wisdom on party"; }
Unit* GetTarget() override;
Value<Unit*>* GetTargetValue() override;
bool Execute(Event event) override;
};
@ -134,12 +120,13 @@ public:
CastBlessingOfKingsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "blessing of kings") {}
};
class CastBlessingOfKingsOnPartyAction : public CastBlessingOnPartyAction
class CastBlessingOfKingsOnPartyAction : public BuffOnPartyAction
{
public:
CastBlessingOfKingsOnPartyAction(PlayerbotAI* botAI) : CastBlessingOnPartyAction(botAI, "blessing of kings") {}
CastBlessingOfKingsOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of kings") {}
std::string const getName() override { return "blessing of kings on party"; }
Unit* GetTarget() override;
Value<Unit*>* GetTargetValue() override; // added for Sanctuary priority
bool Execute(Event event) override; // added for 2 paladins logic
};
@ -156,6 +143,7 @@ public:
CastBlessingOfSanctuaryOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of sanctuary") {}
std::string const getName() override { return "blessing of sanctuary on party"; }
Unit* GetTarget() override;
Value<Unit*>* GetTargetValue() override;
bool Execute(Event event) override;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,267 @@
/*
* 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_PALADINGREATERBLESSINGACTION_H
#define _PLAYERBOT_PALADINGREATERBLESSINGACTION_H
#include <array>
#include <string>
#include <vector>
#include "Action.h"
#include "AiFactory.h"
#include "Playerbots.h"
#include "SharedDefines.h"
class UntypedValue;
namespace ai::gbless
{
enum RoleProfile : uint8
{
ROLE_CASTER = 0,
ROLE_PHYSICAL_DPS = 1,
ROLE_HYBRID_DPS = 2,
ROLE_DRUID_TANK = 3,
ROLE_WARRIOR_DK_TANK = 4,
ROLE_PALADIN_TANK = 5,
ROLE_PROFILE_COUNT = 6
};
enum BlessingType : uint8
{
BLESSING_NONE = 0,
BLESSING_MIGHT_SINGLE = 1,
BLESSING_MIGHT_GREATER = 2,
BLESSING_WISDOM_SINGLE = 3,
BLESSING_WISDOM_GREATER = 4,
BLESSING_KINGS_SINGLE = 5,
BLESSING_KINGS_GREATER = 6,
BLESSING_SANCTUARY_SINGLE = 7,
BLESSING_SANCTUARY_GREATER = 8
};
enum BaseBlessingCategory : uint8
{
BASE_NONE = 0,
BASE_MIGHT = 1,
BASE_WISDOM = 2,
BASE_KINGS = 3,
BASE_SANCTUARY = 4
};
inline constexpr BaseBlessingCategory BaseBlessingOf(BlessingType type)
{
switch (type)
{
case BLESSING_MIGHT_SINGLE:
case BLESSING_MIGHT_GREATER: return BASE_MIGHT;
case BLESSING_WISDOM_SINGLE:
case BLESSING_WISDOM_GREATER: return BASE_WISDOM;
case BLESSING_KINGS_SINGLE:
case BLESSING_KINGS_GREATER: return BASE_KINGS;
case BLESSING_SANCTUARY_SINGLE:
case BLESSING_SANCTUARY_GREATER: return BASE_SANCTUARY;
default: return BASE_NONE;
}
}
inline constexpr bool IsSingleVariant(BlessingType type)
{
return type == BLESSING_MIGHT_SINGLE || type == BLESSING_WISDOM_SINGLE ||
type == BLESSING_KINGS_SINGLE || type == BLESSING_SANCTUARY_SINGLE;
}
inline constexpr bool IsGreaterVariant(BlessingType type)
{
return type == BLESSING_MIGHT_GREATER || type == BLESSING_WISDOM_GREATER ||
type == BLESSING_KINGS_GREATER || type == BLESSING_SANCTUARY_GREATER;
}
inline constexpr BlessingType ToSingleVariant(BaseBlessingCategory category)
{
switch (category)
{
case BASE_MIGHT: return BLESSING_MIGHT_SINGLE;
case BASE_WISDOM: return BLESSING_WISDOM_SINGLE;
case BASE_KINGS: return BLESSING_KINGS_SINGLE;
case BASE_SANCTUARY: return BLESSING_SANCTUARY_SINGLE;
default: return BLESSING_NONE;
}
}
inline constexpr BlessingType ToSingleVariant(BlessingType type)
{
return ToSingleVariant(BaseBlessingOf(type));
}
inline constexpr BlessingType ToGreaterVariant(BaseBlessingCategory category)
{
switch (category)
{
case BASE_MIGHT: return BLESSING_MIGHT_GREATER;
case BASE_WISDOM: return BLESSING_WISDOM_GREATER;
case BASE_KINGS: return BLESSING_KINGS_GREATER;
case BASE_SANCTUARY: return BLESSING_SANCTUARY_GREATER;
default: return BLESSING_NONE;
}
}
inline constexpr BlessingType ToGreaterVariant(BlessingType type)
{
return ToGreaterVariant(BaseBlessingOf(type));
}
inline std::string BlessingSpellName(BlessingType type)
{
switch (type)
{
case BLESSING_MIGHT_SINGLE: return "blessing of might";
case BLESSING_MIGHT_GREATER: return "greater blessing of might";
case BLESSING_WISDOM_SINGLE: return "blessing of wisdom";
case BLESSING_WISDOM_GREATER: return "greater blessing of wisdom";
case BLESSING_KINGS_SINGLE: return "blessing of kings";
case BLESSING_KINGS_GREATER: return "greater blessing of kings";
case BLESSING_SANCTUARY_SINGLE: return "blessing of sanctuary";
case BLESSING_SANCTUARY_GREATER: return "greater blessing of sanctuary";
default: return "";
}
}
struct BaseBlessingPriorityEntry
{
BaseBlessingCategory priorities[4];
};
inline constexpr BaseBlessingPriorityEntry BASE_BLESSING_PRIORITIES[ROLE_PROFILE_COUNT] =
{
// All casters
{{ BASE_KINGS, BASE_WISDOM, BASE_SANCTUARY, BASE_MIGHT }},
// Physical DPS (no mana)
{{ BASE_MIGHT, BASE_KINGS, BASE_SANCTUARY, BASE_NONE }},
// Hybrid DPS
{{ BASE_MIGHT, BASE_KINGS, BASE_WISDOM, BASE_SANCTUARY }},
// Druid tanks
{{ BASE_KINGS, BASE_MIGHT, BASE_SANCTUARY, BASE_WISDOM, }},
// Warrior and DK tanks
{{ BASE_KINGS, BASE_MIGHT, BASE_SANCTUARY, BASE_NONE }},
// Paladin tanks
{{ BASE_SANCTUARY, BASE_MIGHT, BASE_WISDOM, BASE_KINGS }},
};
constexpr uint32 SPELL_IMPROVED_MIGHT_R1 = 20042;
constexpr uint32 SPELL_IMPROVED_MIGHT_R2 = 20045;
constexpr uint32 SPELL_IMPROVED_WISDOM_R1 = 20244;
constexpr uint32 SPELL_IMPROVED_WISDOM_R2 = 20245;
inline RoleProfile ResolveRoleProfile(Player* player)
{
if (!player)
return ROLE_CASTER;
uint8 cls = player->getClass();
int tab = AiFactory::GetPlayerSpecTab(player);
bool isTank = PlayerbotAI::IsTank(player);
switch (cls)
{
case CLASS_WARRIOR:
if (isTank)
return ROLE_WARRIOR_DK_TANK;
return ROLE_PHYSICAL_DPS;
case CLASS_DEATH_KNIGHT:
if (isTank)
return ROLE_WARRIOR_DK_TANK;
return ROLE_PHYSICAL_DPS;
case CLASS_SHAMAN:
if (tab == SHAMAN_TAB_ENHANCEMENT)
return ROLE_HYBRID_DPS;
return ROLE_CASTER;
case CLASS_PALADIN:
if (isTank)
return ROLE_PALADIN_TANK;
if (tab == PALADIN_TAB_HOLY)
return ROLE_CASTER;
return ROLE_HYBRID_DPS;
case CLASS_DRUID:
if (tab == DRUID_TAB_FERAL)
return isTank ? ROLE_DRUID_TANK : ROLE_HYBRID_DPS;
return ROLE_CASTER;
case CLASS_ROGUE:
return ROLE_PHYSICAL_DPS;
case CLASS_HUNTER:
return ROLE_HYBRID_DPS;
case CLASS_MAGE:
return ROLE_CASTER;
case CLASS_WARLOCK:
return ROLE_CASTER;
case CLASS_PRIEST:
return ROLE_CASTER;
default:
return ROLE_CASTER;
}
}
struct GreaterBlessingPlayerAssignment
{
Player* player = nullptr;
BlessingType blessing = BLESSING_NONE;
};
struct CachedBlessingBucketAssignment
{
uint8 classId = 0;
RoleProfile role = ROLE_CASTER;
bool byRole = false;
BlessingType blessing = BLESSING_NONE;
};
struct CachedBlessingAssignments
{
uint32 groupKey = 0;
bool valid = false;
std::vector<CachedBlessingBucketAssignment> assignments;
};
struct CachedPendingBlessingAssignment
{
uint32 groupKey = 0;
bool valid = false;
GreaterBlessingPlayerAssignment assignment;
std::string spellName;
};
UntypedValue* greater_blessing_assignments_value(PlayerbotAI* botAI);
UntypedValue* greater_blessing_pending_assignment_value(PlayerbotAI* botAI);
bool IsEligibleGroupForAutoBlessings(Group const* group);
}
class CastGreaterBlessingAssignmentAction : public Action
{
public:
CastGreaterBlessingAssignmentAction(PlayerbotAI* botAI);
bool Execute(Event event) override;
bool isUseful() override;
bool HasPendingAssignment();
private:
bool FindPendingAssignment(
ai::gbless::GreaterBlessingPlayerAssignment& outAssignment,
std::string& outSpellName);
};
#endif

View File

@ -7,6 +7,7 @@
#include "DpsPaladinStrategy.h"
#include "GenericPaladinNonCombatStrategy.h"
#include "PaladinGreaterBlessingAction.h"
#include "HealPaladinStrategy.h"
#include "NamedObjectContext.h"
#include "OffhealRetPaladinStrategy.h"
@ -70,17 +71,17 @@ class PaladinBuffStrategyFactoryInternal : public NamedObjectContext<Strategy>
public:
PaladinBuffStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
{
creators["bhealth"] = &PaladinBuffStrategyFactoryInternal::bhealth;
creators["bmana"] = &PaladinBuffStrategyFactoryInternal::bmana;
creators["bdps"] = &PaladinBuffStrategyFactoryInternal::bdps;
creators["bstats"] = &PaladinBuffStrategyFactoryInternal::bstats;
creators["bsanc"] = &PaladinBuffStrategyFactoryInternal::bsanc;
creators["bwisdom"] = &PaladinBuffStrategyFactoryInternal::bwisdom;
creators["bmight"] = &PaladinBuffStrategyFactoryInternal::bmight;
creators["bkings"] = &PaladinBuffStrategyFactoryInternal::bkings;
}
private:
static Strategy* bhealth(PlayerbotAI* botAI) { return new PaladinBuffHealthStrategy(botAI); }
static Strategy* bmana(PlayerbotAI* botAI) { return new PaladinBuffManaStrategy(botAI); }
static Strategy* bdps(PlayerbotAI* botAI) { return new PaladinBuffDpsStrategy(botAI); }
static Strategy* bstats(PlayerbotAI* botAI) { return new PaladinBuffStatsStrategy(botAI); }
static Strategy* bsanc(PlayerbotAI* botAI) { return new PaladinBuffHealthStrategy(botAI); }
static Strategy* bwisdom(PlayerbotAI* botAI) { return new PaladinBuffManaStrategy(botAI); }
static Strategy* bmight(PlayerbotAI* botAI) { return new PaladinBuffDpsStrategy(botAI); }
static Strategy* bkings(PlayerbotAI* botAI) { return new PaladinBuffStatsStrategy(botAI); }
};
class PaladinCombatStrategyFactoryInternal : public NamedObjectContext<Strategy>
@ -154,6 +155,7 @@ public:
creators["blessing of sanctuary on party"] = &PaladinTriggerFactoryInternal::blessing_of_sanctuary_on_party;
creators["avenging wrath"] = &PaladinTriggerFactoryInternal::avenging_wrath;
creators["greater blessing needed"] = &PaladinTriggerFactoryInternal::greater_blessing_needed;
}
private:
@ -211,8 +213,8 @@ private:
static Trigger* repentance_on_enemy_healer(PlayerbotAI* botAI) { return new RepentanceOnHealerTrigger(botAI); }
static Trigger* repentance_on_snare_target(PlayerbotAI* botAI) { return new RepentanceSnareTrigger(botAI); }
static Trigger* repentance_interrupt(PlayerbotAI* botAI) { return new RepentanceInterruptTrigger(botAI); }
static Trigger* beacon_of_light_on_main_tank(PlayerbotAI* ai) { return new BeaconOfLightOnMainTankTrigger(ai); }
static Trigger* sacred_shield_on_main_tank(PlayerbotAI* ai) { return new SacredShieldOnMainTankTrigger(ai); }
static Trigger* beacon_of_light_on_main_tank(PlayerbotAI* botAI) { return new BeaconOfLightOnMainTankTrigger(botAI); }
static Trigger* sacred_shield_on_main_tank(PlayerbotAI* botAI) { return new SacredShieldOnMainTankTrigger(botAI); }
static Trigger* hand_of_freedom_on_party(PlayerbotAI* botAI) { return new HandOfFreedomOnPartyTrigger(botAI); }
static Trigger* blessing_of_kings_on_party(PlayerbotAI* botAI) { return new BlessingOfKingsOnPartyTrigger(botAI); }
@ -227,6 +229,10 @@ private:
}
static Trigger* avenging_wrath(PlayerbotAI* botAI) { return new AvengingWrathTrigger(botAI); }
static Trigger* greater_blessing_needed(PlayerbotAI* botAI)
{
return new GreaterBlessingNeededTrigger(botAI);
}
};
class PaladinAiObjectContextInternal : public NamedObjectContext<Action>
@ -316,6 +322,8 @@ public:
creators["divine sacrifice"] = &PaladinAiObjectContextInternal::divine_sacrifice;
creators["cancel divine sacrifice"] = &PaladinAiObjectContextInternal::cancel_divine_sacrifice;
creators["hand of freedom on party"] = &PaladinAiObjectContextInternal::hand_of_freedom_on_party;
creators["cast greater blessing assignment"] =
&PaladinAiObjectContextInternal::cast_greater_blessing_assignment;
}
private:
@ -414,15 +422,41 @@ private:
static Action* sanctity_aura(PlayerbotAI* botAI) { return new CastSanctityAuraAction(botAI); }
static Action* holy_shock(PlayerbotAI* botAI) { return new CastHolyShockAction(botAI); }
static Action* holy_shock_on_party(PlayerbotAI* botAI) { return new CastHolyShockOnPartyAction(botAI); }
static Action* divine_plea(PlayerbotAI* ai) { return new CastDivinePleaAction(ai); }
static Action* shield_of_righteousness(PlayerbotAI* ai) { return new ShieldOfRighteousnessAction(ai); }
static Action* beacon_of_light_on_main_tank(PlayerbotAI* ai) { return new CastBeaconOfLightOnMainTankAction(ai); }
static Action* sacred_shield_on_main_tank(PlayerbotAI* ai) { return new CastSacredShieldOnMainTankAction(ai); }
static Action* avenging_wrath(PlayerbotAI* ai) { return new CastAvengingWrathAction(ai); }
static Action* divine_illumination(PlayerbotAI* ai) { return new CastDivineIlluminationAction(ai); }
static Action* divine_sacrifice(PlayerbotAI* ai) { return new CastDivineSacrificeAction(ai); }
static Action* cancel_divine_sacrifice(PlayerbotAI* ai) { return new CastCancelDivineSacrificeAction(ai); }
static Action* hand_of_freedom_on_party(PlayerbotAI* ai) { return new CastHandOfFreedomOnPartyAction(ai); }
static Action* divine_plea(PlayerbotAI* botAI) { return new CastDivinePleaAction(botAI); }
static Action* shield_of_righteousness(PlayerbotAI* botAI) { return new ShieldOfRighteousnessAction(botAI); }
static Action* beacon_of_light_on_main_tank(PlayerbotAI* botAI) { return new CastBeaconOfLightOnMainTankAction(botAI); }
static Action* sacred_shield_on_main_tank(PlayerbotAI* botAI) { return new CastSacredShieldOnMainTankAction(botAI); }
static Action* avenging_wrath(PlayerbotAI* botAI) { return new CastAvengingWrathAction(botAI); }
static Action* divine_illumination(PlayerbotAI* botAI) { return new CastDivineIlluminationAction(botAI); }
static Action* divine_sacrifice(PlayerbotAI* botAI) { return new CastDivineSacrificeAction(botAI); }
static Action* cancel_divine_sacrifice(PlayerbotAI* botAI) { return new CastCancelDivineSacrificeAction(botAI); }
static Action* hand_of_freedom_on_party(PlayerbotAI* botAI) { return new CastHandOfFreedomOnPartyAction(botAI); }
static Action* cast_greater_blessing_assignment(PlayerbotAI* botAI)
{
return new CastGreaterBlessingAssignmentAction(botAI);
}
};
class PaladinValueContextInternal : public NamedObjectContext<UntypedValue>
{
public:
PaladinValueContextInternal()
{
creators["greater blessing assignments"] = &PaladinValueContextInternal::greater_blessing_assignments;
creators["greater blessing pending assignment"] =
&PaladinValueContextInternal::greater_blessing_pending_assignment;
}
private:
static UntypedValue* greater_blessing_assignments(PlayerbotAI* botAI)
{
return ai::gbless::greater_blessing_assignments_value(botAI);
}
static UntypedValue* greater_blessing_pending_assignment(PlayerbotAI* botAI)
{
return ai::gbless::greater_blessing_pending_assignment_value(botAI);
}
};
SharedNamedObjectContextList<Strategy> PaladinAiObjectContext::sharedStrategyContexts;
@ -467,4 +501,5 @@ void PaladinAiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContext
void PaladinAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
{
AiObjectContext::BuildSharedValueContexts(valueContexts);
valueContexts.Add(new PaladinValueContextInternal());
}

View File

@ -18,6 +18,8 @@ static constexpr uint32 SPELL_HAND_OF_PROTECTION = 1022;
static constexpr uint32 SPELL_HAND_OF_SALVATION = 1038;
static constexpr uint32 SPELL_HAND_OF_FREEDOM = 1044;
static constexpr uint32 SPELL_HAND_OF_SACRIFICE = 6940;
static constexpr uint32 SPELL_BLESSING_OF_SANCTUARY = 20911;
static constexpr uint32 SPELL_GREATER_BLESSING_OF_SANCTUARY = 25899;
inline bool HasHandFromCaster(Unit* target, Player* caster, std::initializer_list<uint32> spellIds)
{

View File

@ -5,10 +5,11 @@
#include "PaladinTriggers.h"
#include "GenericBuffUtils.h"
#include "PaladinGreaterBlessingAction.h"
#include "PaladinActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "PaladinHelper.h"
#include "Playerbots.h"
bool SealTrigger::IsActive()
{
@ -28,8 +29,9 @@ bool CrusaderAuraTrigger::IsActive()
bool BlessingTrigger::IsActive()
{
Unit* target = GetTarget();
return SpellTrigger::IsActive() && !botAI->HasAnyAuraOf(target, "blessing of might", "blessing of wisdom",
"blessing of kings", "blessing of sanctuary", nullptr);
return SpellTrigger::IsActive() &&
!botAI->HasAnyAuraOf(target, "blessing of might", "blessing of wisdom",
"blessing of kings", "blessing of sanctuary", nullptr);
}
bool DivineShieldLowHealthTrigger::IsActive()
@ -62,7 +64,8 @@ bool HandOfFreedomOnPartyTrigger::IsActive()
if (!target)
return false;
if (target != bot && bot->GetExactDist2dSq(target->GetPositionX(), target->GetPositionY()) > 30.0f * 30.0f)
if (target != bot &&
bot->GetExactDist2dSq(target->GetPositionX(), target->GetPositionY()) > 30.0f * 30.0f)
return false;
if (!botAI->CanCastSpell("hand of freedom", target))
@ -75,3 +78,29 @@ bool NotSensingUndeadTrigger::IsActive()
{
return !botAI->HasAura("sense undead", bot);
}
bool GreaterBlessingNeededTrigger::IsActive()
{
if (!ai::gbless::IsEligibleGroupForAutoBlessings(bot->GetGroup()))
return false;
if (ai::buff::ShouldDeferGreaterBlessingAssignmentForRecentLogin(bot))
return false;
Group* group = bot->GetGroup();
uint32 const groupKey = group ? group->GetLeaderGUID().GetCounter() : 0;
Value<ai::gbless::CachedPendingBlessingAssignment>* pendingValue =
context->GetValue<ai::gbless::CachedPendingBlessingAssignment>("greater blessing pending assignment");
if (!pendingValue)
return false;
ai::gbless::CachedPendingBlessingAssignment pendingAssignment = pendingValue->Get();
if (pendingAssignment.groupKey != groupKey)
{
pendingValue->Reset();
pendingAssignment = pendingValue->Get();
}
return pendingAssignment.valid && pendingAssignment.groupKey == groupKey;
}

View File

@ -13,32 +13,6 @@
class PlayerbotAI;
inline std::string const GetActualBlessingOfMight(Unit* target)
{
switch (target->getClass())
{
case CLASS_MAGE:
case CLASS_PRIEST:
case CLASS_WARLOCK:
return "blessing of wisdom";
}
return "blessing of might";
}
inline std::string const GetActualBlessingOfWisdom(Unit* target)
{
switch (target->getClass())
{
case CLASS_WARRIOR:
case CLASS_ROGUE:
case CLASS_DEATH_KNIGHT:
return "blessing of might";
}
return "blessing of wisdom";
}
BUFF_TRIGGER(HolyShieldTrigger, "holy shield");
BUFF_TRIGGER(RighteousFuryTrigger, "righteous fury");
@ -212,42 +186,55 @@ DEBUFF_TRIGGER(AvengerShieldTrigger, "avenger's shield");
class BeaconOfLightOnMainTankTrigger : public BuffOnMainTankTrigger
{
public:
BeaconOfLightOnMainTankTrigger(PlayerbotAI* ai)
: BuffOnMainTankTrigger(ai, "beacon of light", true) {}
BeaconOfLightOnMainTankTrigger(PlayerbotAI* botAI)
: BuffOnMainTankTrigger(botAI, "beacon of light", true) {}
};
class SacredShieldOnMainTankTrigger : public BuffOnMainTankTrigger
{
public:
SacredShieldOnMainTankTrigger(PlayerbotAI* ai) : BuffOnMainTankTrigger(ai, "sacred shield", false) {}
SacredShieldOnMainTankTrigger(PlayerbotAI* botAI)
: BuffOnMainTankTrigger(botAI, "sacred shield", false) {}
};
class BlessingOfKingsOnPartyTrigger : public BuffOnPartyTrigger
class BlessingOfKingsOnPartyTrigger : public BlessingOnPartyTrigger
{
public:
BlessingOfKingsOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of kings", 2 * 2000) {}
: BlessingOnPartyTrigger(botAI)
{
spell = "blessing of kings";
}
};
class BlessingOfWisdomOnPartyTrigger : public BuffOnPartyTrigger
class BlessingOfWisdomOnPartyTrigger : public BlessingOnPartyTrigger
{
public:
BlessingOfWisdomOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {}
: BlessingOnPartyTrigger(botAI)
{
spell = "blessing of might,blessing of wisdom";
}
};
class BlessingOfMightOnPartyTrigger : public BuffOnPartyTrigger
class BlessingOfMightOnPartyTrigger : public BlessingOnPartyTrigger
{
public:
BlessingOfMightOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {}
: BlessingOnPartyTrigger(botAI)
{
spell = "blessing of might,blessing of wisdom";
}
};
class BlessingOfSanctuaryOnPartyTrigger : public BuffOnPartyTrigger
class BlessingOfSanctuaryOnPartyTrigger : public BlessingOnPartyTrigger
{
public:
BlessingOfSanctuaryOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000) {}
: BlessingOnPartyTrigger(botAI)
{
spell = "blessing of sanctuary";
}
};
class HandOfFreedomOnPartyTrigger : public Trigger
@ -266,4 +253,13 @@ public:
AvengingWrathTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "avenging wrath") {}
};
class GreaterBlessingNeededTrigger : public Trigger
{
public:
GreaterBlessingNeededTrigger(PlayerbotAI* botAI)
: Trigger(botAI, "greater blessing needed", 4) {}
bool IsActive() override;
};
#endif

View File

@ -30,4 +30,7 @@ void GenericPaladinNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", ACTION_IDLE + 1.0f) }));
if (specTab == PALADIN_TAB_PROTECTION || specTab == PALADIN_TAB_RETRIBUTION)
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", ACTION_IDLE + 1.0f) }));
triggers.push_back(new TriggerNode("greater blessing needed",
{ NextAction("cast greater blessing assignment", ACTION_NORMAL) }));
}

View File

@ -16,7 +16,7 @@ public:
PaladinBuffManaStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "bmana"; }
std::string const getName() override { return "bwisdom"; }
};
class PaladinBuffHealthStrategy : public Strategy
@ -25,7 +25,7 @@ public:
PaladinBuffHealthStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "bhealth"; }
std::string const getName() override { return "bsanc"; }
};
class PaladinBuffDpsStrategy : public Strategy
@ -34,7 +34,7 @@ public:
PaladinBuffDpsStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "bdps"; }
std::string const getName() override { return "bmight"; }
};
class PaladinBuffArmorStrategy : public Strategy
@ -88,7 +88,7 @@ public:
PaladinBuffStatsStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "bstats"; }
std::string const getName() override { return "bkings"; }
};
class PaladinShadowResistanceStrategy : public Strategy

View File

@ -95,13 +95,14 @@ void TankPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
}
)
);
triggers.push_back(new TriggerNode(
"light aoe",
{
NextAction("avenger's shield", ACTION_HIGH + 5)
}
)
);
triggers.push_back(
new TriggerNode(
"light aoe",
{
NextAction("avenger's shield", ACTION_HIGH + 5)
}
)
);
triggers.push_back(
new TriggerNode(
"medium aoe",
@ -122,13 +123,6 @@ void TankPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(
new TriggerNode(
"medium health",
{ NextAction("holy shield", ACTION_HIGH + 4)
}
)
);
triggers.push_back(
new TriggerNode(
"low health",
{
NextAction("holy shield", ACTION_HIGH + 4)
}
@ -136,20 +130,12 @@ void TankPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
);
triggers.push_back(
new TriggerNode(
"critical health",
"avenging wrath",
{
NextAction("holy shield", ACTION_HIGH + 4)
NextAction("avenging wrath", ACTION_HIGH + 2)
}
)
);
triggers.push_back(
new TriggerNode(
"avenging wrath",
{
NextAction("avenging wrath", ACTION_HIGH + 2)
}
)
);
triggers.push_back(
new TriggerNode(
"target critical health",

View File

@ -13,9 +13,19 @@
class PlayerbotAI;
// disc
BUFF_ACTION(CastPowerWordFortitudeAction, "power word: fortitude");
BUFF_PARTY_ACTION(CastPowerWordFortitudeOnPartyAction, "power word: fortitude");
BUFF_PARTY_ACTION(CastPrayerOfFortitudeOnPartyAction, "prayer of fortitude");
class CastPowerWordFortitudeAction : public GroupBuffSpellAction
{
public:
CastPowerWordFortitudeAction(PlayerbotAI* botAI)
: GroupBuffSpellAction(botAI, "power word: fortitude") {}
};
class CastPowerWordFortitudeOnPartyAction : public GroupBuffOnPartyAction
{
public:
CastPowerWordFortitudeOnPartyAction(PlayerbotAI* botAI)
: GroupBuffOnPartyAction(botAI, "power word: fortitude") {}
};
BUFF_ACTION(CastPowerWordShieldAction, "power word: shield");
BUFF_ACTION(CastInnerFireAction, "inner fire");
@ -26,9 +36,19 @@ CC_ACTION(CastShackleUndeadAction, "shackle undead");
SPELL_ACTION_U(CastManaBurnAction, "mana burn",
AI_VALUE2(uint8, "mana", "self target") < 50 && AI_VALUE2(uint8, "mana", "current target") >= 20);
BUFF_ACTION(CastLevitateAction, "levitate");
BUFF_ACTION(CastDivineSpiritAction, "divine spirit");
BUFF_PARTY_ACTION(CastDivineSpiritOnPartyAction, "divine spirit");
BUFF_PARTY_ACTION(CastPrayerOfSpiritOnPartyAction, "prayer of spirit");
class CastDivineSpiritAction : public GroupBuffSpellAction
{
public:
CastDivineSpiritAction(PlayerbotAI* botAI)
: GroupBuffSpellAction(botAI, "divine spirit") {}
};
class CastDivineSpiritOnPartyAction : public GroupBuffOnPartyAction
{
public:
CastDivineSpiritOnPartyAction(PlayerbotAI* botAI)
: GroupBuffOnPartyAction(botAI, "divine spirit") {}
};
// disc 2.4.3
SPELL_ACTION(CastMassDispelAction, "mass dispel");
@ -103,13 +123,23 @@ SPELL_ACTION(CastMindBlastAction, "mind blast");
SPELL_ACTION(CastPsychicScreamAction, "psychic scream");
DEBUFF_ACTION(CastMindSootheAction, "mind soothe");
BUFF_ACTION_U(CastFadeAction, "fade", bot->GetGroup());
BUFF_ACTION(CastShadowProtectionAction, "shadow protection");
BUFF_PARTY_ACTION(CastShadowProtectionOnPartyAction, "shadow protection");
BUFF_PARTY_ACTION(CastPrayerOfShadowProtectionAction, "prayer of shadow protection");
class CastShadowProtectionAction : public GroupBuffSpellAction
{
public:
CastShadowProtectionAction(PlayerbotAI* botAI)
: GroupBuffSpellAction(botAI, "shadow protection") {}
};
class CastShadowProtectionOnPartyAction : public GroupBuffOnPartyAction
{
public:
CastShadowProtectionOnPartyAction(PlayerbotAI* botAI)
: GroupBuffOnPartyAction(botAI, "shadow protection") {}
};
// shadow talents
SPELL_ACTION(CastMindFlayAction, "mind flay");
DEBUFF_ACTION(CastVampiricEmbraceAction, "vampiric embrace");
BUFF_ACTION(CastVampiricEmbraceAction, "vampiric embrace");
BUFF_ACTION(CastShadowformAction, "shadowform");
SPELL_ACTION(CastSilenceAction, "silence");
ENEMY_HEALER_ACTION(CastSilenceOnEnemyHealerAction, "silence");

View File

@ -92,8 +92,6 @@ public:
creators["shadow protection"] = &PriestTriggerFactoryInternal::shadow_protection;
creators["shadow protection on party"] = &PriestTriggerFactoryInternal::shadow_protection_on_party;
creators["shackle undead"] = &PriestTriggerFactoryInternal::shackle_undead;
creators["prayer of fortitude on party"] = &PriestTriggerFactoryInternal::prayer_of_fortitude_on_party;
creators["prayer of spirit on party"] = &PriestTriggerFactoryInternal::prayer_of_spirit_on_party;
creators["holy fire"] = &PriestTriggerFactoryInternal::holy_fire;
creators["touch of weakness"] = &PriestTriggerFactoryInternal::touch_of_weakness;
creators["hex of weakness"] = &PriestTriggerFactoryInternal::hex_of_weakness;
@ -136,8 +134,6 @@ private:
static Trigger* shadow_protection_on_party(PlayerbotAI* botAI) { return new ShadowProtectionOnPartyTrigger(botAI); }
static Trigger* shadow_protection(PlayerbotAI* botAI) { return new ShadowProtectionTrigger(botAI); }
static Trigger* shackle_undead(PlayerbotAI* botAI) { return new ShackleUndeadTrigger(botAI); }
static Trigger* prayer_of_fortitude_on_party(PlayerbotAI* botAI) { return new PrayerOfFortitudeTrigger(botAI); }
static Trigger* prayer_of_spirit_on_party(PlayerbotAI* botAI) { return new PrayerOfSpiritTrigger(botAI); }
static Trigger* feedback(PlayerbotAI* botAI) { return new FeedbackTrigger(botAI); }
static Trigger* fear_ward(PlayerbotAI* botAI) { return new FearWardTrigger(botAI); }
static Trigger* shadowguard(PlayerbotAI* botAI) { return new ShadowguardTrigger(botAI); }
@ -207,8 +203,6 @@ public:
creators["shadow protection"] = &PriestAiObjectContextInternal::shadow_protection;
creators["shadow protection on party"] = &PriestAiObjectContextInternal::shadow_protection_on_party;
creators["shackle undead"] = &PriestAiObjectContextInternal::shackle_undead;
creators["prayer of fortitude on party"] = &PriestAiObjectContextInternal::prayer_of_fortitude_on_party;
creators["prayer of spirit on party"] = &PriestAiObjectContextInternal::prayer_of_spirit_on_party;
creators["power infusion on party"] = &PriestAiObjectContextInternal::power_infusion_on_party;
creators["silence"] = &PriestAiObjectContextInternal::silence;
creators["silence on enemy healer"] = &PriestAiObjectContextInternal::silence_on_enemy_healer;
@ -311,11 +305,6 @@ private:
static Action* fade(PlayerbotAI* botAI) { return new CastFadeAction(botAI); }
static Action* inner_fire(PlayerbotAI* botAI) { return new CastInnerFireAction(botAI); }
static Action* shackle_undead(PlayerbotAI* botAI) { return new CastShackleUndeadAction(botAI); }
static Action* prayer_of_spirit_on_party(PlayerbotAI* botAI) { return new CastPrayerOfSpiritOnPartyAction(botAI); }
static Action* prayer_of_fortitude_on_party(PlayerbotAI* botAI)
{
return new CastPrayerOfFortitudeOnPartyAction(botAI);
}
static Action* feedback(PlayerbotAI* botAI) { return new CastFeedbackAction(botAI); }
static Action* elunes_grace(PlayerbotAI* botAI) { return new CastElunesGraceAction(botAI); }
static Action* starshards(PlayerbotAI* botAI) { return new CastStarshardsAction(botAI); }

View File

@ -8,10 +8,9 @@
#include "Player.h"
#include "Playerbots.h"
bool PowerWordFortitudeOnPartyTrigger::IsActive()
bool ShadowProtectionTrigger::IsActive()
{
return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("power word : fortitude", GetTarget()) &&
!botAI->HasAura("prayer of fortitude", GetTarget());
return BuffTrigger::IsActive() && !botAI->HasAura("prayer of shadow protection", GetTarget());
}
bool PowerWordFortitudeTrigger::IsActive()
@ -20,43 +19,12 @@ bool PowerWordFortitudeTrigger::IsActive()
!botAI->HasAura("prayer of fortitude", GetTarget());
}
bool DivineSpiritOnPartyTrigger::IsActive()
{
return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("divine spirit", GetTarget()) &&
!botAI->HasAura("prayer of spirit", GetTarget());
}
bool DivineSpiritTrigger::IsActive()
{
return BuffTrigger::IsActive() && !botAI->HasAura("divine spirit", GetTarget()) &&
!botAI->HasAura("prayer of spirit", GetTarget());
}
bool PrayerOfFortitudeTrigger::IsActive()
{
Unit* target = GetTarget();
if (!target || !target->IsPlayer())
return false;
return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("prayer of fortitude", GetTarget()) &&
botAI->GetBot()->IsInSameGroupWith((Player*)GetTarget()) &&
botAI->GetBuffedCount((Player*)GetTarget(), "prayer of fortitude") < 4 &&
!botAI->GetBuffedCount((Player*)GetTarget(), "power word: fortitude");
}
bool PrayerOfSpiritTrigger::IsActive()
{
Unit* target = GetTarget();
if (!target || !target->IsPlayer())
return false;
return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("prayer of spirit", GetTarget()) &&
botAI->GetBot()->IsInSameGroupWith((Player*)GetTarget()) &&
// botAI->GetManaPercent() > 50 &&
botAI->GetBuffedCount((Player*)GetTarget(), "prayer of spirit") < 4 &&
!botAI->GetBuffedCount((Player*)GetTarget(), "divine spirit");
}
bool InnerFireTrigger::IsActive()
{
Unit* target = GetTarget();

View File

@ -27,8 +27,6 @@ BUFF_TRIGGER_A(InnerFireTrigger, "inner fire");
BUFF_TRIGGER_A(ShadowformTrigger, "shadowform");
BOOST_TRIGGER(PowerInfusionTrigger, "power infusion");
BUFF_TRIGGER(InnerFocusTrigger, "inner focus");
BUFF_TRIGGER(ShadowProtectionTrigger, "shadow protection");
BUFF_PARTY_TRIGGER(ShadowProtectionOnPartyTrigger, "shadow protection");
CC_TRIGGER(ShackleUndeadTrigger, "shackle undead");
INTERRUPT_TRIGGER(SilenceTrigger, "silence");
INTERRUPT_HEALER_TRIGGER(SilenceEnemyHealerTrigger, "silence");
@ -44,20 +42,34 @@ SNARE_TRIGGER(ChastiseTrigger, "chastise");
BOOST_TRIGGER_A(ShadowfiendTrigger, "shadowfiend");
class ShadowProtectionTrigger : public BuffTrigger
{
public:
ShadowProtectionTrigger(PlayerbotAI* botAI)
: BuffTrigger(botAI, "shadow protection", 4 * 2000) {}
bool IsActive() override;
};
class ShadowProtectionOnPartyTrigger : public BuffOnPartyTrigger
{
public:
ShadowProtectionOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "shadow protection", 4 * 2000) {}
};
class PowerWordFortitudeOnPartyTrigger : public BuffOnPartyTrigger
{
public:
PowerWordFortitudeOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "power word: fortitude", 4 * 2000)
{
}
bool IsActive() override;
PowerWordFortitudeOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "power word: fortitude", 4 * 2000) {}
};
class PowerWordFortitudeTrigger : public BuffTrigger
{
public:
PowerWordFortitudeTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "power word: fortitude", 4 * 2000) {}
PowerWordFortitudeTrigger(PlayerbotAI* botAI)
: BuffTrigger(botAI, "power word: fortitude", 4 * 2000) {}
bool IsActive() override;
};
@ -65,31 +77,15 @@ public:
class DivineSpiritOnPartyTrigger : public BuffOnPartyTrigger
{
public:
DivineSpiritOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "divine spirit", 4 * 2000) {}
bool IsActive() override;
DivineSpiritOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "divine spirit", 4 * 2000) {}
};
class DivineSpiritTrigger : public BuffTrigger
{
public:
DivineSpiritTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "divine spirit", 4 * 2000) {}
bool IsActive() override;
};
class PrayerOfFortitudeTrigger : public BuffOnPartyTrigger
{
public:
PrayerOfFortitudeTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "prayer of fortitude", 3 * 2000) {}
bool IsActive() override;
};
class PrayerOfSpiritTrigger : public BuffOnPartyTrigger
{
public:
PrayerOfSpiritTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "prayer of spirit", 2 * 2000) {}
DivineSpiritTrigger(PlayerbotAI* botAI)
: BuffTrigger(botAI, "divine spirit", 4 * 2000) {}
bool IsActive() override;
};
@ -106,9 +102,7 @@ class MindSearChannelCheckTrigger : public Trigger
{
public:
MindSearChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2)
: Trigger(botAI, "mind sear channel check"), minEnemies(minEnemies)
{
}
: Trigger(botAI, "mind sear channel check"), minEnemies(minEnemies) {}
bool IsActive() override;

View File

@ -19,6 +19,8 @@ void PriestNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(
new TriggerNode("inner fire",{ NextAction("inner fire", 10.0f) }));
triggers.push_back(
new TriggerNode("vampiric embrace", { NextAction("vampiric embrace", 16.0f) }));
triggers.push_back(new TriggerNode(
"party member dead",{ NextAction("remove shadowform", ACTION_CRITICAL_HEAL + 11),
NextAction("resurrection", ACTION_CRITICAL_HEAL + 10) }));
@ -54,12 +56,6 @@ void PriestBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("prayer of fortitude on party",
{ NextAction("prayer of fortitude on party", 12.0f) }));
triggers.push_back(
new TriggerNode("prayer of spirit on party",
{ NextAction("prayer of spirit on party", 14.0f) }));
triggers.push_back(
new TriggerNode("power word: fortitude on party",
{ NextAction("power word: fortitude on party", 11.0f) }));

View File

@ -30,8 +30,6 @@ public:
creators["flash heal"] = &flash_heal;
creators["flash heal on party"] = &flash_heal_on_party;
creators["circle of healing on party"] = &circle_of_healing;
creators["prayer of fortitude on party"] = &prayer_of_fortitude_on_party;
creators["prayer of spirit on party"] = &prayer_of_spirit_on_party;
}
private:
@ -134,20 +132,6 @@ private:
/*A*/ {},
/*C*/ {});
}
static ActionNode* prayer_of_fortitude_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("prayer of fortitude on party",
/*P*/ { NextAction("remove shadowform") },
/*A*/ { NextAction("power word: fortitude on party") },
/*C*/ {});
}
static ActionNode* prayer_of_spirit_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("prayer of spirit on party",
/*P*/ { NextAction("remove shadowform") },
/*A*/ { NextAction("divine spirit on party") },
/*C*/ {});
}
};
#endif

View File

@ -51,14 +51,6 @@ void ShadowPriestStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
}
)
);
triggers.push_back(
new TriggerNode(
"vampiric embrace",
{
NextAction("vampiric embrace", 16.0f)
}
)
);
triggers.push_back(
new TriggerNode(
"silence",

View File

@ -161,9 +161,7 @@ void ShamanAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
}
else if (tab == SHAMAN_TAB_ENHANCEMENT)
{
triggers.push_back(new TriggerNode("medium aoe",{ NextAction("magma totem", 24.0f),
NextAction("fire nova", 23.0f), }));
triggers.push_back(new TriggerNode("medium aoe",{ NextAction("fire nova", 23.0f), }));
triggers.push_back(new TriggerNode("maelstrom weapon 5 and medium aoe", { NextAction("chain lightning", 22.0f), }));
triggers.push_back(new TriggerNode("maelstrom weapon 4 and medium aoe", { NextAction("chain lightning", 21.0f), }));
triggers.push_back(new TriggerNode("enemy within melee", { NextAction("fire nova", 5.1f), }));

View File

@ -112,6 +112,7 @@ std::vector<NextAction> ArmsWarriorStrategy::getDefaultActions()
return {
NextAction("bladestorm", ACTION_DEFAULT + 0.2f),
NextAction("mortal strike", ACTION_DEFAULT + 0.1f),
NextAction("sunder armor", ACTION_DEFAULT + 0.05f),
NextAction("melee", ACTION_DEFAULT)
};
}

View File

@ -5,6 +5,7 @@
#include "WarriorActions.h"
#include "AiFactory.h"
#include "Playerbots.h"
bool CastBerserkerRageAction::isPossible()
@ -36,6 +37,27 @@ bool CastBerserkerRageAction::isUseful()
bool CastSunderArmorAction::isUseful()
{
Group* group = bot->GetGroup();
if (!group)
return false;
if (!botAI->IsTank(bot, false))
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || member == bot || !member->IsAlive() || !member->IsInWorld() ||
member->GetMapId() != bot->GetMapId())
{
continue;
}
if (member->getClass() == CLASS_WARRIOR &&
botAI->IsTank(member, false))
return false;
}
}
Aura* aura = botAI->GetAura("sunder armor", GetTarget(), false, true);
return !aura || aura->GetStackAmount() < 5 || aura->GetDuration() <= 6000;
}

View File

@ -3,7 +3,7 @@
#include "AiObjectContext.h"
#include "Action.h"
#include "AuchenaiCryptsActions.h"
#include "ACActions.h"
class TbcDungeonAuchenaiCryptsActionContext : public NamedObjectContext<Action>
{

View File

@ -1,7 +1,7 @@
#include "Playerbots.h"
#include "AiFactory.h"
#include "AuchenaiCryptsTriggers.h"
#include "AuchenaiCryptsActions.h"
#include "ACTriggers.h"
#include "ACActions.h"
// Shirrak the Dead Watcher

View File

@ -3,7 +3,7 @@
#include "AttackAction.h"
#include "MovementActions.h"
#include "AuchenaiCryptsTriggers.h"
#include "ACTriggers.h"
// Shirrak the Dead Watcher

View File

@ -1,6 +1,6 @@
#include "AuchenaiCryptsMultipliers.h"
#include "AuchenaiCryptsActions.h"
#include "AuchenaiCryptsTriggers.h"
#include "ACMultipliers.h"
#include "ACActions.h"
#include "ACTriggers.h"
#include "MovementActions.h"
#include "ReachTargetActions.h"
#include "FollowActions.h"

View File

@ -1,6 +1,6 @@
#include "AuchenaiCryptsTriggers.h"
#include "AuchenaiCryptsStrategy.h"
#include "AuchenaiCryptsMultipliers.h"
#include "ACTriggers.h"
#include "ACStrategy.h"
#include "ACMultipliers.h"
void TbcDungeonAuchenaiCryptsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{

View File

@ -3,7 +3,7 @@
#include "AiObjectContext.h"
#include "TriggerContext.h"
#include "AuchenaiCryptsTriggers.h"
#include "ACTriggers.h"
class TbcDungeonAuchenaiCryptsTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -1,5 +1,5 @@
#include "Playerbots.h"
#include "AuchenaiCryptsTriggers.h"
#include "ACTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"

View File

@ -3,7 +3,7 @@
#include "Action.h"
#include "NamedObjectContext.h"
#include "OldKingdomActions.h"
#include "AKActions.h"
class WotlkDungeonOKActionContext : public NamedObjectContext<Action>
{

View File

@ -1,5 +1,5 @@
#include "Playerbots.h"
#include "OldKingdomActions.h"
#include "AKActions.h"
bool AttackNadoxGuardianAction::Execute(Event /*event*/)
{

View File

@ -5,7 +5,7 @@
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "OldKingdomTriggers.h"
#include "AKTriggers.h"
class AttackNadoxGuardianAction : public AttackAction
{

View File

@ -1,9 +1,9 @@
#include "OldKingdomMultipliers.h"
#include "OldKingdomActions.h"
#include "AKMultipliers.h"
#include "AKActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "OldKingdomTriggers.h"
#include "AKTriggers.h"
#include "Action.h"
float ElderNadoxMultiplier::GetValue(Action* action)

View File

@ -1,5 +1,5 @@
#include "OldKingdomStrategy.h"
#include "OldKingdomMultipliers.h"
#include "AKStrategy.h"
#include "AKMultipliers.h"
void WotlkDungeonOKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{

View File

@ -3,7 +3,7 @@
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "OldKingdomTriggers.h"
#include "AKTriggers.h"
class WotlkDungeonOKTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -1,5 +1,5 @@
#include "Playerbots.h"
#include "OldKingdomTriggers.h"
#include "AKTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"

View File

@ -3,7 +3,7 @@
#include "Action.h"
#include "NamedObjectContext.h"
#include "AzjolNerubActions.h"
#include "ANActions.h"
class WotlkDungeonANActionContext : public NamedObjectContext<Action>
{

View File

@ -1,5 +1,5 @@
#include "Playerbots.h"
#include "AzjolNerubActions.h"
#include "ANActions.h"
bool AttackWebWrapAction::isUseful() { return !botAI->IsHeal(bot); }
bool AttackWebWrapAction::Execute(Event /*event*/)

View File

@ -5,7 +5,7 @@
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "AzjolNerubTriggers.h"
#include "ANTriggers.h"
class AttackWebWrapAction : public AttackAction
{

View File

@ -1,9 +1,9 @@
#include "AzjolNerubMultipliers.h"
#include "AzjolNerubActions.h"
#include "ANMultipliers.h"
#include "ANActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "AzjolNerubTriggers.h"
#include "ANTriggers.h"
#include "Action.h"
float KrikthirMultiplier::GetValue(Action* action)

View File

@ -1,5 +1,5 @@
#include "AzjolNerubStrategy.h"
#include "AzjolNerubMultipliers.h"
#include "ANStrategy.h"
#include "ANMultipliers.h"
void WotlkDungeonANStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{

View File

@ -3,7 +3,7 @@
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "AzjolNerubTriggers.h"
#include "ANTriggers.h"
class WotlkDungeonANTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -1,5 +1,5 @@
#include "Playerbots.h"
#include "AzjolNerubTriggers.h"
#include "ANTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"

View File

@ -3,7 +3,7 @@
#include "Action.h"
#include "NamedObjectContext.h"
#include "CullingOfStratholmeActions.h"
#include "CoSActions.h"
class WotlkDungeonCoSActionContext : public NamedObjectContext<Action>
{

View File

@ -1,5 +1,5 @@
#include "Playerbots.h"
#include "CullingOfStratholmeActions.h"
#include "CoSActions.h"
bool ExplodeGhoulSpreadAction::Execute(Event /*event*/)
{

View File

@ -6,7 +6,7 @@
#include "GenericSpellActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "CullingOfStratholmeTriggers.h"
#include "CoSTriggers.h"
class ExplodeGhoulSpreadAction : public MovementAction
{

View File

@ -1,9 +1,9 @@
#include "CullingOfStratholmeMultipliers.h"
#include "CullingOfStratholmeActions.h"
#include "CoSMultipliers.h"
#include "CoSActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "CullingOfStratholmeTriggers.h"
#include "CoSTriggers.h"
#include "Action.h"
float EpochMultiplier::GetValue(Action* action)

Some files were not shown because too many files have changed in this diff Show More