Compare commits

..

4 Commits

Author SHA1 Message Date
Keleborn
085e127e38
Merge pull request #2467 from mod-playerbots/test-staging
Merge test-staging into master
2026-06-12 07:24:30 -07:00
Crow
a76f2ca268
Refactor HasSpell/HasAura and convert spellIds to constants (#2435)
<!--
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 -->

This is a PR to follow-up on a discussion with @kadeshar during the
recent mage armor PR. Main changes:
- Add new PlayerbotAI::HasSpell() overload that accepts a string for the
spell name as an argument, in order to replace repeated
bot->HasSpell(spellId) checks for places where multiple ranks of a spell
are checked, and to replace a few calls to AI_VALUE(uint32, "spell id",
...) where it existed only to check for the spell being known.
- Removed PlayerbotAI::HasAura() overload that takes a spell Id as an
argument. It is rarely used, and the few instances of its usage are
easily replaced with AC's Unit::HasAura(), which is the predominant
usage for spell Id aura checks already anyway.
- When modifying DruidPullStrategy to use the new HasSpell() method, I
went ahead and also simplified the file overall to pare down duplication
(for example, the first check in CanCastSpell is for the spell Id so we
don't need to separately check it before calling CanCastSpell()). I then
did the same for other pull strategies so they can be consistent.
- Changed many magic numbers for spell Ids to be defined as compile-time
constants. I didn't do this throughout the code, but I did in the files
where I was making changes anyway due to the HasSpell() and/or HasAura()
changes.

## 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.

  - Minimum logic is described above.
  - Processing cost shouldn't change much. See Impact Assessment below.

## 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.
-->

## 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?
    - - [ ] No, not at all
    - - [x] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)

- Passing a string for the spell to check for it being known or the aura
being present is more taxing than looking up a spell Id, although the
usefulness of the string-based overload is for situations with spells
that have multiple ranks.

- Does this change modify default bot behavior?
    - - [x] No
    - - [ ] Yes (**explain why**)



- Does this change add new decision branches or increase maintenance
complexity?
    - - [x] No
    - - [ ] Yes (**explain below**)

  - I think these changes should simplify maintenance complexity.

## 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 had AI make many of the changes and then review since they were
pretty mundane (for example, I'd tell it to just take all the magic
numbers from a file and define them in an anonymous namespace, then
replace the magic numbers with the spell constants). There isn't
anything complicated in this PR; it was mostly busywork.

<!--
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.
-->
2026-06-07 08:14:53 -07:00
NoxMax
dda9ff0d40
Fix: Dismount cleanup (#2413)
<!--
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 -->
Cleans some stale flight flags, and gets rid of the parachute given in
forced flight dismount. Refactors some dismount code, and get rid of the
unused mountData parameter in CalculateMasterMountSpeed as well

`MOVEMENTFLAG_WATERWALKING` was removed because it might've been added
to protect MOVEMENTFLAG_CAN_FLY, but if the bot remounts over water,
MOVEMENTFLAG_CAN_FLY would be reapplied anyway. However if it has a
function that I missed, we can re-add it.

## 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.
Feature is currently at minimum cost.


## 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. As GM hover high above Dalaran, but not high enough that you are
outside the resting zone.
2. .summon some lvl +70 bots until you get some that are on a flying
mount.
3. Watch as they lose their mount after 10 seconds, and be given a
parachute as they fall.
4. When they reach the ground, they should be stuck with a parachute
forever, and they shouldn't be stuck hovering just above the ground
forever.


## 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?
    - - [x] No
    - - [ ] Yes (**explain why**)



- Does this change add new decision branches or increase maintenance
complexity?
    - - [ ] No
    - - [x] Yes (**explain below**)
Checks if bot has `HasFeatherFallAura` to remove any stale flight flags.


## 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?
- - [x] No
- - [ ] 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.
-->



<!--
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.
-->

Comment paragraph might be verbose, but worth it for whomever is looking
at the code years from now trying to figure out why bots need special
treatment with dismounting anyway.
2026-06-06 23:58:25 -07:00
Keleborn
af0c5e7c4b
Fix/TellMaster crash (#2434)
<!--
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 -->
If you add +debug to a randombot in the world without the
randomBotSayWithoutMaster it will crash.


## 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.



## 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.
-->



## 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?
    - - [x] No
    - - [ ] Yes (**explain why**)



- 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?
- - [x] No
- - [ ] 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.
-->



<!--
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.
-->
2026-06-06 23:58:05 -07:00
36 changed files with 361 additions and 299 deletions

View File

@ -17,6 +17,7 @@
#include "SpellAuraEffects.h"
static constexpr uint32 SPELL_COLD_WEATHER_FLYING = 54197;
static constexpr float PARACHUTE_LAND_THRESHOLD = 15.0f;
// Define the static map / init bool for caching bot preferred mount data globally
std::unordered_map<uint32, PreferredMountCache> CheckMountStateAction::mountCache;
@ -61,6 +62,21 @@ MountData CollectMountData(const Player* bot)
bool CheckMountStateAction::Execute(Event /*event*/)
{
// Forced flight dismount:
// Bots get stale flight movement flags after a forced dismount (e.g: Dalaran) because the post landing dismount cleanup
// needs MSG_MOVE_FALL_LAND (a client opcode) and client movement packets. The stale flags cause the bot to be stuck with
// the parachute, or even keep the bot hovering indefinitely and block MMAP routing.
// Note: Without MSG_MOVE_FALL_LAND, HandleFall doesn't trigger, meaning bots don't get fall damage in forced dismounts anyway,
// so the parachute usage here is more of an immersion feature.
if (bot->HasFeatherFallAura())
{
float floorZ = bot->GetMapHeight(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
if (floorZ != INVALID_HEIGHT && floorZ != VMAP_INVALID_HEIGHT_VALUE &&
bot->GetPositionZ() - floorZ <= PARACHUTE_LAND_THRESHOLD)
bot->RemoveAurasByType(SPELL_AURA_FEATHER_FALL);
}
ClearStaleFlightFlags();
// Determine if there are no attackers
bool noAttackers = !AI_VALUE2(bool, "combat", "self target") || !AI_VALUE(uint8, "attacker count");
bool enemy = AI_VALUE(Unit*, "enemy player target");
@ -204,7 +220,7 @@ bool CheckMountStateAction::Mount()
// Get bot mount data
MountData mountData = CollectMountData(bot);
int32 masterMountType = GetMountType(master);
int32 masterSpeed = CalculateMasterMountSpeed(master, mountData);
int32 masterSpeed = CalculateMasterMountSpeed(master);
// Try shapeshift
if (TryForms(master, masterMountType, masterSpeed))
@ -234,14 +250,17 @@ void CheckMountStateAction::Dismount()
WorldPacket emptyPacket;
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
bool const wantsFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
bool const isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
bool const isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
bool const hasGravityDisabled = bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
if (!wantsFly && !isWaterWalking && (isFlying || hasGravityDisabled))
ClearStaleFlightFlags();
}
void CheckMountStateAction::ClearStaleFlightFlags()
{
bot->RemoveUnitMovementFlag(
MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY);
if (bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura())
return;
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_DISABLE_GRAVITY))
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_DISABLE_GRAVITY | MOVEMENTFLAG_CAN_FLY);
if (!bot->IsRooted())
bot->SendMovementFlagUpdate();
}
@ -490,7 +509,7 @@ static bool BotCanUseFlyingMount(Player const* bot)
return true;
}
int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const MountData& mountData) const
int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master) const
{
// Check riding skill and level requirements
int32 ridingSkill = bot->GetPureSkillValue(SKILL_RIDING);

View File

@ -53,9 +53,10 @@ private:
float CalculateDismountDistance() const;
float CalculateMountDistance() const;
void Dismount();
void ClearStaleFlightFlags();
bool ShouldFollowMasterMountState(Player* master, bool noAttackers, bool shouldMount) const;
bool ShouldDismountForMaster(Player* master) const;
int32 CalculateMasterMountSpeed(Player* master, const MountData& mountData) const;
int32 CalculateMasterMountSpeed(Player* master) const;
bool CheckForSwiftMount() const;
std::map<uint32, std::map<int32, std::vector<uint32>>> GetAllMountSpells() const;
bool TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const;

View File

@ -422,10 +422,9 @@ bool TameAction::RenamePet(const std::string& newName)
// Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(883))
{
bot->CastSpell(bot, 883, true);
}
constexpr uint32 SPELL_CALL_PET = 883;
if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(SPELL_CALL_PET))
bot->CastSpell(bot, SPELL_CALL_PET, true);
return true;
}

View File

@ -67,9 +67,10 @@ bool TradeStatusExtendedAction::Execute(Event event)
return false;
}
if (bot->getClass() == CLASS_ROGUE && bot->HasSpell(1804) && lockbox->IsLocked()) // Pick Lock spell
constexpr uint32 SPELL_PICK_LOCK = 1804;
if (bot->getClass() == CLASS_ROGUE && bot->HasSpell(SPELL_PICK_LOCK) && lockbox->IsLocked())
{
// botAI->CastSpell(1804, bot, lockbox); // Attempt to cast Pick Lock on the lockbox
// botAI->CastSpell(SPELL_PICK_LOCK, bot, lockbox); // Attempt to cast Pick Lock on the lockbox
botAI->DoSpecificAction("unlock traded item");
botAI->SetNextCheckDelay(4000); // Delay before accepting trade
}

