Compare commits

..

No commits in common. "62ef4b63b1d8f66eccfc920a3b0c19649ec8b757" and "1041b0b0705e3347f766ce22f5799ca03836ccae" have entirely different histories.

450 changed files with 2050 additions and 3800 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +0,0 @@
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();
AutoTeleportForLevel();
AutoUpgradeEquip();
AutoTeleportForLevel();
return true;
}
@ -21,11 +21,13 @@ bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/)
void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel()
{
if (!sPlayerbotAIConfig.autoTeleportForLevel || !sRandomPlayerbotMgr.IsRandomBot(bot))
{
return;
}
if (botAI->HasRealPlayerMaster())
{
return;
}
sRandomPlayerbotMgr.RandomTeleportForLevel(bot);
return;
}
@ -87,17 +89,21 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
{
Quest const* quest = i->second;
if (!quest->GetRequiredClasses() || quest->IsRepeatable() || quest->GetMinLevel() < 10 ||
quest->GetMinLevel() > bot->GetLevel())
{
// 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())
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)
@ -107,26 +113,31 @@ 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;
break; // pussywizard: break and not cast the spell (found is false)
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)
{
@ -156,11 +167,12 @@ std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* s
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
{
if (!sRandomPlayerbotMgr.IsRandomBot(bot))
if (!sPlayerbotAIConfig.autoUpgradeEquip || !sRandomPlayerbotMgr.IsRandomBot(bot))
return;
PlayerbotFactory factory(bot, bot->GetLevel());
// Clean up old consumables before adding new ones
factory.CleanupConsumables();
factory.InitAmmo();
@ -169,6 +181,9 @@ void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
factory.InitConsumables();
factory.InitPotions();
if (sPlayerbotAIConfig.autoUpgradeEquip)
factory.InitEquipment(true);
if (!sPlayerbotAIConfig.equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel)
{
if (sPlayerbotAIConfig.incrementalGearInit)
factory.InitEquipment(true);
}
}

View File

@ -154,11 +154,9 @@ void EquipAction::EquipItem(Item* item)
calculator.SetOverflowPenalty(false);
// Calculate item scores once and store them
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;
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;
// 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 || !spellInfo->IsAutocastable())
if (!spellInfo->IsAutocastable())
continue;
bool shouldApply = true;

View File

@ -6,7 +6,6 @@
#include "GenericSpellActions.h"
#include <ctime>
#include <unordered_set>
#include "Event.h"
#include "ItemTemplate.h"
@ -24,118 +23,10 @@
using ai::buff::MakeAuraQualifierForBuff;
using ai::spell::HasSpellOrCategoryCooldown;
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) {}
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell)
{
}
bool CastSpellAction::Execute(Event /*event*/)
{
@ -162,12 +53,18 @@ bool CastSpellAction::Execute(Event /*event*/)
wstrToLower(wnamepart);
if (!Utf8FitTo(spell, wnamepart) || spellInfo->Effects[0].Effect != SPELL_EFFECT_CREATE_ITEM)
if (!Utf8FitTo(spell, wnamepart))
continue;
if (spellInfo->Effects[0].Effect != SPELL_EFFECT_CREATE_ITEM)
continue;
uint32 itemId = spellInfo->Effects[0].ItemType;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto || bot->CanUseItem(proto) != EQUIP_ERR_OK)
if (!proto)
continue;
if (bot->CanUseItem(proto) != EQUIP_ERR_OK)
continue;
if (spellInfo->Id > castId)
@ -195,7 +92,10 @@ bool CastSpellAction::isUseful()
}
Unit* spellTarget = GetTarget();
if (!spellTarget || !spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId())
if (!spellTarget)
return false;
if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId())
return false;
// float combatReach = bot->GetCombatReach() + target->GetCombatReach();
@ -243,7 +143,10 @@ CastMeleeSpellAction::CastMeleeSpellAction(
bool CastMeleeSpellAction::isUseful()
{
Unit* target = GetTarget();
if (!target || !bot->IsWithinMeleeRange(target))
if (!target)
return false;
if (!bot->IsWithinMeleeRange(target))
return false;
return CastSpellAction::isUseful();
@ -259,7 +162,10 @@ CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(
bool CastMeleeDebuffSpellAction::isUseful()
{
Unit* target = GetTarget();
if (!target || !bot->IsWithinMeleeRange(target))
if (!target)
return false;
if (!bot->IsWithinMeleeRange(target))
return false;
return CastDebuffSpellAction::isUseful();
@ -269,55 +175,14 @@ bool CastAuraSpellAction::isUseful()
{
if (!GetTarget() || !CastSpellAction::isUseful())
return false;
Aura* aura = botAI->GetAura(spell, GetTarget(), isOwner, checkDuration);
if (!aura || (beforeDuration && aura->GetDuration() < beforeDuration))
if (!aura)
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))
if (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) {}
@ -383,16 +248,25 @@ 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)
{
@ -491,32 +365,50 @@ bool CastVehicleSpellAction::Execute(Event /*event*/)
bool CastEveryManForHimselfAction::isPossible()
{
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
return spellId && bot->HasSpell(spellId) && !HasSpellOrCategoryCooldown(bot, spellId);
if (!spellId)
return false;
if (!bot->HasSpell(spellId))
return false;
if (HasSpellOrCategoryCooldown(bot, spellId))
return false;
return true;
}
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);
return spellId && bot->HasSpell(spellId) && !HasSpellOrCategoryCooldown(bot, spellId);
if (!spellId)
return false;
if (!bot->HasSpell(spellId))
return false;
if (HasSpellOrCategoryCooldown(bot, spellId))
return false;
return true;
}
bool CastWillOfTheForsakenAction::isUseful()
{
return (bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
bot->HasAuraType(SPELL_AURA_MOD_CHARM) ||
bot->HasAuraType(SPELL_AURA_AOE_CHARM) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP))
&& CastSpellAction::isUseful();
bot->HasAuraType(SPELL_AURA_MOD_CHARM) ||
bot->HasAuraType(SPELL_AURA_AOE_CHARM) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP))
&& CastSpellAction::isUseful();
}
bool UseTrinketAction::Execute(Event /*event*/)
@ -535,141 +427,70 @@ bool UseTrinketAction::Execute(Event /*event*/)
bool UseTrinketAction::UseTrinket(Item* item)
{
if (bot->CanUseItem(item) != EQUIP_ERR_OK || bot->IsNonMeleeSpellCast(true))
if (bot->CanUseItem(item) != EQUIP_ERR_OK)
return false;
if (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;
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;
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;
applyAura = true;
break;
}
}
if (defensiveTankEffect)
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))
{
uint8 const healthPct = AI_VALUE2(uint8, "health", "self target");
if (healthPct > sPlayerbotAIConfig.lowHealth)
return false;
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;
}
@ -679,8 +500,9 @@ 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,7 +69,9 @@ 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:
@ -88,7 +90,9 @@ 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"; }
@ -100,7 +104,9 @@ 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"; }
@ -113,19 +119,6 @@ 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
@ -158,6 +151,8 @@ public:
// Yunfan: Mana efficiency tell the bot how to save mana. The higher the better.
HealingManaEfficiency manaEfficiency;
uint8 estAmount;
// protected:
};
class CastAoeHealSpellAction : public CastHealingSpellAction
@ -197,7 +192,9 @@ 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(); }
@ -222,7 +219,9 @@ 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(); }
@ -231,25 +230,18 @@ protected:
uint32 dispelType;
};
// Make Bots Paladin, druid, mage use the greater buff rank spell
class BuffOnPartyAction : public CastBuffSpellAction, public PartyMemberActionNameSupport
{
public:
BuffOnPartyAction(PlayerbotAI* botAI, std::string const spell)
: CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) {}
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) {}
: CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) { }
Value<Unit*>* GetTargetValue() override;
bool Execute(Event event) override;
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
};
// End Fix
class CastShootAction : public CastSpellAction
{
@ -331,13 +323,8 @@ 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
@ -474,11 +461,12 @@ 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,21 +44,6 @@ 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,23 +34,24 @@ 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("reset botAI", relevance) }));
new TriggerNode("group set leader", { /*NextAction("leader", relevance),*/ }));
triggers.push_back(new TriggerNode(
"not enough money", { NextAction("tell not enough money", relevance) }));
triggers.push_back(

View File

@ -7,7 +7,6 @@
#include <string>
#include "GenericBuffUtils.h"
#include "CreatureAI.h"
#include "ItemVisitors.h"
#include "LastSpellCastValue.h"
@ -42,50 +41,52 @@ 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; }
@ -100,8 +101,9 @@ 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;
}
@ -162,27 +164,19 @@ 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 || (beforeDuration && aura->GetDuration() < beforeDuration))
if (!aura)
return true;
if (beforeDuration && aura->GetDuration() < beforeDuration)
return true;
return false;
}
Value<Unit*>* BuffOnPartyTrigger::GetTargetValue()
{
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();
return context->GetValue<Unit*>("party member without aura", spell);
}
bool ProtectPartyMemberTrigger::IsActive() { return AI_VALUE(Unit*, "party member to protect"); }
@ -215,14 +209,13 @@ bool MediumThreatTrigger::IsActive()
{
if (!AI_VALUE(Unit*, "main tank"))
return false;
return MyAttackerCountTrigger::IsActive();
}
bool LowTankThreatTrigger::IsActive()
{
Unit* mainTank = AI_VALUE(Unit*, "main tank");
if (!mainTank)
Unit* mt = AI_VALUE(Unit*, "main tank");
if (!mt)
return false;
Unit* current_target = AI_VALUE(Unit*, "current target");
@ -231,7 +224,7 @@ bool LowTankThreatTrigger::IsActive()
ThreatManager& mgr = current_target->GetThreatMgr();
float threat = mgr.GetThreat(bot);
float tankThreat = mgr.GetThreat(mainTank);
float tankThreat = mgr.GetThreat(mt);
return tankThreat == 0.0f || threat > tankThreat * 0.5f;
}
@ -239,8 +232,9 @@ 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)
@ -248,9 +242,10 @@ 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;
}
@ -279,19 +274,20 @@ 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* creature = GetTarget()->ToCreature();
return creature && (creature->IsDungeonBoss() || creature->isWorldBoss());
}
Creature* c = GetTarget()->ToCreature();
return c && ((c->IsDungeonBoss()) || (c->isWorldBoss()));
}
bool SpellTrigger::IsActive() { return GetTarget(); }
@ -321,7 +317,9 @@ 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()
{
@ -332,7 +330,6 @@ bool RandomTrigger::IsActive()
int32 k = (int32)(probability / sPlayerbotAIConfig.randomChangeMultiplier);
if (k < 1)
k = 1;
return (rand() % k) == 0;
}
@ -371,11 +368,9 @@ 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;
}
@ -384,19 +379,20 @@ 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;
if (bot->GetAura(33891)) // Tree of Life
// special check for resto druid (dont remove tree of life frequently)
if (bot->GetAura(33891))
{
LastSpellCast& lastSpell = botAI->GetAiObjectContext()->GetValue<LastSpellCast&>("last spell cast")->Get();
if (lastSpell.timer + 5 > time(nullptr))
@ -405,6 +401,7 @@ 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)
@ -428,7 +425,13 @@ bool InterruptSpellTrigger::IsActive()
bool DeflectSpellTrigger::IsActive()
{
Unit* target = GetTarget();
if (!target || !target->IsNonMeleeSpellCast(true) || target->GetTarget() != bot->GetGUID())
if (!target)
return false;
if (!target->IsNonMeleeSpellCast(true))
return false;
if (target->GetTarget() != bot->GetGUID())
return false;
uint32 spellid = context->GetValue<uint32>("spell id", spell)->Get();
@ -459,7 +462,6 @@ bool DeflectSpellTrigger::IsActive()
return true;
}
}
return false;
}
@ -493,16 +495,17 @@ bool FearSleepSapTrigger::IsActive()
bool HasAuraStackTrigger::IsActive()
{
return botAI->GetAura(getName(), GetTarget(), false, true, stack);
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;
}
bool TimerTrigger::IsActive()
{
time_t now = time(nullptr);
if (now != lastCheck)
if (time(nullptr) != lastCheck)
{
lastCheck = now;
lastCheck = time(nullptr);
return true;
}
@ -549,8 +552,9 @@ 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");
}
@ -558,8 +562,9 @@ bool IsNotBehindTargetTrigger::IsActive()
bool IsNotFacingTargetTrigger::IsActive()
{
if (botAI->HasStrategy("stay", botAI->GetState()))
{
return false;
}
return !AI_VALUE2(bool, "facing", "current target");
}
@ -576,14 +581,12 @@ 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");
@ -601,6 +604,7 @@ 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;
@ -634,10 +638,7 @@ 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()
{
@ -717,24 +718,43 @@ 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 (Pet* pet = bot->GetPet())
// If bot has a pet, get its GUID
if (pet)
{
currentPetGuid = pet->GetGUID();
else if (Guardian* guardian = bot->GetGuardianPet())
currentPetGuid = guardian->GetGUID();
}
else
{
// If no pet, try to get a guardian pet and its GUID
guardian = bot->GetGuardianPet();
if (guardian)
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,7 +20,9 @@ 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;
@ -116,8 +118,8 @@ public:
class TargetWithComboPointsLowerHealTrigger : public ComboPointsAvailableTrigger
{
public:
TargetWithComboPointsLowerHealTrigger(PlayerbotAI* botAI, int32 combo_point = 5, float lifeTime = 8.0f)
: ComboPointsAvailableTrigger(botAI, combo_point), lifeTime(lifeTime)
TargetWithComboPointsLowerHealTrigger(PlayerbotAI* ai, int32 combo_point = 5, float lifeTime = 8.0f)
: ComboPointsAvailableTrigger(ai, combo_point), lifeTime(lifeTime)
{
}
bool IsActive() override;
@ -194,6 +196,7 @@ public:
bool IsActive() override;
};
// TODO: check other targets
class InterruptSpellTrigger : public SpellTrigger
{
public:
@ -214,7 +217,9 @@ 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"; }
@ -264,7 +269,9 @@ 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"; }
@ -310,8 +317,7 @@ 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;
@ -333,10 +339,11 @@ 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"; }
};
@ -386,7 +393,9 @@ 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;
@ -399,7 +408,9 @@ 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;
};
@ -408,7 +419,9 @@ 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"; }
@ -419,7 +432,9 @@ 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"; }
@ -429,7 +444,9 @@ 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;
@ -441,7 +458,9 @@ 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;
@ -453,7 +472,9 @@ class HealerShouldAttackTrigger : public Trigger
{
public:
HealerShouldAttackTrigger(PlayerbotAI* botAI)
: Trigger(botAI, "healer should attack", 1) {}
: Trigger(botAI, "healer should attack", 1)
{
}
bool IsActive() override;
};
@ -559,7 +580,7 @@ public:
class HasPetTrigger : public Trigger
{
public:
HasPetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "has pet", 5 * 1000) {}
HasPetTrigger(PlayerbotAI* ai) : Trigger(ai, "has pet", 5 * 1000) {}
virtual bool IsActive() override;
};
@ -567,7 +588,7 @@ public:
class PetAttackTrigger : public Trigger
{
public:
PetAttackTrigger(PlayerbotAI* botAI) : Trigger(botAI, "pet attack") {}
PetAttackTrigger(PlayerbotAI* ai) : Trigger(ai, "pet attack") {}
virtual bool IsActive() override;
};
@ -576,7 +597,9 @@ 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"; }
@ -590,7 +613,9 @@ 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;
};
@ -598,7 +623,9 @@ 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;
@ -607,8 +634,10 @@ public:
class HasAuraStackTrigger : public Trigger
{
public:
HasAuraStackTrigger(PlayerbotAI* botAI, std::string spell, int stack, int checkInterval = 1)
: Trigger(botAI, spell, checkInterval), stack(stack) {}
HasAuraStackTrigger(PlayerbotAI* ai, std::string spell, int stack, int checkInterval = 1)
: Trigger(ai, spell, checkInterval), stack(stack)
{
}
std::string const GetTargetName() override { return "self target"; }
bool IsActive() override;
@ -829,7 +858,9 @@ 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;
@ -846,7 +877,7 @@ public:
class ReturnToStayPositionTrigger : public Trigger
{
public:
ReturnToStayPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "return to stay position", 2) {}
ReturnToStayPositionTrigger(PlayerbotAI* ai) : Trigger(ai, "return to stay position", 2) {}
virtual bool IsActive() override;
};
@ -861,7 +892,9 @@ 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;
@ -929,7 +962,9 @@ 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();
@ -938,7 +973,7 @@ public:
class SelfResurrectTrigger : public Trigger
{
public:
SelfResurrectTrigger(PlayerbotAI* botAI) : Trigger(botAI, "can self resurrect") {}
SelfResurrectTrigger(PlayerbotAI* ai) : Trigger(ai, "can self resurrect") {}
bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); }
};
@ -946,7 +981,7 @@ public:
class NewPetTrigger : public Trigger
{
public:
NewPetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {}
NewPetTrigger(PlayerbotAI* ai) : Trigger(ai, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {}
bool IsActive() override;

View File

@ -4,89 +4,23 @@
*/
#include "GenericBuffUtils.h"
#include "AiObjectContext.h"
#include "GameTime.h"
#include "Group.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include <map>
#include "Player.h"
#include "Group.h"
#include "SpellMgr.h"
#include "Unit.h"
#include "Chat.h"
#include "PlayerbotAI.h"
#include "ServerFacade.h"
#include "AiObjectContext.h"
#include "Value.h"
#include "Config.h"
#include "PlayerbotTextMgr.h"
namespace ai::buff
{
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
@ -100,89 +34,27 @@ 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)
@ -200,33 +72,75 @@ namespace ai::buff
return false;
}
}
// No reagent required
return true;
}
return false;
}
std::string UpgradeToGroupIfAppropriate(
Player* bot, PlayerbotAI* botAI, std::string const& baseName)
Player* bot,
PlayerbotAI* botAI,
std::string const& baseName,
bool announceOnMissing,
std::function<void(std::string const&)> announce)
{
if (!IsGroupVariantEnabled(bot, baseName))
return 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
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()
if (std::string const groupName = GroupVariantFor(baseName); !groupName.empty())
{
uint32 const groupVariantSpellId = botAI->GetAiObjectContext()
->GetValue<uint32>("spell id", groupName)->Get();
if (groupSpellId && HasRequiredReagents(bot, groupSpellId))
return groupName;
// 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();
return baseName;
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;
}
}

