mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
<!-- Thank you for contributing to mod-playerbots, please make sure that you... 1. Submit your PR to the test-staging branch, not master. 2. Read the guidelines below before submitting. 3. Don't delete parts of this template. DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND PREDICTABILITY over behavioral realism. Every action and decision executes PER BOT AND PER TRIGGER. Small increases in logic complexity scale poorly across thousands of bots and negatively affect all. We prioritize a stable system over a smarter one. Bots don't need to behave perfectly; believable behavior is the goal, not human simulation. Default behavior must be cheap in processing; expensive behavior must be opt-in. Before submitting, make sure your changes aligns with these principles. --> ## Pull Request Description <!-- Describe what this change does and why it is needed --> Fix for issue #2343 I excluded the MISC and FISHING POLE weapon subclasses from weapon enchants. MISC includes the entry profession "weapons" (skinning knife, mining pick, blacksmithing hammer, arclight spanner) and some other crap that I suspect is not enchantable, but even if it is there's no good reason to do so (like Brewfest steins). The subclass doesn't include weapons that can be used for professions but you might actually want to use for fighting (like Finkle's Skinner). To clean things up overall, I removed the intermediate class CastEnchantItemAction between CastSpellAction and CastEnchantItemMainHandAction and CastEnchantItemOffHandAction. CastEnchantItemAction is not doing anything helpful that can't easily be replicated in the MH/OH classes, and I can't think of any future reason for keeping CastEnchantItemAction. I also brought the CanCastSpell check into the MH/OH classes--previously it just wasn't run for the weapon enchant spells, and I can't think of any good reason why it shouldn't be. I also added Execute functions to both CastEnchantItemMainHandAction and CastEnchantItemOffHandAction so they actually directly cast the enchant on the specified hand instead of running through CastSpellAction's Execute (and thus going through item for spell). I wasn't having problems with the wrong hand being applied under the prior approach, but this is a more direct and better approach anyway. Other changes are just formatting. ## Feature Evaluation <!-- If your PR is very minimal (comment typo, wrong ID reference, etc), and it is very obvious it will not have any impact on performance, you may skip these question. If necessary, a maintainer may ask you for them later. --> <!-- Please answer the following: --> - Describe the **minimum logic** required to achieve the intended behavior. - Describe the **processing cost** when this logic executes across many bots. The new path is very similar to the old one but just adds a check that is common to all spells and early returns to avoid invalid results. ## How to Test the Changes <!-- - Step-by-step instructions to test the change. - Any required setup (e.g. multiple players, number of bots, specific configuration). - Expected behavior and how to verify it. --> 1. Log into a Shaman and activate selfbot 2. Check to make sure the correct enchantments are applied (e.g., MH Windfury and OH Flametongue for a dual-wielding Enhancement Shaman) 3. Equip a profession weapon such as a skinning knife and make sure the Shaman does not attempt to enchant it ## Impact Assessment <!-- As a generic test, before and after measure of pmon (playerbot pmon tick) can help you here. --> - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) This just stops Shamans from trying to enchant stuff that they can't. - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## AI Assistance <!-- AI assistance is allowed, but all submitted code must be fully understood, reviewed, and owned by the contributor. We expect contributors to be honest about what they do and do not understand. --> Was AI assistance used while working on this change? - - [ ] No - - [x] Yes (**explain below**) <!-- If yes, please specify: - Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation). - Which parts of the change were influenced or generated, and whether it was thoroughly reviewed. --> I kicked around some ideas with GPT-5.4 with respect to the refactoring aspect of the PR after I had fixed the bug. <!-- TRANSLATIONS: Anything new that the bots say in chat must be in a translatable format. This is done using GetBotTextOrDefault, which you can search for in the codebase to find examples. Your code needs to have English as the default fallback, while the full translations need to be in an SQL update file. The languages in the file are the nine language options supported by AzerothCore: English, Korean, French, German, Chinese, Taiwanese, Spanish, Spanish Mexico, and Russian. See data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as an example of a translation SQL update, whose content are called within the codebase at src/strategy/actions/FishingAction.cpp --> ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Any new bot dialogue lines are translated. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers <!-- Anything else that's helpful to review or test your pull request. -->
509 lines
15 KiB
C++
509 lines
15 KiB
C++
/*
|
|
* 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 "GenericSpellActions.h"
|
|
|
|
#include <ctime>
|
|
|
|
#include "Event.h"
|
|
#include "ItemTemplate.h"
|
|
#include "ObjectDefines.h"
|
|
#include "Opcodes.h"
|
|
#include "Player.h"
|
|
#include "Playerbots.h"
|
|
#include "ServerFacade.h"
|
|
#include "WorldPacket.h"
|
|
#include "Group.h"
|
|
#include "Chat.h"
|
|
#include "GenericBuffUtils.h"
|
|
#include "PlayerbotAI.h"
|
|
|
|
using ai::buff::MakeAuraQualifierForBuff;
|
|
using ai::spell::HasSpellOrCategoryCooldown;
|
|
|
|
CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell)
|
|
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell)
|
|
{
|
|
}
|
|
|
|
bool CastSpellAction::Execute(Event /*event*/)
|
|
{
|
|
if (spell == "conjure food" || spell == "conjure water")
|
|
{
|
|
// uint32 id = AI_VALUE2(uint32, "spell id", spell);
|
|
// if (!id)
|
|
// return false;
|
|
|
|
uint32 castId = 0;
|
|
|
|
for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr)
|
|
{
|
|
uint32 spellId = itr->first;
|
|
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
|
if (!spellInfo)
|
|
continue;
|
|
|
|
std::string const namepart = spellInfo->SpellName[0];
|
|
std::wstring wnamepart;
|
|
if (!Utf8toWStr(namepart, wnamepart))
|
|
return false;
|
|
|
|
wstrToLower(wnamepart);
|
|
|
|
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)
|
|
continue;
|
|
|
|
if (bot->CanUseItem(proto) != EQUIP_ERR_OK)
|
|
continue;
|
|
|
|
if (spellInfo->Id > castId)
|
|
castId = spellInfo->Id;
|
|
}
|
|
|
|
return botAI->CastSpell(castId, bot);
|
|
}
|
|
|
|
return botAI->CastSpell(spell, GetTarget());
|
|
}
|
|
|
|
bool CastSpellAction::isUseful()
|
|
{
|
|
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
|
|
return false;
|
|
|
|
if (spell == "mount" && !bot->IsMounted() && !bot->IsInCombat())
|
|
return true;
|
|
|
|
if (spell == "mount" && bot->IsInCombat())
|
|
{
|
|
bot->Dismount();
|
|
return false;
|
|
}
|
|
|
|
Unit* spellTarget = GetTarget();
|
|
if (!spellTarget)
|
|
return false;
|
|
|
|
if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId())
|
|
return false;
|
|
|
|
// float combatReach = bot->GetCombatReach() + target->GetCombatReach();
|
|
// if (!botAI->IsRanged(bot))
|
|
// combatReach += 4.0f / 3.0f;
|
|
|
|
return AI_VALUE2(bool, "spell cast useful", spell);
|
|
// && ServerFacade::instance().GetDistance2d(bot, target) <= (range + combatReach);
|
|
}
|
|
|
|
bool CastSpellAction::isPossible()
|
|
{
|
|
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
|
|
{
|
|
if (!sPlayerbotAIConfig.logInGroupOnly || (bot->GetGroup() && botAI->HasRealPlayerMaster()))
|
|
{
|
|
LOG_DEBUG("playerbots", "Can cast spell failed. Vehicle. - bot name: {}", bot->GetName());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (spell == "mount" && !bot->IsMounted() && !bot->IsInCombat())
|
|
return true;
|
|
|
|
if (spell == "mount" && bot->IsInCombat())
|
|
{
|
|
if (!sPlayerbotAIConfig.logInGroupOnly || (bot->GetGroup() && botAI->HasRealPlayerMaster()))
|
|
{
|
|
LOG_DEBUG("playerbots", "Can cast spell failed. Mount. - bot name: {}", bot->GetName());
|
|
}
|
|
bot->Dismount();
|
|
return false;
|
|
}
|
|
|
|
// Spell* currentSpell = bot->GetCurrentSpell(CURRENT_GENERIC_SPELL); //not used, line marked for removal.
|
|
return botAI->CanCastSpell(spell, GetTarget());
|
|
}
|
|
|
|
CastMeleeSpellAction::CastMeleeSpellAction(
|
|
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
|
|
{
|
|
range = ATTACK_DISTANCE;
|
|
}
|
|
|
|
bool CastMeleeSpellAction::isUseful()
|
|
{
|
|
Unit* target = GetTarget();
|
|
if (!target)
|
|
return false;
|
|
|
|
if (!bot->IsWithinMeleeRange(target))
|
|
return false;
|
|
|
|
return CastSpellAction::isUseful();
|
|
}
|
|
|
|
CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(
|
|
PlayerbotAI* botAI, std::string const spell, bool isOwner, float needLifeTime) :
|
|
CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
|
|
{
|
|
range = ATTACK_DISTANCE;
|
|
}
|
|
|
|
bool CastMeleeDebuffSpellAction::isUseful()
|
|
{
|
|
Unit* target = GetTarget();
|
|
if (!target)
|
|
return false;
|
|
|
|
if (!bot->IsWithinMeleeRange(target))
|
|
return false;
|
|
|
|
return CastDebuffSpellAction::isUseful();
|
|
}
|
|
|
|
bool CastAuraSpellAction::isUseful()
|
|
{
|
|
if (!GetTarget() || !CastSpellAction::isUseful())
|
|
return false;
|
|
Aura* aura = botAI->GetAura(spell, GetTarget(), isOwner, checkDuration);
|
|
if (!aura)
|
|
return true;
|
|
if (beforeDuration && aura->GetDuration() < beforeDuration)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(
|
|
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
|
|
|
|
bool CastEnchantItemMainHandAction::Execute(Event /*event*/)
|
|
{
|
|
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
|
return item && botAI->CastSpell(spell, bot, item);
|
|
}
|
|
|
|
bool CastEnchantItemMainHandAction::isPossible()
|
|
{
|
|
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
|
if (!item || item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_MISC ||
|
|
item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE ||
|
|
item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return botAI->CanCastSpell(spell, bot, item);
|
|
}
|
|
|
|
CastEnchantItemOffHandAction::CastEnchantItemOffHandAction(
|
|
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
|
|
|
|
bool CastEnchantItemOffHandAction::Execute(Event /*event*/)
|
|
{
|
|
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
|
|
return item && botAI->CastSpell(spell, bot, item);
|
|
}
|
|
|
|
bool CastEnchantItemOffHandAction::isPossible()
|
|
{
|
|
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
|
|
if (!item || item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_MISC ||
|
|
item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return botAI->CanCastSpell(spell, bot, item);
|
|
}
|
|
|
|
CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount,
|
|
HealingManaEfficiency manaEfficiency, bool isOwner)
|
|
: CastAuraSpellAction(botAI, spell, isOwner), estAmount(estAmount), manaEfficiency(manaEfficiency)
|
|
{
|
|
range = botAI->GetRange("heal");
|
|
}
|
|
|
|
bool CastHealingSpellAction::isUseful() { return CastAuraSpellAction::isUseful(); }
|
|
|
|
bool CastAoeHealSpellAction::isUseful() { return CastSpellAction::isUseful(); }
|
|
|
|
CastCureSpellAction::CastCureSpellAction(
|
|
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
|
|
{
|
|
range = botAI->GetRange("heal");
|
|
}
|
|
|
|
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", 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)
|
|
{
|
|
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
|
|
{
|
|
spell = "shoot";
|
|
|
|
switch (pItem->GetTemplate()->SubClass)
|
|
{
|
|
case ITEM_SUBCLASS_WEAPON_GUN:
|
|
spell += " gun";
|
|
shootSpellId = 3018;
|
|
break;
|
|
case ITEM_SUBCLASS_WEAPON_BOW:
|
|
spell += " bow";
|
|
shootSpellId = 3018;
|
|
break;
|
|
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
|
|
spell += " crossbow";
|
|
shootSpellId = 3018;
|
|
break;
|
|
case ITEM_SUBCLASS_WEAPON_THROWN:
|
|
spell = "throw";
|
|
shootSpellId = 2764;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CastShootAction::isPossible()
|
|
{
|
|
if (shootSpellId)
|
|
return botAI->CanCastSpell(shootSpellId, GetTarget(), false);
|
|
|
|
return CastSpellAction::isPossible();
|
|
}
|
|
|
|
bool CastShootAction::Execute(Event /*event*/)
|
|
{
|
|
if (shootSpellId)
|
|
return botAI->CastSpell(shootSpellId, GetTarget());
|
|
|
|
return botAI->CastSpell(spell, GetTarget());
|
|
}
|
|
|
|
Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue()
|
|
{
|
|
return context->GetValue<Unit*>("attacker without aura", spell);
|
|
}
|
|
|
|
Value<Unit*>* CastDebuffSpellOnMeleeAttackerAction::GetTargetValue()
|
|
{
|
|
return context->GetValue<Unit*>("melee attacker without aura", spell);
|
|
}
|
|
|
|
CastBuffSpellAction::CastBuffSpellAction(
|
|
PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration)
|
|
: CastAuraSpellAction(botAI, spell, checkIsOwner, false, beforeDuration)
|
|
{
|
|
range = botAI->GetRange("spell");
|
|
}
|
|
|
|
Value<Unit*>* CastSpellOnEnemyHealerAction::GetTargetValue()
|
|
{
|
|
return context->GetValue<Unit*>("enemy healer target", spell);
|
|
}
|
|
|
|
Value<Unit*>* CastSnareSpellAction::GetTargetValue() { return context->GetValue<Unit*>("snare target", spell); }
|
|
|
|
Value<Unit*>* CastCrowdControlSpellAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", getName()); }
|
|
|
|
bool CastCrowdControlSpellAction::Execute(Event /*event*/) { return botAI->CastSpell(getName(), GetTarget()); }
|
|
|
|
bool CastCrowdControlSpellAction::isPossible() { return botAI->CanCastSpell(getName(), GetTarget()); }
|
|
|
|
bool CastCrowdControlSpellAction::isUseful() { return true; }
|
|
|
|
std::string const CastProtectSpellAction::GetTargetName() { return "party member to protect"; }
|
|
|
|
bool CastProtectSpellAction::isUseful() { return GetTarget() && !botAI->HasAura(spell, GetTarget()); }
|
|
|
|
bool CastVehicleSpellAction::isPossible()
|
|
{
|
|
uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", spell);
|
|
return botAI->CanCastVehicleSpell(spellId, GetTarget());
|
|
}
|
|
|
|
bool CastVehicleSpellAction::isUseful() { return botAI->IsInVehicle(false, true); }
|
|
|
|
bool CastVehicleSpellAction::Execute(Event /*event*/)
|
|
{
|
|
uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", spell);
|
|
return botAI->CastVehicleSpell(spellId, GetTarget());
|
|
}
|
|
|
|
bool CastEveryManForHimselfAction::isPossible()
|
|
{
|
|
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
|
if (!spellId)
|
|
return false;
|
|
|
|
if (!bot->HasSpell(spellId))
|
|
return false;
|
|
|
|
if (HasSpellOrCategoryCooldown(bot, spellId))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
bool CastWillOfTheForsakenAction::isPossible()
|
|
{
|
|
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
|
if (!spellId)
|
|
return false;
|
|
|
|
if (!bot->HasSpell(spellId))
|
|
return false;
|
|
|
|
if (HasSpellOrCategoryCooldown(bot, spellId))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CastWillOfTheForsakenAction::isUseful()
|
|
{
|
|
return (bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
|
|
bot->HasAuraType(SPELL_AURA_MOD_CHARM) ||
|
|
bot->HasAuraType(SPELL_AURA_AOE_CHARM) ||
|
|
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP))
|
|
&& CastSpellAction::isUseful();
|
|
}
|
|
|
|
bool UseTrinketAction::Execute(Event /*event*/)
|
|
{
|
|
Item* trinket1 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET1);
|
|
|
|
if (trinket1 && UseTrinket(trinket1))
|
|
return true;
|
|
|
|
Item* trinket2 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET2);
|
|
if (trinket2 && UseTrinket(trinket2))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UseTrinketAction::UseTrinket(Item* item)
|
|
{
|
|
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;
|
|
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
|
|
{
|
|
if (item->GetTemplate()->Spells[i].SpellId > 0 &&
|
|
item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
|
|
{
|
|
spellId = item->GetTemplate()->Spells[i].SpellId;
|
|
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
|
|
|
if (!spellInfo || !spellInfo->IsPositive())
|
|
return false;
|
|
|
|
bool applyAura = false;
|
|
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
|
|
{
|
|
const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
|
|
if (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA)
|
|
{
|
|
applyAura = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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))
|
|
{
|
|
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);
|
|
return true;
|
|
}
|
|
|
|
Value<Unit*>* BuffOnMainTankAction::GetTargetValue() { return context->GetValue<Unit*>("main tank", spell); }
|
|
|
|
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;
|
|
}
|