View File

@ -23,9 +23,7 @@ bool BossFireResistanceTrigger::IsActive()
return false;
// Check if bot have fire resistance aura
if (bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_5) || bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_4) ||
bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_3) || bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_2) ||
bot->HasAura(SPELL_FIRE_RESISTANCE_AURA_RANK_1))
if (botAI->HasAura("fire resistance aura", bot))
return false;
// Check if bot dont have already have fire resistance strategy
@ -76,9 +74,7 @@ bool BossFrostResistanceTrigger::IsActive()
return false;
// Check if bot have frost resistance aura
if (bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_5) || bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_4) ||
bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_3) || bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_2) ||
bot->HasAura(SPELL_FROST_RESISTANCE_AURA_RANK_1))
if (botAI->HasAura("frost resistance aura", bot))
return false;
// Check if bot dont have already have frost resistance strategy
@ -133,8 +129,7 @@ bool BossNatureResistanceTrigger::IsActive()
return false;
// Check if bot have nature resistance aura
if (bot->HasAura(SPELL_ASPECT_OF_THE_WILD_RANK_4) || bot->HasAura(SPELL_ASPECT_OF_THE_WILD_RANK_3) ||
bot->HasAura(SPELL_ASPECT_OF_THE_WILD_RANK_2) || bot->HasAura(SPELL_ASPECT_OF_THE_WILD_RANK_1))
if (botAI->HasAura("aspect of the wild", bot))
return false;
// Check if bot dont have already setted nature resistance aura
@ -184,11 +179,7 @@ bool BossShadowResistanceTrigger::IsActive()
return false;
// Check if bot have shadow resistance aura
if (bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_5) ||
bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_4) ||
bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_3) ||
bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_2) ||
bot->HasAura(SPELL_SHADOW_RESISTANCE_AURA_RANK_1))
if (botAI->HasAura("shadow resistance aura", bot))
return false;
// Check if bot dont have already have shadow resistance strategy

View File

@ -5,39 +5,27 @@
#include "DeathKnightPullStrategy.h"
#include "AiObjectContext.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
std::string DeathKnightPullStrategy::GetPullActionName() const
{
Player* bot = botAI->GetBot();
Unit* target = GetTarget();
if (!bot || !target ||
if (!target ||
(!botAI->HasStrategy("blood", BOT_STATE_COMBAT) && !botAI->HasStrategy("blood", BOT_STATE_NON_COMBAT)))
{
return PullStrategy::GetPullActionName();
}
uint32 const deathGripSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "death grip")->Get();
if (deathGripSpellId && bot->HasSpell(deathGripSpellId) &&
botAI->CanCastSpell(deathGripSpellId, target))
{
if (botAI->CanCastSpell("death grip", target))
return "death grip";
}
uint32 const icyTouchSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "icy touch")->Get();
if (!icyTouchSpellId || !bot->HasSpell(icyTouchSpellId) ||
!botAI->CanCastSpell(icyTouchSpellId, target))
{
uint32 const darkCommandSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "dark command")->Get();
if (darkCommandSpellId && bot->HasSpell(darkCommandSpellId) &&
botAI->CanCastSpell(darkCommandSpellId, target))
if (!botAI->CanCastSpell("icy touch", target) &&
botAI->CanCastSpell("dark command", target))
{
return "dark command";
}
}
return PullStrategy::GetPullActionName();
}

View File

@ -50,9 +50,10 @@ bool CastCancelDruidAction::Execute(Event /*event*/)
return true;
}
bool CastCancelDruidAction::isUseful() { return botAI->HasAura(auraId, bot); }
bool CastCancelDruidAction::isUseful() { return bot->HasAura(auraId); }
bool CastTreeFormAction::isUseful()
{
return GetTarget() && CastSpellAction::isUseful() && !botAI->HasAura(33891, bot);
constexpr uint32 SPELL_TREE_OF_LIFE = 33891;
return GetTarget() && CastSpellAction::isUseful() && !bot->HasAura(SPELL_TREE_OF_LIFE);
}

View File

@ -28,7 +28,11 @@ bool ThornsTrigger::IsActive() { return BuffTrigger::IsActive() && !botAI->HasAu
bool BearFormTrigger::IsActive() { return !botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr); }
bool TreeFormTrigger::IsActive() { return !botAI->HasAura(33891, bot); }
bool TreeFormTrigger::IsActive()
{
constexpr uint32 SPELL_TREE_OF_LIFE = 33891;
return !bot->HasAura(SPELL_TREE_OF_LIFE);
}
bool CatFormTrigger::IsActive() { return !botAI->HasAura("cat form", bot); }
@ -43,8 +47,11 @@ bool ProwlTrigger::IsActive()
if (botAI->HasAura("prowl", bot) || bot->IsInCombat())
return false;
uint32 prowlId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "prowl")->Get();
if (!prowlId || !bot->HasSpell(prowlId) || bot->HasSpellCooldown(prowlId))
if (!botAI->HasSpell("prowl"))
return false;
uint32 const prowlId = AI_VALUE2(uint32, "spell id", "prowl");
if (bot->HasSpellCooldown(prowlId))
return false;
float distance = 30.f;

View File

@ -393,14 +393,14 @@ public:
class FerociousBiteExecuteTrigger : public Trigger
{
public:
FerociousBiteExecuteTrigger(PlayerbotAI* ai) : Trigger(ai, "ferocious bite execute") {}
FerociousBiteExecuteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "ferocious bite execute") {}
bool IsActive() override
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target || !target->IsAlive())
return false;
if (!AI_VALUE2(uint32, "spell id", "ferocious bite"))
if (!botAI->HasSpell("ferocious bite"))
return false;
if (AI_VALUE2(uint8, "combo", "current target") < 1)

View File

@ -5,34 +5,23 @@
#include "DruidPullStrategy.h"
#include "AiObjectContext.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
std::string DruidPullStrategy::GetPullActionName() const
{
Player* bot = botAI->GetBot();
std::string actionName = PullStrategy::GetPullActionName();
if (!bot)
return actionName;
uint32 const faerieFireFeralId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "faerie fire (feral)")->Get();
if (faerieFireFeralId && bot->HasSpell(faerieFireFeralId) &&
(botAI->HasStrategy("bear", BOT_STATE_COMBAT) || botAI->HasStrategy("cat", BOT_STATE_COMBAT)))
{
actionName = "faerie fire (feral)";
}
std::string const pullActionName = PullStrategy::GetPullActionName();
std::string const actionName =
botAI->HasSpell("faerie fire (feral)") &&
(botAI->HasStrategy("bear", BOT_STATE_COMBAT) || botAI->HasStrategy("cat", BOT_STATE_COMBAT))
? "faerie fire (feral)" : pullActionName;
Unit* target = GetTarget();
uint32 const faerieFireSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", actionName)->Get();
if (target && (!faerieFireSpellId || !bot->HasSpell(faerieFireSpellId) ||
!botAI->CanCastSpell(faerieFireSpellId, target)))
{
uint32 const growlSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "growl")->Get();
if (growlSpellId && bot->HasSpell(growlSpellId) && botAI->CanCastSpell(growlSpellId, target))
if (!target)
return actionName;
if (!botAI->CanCastSpell(actionName, target) && botAI->CanCastSpell("growl", target))
return "growl";
}
return actionName;
}

View File