View File

@ -6,40 +6,63 @@
#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
{
bool IsGroupVariantEnabled(Player* bot, std::string const& name);
// Build an aura qualifier "single + greater" to avoid double-buffing
std::string MakeAuraQualifierForBuff(std::string const& name);
// Returns the group spell name for a given single-target buff.
// If no group equivalent exists, returns "".
std::string GroupVariantFor(std::string const& name);
bool NeedsPostLoginBuffGrace(std::string const& name);
bool ShouldDeferPartyBuffEvaluationForRecentLogin(
Player* bot,
Unit* target,
std::string const& spell);
bool ShouldDeferGreaterBlessingAssignmentForRecentLogin(Player* bot);
// Checks if the bot has the required reagents to cast a spell (by its spellId).
// Returns false if the spellId is invalid.
bool HasRequiredReagents(Player* bot, uint32 spellId);
// Applies the "switch to group buff" policy if: the bot is in a group of size x+,
// the group variant is known/useful, and reagents are available. Otherwise, returns baseName.
// If announceOnMissing == true and reagents are missing, calls the 'announce' callback
// (if provided) to notify the party/raid.
std::string UpgradeToGroupIfAppropriate(
Player* bot,
PlayerbotAI* botAI,
std::string const& baseName);
std::string const& baseName,
bool announceOnMissing = false,
std::function<void(std::string const&)> announce = {}
);
}
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 GroupBuffSpellAction
class CastMarkOfTheWildAction : public CastBuffSpellAction
{
public:
CastMarkOfTheWildAction(PlayerbotAI* botAI) : GroupBuffSpellAction(botAI, "mark of the wild") {}
CastMarkOfTheWildAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mark of the wild") {}
};
class CastMarkOfTheWildOnPartyAction : public GroupBuffOnPartyAction
class CastMarkOfTheWildOnPartyAction : public BuffOnPartyAction
{
public:
CastMarkOfTheWildOnPartyAction(PlayerbotAI* botAI) : GroupBuffOnPartyAction(botAI, "mark of the wild") {}
CastMarkOfTheWildOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "mark of the wild") {}
};
class CastSurvivalInstinctsAction : public CastBuffSpellAction