@ -19,23 +19,13 @@ bool CastViperStingAction::isUseful()
bool CastAspectOfTheHawkAction::isUseful()
{
Unit* target = GetTarget();
if (!target)
return false;
if (bot->HasSpell(61846) || bot->HasSpell(61847)) // Aspect of the Dragonhawk spell IDs
return false;
return true;
return target && !botAI->HasSpell("aspect of the dragonhawk");
}
bool CastArcaneShotAction::isUseful()
{
Unit* target = GetTarget();
if (!target)
return false;
if (bot->HasSpell(53301) || bot->HasSpell(60051) ||
bot->HasSpell(60052) || bot->HasSpell(60053)) // Explosive Shot spell IDs
if (!target || !botAI->HasSpell("explosive shot"))
return false;
// Armor Penetration rating check - will not cast Arcane Shot above 435 ArP
@ -50,14 +40,7 @@ bool CastArcaneShotAction::isUseful()
bool CastImmolationTrapAction::isUseful()
{
Unit* target = GetTarget();
if (!target)
return false;
if (bot->HasSpell(13813) || bot->HasSpell(14316) || bot->HasSpell(14317) || bot->HasSpell(27025) ||
bot->HasSpell(49066) || bot->HasSpell(49067)) // Explosive Trap spell IDs
return false;
return true;
return target && !botAI->HasSpell("explosive trap");
}
Value<Unit*>* CastFreezingTrap::GetTargetValue()

View File

@ -13,7 +13,7 @@
std::vector<NextAction> CastMoltenArmorAction::getAlternatives()
{
if (!AI_VALUE2(uint32, "spell id", "molten armor"))
if (!botAI->HasSpell("molten armor"))
return NextAction::merge({ NextAction("mage armor") }, CastBuffSpellAction::getAlternatives());
return CastBuffSpellAction::getAlternatives();
@ -21,7 +21,7 @@ std::vector<NextAction> CastMoltenArmorAction::getAlternatives()
std::vector<NextAction> CastMageArmorAction::getAlternatives()
{
if (!AI_VALUE2(uint32, "spell id", "mage armor"))
if (!botAI->HasSpell("mage armor"))
return NextAction::merge({ NextAction("ice armor") }, CastBuffSpellAction::getAlternatives());
return CastBuffSpellAction::getAlternatives();

View File

@ -39,26 +39,16 @@ bool ArcaneIntellectTrigger::IsActive()
bool MageArmorTrigger::IsActive()
{
Unit* target = GetTarget();
if (botAI->HasAura("mage armor", target))
return false;
if (AI_VALUE2(uint32, "spell id", "mage armor"))
return true;
return !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) &&
return botAI->HasSpell("mage armor") && !botAI->HasAura("mage armor", target) &&
!botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) &&
!botAI->HasAura("molten armor", target);
}
bool MoltenArmorTrigger::IsActive()
{
Unit* target = GetTarget();
if (botAI->HasAura("molten armor", target))
return false;
if (AI_VALUE2(uint32, "spell id", "molten armor"))
return true;
return !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) &&
return botAI->HasSpell("molten armor") && !botAI->HasAura("molten armor", target) &&
!botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) &&
!botAI->HasAura("mage armor", target);
}
@ -82,7 +72,8 @@ bool FrostbiteOnTargetTrigger::IsActive()
bool NoFocusMagicTrigger::IsActive()
{
if (!bot->HasSpell(54646)) // Focus Magic
constexpr uint32 SPELL_FOCUS_MAGIC = 54646;
if (!bot->HasSpell(SPELL_FOCUS_MAGIC))
return false;
Group* group = bot->GetGroup();
@ -95,7 +86,7 @@ bool NoFocusMagicTrigger::IsActive()
if (!member || member == bot || !member->IsAlive())
continue;
if (member->HasAura(54646, bot->GetGUID()))
if (member->HasAura(SPELL_FOCUS_MAGIC, bot->GetGUID()))
return false;
}
return true;
@ -103,11 +94,9 @@ bool NoFocusMagicTrigger::IsActive()
bool DeepFreezeCooldownTrigger::IsActive()
{
// If the bot does NOT have Deep Freeze, treat as "on cooldown"
if (!bot->HasSpell(44572)) // Deep Freeze
return true;
return SpellCooldownTrigger::IsActive();
constexpr uint32 SPELL_DEEP_FREEZE = 44572;
return !bot->HasSpell(SPELL_DEEP_FREEZE) ||
SpellCooldownTrigger::IsActive();
}
const std::unordered_set<uint32> FlamestrikeNearbyTrigger::FLAMESTRIKE_SPELL_IDS = {

View File

@ -8,6 +8,18 @@
#include "Playerbots.h"
#include "RangedCombatStrategy.h"
namespace
{
constexpr uint32 SPELL_CONJURE_MANA_SAPPHIRE = 42985;
constexpr uint32 SPELL_CONJURE_MANA_EMERALD = 27101;
constexpr uint32 SPELL_CONJURE_MANA_RUBY = 10054;
constexpr uint32 SPELL_CONJURE_MANA_CITRINE = 10053;
constexpr uint32 SPELL_CONJURE_MANA_JADE = 3552;
constexpr uint32 SPELL_CONJURE_MANA_AGATE = 759;
constexpr uint32 SPELL_FROSTFIRE_BOLT = 44614;
constexpr uint32 SPELL_ICE_SHARDS = 15047;
}
class GenericMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
@ -102,17 +114,17 @@ void GenericMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// Mana Threshold Triggers
Player* bot = botAI->GetBot();
if (bot->HasSpell(42985)) // Mana Sapphire
if (bot->HasSpell(SPELL_CONJURE_MANA_SAPPHIRE))
triggers.push_back(new TriggerNode("high mana", { NextAction("use mana sapphire", 90.0f) }));
else if (bot->HasSpell(27101)) // Mana Emerald
else if (bot->HasSpell(SPELL_CONJURE_MANA_EMERALD))
triggers.push_back(new TriggerNode("high mana", { NextAction("use mana emerald", 90.0f) }));
else if (bot->HasSpell(10054)) // Mana Ruby
else if (bot->HasSpell(SPELL_CONJURE_MANA_RUBY))
triggers.push_back(new TriggerNode("high mana", { NextAction("use mana ruby", 90.0f) }));
else if (bot->HasSpell(10053)) // Mana Citrine
else if (bot->HasSpell(SPELL_CONJURE_MANA_CITRINE))
triggers.push_back(new TriggerNode("high mana", { NextAction("use mana citrine", 90.0f) }));
else if (bot->HasSpell(3552)) // Mana Jade
else if (bot->HasSpell(SPELL_CONJURE_MANA_JADE))
triggers.push_back(new TriggerNode("high mana", { NextAction("use mana jade", 90.0f) }));
else if (bot->HasSpell(759)) // Mana Agate
else if (bot->HasSpell(SPELL_CONJURE_MANA_AGATE))
triggers.push_back(new TriggerNode("high mana", { NextAction("use mana agate", 90.0f) }));
triggers.push_back(new TriggerNode("medium mana", { NextAction("mana potion", 90.0f) }));
@ -142,7 +154,7 @@ void MageBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
}
else if (tab == MAGE_TAB_FIRE)
{
if (bot->HasSpell(44614) /*Frostfire Bolt*/ && bot->HasAura(15047) /*Ice Shards*/)
if (bot->HasSpell(SPELL_FROSTFIRE_BOLT) && bot->HasAura(SPELL_ICE_SHARDS))
{ // Frostfire
triggers.push_back(new TriggerNode("combustion", { NextAction("combustion", 18.0f) }));
triggers.push_back(new TriggerNode("icy veins", { NextAction("icy veins", 17.5f) }));

View File

@ -1145,8 +1145,7 @@ bool CastGreaterBlessingAssignmentAction::Execute(Event /*event*/)
if (!FindPendingAssignment(assignment, spellName))
return false;
uint32 finalId = AI_VALUE2(uint32, "spell id", spellName);
if (!finalId)
if (!botAI->HasSpell(spellName))
return false;
return botAI->CastSpell(spellName, assignment.player);

View File

@ -5,34 +5,23 @@
#include "PaladinPullStrategy.h"
#include "AiObjectContext.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
std::string PaladinPullStrategy::GetPullActionName() const
{
Player* bot = botAI->GetBot();
Unit* target = GetTarget();
if (!bot || !target ||
if (!target ||
(!botAI->HasStrategy("tank", BOT_STATE_COMBAT) && !botAI->HasStrategy("tank", BOT_STATE_NON_COMBAT)))
{
return PullStrategy::GetPullActionName();
}
uint32 const avengersShieldSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "avenger's shield")->Get();
if (avengersShieldSpellId && bot->HasSpell(avengersShieldSpellId) &&
botAI->CanCastSpell(avengersShieldSpellId, target))
{
if (botAI->CanCastSpell("avenger's shield", target))
return "avenger's shield";
}
uint32 const handOfReckoningSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "hand of reckoning")->Get();
if (handOfReckoningSpellId && bot->HasSpell(handOfReckoningSpellId) &&
botAI->CanCastSpell(handOfReckoningSpellId, target))
{
if (botAI->CanCastSpell("hand of reckoning", target))
return "hand of reckoning";
}
return PullStrategy::GetPullActionName();
}

View File

@ -11,6 +11,14 @@
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
namespace
{
constexpr uint32 SPELL_WARSONG_FLAG = 23333;
constexpr uint32 SPELL_SILVERWING_FLAG = 23335;
constexpr uint32 SPELL_NETHERSTORM_FLAG = 34976;
constexpr uint32 SPELL_MASTER_POISONER_RANK_3 = 58410;
}
bool CastStealthAction::isUseful()
{
Unit* target = AI_VALUE(Unit*, "current target");
@ -22,7 +30,8 @@ bool CastStealthAction::isUseful()
bool CastStealthAction::isPossible()
{
// do not use with WSG flag or EYE flag
return !botAI->HasAura(23333, bot) && !botAI->HasAura(23335, bot) && !botAI->HasAura(34976, bot);
return !bot->HasAura(SPELL_WARSONG_FLAG) && !bot->HasAura(SPELL_SILVERWING_FLAG) &&
!bot->HasAura(SPELL_NETHERSTORM_FLAG);
}
bool UnstealthAction::Execute(Event /*event*/)
@ -50,7 +59,8 @@ bool CheckStealthAction::Execute(Event /*event*/)
bool CastVanishAction::isUseful()
{
// do not use with WSG flag or EYE flag
return !botAI->HasAura(23333, bot) && !botAI->HasAura(23335, bot) && !botAI->HasAura(34976, bot);
return !bot->HasAura(SPELL_WARSONG_FLAG) && !bot->HasAura(SPELL_SILVERWING_FLAG) &&
!bot->HasAura(SPELL_NETHERSTORM_FLAG);
}
bool CastEnvenomAction::isUseful()
@ -61,7 +71,7 @@ bool CastEnvenomAction::isUseful()
bool CastEnvenomAction::isPossible()
{
// alternate to eviscerate if talents unlearned
return botAI->HasAura(58410, bot) /* Master Poisoner Rank 3 */;
return bot->HasAura(SPELL_MASTER_POISONER_RANK_3);
}
bool CastTricksOfTheTradeOnMainTankAction::isUseful()

View File

@ -9,6 +9,12 @@
#include "Playerbots.h"
#include "ServerFacade.h"
namespace
{
constexpr uint32 SPELL_STEALTH = 1784;
constexpr uint32 SPELL_SPRINT_RANK_1 = 2983;
}
// bool AdrenalineRushTrigger::isPossible()
// {
// return !botAI->HasAura("stealth", bot);
@ -29,7 +35,7 @@ bool UnstealthTrigger::IsActive()
bool StealthTrigger::IsActive()
{
if (botAI->HasAura("stealth", bot) || bot->IsInCombat() || bot->HasSpellCooldown(1784))
if (bot->HasAura(SPELL_STEALTH) || bot->IsInCombat() || bot->HasSpellCooldown(SPELL_STEALTH))
return false;
float distance = 30.f;
@ -63,13 +69,13 @@ bool StealthTrigger::IsActive()
return target && ServerFacade::instance().GetDistance2d(bot, target) < distance;
}
bool SapTrigger::IsPossible() { return bot->GetLevel() > 10 && bot->HasSpell(6770) && !bot->IsInCombat(); }
bool SapTrigger::IsPossible() { return bot->GetLevel() > 10 && botAI->HasSpell("sap") && !bot->IsInCombat(); }
bool SprintTrigger::IsPossible() { return bot->HasSpell(2983); }
bool SprintTrigger::IsPossible() { return bot->HasSpell(SPELL_SPRINT_RANK_1); }
bool SprintTrigger::IsActive()
{
if (bot->HasSpellCooldown(2983))
if (bot->HasSpellCooldown(SPELL_SPRINT_RANK_1))
return false;
float distance = botAI->GetMaster() ? 45.0f : 35.0f;
@ -105,7 +111,7 @@ bool SprintTrigger::IsActive()
bool ExposeArmorTrigger::IsActive()
{
Unit* target = AI_VALUE(Unit*, "current target"); // Get the bot's current target
Unit* target = AI_VALUE(Unit*, "current target");
return DebuffTrigger::IsActive() && !botAI->HasAura("sunder armor", target, false, false, -1, true) &&
AI_VALUE2(uint8, "combo", "current target") <= 3;
}

View File

@ -6,6 +6,17 @@
#include "TotemsShamanStrategy.h"
#include "Playerbots.h"
namespace
{
constexpr uint32 SPELL_TOTEM_OF_WRATH = 30706;
constexpr uint32 SPELL_FLAMETONGUE_TOTEM = 8227;
constexpr uint32 SPELL_CLEANSING_TOTEM = 8170;
constexpr uint32 SPELL_MANA_SPRING_TOTEM = 5675;
constexpr uint32 SPELL_WRATH_OF_AIR_TOTEM = 3738;
constexpr uint32 SPELL_GROUNDING_TOTEM = 8177;
constexpr uint32 SPELL_WINDFURY_TOTEM = 8512;
}
// These combat strategies are used to set the corresponding totems on the bar, and cast the totem when it's missing.
// There are special cases for Totem of Wrath, Windfury Totem, Wrath of Air totem, and Cleansing totem - these totems
// aren't learned at level 30, and have fallbacks in order to prevent the trigger from continuously firing.
@ -74,9 +85,9 @@ void TotemOfWrathStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
GenericShamanStrategy::InitTriggers(triggers);
// If the bot hasn't learned Totem of Wrath yet, set Flametongue Totem instead.
Player* bot = botAI->GetBot();
if (bot->HasSpell(30706))
if (bot->HasSpell(SPELL_TOTEM_OF_WRATH))
triggers.push_back(new TriggerNode("set totem of wrath", { NextAction("set totem of wrath", 60.0f) }));
else if (bot->HasSpell(8227))
else if (bot->HasSpell(SPELL_FLAMETONGUE_TOTEM))
triggers.push_back(new TriggerNode("set flametongue totem", { NextAction("set flametongue totem", 60.0f) }));
triggers.push_back(new TriggerNode("no fire totem", { NextAction("totem of wrath", 55.0f) }));
}
@ -112,9 +123,9 @@ void CleansingTotemStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
GenericShamanStrategy::InitTriggers(triggers);
// If the bot hasn't learned Cleansing Totem yet, set Mana Spring Totem instead.
Player* bot = botAI->GetBot();
if (bot->HasSpell(8170))
if (bot->HasSpell(SPELL_CLEANSING_TOTEM))
triggers.push_back(new TriggerNode("set cleansing totem", { NextAction("set cleansing totem", 60.0f) }));
else if (bot->HasSpell(5675))
else if (bot->HasSpell(SPELL_MANA_SPRING_TOTEM))
triggers.push_back(new TriggerNode("set mana spring totem", { NextAction("set mana spring totem", 60.0f) }));
triggers.push_back(new TriggerNode("no water totem", { NextAction("cleansing totem", 55.0f) }));
}
@ -134,9 +145,9 @@ void WrathOfAirTotemStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
GenericShamanStrategy::InitTriggers(triggers);
// If the bot hasn't learned Wrath of Air Totem yet, set Grounding Totem instead.
Player* bot = botAI->GetBot();
if (bot->HasSpell(3738))
if (bot->HasSpell(SPELL_WRATH_OF_AIR_TOTEM))
triggers.push_back(new TriggerNode("set wrath of air totem", { NextAction("set wrath of air totem", 60.0f) }));
else if (bot->HasSpell(8177))
else if (bot->HasSpell(SPELL_GROUNDING_TOTEM))
triggers.push_back(new TriggerNode("set grounding totem", { NextAction("set grounding totem", 60.0f) }));
triggers.push_back( new TriggerNode("no air totem", { NextAction("wrath of air totem", 55.0f) }));
}
@ -147,9 +158,9 @@ void WindfuryTotemStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
GenericShamanStrategy::InitTriggers(triggers);
// If the bot hasn't learned Windfury Totem yet, set Grounding Totem instead.
Player* bot = botAI->GetBot();
if (bot->HasSpell(8512))
if (bot->HasSpell(SPELL_WINDFURY_TOTEM))
triggers.push_back(new TriggerNode("set windfury totem", { NextAction("set windfury totem", 60.0f) }));
else if (bot->HasSpell(8177))
else if (bot->HasSpell(SPELL_GROUNDING_TOTEM))
triggers.push_back(new TriggerNode("set grounding totem", { NextAction("set grounding totem", 60.0f) }));
triggers.push_back(new TriggerNode("no air totem", { NextAction("windfury totem", 55.0f) }));
}