View File

@ -9,6 +9,11 @@
#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,13 +23,15 @@ class PlayerbotAI;
class MarkOfTheWildOnPartyTrigger : public BuffOnPartyTrigger
{
public:
MarkOfTheWildOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "mark of the wild", 4 * 2000) {}
MarkOfTheWildOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "mark of the wild", 2 * 2000) {}
bool IsActive() override;
};
class MarkOfTheWildTrigger : public BuffTrigger
{
public:
MarkOfTheWildTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "mark of the wild", 4 * 2000) {}
MarkOfTheWildTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "mark of the wild", 2 * 2000) {}
bool IsActive() override;
};

View File

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

View File

@ -31,6 +31,11 @@ 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,13 +19,14 @@ class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger
{
public:
ArcaneIntellectOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "arcane intellect", 4 * 2000) {}
: BuffOnPartyTrigger(botAI, "arcane intellect", 2 * 2000) {}
bool IsActive() override;
};
class ArcaneIntellectTrigger : public BuffTrigger
{
public:
ArcaneIntellectTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane intellect", 4 * 2000) {}
ArcaneIntellectTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane intellect", 2 * 2000) {}
bool IsActive() override;
};

View File

@ -0,0 +1,529 @@
/*
* 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

@ -8,6 +8,10 @@
#include "AiObject.h"
#include "GenericSpellActions.h"
#include "SharedDefines.h"
class PlayerbotAI;
class Unit;
// seals
BUFF_ACTION(CastSealOfRighteousnessAction, "seal of righteousness");
@ -84,13 +88,24 @@ 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;
};
@ -109,7 +124,6 @@ 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;
};
@ -120,13 +134,12 @@ public:
CastBlessingOfKingsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "blessing of kings") {}
};
class CastBlessingOfKingsOnPartyAction : public BuffOnPartyAction
class CastBlessingOfKingsOnPartyAction : public CastBlessingOnPartyAction
{
public:
CastBlessingOfKingsOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of kings") {}
CastBlessingOfKingsOnPartyAction(PlayerbotAI* botAI) : CastBlessingOnPartyAction(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
};
@ -143,7 +156,6 @@ 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;
};

View File

@ -1,609 +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 "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);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,267 +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.
*/
#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,7 +7,6 @@
#include "DpsPaladinStrategy.h"
#include "GenericPaladinNonCombatStrategy.h"
#include "PaladinGreaterBlessingAction.h"
#include "HealPaladinStrategy.h"
#include "NamedObjectContext.h"
#include "OffhealRetPaladinStrategy.h"
@ -71,17 +70,17 @@ class PaladinBuffStrategyFactoryInternal : public NamedObjectContext<Strategy>
public:
PaladinBuffStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
{
creators["bsanc"] = &PaladinBuffStrategyFactoryInternal::bsanc;
creators["bwisdom"] = &PaladinBuffStrategyFactoryInternal::bwisdom;
creators["bmight"] = &PaladinBuffStrategyFactoryInternal::bmight;
creators["bkings"] = &PaladinBuffStrategyFactoryInternal::bkings;
creators["bhealth"] = &PaladinBuffStrategyFactoryInternal::bhealth;
creators["bmana"] = &PaladinBuffStrategyFactoryInternal::bmana;
creators["bdps"] = &PaladinBuffStrategyFactoryInternal::bdps;
creators["bstats"] = &PaladinBuffStrategyFactoryInternal::bstats;
}
private:
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); }
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); }
};
class PaladinCombatStrategyFactoryInternal : public NamedObjectContext<Strategy>
@ -155,7 +154,6 @@ 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:
@ -213,8 +211,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* botAI) { return new BeaconOfLightOnMainTankTrigger(botAI); }
static Trigger* sacred_shield_on_main_tank(PlayerbotAI* botAI) { return new SacredShieldOnMainTankTrigger(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* hand_of_freedom_on_party(PlayerbotAI* botAI) { return new HandOfFreedomOnPartyTrigger(botAI); }
static Trigger* blessing_of_kings_on_party(PlayerbotAI* botAI) { return new BlessingOfKingsOnPartyTrigger(botAI); }
@ -229,10 +227,6 @@ 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>
@ -322,8 +316,6 @@ 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:
@ -422,41 +414,15 @@ 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* 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);
}
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); }
};
SharedNamedObjectContextList<Strategy> PaladinAiObjectContext::sharedStrategyContexts;
@ -501,5 +467,4 @@ void PaladinAiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContext
void PaladinAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
{
AiObjectContext::BuildSharedValueContexts(valueContexts);
valueContexts.Add(new PaladinValueContextInternal());
}

View File

@ -30,7 +30,4 @@ 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 "bwisdom"; }
std::string const getName() override { return "bmana"; }
};
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 "bsanc"; }
std::string const getName() override { return "bhealth"; }
};
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 "bmight"; }
std::string const getName() override { return "bdps"; }
};
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 "bkings"; }
std::string const getName() override { return "bstats"; }
};
class PaladinShadowResistanceStrategy : public Strategy

View File

@ -95,14 +95,13 @@ 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",
@ -123,6 +122,13 @@ 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)
}
@ -130,12 +136,20 @@ void TankPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
);
triggers.push_back(
new TriggerNode(
"avenging wrath",
"critical health",
{
NextAction("avenging wrath", ACTION_HIGH + 2)
NextAction("holy shield", ACTION_HIGH + 4)
}
)
);
triggers.push_back(
new TriggerNode(
"avenging wrath",
{
NextAction("avenging wrath", ACTION_HIGH + 2)
}
)
);
triggers.push_back(
new TriggerNode(
"target critical health",

View File

@ -5,11 +5,10 @@
#include "PaladinTriggers.h"
#include "GenericBuffUtils.h"
#include "PaladinGreaterBlessingAction.h"
#include "PaladinActions.h"
#include "PaladinHelper.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "PaladinHelper.h"
bool SealTrigger::IsActive()
{
@ -29,9 +28,8 @@ 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()
@ -64,8 +62,7 @@ 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))
@ -78,29 +75,3 @@ 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,6 +13,32 @@
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");
@ -186,55 +212,42 @@ DEBUFF_TRIGGER(AvengerShieldTrigger, "avenger's shield");
class BeaconOfLightOnMainTankTrigger : public BuffOnMainTankTrigger
{
public:
BeaconOfLightOnMainTankTrigger(PlayerbotAI* botAI)
: BuffOnMainTankTrigger(botAI, "beacon of light", true) {}
BeaconOfLightOnMainTankTrigger(PlayerbotAI* ai)
: BuffOnMainTankTrigger(ai, "beacon of light", true) {}
};
class SacredShieldOnMainTankTrigger : public BuffOnMainTankTrigger
{
public:
SacredShieldOnMainTankTrigger(PlayerbotAI* botAI)
: BuffOnMainTankTrigger(botAI, "sacred shield", false) {}
SacredShieldOnMainTankTrigger(PlayerbotAI* ai) : BuffOnMainTankTrigger(ai, "sacred shield", false) {}
};
class BlessingOfKingsOnPartyTrigger : public BlessingOnPartyTrigger
class BlessingOfKingsOnPartyTrigger : public BuffOnPartyTrigger
{
public:
BlessingOfKingsOnPartyTrigger(PlayerbotAI* botAI)
: BlessingOnPartyTrigger(botAI)
{
spell = "blessing of kings";
}
: BuffOnPartyTrigger(botAI, "blessing of kings", 2 * 2000) {}
};
class BlessingOfWisdomOnPartyTrigger : public BlessingOnPartyTrigger
class BlessingOfWisdomOnPartyTrigger : public BuffOnPartyTrigger
{
public:
BlessingOfWisdomOnPartyTrigger(PlayerbotAI* botAI)
: BlessingOnPartyTrigger(botAI)
{
spell = "blessing of might,blessing of wisdom";
}
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {}
};
class BlessingOfMightOnPartyTrigger : public BlessingOnPartyTrigger
class BlessingOfMightOnPartyTrigger : public BuffOnPartyTrigger
{
public:
BlessingOfMightOnPartyTrigger(PlayerbotAI* botAI)
: BlessingOnPartyTrigger(botAI)
{
spell = "blessing of might,blessing of wisdom";
}
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {}
};
class BlessingOfSanctuaryOnPartyTrigger : public BlessingOnPartyTrigger
class BlessingOfSanctuaryOnPartyTrigger : public BuffOnPartyTrigger
{
public:
BlessingOfSanctuaryOnPartyTrigger(PlayerbotAI* botAI)
: BlessingOnPartyTrigger(botAI)
{
spell = "blessing of sanctuary";
}
: BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000) {}
};
class HandOfFreedomOnPartyTrigger : public Trigger
@ -253,13 +266,4 @@ 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