View File

@ -77,11 +77,7 @@ bool CastShadowflameAction::isUseful()
bool CastRainOfFireAction::isUseful()
{
Unit* target = GetTarget();
if (!target)
return false;
if (bot->HasSpell(27243) || bot->HasSpell(47835) || bot->HasSpell(47836)) // Seed of Corruption spell IDs
return false;
return true;
return target && !botAI->HasSpell("seed of corruption");
}
// Checks if the enemies are close enough to use Hellfire

View File

@ -104,10 +104,8 @@ bool LifeTapTrigger::IsActive()
// Checks if the Life Tap Glyph buff is active
bool LifeTapGlyphBuffTrigger::IsActive()
{
if (!botAI->HasAura(63320, bot))
return false;
return BuffTrigger::IsActive();
constexpr uint32 SPELL_LIFE_TAP_GLYPH = 63320;
return bot->HasAura(SPELL_LIFE_TAP_GLYPH) && BuffTrigger::IsActive();
}
// Checks if the target has a conflicting debuff that is equal to Curse of the Elements

View File

@ -5,23 +5,16 @@
#include "WarriorPullStrategy.h"
#include "AiObjectContext.h"
#include "Player.h"
#include "PlayerbotAI.h"
std::string WarriorPullStrategy::GetPullActionName() const
{
Player* bot = botAI->GetBot();
Unit* target = GetTarget();
if (!bot || !target)
if (!target)
return PullStrategy::GetPullActionName();
uint32 const heroicThrowSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "heroic throw")->Get();
if (heroicThrowSpellId && bot->HasSpell(heroicThrowSpellId) &&
botAI->CanCastSpell(heroicThrowSpellId, target))
{
if (botAI->CanCastSpell("heroic throw", target))
return "heroic throw";
}
return PullStrategy::GetPullActionName();
}