@ -18,8 +18,6 @@ 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

@ -13,19 +13,9 @@
class PlayerbotAI;
// disc
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(CastPowerWordFortitudeAction, "power word: fortitude");
BUFF_PARTY_ACTION(CastPowerWordFortitudeOnPartyAction, "power word: fortitude");
BUFF_PARTY_ACTION(CastPrayerOfFortitudeOnPartyAction, "prayer of fortitude");
BUFF_ACTION(CastPowerWordShieldAction, "power word: shield");
BUFF_ACTION(CastInnerFireAction, "inner fire");
@ -36,19 +26,9 @@ 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");
class CastDivineSpiritAction : public GroupBuffSpellAction
{
public:
CastDivineSpiritAction(PlayerbotAI* botAI)
: GroupBuffSpellAction(botAI, "divine spirit") {}
};
class CastDivineSpiritOnPartyAction : public GroupBuffOnPartyAction
{
public:
CastDivineSpiritOnPartyAction(PlayerbotAI* botAI)
: GroupBuffOnPartyAction(botAI, "divine spirit") {}
};
BUFF_ACTION(CastDivineSpiritAction, "divine spirit");
BUFF_PARTY_ACTION(CastDivineSpiritOnPartyAction, "divine spirit");
BUFF_PARTY_ACTION(CastPrayerOfSpiritOnPartyAction, "prayer of spirit");
// disc 2.4.3
SPELL_ACTION(CastMassDispelAction, "mass dispel");
@ -123,23 +103,13 @@ SPELL_ACTION(CastMindBlastAction, "mind blast");
SPELL_ACTION(CastPsychicScreamAction, "psychic scream");
DEBUFF_ACTION(CastMindSootheAction, "mind soothe");
BUFF_ACTION_U(CastFadeAction, "fade", bot->GetGroup());
class CastShadowProtectionAction : public GroupBuffSpellAction
{
public:
CastShadowProtectionAction(PlayerbotAI* botAI)
: GroupBuffSpellAction(botAI, "shadow protection") {}
};
class CastShadowProtectionOnPartyAction : public GroupBuffOnPartyAction
{
public:
CastShadowProtectionOnPartyAction(PlayerbotAI* botAI)
: GroupBuffOnPartyAction(botAI, "shadow protection") {}
};
BUFF_ACTION(CastShadowProtectionAction, "shadow protection");
BUFF_PARTY_ACTION(CastShadowProtectionOnPartyAction, "shadow protection");
BUFF_PARTY_ACTION(CastPrayerOfShadowProtectionAction, "prayer of shadow protection");
// shadow talents
SPELL_ACTION(CastMindFlayAction, "mind flay");
BUFF_ACTION(CastVampiricEmbraceAction, "vampiric embrace");
DEBUFF_ACTION(CastVampiricEmbraceAction, "vampiric embrace");
BUFF_ACTION(CastShadowformAction, "shadowform");
SPELL_ACTION(CastSilenceAction, "silence");
ENEMY_HEALER_ACTION(CastSilenceOnEnemyHealerAction, "silence");

View File

@ -92,6 +92,8 @@ 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;
@ -134,6 +136,8 @@ 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); }
@ -203,6 +207,8 @@ 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;
@ -305,6 +311,11 @@ 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

@ -19,8 +19,6 @@ 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) }));
@ -56,6 +54,12 @@ 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,6 +30,8 @@ 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:
@ -132,6 +134,20 @@ 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,6 +51,14 @@ 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

@ -8,9 +8,10 @@
#include "Player.h"
#include "Playerbots.h"
bool ShadowProtectionTrigger::IsActive()
bool PowerWordFortitudeOnPartyTrigger::IsActive()
{
return BuffTrigger::IsActive() && !botAI->HasAura("prayer of shadow protection", GetTarget());
return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("power word : fortitude", GetTarget()) &&
!botAI->HasAura("prayer of fortitude", GetTarget());
}
bool PowerWordFortitudeTrigger::IsActive()
@ -19,12 +20,43 @@ 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,6 +27,8 @@ 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");
@ -42,34 +44,20 @@ 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) {}
PowerWordFortitudeOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "power word: fortitude", 4 * 2000)
{
}
bool IsActive() override;
};
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;
};
@ -77,15 +65,31 @@ public:
class DivineSpiritOnPartyTrigger : public BuffOnPartyTrigger
{
public:
DivineSpiritOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "divine spirit", 4 * 2000) {}
DivineSpiritOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "divine spirit", 4 * 2000) {}
bool IsActive() override;
};
class DivineSpiritTrigger : public BuffTrigger
{
public:
DivineSpiritTrigger(PlayerbotAI* botAI)
: BuffTrigger(botAI, "divine spirit", 4 * 2000) {}
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) {}
bool IsActive() override;
};
@ -102,7 +106,9 @@ 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