View File

@ -8,6 +8,15 @@
#include "AiFactory.h"
#include "Playerbots.h"
namespace
{
constexpr uint32 SPELL_RETALIATION = 20230;
constexpr uint32 SPELL_DIVINE_SHIELD = 642;
constexpr uint32 SPELL_ICE_BLOCK = 45438;
constexpr uint32 SPELL_BLESSING_OF_PROTECTION = 41450;
constexpr uint32 SPELL_SHATTERING_THROW = 64382;
}
bool CastBerserkerRageAction::isPossible()
{
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
@ -153,14 +162,8 @@ bool CastVigilanceAction::Execute(Event /*event*/)
bool CastRetaliationAction::isUseful()
{
// Spell cooldown check
if (!bot->HasSpell(20230))
{
return false;
}
// Spell cooldown check
if (bot->HasSpellCooldown(20230))
if (!bot->HasSpell(SPELL_RETALIATION) || bot->HasSpellCooldown(SPELL_RETALIATION) ||
bot->HasAura(SPELL_RETALIATION))
{
return false;
}
@ -199,8 +202,8 @@ bool CastRetaliationAction::isUseful()
break;
}
// Only cast Retaliation if there are at least 2 melee attackers and the buff is not active
return meleeAttackers >= 2 && !botAI->HasAura("retaliation", bot);
// Only cast Retaliation if there are at least 2 melee attackers
return meleeAttackers >= 2;
}
Unit* CastShatteringThrowAction::GetTarget()
@ -214,15 +217,15 @@ Unit* CastShatteringThrowAction::GetTarget()
continue;
if (bot->IsWithinDistInMap(enemy, 25.0f) &&
(enemy->HasAura(642) || // Divine Shield
enemy->HasAura(45438) || // Ice Block
enemy->HasAura(41450))) // Blessing of Protection
(enemy->HasAura(SPELL_DIVINE_SHIELD) ||
enemy->HasAura(SPELL_ICE_BLOCK) ||
enemy->HasAura(SPELL_BLESSING_OF_PROTECTION)))
{
return enemy;
}
}
return nullptr; // No valid target
return nullptr;
}
bool CastShatteringThrowAction::Execute(Event /*event*/)
@ -236,7 +239,7 @@ bool CastShatteringThrowAction::Execute(Event /*event*/)
bool CastShatteringThrowAction::isUseful()
{
if (!bot->HasSpell(64382) || bot->HasSpellCooldown(64382))
if (!bot->HasSpell(SPELL_SHATTERING_THROW) || bot->HasSpellCooldown(SPELL_SHATTERING_THROW))
return false;
GuidVector enemies = AI_VALUE(GuidVector, "possible targets");
@ -247,17 +250,16 @@ bool CastShatteringThrowAction::isUseful()
if (!enemy || !enemy->IsAlive() || enemy->IsFriendlyTo(bot))
continue;
// Check if the enemy is within 25 yards and has the specific auras
if (bot->IsWithinDistInMap(enemy, 25.0f) &&
(enemy->HasAura(642) || // Divine Shield
enemy->HasAura(45438) || // Ice Block
enemy->HasAura(41450))) // Blessing of Protection
(enemy->HasAura(SPELL_DIVINE_SHIELD) ||
enemy->HasAura(SPELL_ICE_BLOCK) ||
enemy->HasAura(SPELL_BLESSING_OF_PROTECTION)))
{
return true;
}
}
return false; // No valid targets within range
return false;
}
bool CastShatteringThrowAction::isPossible()

View File

@ -6,6 +6,16 @@
#include "WarriorTriggers.h"
#include "Playerbots.h"
namespace
{
constexpr uint32 SPELL_VIGILANCE = 50720;
constexpr uint32 SPELL_SHATTERING_THROW = 64382;
constexpr uint32 SPELL_DIVINE_SHIELD = 642;
constexpr uint32 SPELL_ICE_BLOCK = 45438;
constexpr uint32 SPELL_BLESSING_OF_PROTECTION = 41450;
constexpr uint32 SPELL_COMMANDING_PRESENCE_RANKS[] = { 12318, 12857, 12858, 12860, 12861 };
}
bool BloodrageBuffTrigger::IsActive()
{
return AI_VALUE2(uint8, "health", "self target") >= sPlayerbotAIConfig.mediumHealth &&
@ -14,7 +24,7 @@ bool BloodrageBuffTrigger::IsActive()
bool VigilanceTrigger::IsActive()
{
if (!bot->HasSpell(50720))
if (!bot->HasSpell(SPELL_VIGILANCE))
return false;
Group* group = bot->GetGroup();
@ -64,7 +74,7 @@ bool VigilanceTrigger::IsActive()
bool ShatteringThrowTrigger::IsActive()
{
if (!bot->HasSpell(64382) || bot->HasSpellCooldown(64382))
if (!bot->HasSpell(SPELL_SHATTERING_THROW) || bot->HasSpellCooldown(SPELL_SHATTERING_THROW))
return false;
GuidVector enemies = AI_VALUE(GuidVector, "possible targets");
@ -76,9 +86,9 @@ bool ShatteringThrowTrigger::IsActive()
continue;
if (bot->IsWithinDistInMap(enemy, 25.0f) &&
(enemy->HasAura(642) || // Divine Shield
enemy->HasAura(45438) || // Ice Block
enemy->HasAura(41450))) // Blessing of Protection
(enemy->HasAura(SPELL_DIVINE_SHIELD) ||
enemy->HasAura(SPELL_ICE_BLOCK) ||
enemy->HasAura(SPELL_BLESSING_OF_PROTECTION)))
{
return true;
}
@ -112,15 +122,13 @@ bool BattleShoutTrigger::IsActive()
if (!bsApValue)
return false;
static const uint32 commandingPresenceSpells[] = {
12318, 12857, 12858, 12860, 12861 };
static const float commandingPresenceBonus[] = {
0.05f, 0.10f, 0.15f, 0.20f, 0.25f };
float cpBonus = 0.0f;
for (int rank = 4; rank >= 0; --rank)
{
if (bot->HasAura(commandingPresenceSpells[rank]))
if (bot->HasAura(SPELL_COMMANDING_PRESENCE_RANKS[rank]))
{
cpBonus = commandingPresenceBonus[rank];
break;

View File

@ -5,6 +5,13 @@
#include "Playerbots.h"
#include "SharedDefines.h"
namespace
{
constexpr uint32 SPELL_UNDERSTUDY_TAUNT = 29060;
constexpr uint32 SPELL_BONE_BARRIER = 29061;
constexpr uint32 SPELL_BLOOD_STRIKE = 61696;
}
bool RazuviousUseObedienceCrystalAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
@ -42,7 +49,8 @@ bool RazuviousUseObedienceCrystalAction::Execute(Event /*event*/)
bool tauntUseful = true;
if (forceObedience->GetDuration() <= (duration_time - 5000))
{
if (target->GetVictim() && botAI->HasAura(29061, target->GetVictim()))
Unit* victim = target->GetVictim();
if (victim && victim->HasAura(SPELL_BONE_BARRIER))
tauntUseful = false;
if (forceObedience->GetDuration() <= 3000)
@ -55,19 +63,19 @@ bool RazuviousUseObedienceCrystalAction::Execute(Event /*event*/)
if (tauntUseful && !charm->HasSpellCooldown(29060))
{
// shield
if (!charm->HasSpellCooldown(29061))
if (!charm->HasSpellCooldown(SPELL_BONE_BARRIER))
{
charm->CastSpell(charm, 29061, true);
charm->AddSpellCooldown(29061, 0, 30 * 1000);
charm->CastSpell(charm, SPELL_BONE_BARRIER, true);
charm->AddSpellCooldown(SPELL_BONE_BARRIER, 0, 30 * 1000);
}
charm->CastSpell(target, 29060, true);
charm->AddSpellCooldown(29060, 0, 20 * 1000);
charm->CastSpell(target, SPELL_UNDERSTUDY_TAUNT, true);
charm->AddSpellCooldown(SPELL_UNDERSTUDY_TAUNT, 0, 20 * 1000);
}
// strike
if (!charm->HasSpellCooldown(61696))
if (!charm->HasSpellCooldown(SPELL_BLOOD_STRIKE))
{
charm->CastSpell(target, 61696, true);
charm->AddSpellCooldown(61696, 0, 4 * 1000);
charm->CastSpell(target, SPELL_BLOOD_STRIKE, true);
charm->AddSpellCooldown(SPELL_BLOOD_STRIKE, 0, 4 * 1000);
}
}
}

View File

@ -75,7 +75,7 @@ bool SapphironFlightPositionAction::MoveToNearestIcebolt()
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
if (NaxxSpellIds::HasAnyAura(member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
botAI->HasAura("icebolt", member, false, false, -1, true))
{
if (!playerWithIcebolt || minDistance > bot->GetDistance(member))

View File

@ -111,15 +111,13 @@ bool ThaddiusMovePolarityAction::Execute(Event /*event*/)
{3504.68f, -2936.68f},
};
uint32 idx;
if (NaxxSpellIds::HasAnyAura(
botAI, bot,
if (NaxxSpellIds::HasAnyAura(bot,
{NaxxSpellIds::NegativeCharge10, NaxxSpellIds::NegativeCharge25, NaxxSpellIds::NegativeChargeStack}) ||
botAI->HasAura("negative charge", bot, false, false, -1, true))
{
idx = 0;
}
else if (NaxxSpellIds::HasAnyAura(
botAI, bot,
else if (NaxxSpellIds::HasAnyAura(bot,
{NaxxSpellIds::PositiveCharge10, NaxxSpellIds::PositiveCharge25, NaxxSpellIds::PositiveChargeStack}) ||
botAI->HasAura("positive charge", bot, false, false, -1, true))
{

View File

@ -192,7 +192,7 @@ public:
{
Player* member = ref->GetSource();
if (member &&
(NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
(NaxxSpellIds::HasAnyAura(member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
botAI->HasAura("icebolt", member, false, false, -1, true)))
{
return true;

View File

@ -236,7 +236,7 @@ float AnubrekhanGenericMultiplier::GetValue(Action* action)
return 1.0f;
if (NaxxSpellIds::HasAnyAura(
botAI, boss, {NaxxSpellIds::LocustSwarm10, NaxxSpellIds::LocustSwarm10Alt, NaxxSpellIds::LocustSwarm25}) ||
boss, {NaxxSpellIds::LocustSwarm10, NaxxSpellIds::LocustSwarm10Alt, NaxxSpellIds::LocustSwarm25}) ||
botAI->HasAura("locust swarm", boss))
{
if (dynamic_cast<FleeAction*>(action))

View File

@ -123,14 +123,14 @@ namespace NaxxSpellIds
SPELL_INEVITABLE_DOOM = 29204,
SPELL_BERSERK = 26662
*/
inline bool HasAnyAura(PlayerbotAI* botAI, Unit* unit, std::initializer_list<uint32> spellIds)
inline bool HasAnyAura(Unit* unit, std::initializer_list<uint32> spellIds)
{
if (!botAI || !unit)
if (!unit)
return false;
for (uint32 spellId : spellIds)
{
if (botAI->HasAura(spellId, unit))
if (unit->HasAura(spellId))
return true;
}
return false;

View File

@ -1531,7 +1531,7 @@ bool VezaxShadowCrashTrigger::IsActive()
if (!boss || !boss->IsAlive())
return false;
return botAI->HasAura(SPELL_VEZAX_SHADOW_CRASH, bot);
return bot->HasAura(SPELL_VEZAX_SHADOW_CRASH);
}
bool VezaxMarkOfTheFacelessTrigger::IsActive()
@ -1542,7 +1542,7 @@ bool VezaxMarkOfTheFacelessTrigger::IsActive()
if (!boss || !boss->IsAlive())
return false;
if (!botAI->HasAura(SPELL_MARK_OF_THE_FACELESS, bot))
if (!bot->HasAura(SPELL_MARK_OF_THE_FACELESS))
return false;
float distance = bot->GetDistance2d(ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionX(),

View File

@ -26,6 +26,15 @@
#include "WarlockAiObjectContext.h"
#include "WarriorAiObjectContext.h"
namespace
{
constexpr uint32 SPELL_FROSTFIRE_BOLT = 44614;
constexpr uint32 SPELL_ICE_SHARDS = 15047;
constexpr uint32 SPELL_WHIRLWIND = 1680;
constexpr uint32 SPELL_CAT_FORM = 768;
constexpr uint32 SPELL_DRUID_THICK_HIDE = 16931;
}
AiObjectContext* AiFactory::createAiObjectContext(Player* player, PlayerbotAI* botAI)
{
switch (player->getClass())
@ -300,7 +309,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
engine->addStrategiesNoInit("arcane", "bdps", nullptr);
else if (tab == MAGE_TAB_FIRE)
{
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
if (player->HasSpell(SPELL_FROSTFIRE_BOLT) && player->HasAura(SPELL_ICE_SHARDS))
engine->addStrategiesNoInit("frostfire", "bdps", nullptr);
else
engine->addStrategiesNoInit("fire", "bdps", nullptr);
@ -313,7 +322,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
case CLASS_WARRIOR:
if (tab == WARRIOR_TAB_PROTECTION)
engine->addStrategiesNoInit("tank", "tank assist", "pull", "pull back", "aoe", nullptr);
else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(1680)) // Whirlwind
else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(SPELL_WHIRLWIND))
engine->addStrategiesNoInit("arms", "aoe", "dps assist", nullptr);
else // if (tab == WARRIOR_TAB_FURY)
engine->addStrategiesNoInit("fury", "aoe", "dps assist", nullptr);
@ -345,7 +354,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
engine->addStrategiesNoInit("resto", "cure", "dps assist", "blanketing", "tranquility", nullptr);
else
{
if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/)
if (player->HasSpell(SPELL_CAT_FORM) && !player->HasAura(SPELL_DRUID_THICK_HIDE))
engine->addStrategiesNoInit("cat", "aoe", "cc", "dps assist", "feral charge", nullptr);
else
engine->addStrategiesNoInit("bear", "tank assist", "pull", "pull back", "feral charge", nullptr);
@ -535,7 +544,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
case CLASS_DRUID:
if (tab == DRUID_TAB_FERAL)
{
if (player->GetLevel() >= 20 && !player->HasAura(16931) /*thick hide*/)
if (player->GetLevel() >= 20 && !player->HasAura(SPELL_DRUID_THICK_HIDE))
nonCombatEngine->addStrategy("dps assist", false);
else
nonCombatEngine->addStrategiesNoInit("tank assist", "pull", nullptr);

View File

@ -61,6 +61,56 @@ std::vector<uint32> PlayerbotFactory::enchantGemIdCache;
std::unordered_map<uint32, std::vector<uint32>> PlayerbotFactory::trainerIdCache;
std::vector<uint32> PlayerbotFactory::ccBreakTrinketCache;
namespace
{
constexpr uint32 SPELL_DRUID_THICK_HIDE = 16931;
constexpr uint32 SPELL_OWLKIN_FRENZY = 48393;
constexpr uint32 SPELL_PRIMAL_TENACITY = 33957;
constexpr uint32 SPELL_IMPROVED_BARKSKIN = 63411;
constexpr uint32 SPELL_SECOND_WIND = 29838;
constexpr uint32 SPELL_BLOOD_CRAZE = 16492;
constexpr uint32 SPELL_GAG_ORDER = 12958;
constexpr uint32 SPELL_SACRED_CLEANSING = 53553;
constexpr uint32 SPELL_RECKONING = 20179;
constexpr uint32 SPELL_DIVINE_PURPOSE = 31872;
constexpr uint32 SPELL_HUNTER_THICK_HIDE = 19612;
constexpr uint32 SPELL_CONCUSSIVE_BARRAGE = 35102;
constexpr uint32 SPELL_ENTRAPMENT = 19388;
constexpr uint32 SPELL_DEADLY_BREW = 51626;
constexpr uint32 SPELL_THROWING_SPECIALIZATION = 51679;
constexpr uint32 SPELL_WAYLAY = 51696;
constexpr uint32 SPELL_IMPROVED_MANA_BURN = 14772;
constexpr uint32 SPELL_BODY_AND_SOUL = 64129;
constexpr uint32 SPELL_IMPROVED_VAMPIRIC_EMBRACE = 27840;
constexpr uint32 SPELL_ABOMINATIONS_MIGHT = 53138;
constexpr uint32 SPELL_IMPROVED_ICY_TALONS = 55610;
constexpr uint32 SPELL_SUDDEN_DOOM = 49529;
constexpr uint32 SPELL_ACCLIMATION = 50152;
constexpr uint32 SPELL_MAGIC_SUPPRESSION = 49611;
constexpr uint32 SPELL_SHAMAN_DUAL_WIELD = 30798;
constexpr uint32 SPELL_ASTRAL_SHIFT = 51479;
constexpr uint32 SPELL_EARTHEN_POWER = 51524;
constexpr uint32 SPELL_FOCUSED_MIND = 30866;
constexpr uint32 SPELL_BURNOUT = 44472;
constexpr uint32 SPELL_ICE_SHARDS = 15047;
constexpr uint32 SPELL_IMPROVED_BLINK = 31570;
constexpr uint32 SPELL_FIERY_PAYBACK = 64357;
constexpr uint32 SPELL_SHATTERED_BARRIER = 54787;
constexpr uint32 SPELL_IMPROVED_HOWL_OF_TERROR = 30057;
constexpr uint32 SPELL_NEMESIS = 63123;
constexpr uint32 SPELL_INTENSITY = 18136;
constexpr uint32 SPELL_NETHER_PROTECTION = 30302;
}
bool PlayerbotFactory::IsPrimaryTradeSkill(uint16 skillId)
{
SkillLineEntry const* skillLine = sSkillLineStore.LookupEntry(skillId);
@ -1440,7 +1490,7 @@ uint32 PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_temp
/// @todo: fix cat druid hardcode
if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20)
{
bool isCat = !bot->HasAura(16931);
bool isCat = !bot->HasAura(SPELL_DRUID_THICK_HIDE);
if (!isCat && bot->GetLevel() == 20)
{
uint32 bearP = sPlayerbotAIConfig.randomClassSpecProb[cls][1];
@ -1495,7 +1545,7 @@ uint32 PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_temp
if (bot->GetFreeTalentPoints())
InitTalents((specTab + 2) % 3);
if (bot->getClass() == CLASS_SHAMAN && bot->HasSpell(30798))
if (bot->getClass() == CLASS_SHAMAN && bot->HasSpell(SPELL_SHAMAN_DUAL_WIELD))
{
bot->SetSkill(SKILL_DUAL_WIELD, 0, 1, 1);
bot->SetCanDualWield(true);
@ -1581,7 +1631,7 @@ void PlayerbotFactory::InitTalentsBySpecNo(Player* bot, int specNo, bool reset)
}
}
if (bot->getClass() == CLASS_SHAMAN && bot->HasSpell(30798))
if (bot->getClass() == CLASS_SHAMAN && bot->HasSpell(SPELL_SHAMAN_DUAL_WIELD))
{
bot->SetSkill(SKILL_DUAL_WIELD, 0, 1, 1);
bot->SetCanDualWield(true);
@ -4170,13 +4220,13 @@ void PlayerbotFactory::InitGlyphs(bool increment)
if (bot->getClass() == CLASS_WARRIOR)
{
// Arms PvP (spec index 3): If the bot has the Second Wind talent
if (bot->HasAura(29838))
if (bot->HasAura(SPELL_SECOND_WIND))
tab = 3;
// Fury PvP (spec index 4): If the bot has the Blood Craze talent
else if (bot->HasAura(16492))
else if (bot->HasAura(SPELL_BLOOD_CRAZE))
tab = 4;
// Protection PvP (spec index 5): If the bot has the Gag Order talent
else if (bot->HasAura(12958))
else if (bot->HasAura(SPELL_GAG_ORDER))
tab = 5;
}
@ -4184,13 +4234,13 @@ void PlayerbotFactory::InitGlyphs(bool increment)
if (bot->getClass() == CLASS_PALADIN)
{
// Holy PvP (spec index 3): If the bot has the Sacred Cleansing talent
if (bot->HasAura(53553))
if (bot->HasAura(SPELL_SACRED_CLEANSING))
tab = 3;
// Protection PvP (spec index 4): If the bot has the Reckoning talent
else if (bot->HasAura(20179))
else if (bot->HasAura(SPELL_RECKONING))
tab = 4;
// Retribution PvP (spec index 5): If the bot has the Divine Purpose talent
else if (bot->HasAura(31872))
else if (bot->HasAura(SPELL_DIVINE_PURPOSE))
tab = 5;
}
@ -4198,13 +4248,13 @@ void PlayerbotFactory::InitGlyphs(bool increment)
if (bot->getClass() == CLASS_HUNTER)
{
// Beast Mastery PvP (spec index 3): If the bot has the Thick Hide talent
if (bot->HasAura(19612))
if (bot->HasAura(SPELL_HUNTER_THICK_HIDE))
tab = 3;
// Marksmanship PvP (spec index 4): If the bot has the Concussive Barrage talent
else if (bot->HasAura(35102))
else if (bot->HasAura(SPELL_CONCUSSIVE_BARRAGE))
tab = 4;
// Survival PvP (spec index 5): If the bot has the Entrapment talent and does NOT have the Concussive Barrage talent
else if (bot->HasAura(19388) && !bot->HasAura(35102))
else if (bot->HasAura(SPELL_ENTRAPMENT) && !bot->HasAura(SPELL_CONCUSSIVE_BARRAGE))
tab = 5;
}
@ -4212,13 +4262,13 @@ void PlayerbotFactory::InitGlyphs(bool increment)
if (bot->getClass() == CLASS_ROGUE)
{
// Assassination PvP (spec index 3): If the bot has the Deadly Brew talent
if (bot->HasAura(51626))
if (bot->HasAura(SPELL_DEADLY_BREW))
tab = 3;
// Combat PvP (spec index 4): If the bot has the Throwing Specialization talent
else if (bot->HasAura(51679))
else if (bot->HasAura(SPELL_THROWING_SPECIALIZATION))
tab = 4;
// Subtlety PvP (spec index 5): If the bot has the Waylay talent
else if (bot->HasAura(51696))
else if (bot->HasAura(SPELL_WAYLAY))
tab = 5;
}
@ -4226,13 +4276,13 @@ void PlayerbotFactory::InitGlyphs(bool increment)
if (bot->getClass() == CLASS_PRIEST)
{
// Discipline PvP (spec index 3): If the bot has the Improved Mana Burn talent
if (bot->HasAura(14772))
if (bot->HasAura(SPELL_IMPROVED_MANA_BURN))
tab = 3;
// Holy PvP (spec index 4): If the bot has the Body and Soul talent
else if (bot->HasAura(64129))
else if (bot->HasAura(SPELL_BODY_AND_SOUL))
tab = 4;
// Shadow PvP (spec index 5): If the bot has the Improved Vampiric Embrace talent
else if (bot->HasAura(27840))
else if (bot->HasAura(SPELL_IMPROVED_VAMPIRIC_EMBRACE))
tab = 5;
}
@ -4241,16 +4291,16 @@ void PlayerbotFactory::InitGlyphs(bool increment)
{
// Double Aura Blood PvE (spec index 3): If the bot has both the Abomination's Might and Improved Icy Talons
// talents
if (bot->HasAura(53138) && bot->HasAura(55610))
if (bot->HasAura(SPELL_ABOMINATIONS_MIGHT) && bot->HasAura(SPELL_IMPROVED_ICY_TALONS))
tab = 3;
// Blood PvP (spec index 4): If the bot has the Sudden Doom talent
else if (bot->HasAura(49529))
else if (bot->HasAura(SPELL_SUDDEN_DOOM))
tab = 4;
// Frost PvP (spec index 5): If the bot has the Acclimation talent
else if (bot->HasAura(50152))
else if (bot->HasAura(SPELL_ACCLIMATION))
tab = 5;
// Unholy PvP (spec index 6): If the bot has the Magic Suppression talent
else if (bot->HasAura(49611))
else if (bot->HasAura(SPELL_MAGIC_SUPPRESSION))
tab = 6;
}
@ -4258,13 +4308,13 @@ void PlayerbotFactory::InitGlyphs(bool increment)
if (bot->getClass() == CLASS_SHAMAN)
{
// Elemental PvP (spec index 3): If the bot has the Astral Shift talent
if (bot->HasAura(51479))
if (bot->HasAura(SPELL_ASTRAL_SHIFT))
tab = 3;
// Enhancement PvP (spec index 4): If the bot has the Earthen Power talent
else if (bot->HasAura(51524))
else if (bot->HasAura(SPELL_EARTHEN_POWER))
tab = 4;
// Restoration PvP (spec index 5): If the bot has the Focused Mind talent
else if (bot->HasAura(30866))
else if (bot->HasAura(SPELL_FOCUSED_MIND))
tab = 5;
}
@ -4272,16 +4322,16 @@ void PlayerbotFactory::InitGlyphs(bool increment)
if (bot->getClass() == CLASS_MAGE)
{
// Frostfire PvE (spec index 3): If the bot has both the Burnout talent and the Ice Shards talent
if (bot->HasAura(44472) && bot->HasAura(15047))
if (bot->HasAura(SPELL_BURNOUT) && bot->HasAura(SPELL_ICE_SHARDS))
tab = 3;
// Arcane PvP (spec index 4): If the bot has the Improved Blink talent
else if (bot->HasAura(31570))
else if (bot->HasAura(SPELL_IMPROVED_BLINK))
tab = 4;
// Fire PvP (spec index 5): If the bot has the Fiery Payback talent
else if (bot->HasAura(64357))
else if (bot->HasAura(SPELL_FIERY_PAYBACK))
tab = 5;
// Frost PvP (spec index 6): If the bot has the Shattered Barrier talent
else if (bot->HasAura(54787))
else if (bot->HasAura(SPELL_SHATTERED_BARRIER))
tab = 6;
}
@ -4289,13 +4339,13 @@ void PlayerbotFactory::InitGlyphs(bool increment)
if (bot->getClass() == CLASS_WARLOCK)
{
// Affliction PvP (spec index 3): If the bot has the Improved Howl of Terror talent
if (bot->HasAura(30057))
if (bot->HasAura(SPELL_IMPROVED_HOWL_OF_TERROR))
tab = 3;
// Demonology PvP (spec index 4): If the bot has both the Nemesis talent and the Intensity talent
else if (bot->HasAura(63123) && bot->HasAura(18136))
else if (bot->HasAura(SPELL_NEMESIS) && bot->HasAura(SPELL_INTENSITY))
tab = 4;
// Destruction PvP (spec index 5): If the bot has the Nether Protection talent
else if (bot->HasAura(30302))
else if (bot->HasAura(SPELL_NETHER_PROTECTION))
tab = 5;
}
@ -4303,16 +4353,16 @@ void PlayerbotFactory::InitGlyphs(bool increment)
if (bot->getClass() == CLASS_DRUID)
{
// Cat PvE (spec index 3): If the bot is Feral spec, level 20 or higher, and does NOT have the Thick Hide talent
if (tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(16931))
if (tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(SPELL_DRUID_THICK_HIDE))
tab = 3;
// Balance PvP (spec index 4): If the bot has the Owlkin Frenzy talent
else if (bot->HasAura(48393))
else if (bot->HasAura(SPELL_OWLKIN_FRENZY))
tab = 4;
// Feral PvP (spec index 5): If the bot has the Primal Tenacity talent
else if (bot->HasAura(33957))
else if (bot->HasAura(SPELL_PRIMAL_TENACITY))
tab = 5;
// Resto PvP (spec index 6): If the bot has the Improved Barkskin talent
else if (bot->HasAura(63411))
else if (bot->HasAura(SPELL_IMPROVED_BARKSKIN))
tab = 6;
}

View File

@ -3027,6 +3027,8 @@ bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel secu
{
if (sPlayerbotAIConfig.randomBotSayWithoutMaster)
return TellMasterNoFacing(text, securityLevel);
return false;
}
if (!TellMasterNoFacing(text, securityLevel))
return false;
@ -3150,20 +3152,10 @@ bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, boo
return false;
}
bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit)
bool PlayerbotAI::HasSpell(std::string const spellName) const
{
if (!spellId || !unit)
return false;
return unit->HasAura(spellId);
// for (uint8 effect = EFFECT_0; effect <= EFFECT_2; effect++)
// {
// AuraEffect const* aurEff = unit->GetAuraEffect(spellId, effect);
// if (IsRealAura(bot, aurEff, unit))
// return true;
// }
// return false;
uint32 const spellId = aiObjectContext->GetValue<uint32>("spell id", spellName)->Get();
return spellId && bot->HasSpell(spellId);
}
Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack)
@ -4267,7 +4259,7 @@ void PlayerbotAI::InterruptSpell()
void PlayerbotAI::RemoveAura(std::string const name)
{
uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", name)->Get();
if (spellid && HasAura(spellid, bot))
if (spellid && bot->HasAura(spellid))
bot->RemoveAurasDueToSpell(spellid);
}

View File

@ -497,6 +497,7 @@ public:
virtual bool CanCastSpell(std::string const name, Unit* target, Item* itemTarget = nullptr);
virtual bool CastSpell(std::string const name, Unit* target, Item* itemTarget = nullptr);
virtual bool HasSpell(std::string const spellName) const;
virtual bool HasAura(std::string const spellName, Unit* player, bool maxStack = false, bool checkIsOwner = false,
int maxAmount = -1, bool checkDuration = false);
virtual bool HasAnyAuraOf(Unit* player, ...);
@ -509,7 +510,6 @@ public:
bool CanCastSpell(uint32 spellid, float x, float y, float z, bool checkHasSpell = true,
Item* itemTarget = nullptr);
bool HasAura(uint32 spellId, Unit const* player);
Aura* GetAura(std::string const spellName, Unit* unit, bool checkIsOwner = false, bool checkDuration = false,
int checkStack = -1);
bool CastSpell(uint32 spellId, Unit* target, Item* itemTarget = nullptr);

View File

@ -23,13 +23,29 @@
namespace
{
constexpr uint32 SPELL_MOLTEN_ARMOR_RANK_1 = 30482;
constexpr uint32 SPELL_MOLTEN_ARMOR_RANK_2 = 43045;
constexpr uint32 SPELL_MOLTEN_ARMOR_RANK_3 = 43046;
constexpr uint32 SPELL_FEL_ARMOR_RANK_1 = 28176;
constexpr uint32 SPELL_FEL_ARMOR_RANK_2 = 28189;
constexpr uint32 SPELL_FEL_ARMOR_RANK_3 = 47892;
constexpr uint32 SPELL_FEL_ARMOR_RANK_4 = 47893;
constexpr uint32 SPELL_MOLTEN_ARMOR_RANKS[] = { 30482, 43045, 43046 };
constexpr uint32 SPELL_FEL_ARMOR_RANKS[] = { 28176, 28189, 47892, 47893 };
constexpr uint32 SPELL_CAREFUL_AIM = 34484;
constexpr uint32 SPELL_HUNTER_VS_WILD = 56341;
constexpr uint32 SPELL_ARMORED_TO_THE_TEETH = 61222;
constexpr uint32 SPELL_MENTAL_DEXTERITY = 51885;
constexpr uint32 SPELL_ROGUE_SWORD_SPECIALIZATION = 13964;
constexpr uint32 SPELL_POLEAXE_SPECIALIZATION = 12785;
constexpr uint32 SPELL_NERVES_OF_COLD_STEEL = 50138;
constexpr uint32 SPELL_SHADOW_FOCUS = 15835;
constexpr uint32 SPELL_ARCANE_FOCUS = 12840;
}
template <size_t Size>
bool HasAnySpell(Player* player, uint32 const (&spellIds)[Size])
{
for (uint32 const spellId : spellIds)
{
if (player->HasSpell(spellId))
return true;
}
return false;
}
StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
@ -512,26 +528,24 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player)
// int tab = AiFactory::GetPlayerSpecTab(player);
if (cls == CLASS_HUNTER)
{
if (player->HasAura(34484))
if (player->HasAura(SPELL_CAREFUL_AIM))
stats_weights_[STATS_TYPE_INTELLECT] += 1.1f;
if (player->HasAura(56341))
if (player->HasAura(SPELL_HUNTER_VS_WILD))
stats_weights_[STATS_TYPE_STAMINA] += 0.3f;
}
else if (cls == CLASS_WARRIOR)
{
if (player->HasAura(61222))
if (player->HasAura(SPELL_ARMORED_TO_THE_TEETH))
stats_weights_[STATS_TYPE_ARMOR] += 0.03f;
}
else if (cls == CLASS_SHAMAN)
{
if (player->HasAura(51885))
if (player->HasAura(SPELL_MENTAL_DEXTERITY))
stats_weights_[STATS_TYPE_INTELLECT] += 1.1f;
}
else if (cls == CLASS_MAGE)
{
if (!player->HasSpell(SPELL_MOLTEN_ARMOR_RANK_1)
&& !player->HasSpell(SPELL_MOLTEN_ARMOR_RANK_2)
&& !player->HasSpell(SPELL_MOLTEN_ARMOR_RANK_3))
if (!HasAnySpell(player, SPELL_MOLTEN_ARMOR_RANKS))
{
if (tab != MAGE_TAB_FIRE)
stats_weights_[STATS_TYPE_SPIRIT] -= 0.6f;
@ -541,8 +555,7 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player)
}
else if (cls == CLASS_WARLOCK)
{
if (!player->HasSpell(SPELL_FEL_ARMOR_RANK_1) && !player->HasSpell(SPELL_FEL_ARMOR_RANK_2) &&
!player->HasSpell(SPELL_FEL_ARMOR_RANK_3) && !player->HasSpell(SPELL_FEL_ARMOR_RANK_4))
if (!HasAnySpell(player, SPELL_FEL_ARMOR_RANKS))
stats_weights_[STATS_TYPE_SPIRIT] -= 0.4f;
}
@ -690,17 +703,17 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
weight_ *= 1.5;
}
if (cls == CLASS_ROGUE && player_->HasAura(13964) &&
if (cls == CLASS_ROGUE && player_->HasAura(SPELL_ROGUE_SWORD_SPECIALIZATION) &&
(proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE))
{
weight_ *= 1.1;
}
if (cls == CLASS_WARRIOR && player_->HasAura(12785) &&
if (cls == CLASS_WARRIOR && player_->HasAura(SPELL_POLEAXE_SPECIALIZATION) &&
(proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2))
{
weight_ *= 1.1;
}
if (cls == CLASS_DEATH_KNIGHT && player_->HasAura(50138) && !isDoubleHand)
if (cls == CLASS_DEATH_KNIGHT && player_->HasAura(SPELL_NERVES_OF_COLD_STEEL) && !isDoubleHand)
{
weight_ *= 1.3;
}
@ -739,9 +752,9 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
player->GetTotalAuraModifier(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT); // suppression (18176)
hit_current += player->GetRatingBonusValue(CR_HIT_SPELL);
if (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW && player->HasAura(15835)) // Shadow Focus
if (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW && player->HasAura(SPELL_SHADOW_FOCUS))
hit_current += 3;
if (cls == CLASS_MAGE && tab == MAGE_TAB_ARCANE && player->HasAura(12840)) // Arcane Focus
if (cls == CLASS_MAGE && tab == MAGE_TAB_ARCANE && player->HasAura(SPELL_ARCANE_FOCUS))
hit_current += 3;
hit_overflow = SPELL_HIT_OVERFLOW;