@ -161,7 +161,9 @@ void ShamanAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
}
else if (tab == SHAMAN_TAB_ENHANCEMENT)
{
triggers.push_back(new TriggerNode("medium aoe",{ NextAction("fire nova", 23.0f), }));
triggers.push_back(new TriggerNode("medium aoe",{ NextAction("magma totem", 24.0f),
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

@ -5,7 +5,6 @@
#include "WarriorActions.h"
#include "AiFactory.h"
#include "Playerbots.h"
bool CastBerserkerRageAction::isPossible()
@ -37,27 +36,6 @@ 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

@ -112,7 +112,6 @@ 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
#include "Playerbots.h"
#include "ANActions.h"
#include "AzjolNerubActions.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 "ANTriggers.h"
#include "AzjolNerubTriggers.h"
class AttackWebWrapAction : public AttackAction
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "CoSTriggers.h"
#include "CullingOfStratholmeTriggers.h"
class WotlkDungeonCoSTriggerContext : public NamedObjectContext<Trigger>
{

View File

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

View File

@ -1,5 +1,5 @@
#include "CoSStrategy.h"
#include "CoSMultipliers.h"
#include "CullingOfStratholmeStrategy.h"
#include "CullingOfStratholmeMultipliers.h"
void WotlkDungeonCoSStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{

View File

@ -1,5 +1,5 @@
#include "Playerbots.h"
#include "CoSTriggers.h"
#include "CullingOfStratholmeTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"

View File

@ -1,5 +1,5 @@
#include "Playerbots.h"
#include "DTKActions.h"
#include "DrakTharonKeepActions.h"
bool CorpseExplodeSpreadAction::Execute(Event /*event*/)
{

View File

@ -6,7 +6,7 @@
#include "GenericSpellActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "DTKTriggers.h"
#include "DrakTharonKeepTriggers.h"
const Position NOVOS_PARTY_POSITION = Position(-378.852f, -760.349f, 28.587f);

View File

@ -3,7 +3,7 @@
#include "Action.h"
#include "NamedObjectContext.h"
#include "DTKActions.h"
#include "DrakTharonKeepActions.h"
class WotlkDungeonDTKActionContext : public NamedObjectContext<Action>
{

View File

@ -3,7 +3,7 @@
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "DTKTriggers.h"
#include "DrakTharonKeepTriggers.h"
class WotlkDungeonDTKTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -1,9 +1,9 @@
#include "DTKMultipliers.h"
#include "DTKActions.h"
#include "DrakTharonKeepMultipliers.h"
#include "DrakTharonKeepActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "DTKTriggers.h"
#include "DrakTharonKeepTriggers.h"
#include "Action.h"
float NovosMultiplier::GetValue(Action* action)

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